mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
e31aa72c9d
Read bounded stable prefixes through the installation file owner, preserve unknown header diagnostics, and propagate cancellation or incomplete cleanup. Share extent and identity validation with full artifact reads. Validated with 269 focused tests, check:fast, and docs:check. Controlled Darwin file faults reproduce the original lost owner; real Ghidra and native Windows/Linux verification remain unperformed. Refs #4676
89 lines
3.1 KiB
TypeScript
89 lines
3.1 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import type { GhidraExecutableHeaderProbe } from "./GhidraInstallation.js";
|
|
import type { GhidraInstallationFileOptions } from "./GhidraInstallationProperties.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
|
import { AnalysisResourceConstraintError } from "../domain/analysisErrorCore.js";
|
|
import { withArtifactRegularFile } from "../artifacts/ArtifactFileOperation.js";
|
|
import { readStableFilePrefix } from "../filesystem/readStableFileBytes.js";
|
|
import { ghidraFileFailure } from "./GhidraFileArtifacts.js";
|
|
|
|
/** Only this prefix is examined; the remainder is never retained or read. */
|
|
export const GHIDRA_EXECUTABLE_HEADER_BYTES = 4096;
|
|
|
|
/** Retain complete prefix observations independently of final descriptor cleanup. */
|
|
export const readGhidraExecutableHeader = async (
|
|
path: string,
|
|
options: GhidraInstallationFileOptions,
|
|
): Promise<Result<GhidraExecutableHeaderProbe, AnalysisError>> => {
|
|
let completedHeader: GhidraExecutableHeaderProbe | undefined;
|
|
try {
|
|
return ok(
|
|
await options.resourceOwner.run(() =>
|
|
withArtifactRegularFile(
|
|
path,
|
|
{
|
|
retain: (resource) =>
|
|
options.resourceOwner.retain(
|
|
{ close: () => resource.lease.release() },
|
|
resource.resource,
|
|
),
|
|
},
|
|
options.signal,
|
|
async (file) => {
|
|
const observed = await readStableFilePrefix(
|
|
file,
|
|
path,
|
|
GHIDRA_EXECUTABLE_HEADER_BYTES,
|
|
{
|
|
symlinks: "follow",
|
|
...(options.signal === undefined
|
|
? {}
|
|
: { signal: options.signal }),
|
|
},
|
|
);
|
|
if (observed.size > BigInt(Number.MAX_SAFE_INTEGER))
|
|
throw new AnalysisResourceConstraintError(
|
|
"ghidra:installation",
|
|
"file-size",
|
|
"Executable extent cannot be represented exactly by the header parser",
|
|
{
|
|
maximum_bytes: Number.MAX_SAFE_INTEGER,
|
|
observed_bytes: observed.size.toString(),
|
|
},
|
|
{
|
|
diagnostics: { path, stage: "native_decompiler_header" },
|
|
retryable: false,
|
|
},
|
|
);
|
|
completedHeader = {
|
|
bytes: observed.bytes,
|
|
size: Number(observed.size),
|
|
};
|
|
return completedHeader;
|
|
},
|
|
"follow",
|
|
),
|
|
),
|
|
);
|
|
} catch (cause: unknown) {
|
|
return err(
|
|
ghidraFileFailure(cause, "ghidra:installation", {
|
|
path,
|
|
stage: "native_decompiler_header",
|
|
...(completedHeader === undefined
|
|
? {}
|
|
: {
|
|
completed_header: {
|
|
file_bytes: completedHeader.size,
|
|
examined_bytes: completedHeader.bytes.length,
|
|
prefix_sha256: createHash("sha256")
|
|
.update(completedHeader.bytes)
|
|
.digest("hex"),
|
|
},
|
|
}),
|
|
}),
|
|
);
|
|
}
|
|
};
|