mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
26356dc34a
* fix(process): record withheld unrelated Linux processes as unverified The token sweep after an owned command (for example the Ghidra Java probe run by setup and doctor) failed cleanup when an unrelated same-UID process started during the sweep and Linux denied its /proc/<pid>/environ. Setgid or setuid programs (mismatched real/saved IDs) and non-dumpable processes are withheld by the kernel exactly like another account's processes, which were already recorded as unverified. Derive the kernel restriction from the same stat-bracketed procfs read that already supplies the effective UID: all Uid/Gid IDs from status, and the root ownership procfs reports for a non-dumpable process's environ. Keep that fact through exit settlement. Related candidates still fail closed. Fixes #6204 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(process): define caller credentials on hosts without geteuid Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.2 KiB
TypeScript
61 lines
2.2 KiB
TypeScript
/** Real, effective, saved and filesystem IDs from one Linux status row. */
|
|
export type LinuxProcessIds = readonly [number, number, number, number];
|
|
|
|
/** Parse the single `Uid:` or `Gid:` row of one Linux `/proc/<pid>/status` record. */
|
|
export const parseLinuxProcessIds = (
|
|
value: string,
|
|
row: "Uid" | "Gid",
|
|
): LinuxProcessIds | undefined => {
|
|
let ids: LinuxProcessIds | undefined;
|
|
for (const line of value.split("\n")) {
|
|
if (!line.startsWith(`${row}:`)) continue;
|
|
if (ids !== undefined) return undefined;
|
|
const match =
|
|
/^[UG]id:\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s*$/u.exec(line);
|
|
if (match === null) return undefined;
|
|
const [real, effective, saved, filesystem] = match.slice(1).map(Number);
|
|
if (
|
|
real === undefined ||
|
|
effective === undefined ||
|
|
saved === undefined ||
|
|
filesystem === undefined ||
|
|
[real, effective, saved, filesystem].some(
|
|
(id) => !Number.isSafeInteger(id) || id < 0,
|
|
)
|
|
)
|
|
return undefined;
|
|
ids = [real, effective, saved, filesystem];
|
|
}
|
|
return ids;
|
|
};
|
|
|
|
/**
|
|
* Why Linux withholds `/proc/<pid>/environ` from a caller without
|
|
* CAP_SYS_PTRACE (PTRACE_MODE_READ_FSCREDS): the target's real, effective or
|
|
* saved UID/GID differs from the caller's, or the target is non-dumpable, which
|
|
* procfs reports as root ownership of `/proc/<pid>/environ`.
|
|
*/
|
|
export type LinuxEnvironmentRestriction = "credentials" | "non-dumpable";
|
|
|
|
/** Derive the kernel's environment restriction from one generation's procfs facts. */
|
|
export const linuxEnvironmentRestriction = (facts: {
|
|
readonly uids: LinuxProcessIds;
|
|
readonly gids: LinuxProcessIds | undefined;
|
|
readonly procfsOwnerUid: number | undefined;
|
|
readonly callerUid: number | undefined;
|
|
readonly callerGid: number | undefined;
|
|
}): LinuxEnvironmentRestriction | undefined => {
|
|
const { uids, gids, procfsOwnerUid, callerUid, callerGid } = facts;
|
|
if (
|
|
(callerUid !== undefined &&
|
|
uids.slice(0, 3).some((uid) => uid !== callerUid)) ||
|
|
(callerGid !== undefined &&
|
|
gids !== undefined &&
|
|
gids.slice(0, 3).some((gid) => gid !== callerGid))
|
|
)
|
|
return "credentials";
|
|
return procfsOwnerUid !== undefined && procfsOwnerUid !== uids[1]
|
|
? "non-dumpable"
|
|
: undefined;
|
|
};
|