mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(rizin): keep protocol failures outside exit recovery (#5290)
Separate debugger exit-policy readback from process descriptor checks. Require the final native readback before considering inferior-end proof, and regress an ambiguous reply delivered as the real owned inferior exits. Preserve debugger ownership on failure. Fixes #5282 Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
This commit is contained in:
1 parent
e84e17e30f
commit
909b9f7b6b
2 files changed
+42
-20
No files matched your search
@@ -1189,7 +1189,8 @@ export class RizinDebugSessionManager {
|
||||
identity.startTime,
|
||||
await readLinuxProcessStartTime(pid),
|
||||
);
|
||||
await this.#verifyInferiorOutputConfiguration(session, pid);
|
||||
await this.#verifyDebuggerExitPolicy(session);
|
||||
await this.#verifyInferiorOutputDescriptors(session, pid);
|
||||
await this.#runInternalCommand(session, `o- ${fd}`);
|
||||
const after = parseRizinPtraceRows(
|
||||
await this.#runInternalCommand(session, "olj"),
|
||||
@@ -1203,12 +1204,14 @@ export class RizinDebugSessionManager {
|
||||
throw new Error(
|
||||
"Rizin did not confirm that the selected inferior FD was detached.",
|
||||
);
|
||||
// Protocol readback must never be waived by proof that a process ended.
|
||||
await this.#verifyDebuggerExitPolicy(session);
|
||||
try {
|
||||
assertRizinProcessStartTimeStable(
|
||||
identity.startTime,
|
||||
await readLinuxProcessStartTime(pid),
|
||||
);
|
||||
await this.#verifyInferiorOutputConfiguration(session, pid);
|
||||
await this.#verifyInferiorOutputDescriptors(session, pid);
|
||||
} catch (cause) {
|
||||
// Detach resumes the inferior. Its identity or descriptors may disappear
|
||||
// before readback; only a previously identified process can prove its end.
|
||||
@@ -1219,15 +1222,18 @@ export class RizinDebugSessionManager {
|
||||
return { status: "detached", outputSink: "durable" };
|
||||
}
|
||||
|
||||
async #verifyInferiorOutputConfiguration(
|
||||
session: RizinDebugSession,
|
||||
pid: string,
|
||||
): Promise<void> {
|
||||
async #verifyDebuggerExitPolicy(session: RizinDebugSession): Promise<void> {
|
||||
const exitKills = (
|
||||
await this.#runInternalCommand(session, "e dbg.exitkills")
|
||||
).trim();
|
||||
if (exitKills !== "false")
|
||||
throw new Error("Rizin dbg.exitkills readback did not confirm false.");
|
||||
}
|
||||
|
||||
async #verifyInferiorOutputDescriptors(
|
||||
session: RizinDebugSession,
|
||||
pid: string,
|
||||
): Promise<void> {
|
||||
for (const descriptor of ["1", "2"]) {
|
||||
const descriptorPath = `/proc/${pid}/fd/${descriptor}`;
|
||||
const destination = await readlink(descriptorPath);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, expect, it } from "vitest";
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
ProviderProcessSupervisor,
|
||||
@@ -33,7 +33,9 @@ afterEach(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.skipIf(process.platform !== "linux").each(["exit", "redirect"] as const)(
|
||||
it
|
||||
.skipIf(process.platform !== "linux")
|
||||
.each(["exit", "redirect", "ambiguous-policy"] as const)(
|
||||
"verifies the original inferior after %s during detach",
|
||||
async (mode) => {
|
||||
const directory = await mkdtemp(join(tmpdir(), "rea-rizin-exit-"));
|
||||
@@ -98,17 +100,25 @@ process.stdin.resume();`,
|
||||
);
|
||||
else if (line === "o- 3") {
|
||||
descriptorOpen = false;
|
||||
launched.process.stdin!.write(`${mode}\n`);
|
||||
if (mode === "exit")
|
||||
if (mode !== "ambiguous-policy") {
|
||||
launched.process.stdin!.write(`${mode}\n`);
|
||||
if (mode === "exit")
|
||||
expect(await supervisor.waitForExit(1_000)).toBe(true);
|
||||
else
|
||||
await expect
|
||||
.poll(() => readFile(redirected, "utf8"), {
|
||||
timeout: 1_000,
|
||||
})
|
||||
.toBe("redirected");
|
||||
}
|
||||
} else if (line === "e dbg.exitkills") {
|
||||
if (mode === "ambiguous-policy" && !descriptorOpen) {
|
||||
// End the identified process only during the final policy query.
|
||||
launched.process.stdin!.write("exit\n");
|
||||
expect(await supervisor.waitForExit(1_000)).toBe(true);
|
||||
else
|
||||
await expect
|
||||
.poll(() => readFile(redirected, "utf8"), {
|
||||
timeout: 1_000,
|
||||
})
|
||||
.toBe("redirected");
|
||||
} else if (line === "e dbg.exitkills") control.output("false\0");
|
||||
else if (line.startsWith("!echo "))
|
||||
control.output("ambiguous\0");
|
||||
} else control.output("false\0");
|
||||
} else if (line.startsWith("!echo "))
|
||||
control.output(`${line.slice(6)}\0`);
|
||||
}
|
||||
})
|
||||
@@ -118,6 +128,7 @@ process.stdin.resume();`,
|
||||
});
|
||||
},
|
||||
});
|
||||
const debuggerExit = vi.spyOn(injected.control, "exit");
|
||||
const manager = createRizinTestManager(
|
||||
{},
|
||||
{
|
||||
@@ -146,10 +157,15 @@ process.stdin.resume();`,
|
||||
} else {
|
||||
expect(closed.ok).toBe(false);
|
||||
expect(manager.status(started.value.session_id)).toBeDefined();
|
||||
expect(supervisor.observedExit()).toBeUndefined();
|
||||
expect(debuggerExit).not.toHaveBeenCalled();
|
||||
if (mode === "redirect")
|
||||
expect(supervisor.observedExit()).toBeUndefined();
|
||||
else expect(supervisor.observedExit()?.code).toBe(0);
|
||||
if (!closed.ok)
|
||||
expect(closed.error.message).toContain(
|
||||
"not routed to the selected TTY",
|
||||
mode === "redirect"
|
||||
? "not routed to the selected TTY"
|
||||
: "dbg.exitkills readback did not confirm false",
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
|
||||
Reference in new issue
Block a user