mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix: identify missing Electron lineage ownership markers
This commit is contained in:
@@ -182,6 +182,15 @@ If cleanup cannot be verified, the error retains collected observations in
|
||||
`details.partial_observation` and marks their application cleanup `unverified`.
|
||||
Cleanup uncertainty does not authorize signaling unrelated processes.
|
||||
|
||||
On Linux, Chromium-based processes can overwrite the initial environment memory
|
||||
exposed through `/proc/<pid>/environ`. The environment may be readable while
|
||||
`REA_PROCESS_RUN_ID` is absent, even if the application still sees it through
|
||||
`process.env`. REA reports the missing marker and leaves lineage ownership and
|
||||
cleanup unverified; the PID, executable, or parent relationship cannot replace
|
||||
that ownership proof. This limitation can affect owned Electron captures on
|
||||
native Linux as well as WSL. It does not establish that all Electron versions or
|
||||
Linux environments behave the same way.
|
||||
|
||||
Main-process IPC recording keeps original-function removal for `on` and `once`
|
||||
registrations and records a fired `once` callback once. Native Node.js owns
|
||||
`once` registration, firing and callable dispatch as well as removal order and
|
||||
|
||||
@@ -43,6 +43,52 @@ const nativeIdentity = (
|
||||
},
|
||||
});
|
||||
|
||||
it.each(
|
||||
[false, true].flatMap((native) =>
|
||||
([undefined, "foreign"] as const).map((runId) => ({ native, runId })),
|
||||
),
|
||||
)(
|
||||
"keeps missing ownership distinct from an observed foreign token, native=$native, runId=$runId",
|
||||
async ({ native, runId }) => {
|
||||
const signalGroup = vi.fn();
|
||||
const host: ProcessOwnershipHost = {
|
||||
platform: "linux",
|
||||
listProcesses: async () => [launcher],
|
||||
environment: async () =>
|
||||
runId === undefined ? {} : { REA_PROCESS_RUN_ID: runId },
|
||||
...(native
|
||||
? {
|
||||
processIdentities: async (rows: readonly ProcessTableEntry[]) =>
|
||||
new Map(rows.map((row) => [row.pid, nativeIdentity(row)])),
|
||||
}
|
||||
: {}),
|
||||
signalGroup,
|
||||
};
|
||||
const ownership = {
|
||||
runId: "owned",
|
||||
leaderPid: launcher.pid,
|
||||
processGroupId: launcher.processGroupId,
|
||||
expectedParentPid: launcher.parentPid,
|
||||
expectedCommand: launcher.command,
|
||||
};
|
||||
await expect(
|
||||
observeOwnedProcessLineage(ownership, host),
|
||||
).resolves.toMatchObject({
|
||||
status: "unavailable",
|
||||
reason:
|
||||
runId === undefined
|
||||
? "REA_PROCESS_RUN_ID was absent from the readable environment for PID 100; process lineage ownership remains unverified"
|
||||
: "process lineage contains an unowned or PID-reused process",
|
||||
});
|
||||
await expect(
|
||||
cleanupOwnedProcessGroup(ownership, host),
|
||||
).resolves.toMatchObject({
|
||||
cleaned: false,
|
||||
});
|
||||
expect(signalGroup).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it.each(
|
||||
(["parent", "group", "command"] as const).flatMap((mismatch) =>
|
||||
(
|
||||
|
||||
@@ -275,7 +275,7 @@ export const observeOwnedProcessLineage = async (
|
||||
if (observation.runId !== ownership.runId)
|
||||
return unavailableLineage(
|
||||
ownership,
|
||||
"process lineage contains an unowned or PID-reused process",
|
||||
lineageTokenMismatchReason(member.pid, observation.runId),
|
||||
);
|
||||
if (member.pid !== launcher.pid) verifiedDescendants.push(member);
|
||||
const seeded = examinedRefresh.get(member.pid);
|
||||
@@ -476,7 +476,7 @@ const observeNativeOwnedProcessLineage = async (
|
||||
if (fact.token.runId !== ownership.runId)
|
||||
return unavailableLineage(
|
||||
ownership,
|
||||
"process lineage contains an unowned or PID-reused process",
|
||||
lineageTokenMismatchReason(member.pid, fact.token.runId),
|
||||
);
|
||||
const nativeMember = processWithNativeRelation(
|
||||
fact.process,
|
||||
@@ -585,7 +585,7 @@ const classifyIdentitylessLineageRefresh = async (
|
||||
if (observation.runId !== ownership.runId)
|
||||
return {
|
||||
status: "unavailable",
|
||||
reason: "process lineage contains an unowned or PID-reused process",
|
||||
reason: lineageTokenMismatchReason(launcherPid, observation.runId),
|
||||
};
|
||||
}
|
||||
const identityFailure = launcherIdentityFailure(refreshedLauncher, ownership);
|
||||
@@ -621,7 +621,7 @@ const classifyIdentitylessLineageRefresh = async (
|
||||
if (inTree.has(process.pid))
|
||||
return {
|
||||
status: "unavailable",
|
||||
reason: "process lineage contains an unowned or PID-reused process",
|
||||
reason: lineageTokenMismatchReason(process.pid, observation.runId),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
@@ -679,6 +679,14 @@ const processWithNativeRelation = (
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const lineageTokenMismatchReason = (
|
||||
pid: number,
|
||||
observedRunId: string | undefined,
|
||||
): string =>
|
||||
observedRunId === undefined
|
||||
? `REA_PROCESS_RUN_ID was absent from the readable environment for PID ${String(pid)}; process lineage ownership remains unverified`
|
||||
: "process lineage contains an unowned or PID-reused process";
|
||||
|
||||
const unavailableLineage = (
|
||||
ownership: OwnedProcessGroup,
|
||||
reason: string,
|
||||
|
||||
Reference in New Issue
Block a user