mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 19:05:45 +00:00
fix(native): retain completed debugger evidence through reply cleanup
This commit is contained in:
1 parent
f8161ad7b3
commit
c06dc2d356
3 files changed
+387
-48
No files matched your search
@@ -2,6 +2,7 @@ import {
|
||||
bindArtifactFileCleanup,
|
||||
artifactFileCancellationCause,
|
||||
} from "../../artifacts/ArtifactFileOperation.js";
|
||||
import { safeParseUtf8Json } from "../../domain/safeJson.js";
|
||||
import { access, mkdir, stat, writeFile } from "node:fs/promises";
|
||||
import { constants } from "node:fs";
|
||||
import { randomUUID } from "node:crypto";
|
||||
@@ -20,10 +21,15 @@ import {
|
||||
recordedCrashDebuggerSchema,
|
||||
type RecordedCrash,
|
||||
} from "../../domain/native/recordedCrash.js";
|
||||
import { readStableArtifact } from "../../artifacts/readStableArtifact.js";
|
||||
import {
|
||||
completedStableArtifact,
|
||||
readStableArtifact,
|
||||
} from "../../artifacts/readStableArtifact.js";
|
||||
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
|
||||
import type { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
||||
import { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js";
|
||||
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
|
||||
import {
|
||||
capturedPwntoolsOutput,
|
||||
pwntoolsDecoderFailure,
|
||||
@@ -48,6 +54,12 @@ const replySchema = z.discriminatedUnion("ok", [
|
||||
}),
|
||||
]);
|
||||
|
||||
/** Completed debugger observations stay separate from their reply descriptor's close. */
|
||||
export interface PwndbgCoreInspection {
|
||||
readonly context: Extract<RecordedCrash["debugger"], { status: "available" }>;
|
||||
readonly finalization: AnalysisError | undefined;
|
||||
}
|
||||
|
||||
/** Add mapping candidates from an owned core-only debugger, using the existing snapshot. */
|
||||
export const inspectPwndbgCore = async ({
|
||||
environment,
|
||||
@@ -74,7 +86,7 @@ export const inspectPwndbgCore = async ({
|
||||
kind: "file" | "directory",
|
||||
mode: number,
|
||||
) => Promise<void>;
|
||||
}): Promise<RecordedCrash["debugger"]> => {
|
||||
}): Promise<PwndbgCoreInspection> => {
|
||||
const gdb = environment.REA_PWNDBG_GDB ?? "/usr/bin/gdb";
|
||||
const entry = environment.REA_PWNDBG_GDBINIT ?? "";
|
||||
const venv = environment.REA_PWNDBG_VENV_PATH ?? "";
|
||||
@@ -189,15 +201,33 @@ export const inspectPwndbgCore = async ({
|
||||
);
|
||||
output = capturedPwntoolsOutput(execution);
|
||||
let reply: z.output<typeof replySchema>;
|
||||
let finalization: AnalysisError | undefined;
|
||||
try {
|
||||
const raw = await readStableArtifact(
|
||||
replyPath,
|
||||
PWNTOOLS_LIMITS.outputBytes,
|
||||
options?.signal,
|
||||
undefined,
|
||||
resources,
|
||||
);
|
||||
reply = replySchema.parse(JSON.parse(raw.bytes.toString("utf8")));
|
||||
let raw;
|
||||
try {
|
||||
raw = await readStableArtifact(
|
||||
replyPath,
|
||||
PWNTOOLS_LIMITS.outputBytes,
|
||||
options?.signal,
|
||||
undefined,
|
||||
resources,
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
const completed = completedStableArtifact(cause);
|
||||
if (
|
||||
!(cause instanceof ArtifactReaderFailure) ||
|
||||
completed === undefined
|
||||
)
|
||||
throw cause;
|
||||
finalization = completedReplyFailure(
|
||||
cause,
|
||||
recordedCrashStageOutput(diagnostics, output),
|
||||
);
|
||||
raw = completed;
|
||||
}
|
||||
const decoded = safeParseUtf8Json(raw.bytes);
|
||||
if (!decoded.ok) throw decoded.cause;
|
||||
reply = replySchema.parse(decoded.value);
|
||||
} catch (cause: unknown) {
|
||||
if (
|
||||
cause instanceof ArtifactReaderFailure &&
|
||||
@@ -212,7 +242,7 @@ export const inspectPwndbgCore = async ({
|
||||
: {}),
|
||||
});
|
||||
bindArtifactFileCleanup(cause, error);
|
||||
throw error;
|
||||
throw pwndbgContextFinalizationFailure(error, finalization);
|
||||
}
|
||||
const error = new AnalysisOutputError(
|
||||
OPERATION,
|
||||
@@ -226,38 +256,52 @@ export const inspectPwndbgCore = async ({
|
||||
},
|
||||
);
|
||||
bindArtifactFileCleanup(cause, error);
|
||||
throw error;
|
||||
throw pwndbgContextFinalizationFailure(error, finalization);
|
||||
}
|
||||
if (!reply.ok) {
|
||||
if (reply.reason === "unavailable" || reply.reason === "unsupported")
|
||||
throw new AnalysisCapabilityUnavailableError(
|
||||
"pwndbg-core",
|
||||
OPERATION,
|
||||
reply.message,
|
||||
{
|
||||
userMessage: reply.message,
|
||||
capturedOutput: recordedCrashStageOutput(diagnostics, output),
|
||||
},
|
||||
throw pwndbgContextFinalizationFailure(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
"pwndbg-core",
|
||||
OPERATION,
|
||||
reply.message,
|
||||
{
|
||||
userMessage: reply.message,
|
||||
capturedOutput: recordedCrashStageOutput(diagnostics, output),
|
||||
},
|
||||
),
|
||||
finalization,
|
||||
);
|
||||
if (reply.reason === "format" || reply.reason === "output-limit")
|
||||
throw new AnalysisOutputError(OPERATION, reply.message, {
|
||||
capturedOutput: recordedCrashStageOutput(diagnostics, output),
|
||||
});
|
||||
throw new ProviderAdapterError("pwndbg-core", OPERATION, {
|
||||
diagnostics: {
|
||||
stage: "debugger",
|
||||
reason: reply.message,
|
||||
captured_output: { ...recordedCrashStageOutput(diagnostics, output) },
|
||||
},
|
||||
});
|
||||
throw pwndbgContextFinalizationFailure(
|
||||
new AnalysisOutputError(OPERATION, reply.message, {
|
||||
capturedOutput: recordedCrashStageOutput(diagnostics, output),
|
||||
}),
|
||||
finalization,
|
||||
);
|
||||
throw pwndbgContextFinalizationFailure(
|
||||
new ProviderAdapterError("pwndbg-core", OPERATION, {
|
||||
diagnostics: {
|
||||
stage: "debugger",
|
||||
reason: reply.message,
|
||||
captured_output: {
|
||||
...recordedCrashStageOutput(diagnostics, output),
|
||||
},
|
||||
},
|
||||
}),
|
||||
finalization,
|
||||
);
|
||||
}
|
||||
if (reply.value.status !== "available")
|
||||
throw new AnalysisOutputError(
|
||||
OPERATION,
|
||||
"Requested debugger context returned no core evidence.",
|
||||
{ capturedOutput: recordedCrashStageOutput(diagnostics, output) },
|
||||
throw pwndbgContextFinalizationFailure(
|
||||
new AnalysisOutputError(
|
||||
OPERATION,
|
||||
"Requested debugger context returned no core evidence.",
|
||||
{ capturedOutput: recordedCrashStageOutput(diagnostics, output) },
|
||||
),
|
||||
finalization,
|
||||
);
|
||||
return { ...reply.value, diagnostics: output };
|
||||
return { context: { ...reply.value, diagnostics: output }, finalization };
|
||||
} catch (cause: unknown) {
|
||||
if (cause instanceof AnalysisError && cause.cleanupIncomplete) throw cause;
|
||||
const primary = pwntoolsDecoderFailure(
|
||||
@@ -272,6 +316,51 @@ export const inspectPwndbgCore = async ({
|
||||
}
|
||||
};
|
||||
|
||||
const completedReplyFailure = (
|
||||
cause: ArtifactReaderFailure,
|
||||
capturedOutput: AnalysisCapturedOutput,
|
||||
): AnalysisError => {
|
||||
const cancellation =
|
||||
cause.reason === "cancelled"
|
||||
? new AnalysisCancelledError(OPERATION, {
|
||||
cause: artifactFileCancellationCause(cause),
|
||||
capturedOutput,
|
||||
})
|
||||
: undefined;
|
||||
if (cause.cleanup === undefined) {
|
||||
if (cancellation !== undefined) return cancellation;
|
||||
return new AnalysisOutputError(OPERATION, cause.message, {
|
||||
cause,
|
||||
capturedOutput,
|
||||
});
|
||||
}
|
||||
const cleanup = new ProviderCleanupError(
|
||||
"pwndbg-core",
|
||||
cause.cleanup.resources,
|
||||
{ stage: "debugger_reply_descriptor_close", reason: cause.cleanup.reason },
|
||||
{ operation: OPERATION, cause, capturedOutput },
|
||||
);
|
||||
bindArtifactFileCleanup(cause, cleanup);
|
||||
return cancellation === undefined
|
||||
? cleanup
|
||||
: pwndbgContextFinalizationFailure(cancellation, cleanup);
|
||||
};
|
||||
|
||||
/** A rejected/malformed context stays primary over its descriptor finalization. */
|
||||
export const pwndbgContextFinalizationFailure = (
|
||||
primary: AnalysisError,
|
||||
finalization: AnalysisError | undefined,
|
||||
): AnalysisError =>
|
||||
finalization === undefined
|
||||
? primary
|
||||
: finalization.cleanupIncomplete
|
||||
? analysisErrorWithCleanupFailure(
|
||||
primary,
|
||||
finalization,
|
||||
OPERATION,
|
||||
).bindCleanupDependencies([primary, finalization])
|
||||
: primary.retainSecondaryFailure(finalization);
|
||||
|
||||
/** Preserve both stages' exact text and their independently observed truncation state. */
|
||||
export const recordedCrashStageOutput = (
|
||||
basic: AnalysisCapturedOutput,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { validateRecordedCrashSources } from "./RecordedCrashSourceBindings.js";
|
||||
import { validateRecordedCrashStructure } from "./RecordedCrashStructureBindings.js";
|
||||
import {
|
||||
inspectPwndbgCore,
|
||||
pwndbgContextFinalizationFailure,
|
||||
recordedCrashStageOutput,
|
||||
} from "../pwndbg/PwndbgCoreContext.js";
|
||||
import type { z } from "zod";
|
||||
@@ -133,7 +134,7 @@ export class PwntoolsRecordedCrashProvider
|
||||
snapshot.bytes,
|
||||
);
|
||||
if (!authority.ok) throw authority.error;
|
||||
const context = await inspectPwndbgCore({
|
||||
const completed = await inspectPwndbgCore({
|
||||
environment,
|
||||
architecture: value.architecture,
|
||||
rootPath,
|
||||
@@ -143,20 +144,25 @@ export class PwntoolsRecordedCrashProvider
|
||||
runCommand,
|
||||
timeoutMs,
|
||||
});
|
||||
const context = completed.context;
|
||||
const enriched = validate(context);
|
||||
if (!enriched.success)
|
||||
throw new AnalysisOutputError(
|
||||
"inspect_recorded_crash",
|
||||
`Recorded debugger context is invalid: ${enriched.error.issues[0]?.message ?? "schema mismatch"}`,
|
||||
{
|
||||
capturedOutput:
|
||||
context.status === "available"
|
||||
? recordedCrashStageOutput(
|
||||
diagnostics,
|
||||
context.diagnostics,
|
||||
)
|
||||
: diagnostics,
|
||||
},
|
||||
throw pwndbgContextFinalizationFailure(
|
||||
new AnalysisOutputError(
|
||||
"inspect_recorded_crash",
|
||||
`Recorded debugger context is invalid: ${enriched.error.issues[0]?.message ?? "schema mismatch"}`,
|
||||
{
|
||||
capturedOutput: recordedCrashStageOutput(
|
||||
diagnostics,
|
||||
context.diagnostics,
|
||||
),
|
||||
},
|
||||
),
|
||||
completed.finalization,
|
||||
);
|
||||
if (completed.finalization !== undefined)
|
||||
throw completed.finalization.retainPartialObservation(
|
||||
enriched.data,
|
||||
);
|
||||
return enriched.data;
|
||||
} catch (cause: unknown) {
|
||||
|
||||
Reference in new issue
Block a user