mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
685 lines
22 KiB
TypeScript
685 lines
22 KiB
TypeScript
import type { AnalysisError } from "../../../src/domain/analysisErrorBase.js";
|
|
import type { RecordedCrash } from "../../../src/domain/native/recordedCrash.js";
|
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { createHash } from "node:crypto";
|
|
import { join } from "node:path";
|
|
import { expect, it, onTestFinished } from "vitest";
|
|
import { z } from "zod";
|
|
import {
|
|
createTestWorkspace,
|
|
removeTestWorkspace,
|
|
} from "../../support/workspace/workspaceFixture.js";
|
|
import { PwntoolsRecordedCrashProvider } from "../../../src/native/pwntools/PwntoolsRecordedCrashProvider.js";
|
|
import { PWNTOOLS_PROVIDER_IDENTITY } from "../../../src/native/pwntools/PwntoolsRelease.js";
|
|
import {
|
|
spawnOwnedProviderProcess,
|
|
type OwnedProviderProcessSpawnOptions,
|
|
type SpawnedOwnedProviderProcess,
|
|
} from "../../../src/process/ProviderProcess.js";
|
|
import { cleanupOwnedProcessGroup } from "../../../src/process/ProcessOwnership.js";
|
|
import {
|
|
stopProviderProcessFixture,
|
|
waitForProviderProcessReady,
|
|
} from "../../fixtures/providerProcess.js";
|
|
import {
|
|
recordedCrashFixture,
|
|
recordedCrashFileMappingFixture,
|
|
recordedCrashDebuggerFixture,
|
|
recordedCrashFixtureBytes,
|
|
recordedCrashSignalFixture,
|
|
} from "../../fixtures/binaryDiagnostics/recordedCrash.js";
|
|
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
|
|
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js";
|
|
|
|
const requestSchema = z.object({
|
|
snapshot_path: z.string(),
|
|
reply_path: z.string(),
|
|
});
|
|
const host = process.platform === "linux" && process.arch === "x64";
|
|
|
|
const withCleanupGate = (
|
|
launched: SpawnedOwnedProviderProcess,
|
|
cleanupAllowed: () => boolean,
|
|
): SpawnedOwnedProviderProcess => ({
|
|
...launched,
|
|
cleanup: async () => {
|
|
const result = await cleanupOwnedProcessGroup(launched.ownership);
|
|
if (!cleanupAllowed())
|
|
throw new Error("source-owned post-cleanup reporting failure");
|
|
return result;
|
|
},
|
|
});
|
|
|
|
const spawnDebuggerFixture = (
|
|
spawn: OwnedProviderProcessSpawnOptions,
|
|
scenario: string,
|
|
): Promise<SpawnedOwnedProviderProcess> => {
|
|
const script =
|
|
scenario === "process-failure"
|
|
? "process.stderr.write('debugger failed',()=>{process.exitCode=9;})"
|
|
: scenario.startsWith("cancelled")
|
|
? "process.stdout.write('ready\\n'); setInterval(() => {}, 1000)"
|
|
: "process.stdout.write('debugger stage diagnostic')";
|
|
const exec = scenario.startsWith("cancelled-exec");
|
|
return spawnOwnedProviderProcess({
|
|
...spawn,
|
|
command: exec ? "/bin/sh" : process.execPath,
|
|
arguments: exec
|
|
? [
|
|
"-c",
|
|
scenario === "cancelled-exec-foreign"
|
|
? 'REA_PROCESS_RUN_ID=source-owned-foreign exec "$1" -e "$2"'
|
|
: 'exec "$1" -e "$2"',
|
|
"recorded-debugger-launcher",
|
|
process.execPath,
|
|
script,
|
|
]
|
|
: ["-e", script],
|
|
});
|
|
};
|
|
|
|
const expectForeignDebuggerPreserved = async (
|
|
child: SpawnedOwnedProviderProcess["process"] | undefined,
|
|
): Promise<void> => {
|
|
if (child === undefined || child.pid === undefined)
|
|
throw new Error("Expected an owned fixture PID");
|
|
const environment = await readFile(`/proc/${child.pid}/environ`);
|
|
expect(
|
|
environment.includes(
|
|
Buffer.from("REA_PROCESS_RUN_ID=source-owned-foreign\0"),
|
|
),
|
|
).toBe(true);
|
|
expect(child.exitCode).toBeNull();
|
|
expect(child.signalCode).toBeNull();
|
|
};
|
|
|
|
it
|
|
.runIf(host)
|
|
.each([
|
|
"success",
|
|
"success-aarch64",
|
|
"wrong-register",
|
|
"wrong-register-source",
|
|
"missing-thread",
|
|
"duplicate-thread",
|
|
"missing-signal",
|
|
"duplicate-signal",
|
|
"missing-register",
|
|
"duplicated-physical-thread",
|
|
"duplicated-physical-signal",
|
|
"omitted-physical-note",
|
|
"hidden-segment",
|
|
"wrong-header-range",
|
|
"wrong-ei-version-zero",
|
|
"wrong-ei-version-two",
|
|
"wrong-e-version-zero",
|
|
"wrong-e-version-two",
|
|
"wrong-note-kind",
|
|
"wrong-owner-display",
|
|
"wrong-pid",
|
|
"wrong-current-signal",
|
|
"wrong-signal-number",
|
|
"wrong-signal-code",
|
|
"wrong-signal-errno",
|
|
"wrong-signal-address",
|
|
"wrong-raw-note",
|
|
"malformed-range",
|
|
] as const)(
|
|
"validates recorded source bytes and cleanup: %s",
|
|
async (scenario) => {
|
|
const architecture = scenario.endsWith("aarch64")
|
|
? "aarch64-little-endian"
|
|
: "x86_64-little-endian";
|
|
const { path, value, bytes } = await fixture((path) => {
|
|
const value = recordedCrashSignalFixture(path, architecture);
|
|
if (scenario === "wrong-owner-display") {
|
|
value.notes = value.notes.map((note) =>
|
|
note.index === 0
|
|
? {
|
|
...note,
|
|
owner_bytes_base64: Buffer.from("EVIL\0").toString("base64"),
|
|
owner_display: "EVIL",
|
|
}
|
|
: note,
|
|
);
|
|
value.threads = [];
|
|
}
|
|
return value;
|
|
});
|
|
if (scenario.startsWith("wrong-ei-version-"))
|
|
bytes[6] = Number(scenario.at(-1));
|
|
if (scenario.startsWith("wrong-e-version-"))
|
|
bytes.writeUInt32LE(Number(scenario.at(-1)), 20);
|
|
if (scenario.startsWith("wrong-e")) await writeFile(path, bytes);
|
|
let owned = "";
|
|
const provider = new PwntoolsRecordedCrashProvider(
|
|
{ REA_PWNTOOLS_PYTHON: process.execPath },
|
|
async (spawn) => {
|
|
owned = spawn.cwd ?? "";
|
|
const request = requestSchema.parse(
|
|
JSON.parse(await readFile(spawn.arguments.at(-1) ?? "", "utf8")),
|
|
);
|
|
expect(
|
|
Buffer.compare(await readFile(request.snapshot_path), bytes),
|
|
).toBe(0);
|
|
const payload = structuredClone(value);
|
|
alterRecordedCrashReply(payload, scenario);
|
|
const {
|
|
artifact: _artifact,
|
|
diagnostics: _diagnostics,
|
|
debugger: _debugger,
|
|
decoder_diagnostics: _decoderDiagnostics,
|
|
...raw
|
|
} = payload;
|
|
await writeFile(
|
|
request.reply_path,
|
|
JSON.stringify({
|
|
ok: true,
|
|
profile: PWNTOOLS_PROVIDER_IDENTITY.version,
|
|
value: raw,
|
|
}),
|
|
);
|
|
return spawnOwnedProviderProcess({
|
|
...spawn,
|
|
command: process.execPath,
|
|
arguments: ["-e", "process.stdout.write('recorded warning')"],
|
|
});
|
|
},
|
|
);
|
|
const result = await provider.inspect({
|
|
path,
|
|
include_debugger_context: false,
|
|
});
|
|
if (scenario === "success" || scenario === "success-aarch64") {
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.architecture).toBe(architecture);
|
|
expect(result.value.threads).toEqual(value.threads);
|
|
expect(result.value.diagnostics.stdout).toBe("recorded warning");
|
|
} else
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
_tag: "AnalysisOutputError",
|
|
capturedOutput: { stdout: "recorded warning" },
|
|
},
|
|
});
|
|
expect(Buffer.compare(await readFile(path), bytes)).toBe(0);
|
|
await expect(access(owned)).rejects.toMatchObject({ code: "ENOENT" });
|
|
},
|
|
);
|
|
|
|
it
|
|
.runIf(host)
|
|
.each([
|
|
"unconfigured",
|
|
"unsafe-mapping",
|
|
"success",
|
|
"process-failure",
|
|
"invalid-context",
|
|
"cancelled",
|
|
"cancelled-exec",
|
|
"cancelled-exec-foreign",
|
|
"cleanup-failure",
|
|
"decoder-cleanup-failure",
|
|
] as const)(
|
|
"owns optional context from the same snapshot: %s",
|
|
async (scenario) => {
|
|
const { path, value, bytes, root } = await fixture(
|
|
scenario === "unsafe-mapping"
|
|
? (path) =>
|
|
recordedCrashFileMappingFixture(
|
|
[Buffer.from("../../outside-owned-root")],
|
|
path,
|
|
)
|
|
: recordedCrashFixture,
|
|
);
|
|
const controller = new AbortController();
|
|
let owned = "";
|
|
let snapshotPath = "";
|
|
let cleanupAllowed = false;
|
|
let launches = 0;
|
|
let debuggerProcess: SpawnedOwnedProviderProcess["process"] | undefined;
|
|
const venv = join(root, "venv");
|
|
const plugin = join(root, "gdbinit.py");
|
|
await mkdir(venv);
|
|
await writeFile(plugin, "");
|
|
const provider = new PwntoolsRecordedCrashProvider(
|
|
{
|
|
REA_PWNTOOLS_PYTHON: process.execPath,
|
|
...(scenario === "unconfigured"
|
|
? {}
|
|
: {
|
|
REA_PWNDBG_GDB: process.execPath,
|
|
REA_PWNDBG_GDBINIT: plugin,
|
|
REA_PWNDBG_VENV_PATH: venv,
|
|
}),
|
|
},
|
|
async (spawn) => {
|
|
launches++;
|
|
const request = requestSchema.parse(
|
|
JSON.parse(await readFile(spawn.arguments.at(-1) ?? "", "utf8")),
|
|
);
|
|
expect(
|
|
Buffer.compare(await readFile(request.snapshot_path), bytes),
|
|
).toBe(0);
|
|
if (snapshotPath === "") {
|
|
snapshotPath = request.snapshot_path;
|
|
owned = spawn.cwd ?? "";
|
|
const {
|
|
artifact: _artifact,
|
|
diagnostics: _diagnostics,
|
|
debugger: _debugger,
|
|
decoder_diagnostics: _decoderDiagnostics,
|
|
...raw
|
|
} = value;
|
|
await writeFile(
|
|
request.reply_path,
|
|
JSON.stringify({
|
|
ok: true,
|
|
profile: PWNTOOLS_PROVIDER_IDENTITY.version,
|
|
value: raw,
|
|
}),
|
|
);
|
|
const launched = await spawnOwnedProviderProcess({
|
|
...spawn,
|
|
command: process.execPath,
|
|
arguments: ["-e", "process.stdout.write('basic stage diagnostic')"],
|
|
});
|
|
return scenario === "decoder-cleanup-failure"
|
|
? withCleanupGate(launched, () => cleanupAllowed)
|
|
: launched;
|
|
}
|
|
expect(request.snapshot_path).toBe(snapshotPath);
|
|
const context = recordedCrashDebuggerFixture();
|
|
if (scenario === "invalid-context")
|
|
context.maps = context.maps.map((map) => ({ ...map, end: "0x0" }));
|
|
await writeFile(
|
|
request.reply_path,
|
|
JSON.stringify({ ok: true, value: context }),
|
|
);
|
|
const launched = await spawnDebuggerFixture(spawn, scenario);
|
|
debuggerProcess = launched.process;
|
|
if (scenario.startsWith("cancelled")) {
|
|
await waitForProviderProcessReady(launched.process);
|
|
setImmediate(() => controller.abort());
|
|
}
|
|
return scenario === "cleanup-failure"
|
|
? withCleanupGate(launched, () => cleanupAllowed)
|
|
: launched;
|
|
},
|
|
);
|
|
onTestFinished(async () => {
|
|
cleanupAllowed = true;
|
|
// The test retains its creation handle even when the provider must refuse
|
|
// an exec'd worker with a foreign token, or a regression leaves it live.
|
|
if (debuggerProcess !== undefined)
|
|
await stopProviderProcessFixture(debuggerProcess);
|
|
await provider.close();
|
|
});
|
|
const result = await provider.inspect(
|
|
{
|
|
path,
|
|
include_debugger_context: true,
|
|
},
|
|
{ signal: controller.signal },
|
|
);
|
|
if (scenario === "success") {
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.debugger).toMatchObject({
|
|
status: "available",
|
|
maps: [{ permissions: null }],
|
|
diagnostics: { stdout: "debugger stage diagnostic" },
|
|
});
|
|
} else {
|
|
if (result.ok) throw new Error("Expected optional debugger failure");
|
|
expectOptionalContextFailure(scenario, result.error, value);
|
|
if (scenario === "unsafe-mapping") {
|
|
expect(launches).toBe(1);
|
|
await expect(access(owned)).rejects.toMatchObject({ code: "ENOENT" });
|
|
snapshotPath = "";
|
|
await expectBasicCoreFallback(provider, path, value.notes);
|
|
expect(launches).toBe(2);
|
|
}
|
|
if (scenario === "cancelled-exec-foreign")
|
|
await expectForeignDebuggerPreserved(debuggerProcess);
|
|
}
|
|
if (scenario === "cancelled" || scenario === "cancelled-exec")
|
|
expectDebuggerStopped(debuggerProcess);
|
|
expect(Buffer.compare(await readFile(path), bytes)).toBe(0);
|
|
if (
|
|
scenario === "cleanup-failure" ||
|
|
scenario === "decoder-cleanup-failure" ||
|
|
scenario === "cancelled-exec-foreign"
|
|
) {
|
|
await expect(access(owned)).resolves.toBeUndefined();
|
|
expect(Buffer.compare(await readFile(snapshotPath), bytes)).toBe(0);
|
|
const refused = await provider.inspect({
|
|
path,
|
|
include_debugger_context: true,
|
|
});
|
|
if (refused.ok)
|
|
throw new Error("Unreleased ownership must block a launch");
|
|
expect(refused.error.cleanupResources).toContain(owned);
|
|
expect(launches).toBe(scenario === "decoder-cleanup-failure" ? 1 : 2);
|
|
await expect(provider.close()).rejects.toMatchObject({
|
|
cleanupIncomplete: true,
|
|
});
|
|
await expect(access(owned)).resolves.toBeUndefined();
|
|
cleanupAllowed = true;
|
|
if (
|
|
scenario === "cancelled-exec-foreign" &&
|
|
debuggerProcess !== undefined
|
|
)
|
|
await stopProviderProcessFixture(debuggerProcess);
|
|
await provider.close();
|
|
}
|
|
await expect(access(owned)).rejects.toMatchObject({ code: "ENOENT" });
|
|
},
|
|
);
|
|
|
|
const expectDebuggerStopped = (
|
|
child: SpawnedOwnedProviderProcess["process"] | undefined,
|
|
): void => {
|
|
if (child === undefined)
|
|
throw new Error("Expected an owned debugger process");
|
|
expect(child.exitCode !== null || child.signalCode !== null).toBe(true);
|
|
};
|
|
|
|
const expectBasicCoreFallback = async (
|
|
provider: PwntoolsRecordedCrashProvider,
|
|
path: string,
|
|
notes: RecordedCrash["notes"],
|
|
) => {
|
|
const basic = await provider.inspect({
|
|
path,
|
|
include_debugger_context: false,
|
|
});
|
|
if (!basic.ok) throw basic.error;
|
|
expect(basic.value.notes).toEqual(notes);
|
|
expect(basic.value.debugger).toEqual({ status: "not-requested" });
|
|
};
|
|
|
|
const expectOptionalContextFailure = (
|
|
scenario: string,
|
|
error: AnalysisError,
|
|
value: RecordedCrash,
|
|
) => {
|
|
const projected = projectAnalysisError(error);
|
|
if (scenario === "unsafe-mapping")
|
|
expect(error).toMatchObject({
|
|
_tag: "AnalysisCapabilityUnavailableError",
|
|
partialObservation: {
|
|
notes: value.notes,
|
|
debugger: { status: "unavailable", requested: true },
|
|
},
|
|
});
|
|
if (scenario === "cancelled-exec-foreign")
|
|
expect(projected.code).toBe("cleanup_incomplete");
|
|
if (scenario === "unconfigured" || scenario === "unsafe-mapping")
|
|
expect(projected).toMatchObject({
|
|
details: { captured_output: { stdout: "basic stage diagnostic" } },
|
|
});
|
|
else if (scenario === "decoder-cleanup-failure")
|
|
expect(projected).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
details: {
|
|
diagnostics: { captured_output: { stdout: "basic stage diagnostic" } },
|
|
},
|
|
});
|
|
else if (
|
|
scenario === "invalid-context" ||
|
|
scenario.startsWith("cancelled") ||
|
|
scenario === "cleanup-failure"
|
|
) {
|
|
const serialized = JSON.stringify(projected);
|
|
expect(serialized).toContain("basic stage diagnostic");
|
|
expect(serialized).toContain(
|
|
scenario.startsWith("cancelled") ? "ready" : "debugger stage diagnostic",
|
|
);
|
|
expect(error.partialObservation).toMatchObject({
|
|
artifact: {
|
|
...value.artifact,
|
|
sha256: createHash("sha256")
|
|
.update(recordedCrashFixtureBytes(value))
|
|
.digest("hex"),
|
|
},
|
|
threads: value.threads,
|
|
notes: value.notes,
|
|
});
|
|
expect(error._tag).toBe(
|
|
scenario === "invalid-context"
|
|
? "AnalysisOutputError"
|
|
: scenario.startsWith("cancelled") &&
|
|
scenario !== "cancelled-exec-foreign"
|
|
? "AnalysisCancelledError"
|
|
: "ProviderAdapterError",
|
|
);
|
|
if (scenario === "cleanup-failure" || scenario === "invalid-context") {
|
|
if (scenario === "cleanup-failure")
|
|
expect(error).toBeInstanceOf(ProviderCleanupError);
|
|
expect(projected).toMatchObject({
|
|
details: {
|
|
partial_observation: {
|
|
debugger: { status: "unavailable", requested: true },
|
|
},
|
|
},
|
|
});
|
|
}
|
|
} else
|
|
expect(projected).toMatchObject({
|
|
category: "execution_failure",
|
|
details: {
|
|
diagnostics: {
|
|
preceding_output: { stdout: "basic stage diagnostic" },
|
|
stderr: "debugger failed",
|
|
},
|
|
},
|
|
});
|
|
};
|
|
|
|
it.runIf(host)(
|
|
"reports a non-default debugger deadline when pwndbg times out",
|
|
async () => {
|
|
const { path, value, bytes, root } = await fixture();
|
|
const venv = join(root, "venv");
|
|
const plugin = join(root, "gdbinit.py");
|
|
await mkdir(venv);
|
|
await writeFile(plugin, "");
|
|
let stage = 0;
|
|
const provider = new PwntoolsRecordedCrashProvider(
|
|
{
|
|
REA_PWNTOOLS_PYTHON: process.execPath,
|
|
REA_PWNDBG_GDB: process.execPath,
|
|
REA_PWNDBG_GDBINIT: plugin,
|
|
REA_PWNDBG_VENV_PATH: venv,
|
|
},
|
|
async (spawn) => {
|
|
stage += 1;
|
|
const request = requestSchema.parse(
|
|
JSON.parse(await readFile(spawn.arguments.at(-1) ?? "", "utf8")),
|
|
);
|
|
if (stage === 1) {
|
|
const {
|
|
artifact: _artifact,
|
|
diagnostics: _diagnostics,
|
|
debugger: _debugger,
|
|
decoder_diagnostics: _decoderDiagnostics,
|
|
...raw
|
|
} = value;
|
|
await writeFile(
|
|
request.reply_path,
|
|
JSON.stringify({
|
|
ok: true,
|
|
profile: PWNTOOLS_PROVIDER_IDENTITY.version,
|
|
value: raw,
|
|
}),
|
|
);
|
|
return spawnOwnedProviderProcess({
|
|
...spawn,
|
|
command: process.execPath,
|
|
arguments: ["-e", "process.stdout.write('basic stage diagnostic')"],
|
|
});
|
|
}
|
|
await new Promise<void>((resolve) => {
|
|
if (spawn.signal?.aborted === true) {
|
|
resolve();
|
|
return;
|
|
}
|
|
spawn.signal?.addEventListener("abort", () => resolve(), {
|
|
once: true,
|
|
});
|
|
});
|
|
return spawnOwnedProviderProcess({
|
|
...spawn,
|
|
command: process.execPath,
|
|
arguments: ["-e", "process.exit(0)"],
|
|
});
|
|
},
|
|
);
|
|
onTestFinished(() => provider.close());
|
|
const result = await provider.inspect({
|
|
path,
|
|
include_debugger_context: true,
|
|
pwntools_timeout_ms: 1_500,
|
|
});
|
|
expect(Buffer.compare(await readFile(path), bytes)).toBe(0);
|
|
if (result.ok) throw new Error("expected debugger timeout");
|
|
const projected = projectAnalysisError(result.error);
|
|
expect(projected.code).toBe("provider_timeout");
|
|
expect(projected.message).toContain("of 1500 ms");
|
|
expect(projected.remediation.action).toContain("above 1500");
|
|
expect(projected.remediation.action).not.toContain("above 30000");
|
|
},
|
|
10_000,
|
|
);
|
|
|
|
const fixture = async (
|
|
createReport: (path: string) => RecordedCrash = recordedCrashFixture,
|
|
) => {
|
|
const workspace = await createTestWorkspace("rea-recorded-core-seam-");
|
|
onTestFinished(() => removeTestWorkspace(workspace.root));
|
|
const path = join(workspace.root, "recorded.core");
|
|
const value = createReport(path);
|
|
const bytes = recordedCrashFixtureBytes(value);
|
|
await writeFile(path, bytes);
|
|
return { path, value, bytes, root: workspace.root };
|
|
};
|
|
|
|
const alterRecordedCrashReply = (
|
|
payload: RecordedCrash,
|
|
scenario: string,
|
|
): void => {
|
|
if (scenario === "wrong-owner-display")
|
|
payload.notes = payload.notes.map((note) =>
|
|
note.index === 0 ? { ...note, owner_display: "FORGED" } : note,
|
|
);
|
|
if (
|
|
scenario === "duplicated-physical-thread" ||
|
|
scenario === "duplicated-physical-signal"
|
|
) {
|
|
const noteIndex = scenario === "duplicated-physical-thread" ? 0 : 1;
|
|
const note = payload.notes[noteIndex];
|
|
if (note === undefined) throw new Error("missing fixture note");
|
|
payload.notes.push({ ...note, index: 2 });
|
|
if (noteIndex === 0)
|
|
payload.threads.push(
|
|
...payload.threads.map((thread) => ({
|
|
...thread,
|
|
note_index: 2,
|
|
})),
|
|
);
|
|
else
|
|
payload.signals.push(
|
|
...payload.signals.map((signal) => ({
|
|
...signal,
|
|
note_index: 2,
|
|
})),
|
|
);
|
|
}
|
|
if (scenario === "omitted-physical-note") {
|
|
payload.notes = payload.notes.filter((note) => note.index === 0);
|
|
payload.signals = [];
|
|
}
|
|
if (scenario === "hidden-segment") {
|
|
payload.segments = [];
|
|
payload.notes = [];
|
|
payload.threads = [];
|
|
payload.signals = [];
|
|
}
|
|
if (scenario === "wrong-header-range")
|
|
payload.segments = payload.segments.map((segment) => ({
|
|
...segment,
|
|
header_location: { offset: "0x0", bytes: "0x38" },
|
|
}));
|
|
if (scenario === "wrong-note-kind") {
|
|
payload.notes = payload.notes.map((note) =>
|
|
note.index === 0 ? { ...note, type: "NT_AUXV", type_id: 6 } : note,
|
|
);
|
|
payload.threads = [];
|
|
}
|
|
if (scenario === "wrong-register-source")
|
|
payload.threads = payload.threads.map((thread) => ({
|
|
...thread,
|
|
registers: thread.registers.map((register) =>
|
|
register.name === "rdi"
|
|
? {
|
|
...register,
|
|
value: "0x0",
|
|
location: { offset: "0x104", bytes: "0x8" },
|
|
}
|
|
: register,
|
|
),
|
|
}));
|
|
if (scenario === "missing-thread") payload.threads = [];
|
|
if (scenario === "duplicate-thread")
|
|
payload.threads = [...payload.threads, ...payload.threads];
|
|
if (scenario === "missing-signal") payload.signals = [];
|
|
if (scenario === "duplicate-signal")
|
|
payload.signals = [...payload.signals, ...payload.signals];
|
|
if (scenario === "missing-register")
|
|
payload.threads = payload.threads.map((thread) => ({
|
|
...thread,
|
|
registers: thread.registers.slice(1),
|
|
}));
|
|
if (scenario === "wrong-register")
|
|
payload.threads = payload.threads.map((t) => ({
|
|
...t,
|
|
registers: t.registers.map((r) => ({ ...r, value: "0x1" })),
|
|
}));
|
|
if (scenario === "wrong-pid")
|
|
payload.threads = payload.threads.map((thread) => ({
|
|
...thread,
|
|
historical_pid: -2,
|
|
}));
|
|
if (scenario === "wrong-current-signal")
|
|
payload.threads = payload.threads.map((thread) => ({
|
|
...thread,
|
|
recorded_current_signal: -1,
|
|
}));
|
|
if (scenario.startsWith("wrong-signal"))
|
|
payload.signals = payload.signals.map((signal) => ({
|
|
...signal,
|
|
...(scenario === "wrong-signal-number"
|
|
? {
|
|
number: 10,
|
|
fault_address: null,
|
|
fault_address_meaning: "unknown" as const,
|
|
}
|
|
: scenario === "wrong-signal-code"
|
|
? { code: 2 }
|
|
: scenario === "wrong-signal-errno"
|
|
? { errno: 0 }
|
|
: { fault_address: "0x1" }),
|
|
}));
|
|
if (scenario === "wrong-raw-note")
|
|
payload.notes = payload.notes.map((n) => ({
|
|
...n,
|
|
descriptor_bytes_base64: Buffer.alloc(336, 1).toString("base64"),
|
|
}));
|
|
if (scenario === "malformed-range")
|
|
payload.notes = payload.notes.map((n) => ({
|
|
...n,
|
|
descriptor_location: { offset: "0x200", bytes: "0x150" },
|
|
}));
|
|
};
|