mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
2edf3b23c1
Inspect selected ELF32/ELF64 PT_NOTE and SHT_NOTE declarations without executing the artifact, retaining independent table identities, source ranges, descriptor digests, GNU build-id/ABI fields and explicit coverage limitations. Integrate mvanhorn's implementation and hardening with current main while preserving byte-entropy registration and the Windows curated regression suite. The maintainer follow-up uses ordinary merges/pushes, not history rewriting. Executed validation: 96 focused/shared/docs tests, complete static/docs checks, eleven real CLI/MCP pairs including GCC ELF32 and Clang big-endian PowerPC64, and two actual Codex task traces checked against their Evidence. Full costs, grader correction and verification limits are recorded in PR #4837. Refs #6262.
208 lines
7.2 KiB
TypeScript
208 lines
7.2 KiB
TypeScript
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
|
|
import { readStableArtifact } from "../../artifacts/readStableArtifact.js";
|
|
import {
|
|
ArtifactFileCleanupFailure,
|
|
bindArtifactFileCleanup,
|
|
artifactFileCancellationCause,
|
|
} from "../../artifacts/ArtifactFileOperation.js";
|
|
import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
|
import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js";
|
|
import type { ElfNotesPort } from "../../application/binaryDiagnostics/ElfNotesPort.js";
|
|
import type { ExecutionOptions } from "../../application/AnalysisProvider.js";
|
|
import { ArtifactOperationError } from "../../domain/artifactOperationError.js";
|
|
import {
|
|
AnalysisAccessDeniedError,
|
|
AnalysisArtifactChangedError,
|
|
AnalysisCancelledError,
|
|
AnalysisInputError,
|
|
AnalysisResourceConstraintError,
|
|
} from "../../domain/analysisErrorCore.js";
|
|
import { fixedAllowanceOptions } from "../../domain/fixedAllowanceRemediation.js";
|
|
import {
|
|
MAX_INSPECT_ELF_NOTE_ENTRIES,
|
|
MAX_INSPECT_ELF_NOTE_FILE_BYTES,
|
|
type InspectElfNotesInput,
|
|
} from "../../domain/native/elfNotes.js";
|
|
import { err, ok } from "../../domain/result.js";
|
|
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
|
|
import { ELF_NOTES_PROVIDER } from "../../application/InvestigationProviders.js";
|
|
import { parseElfNotes } from "./ElfNoteParser.js";
|
|
|
|
const OPERATION = "inspect_elf_notes";
|
|
|
|
/** Portable parser over one bounded, identity-checked regular file. */
|
|
export class ElfNotesProvider implements ElfNotesPort {
|
|
readonly identity = ELF_NOTES_PROVIDER;
|
|
readonly #resources = new ArtifactResourceScope();
|
|
|
|
async close(): Promise<void> {
|
|
try {
|
|
await this.#resources.close();
|
|
} catch (cause: unknown) {
|
|
if (
|
|
!(cause instanceof ArtifactReaderFailure) ||
|
|
cause.cleanup === undefined
|
|
)
|
|
throw cause;
|
|
const error = new ProviderCleanupError(
|
|
ELF_NOTES_PROVIDER.id,
|
|
cause.cleanup.resources,
|
|
{ reason: cause.cleanup.reason },
|
|
{ cause, operation: OPERATION },
|
|
);
|
|
bindArtifactFileCleanup(cause, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async inspect(input: InspectElfNotesInput, options?: ExecutionOptions) {
|
|
try {
|
|
return await this.#resources.run(async () => {
|
|
const snapshot = await readStableArtifact(
|
|
input.path,
|
|
input.max_file_bytes,
|
|
options?.signal,
|
|
undefined,
|
|
this.#resources,
|
|
);
|
|
return ok(
|
|
parseElfNotes(
|
|
snapshot.bytes,
|
|
{
|
|
path: input.path,
|
|
sha256: snapshot.sha256,
|
|
bytes: snapshot.bytes.length,
|
|
},
|
|
input,
|
|
),
|
|
);
|
|
});
|
|
} catch (cause) {
|
|
return err(elfNotesFailure(cause, input, options?.signal));
|
|
}
|
|
}
|
|
}
|
|
|
|
const NOTE_TEXT_BUDGET =
|
|
"The ELF note text, hash, and scan budget tied to the admitted image length";
|
|
|
|
const inputBudgetRemediation = (
|
|
boundary: "file-size" | "capacity",
|
|
input: InspectElfNotesInput,
|
|
): string =>
|
|
boundary === "file-size"
|
|
? input.max_file_bytes >= MAX_INSPECT_ELF_NOTE_FILE_BYTES
|
|
? `The image exceeds the ${String(MAX_INSPECT_ELF_NOTE_FILE_BYTES)}-byte input ceiling this operation accepts; select a smaller artifact.`
|
|
: `Raise max_file_bytes above ${String(input.max_file_bytes)} bytes or select a smaller artifact; the limit is the caller-selected input read budget.`
|
|
: input.max_entries >= MAX_INSPECT_ELF_NOTE_ENTRIES
|
|
? `The image exceeds the ${String(MAX_INSPECT_ELF_NOTE_ENTRIES)}-entry note-table ceiling this operation accepts; select a smaller artifact.`
|
|
: `Raise max_entries above ${String(input.max_entries)} or select a smaller artifact; the limit is the caller-selected note entry budget.`;
|
|
|
|
const elfNotesFailurePrimary = (
|
|
cause: unknown,
|
|
input: InspectElfNotesInput,
|
|
) => {
|
|
if (cause instanceof ArtifactReaderFailure && cause.reason === "integrity")
|
|
return new AnalysisArtifactChangedError(
|
|
OPERATION,
|
|
input.path,
|
|
cause.message,
|
|
{
|
|
cause,
|
|
},
|
|
);
|
|
if (cause instanceof ArtifactReaderFailure && cause.reason === "cancelled")
|
|
return new AnalysisCancelledError(OPERATION, {
|
|
cause: artifactFileCancellationCause(cause),
|
|
});
|
|
if (cause instanceof ArtifactReaderFailure) {
|
|
if (cause.reason === "limit") {
|
|
// Caller-selected read and entry budgets name the bound to raise.
|
|
// The note text, hash, and scan ceiling equals the admitted image
|
|
// length, so host memory cannot raise it.
|
|
const noteTextBudget = cause.boundary === "memory";
|
|
return new AnalysisResourceConstraintError(
|
|
OPERATION,
|
|
cause.boundary ?? "memory",
|
|
cause.message,
|
|
{
|
|
max_file_bytes: input.max_file_bytes,
|
|
max_entries: input.max_entries,
|
|
},
|
|
{
|
|
cause,
|
|
...(cause.boundary === undefined
|
|
? {}
|
|
: noteTextBudget
|
|
? fixedAllowanceOptions(NOTE_TEXT_BUDGET, "parser")
|
|
: {
|
|
retryable: false as const,
|
|
remediationAction: inputBudgetRemediation(
|
|
cause.boundary,
|
|
input,
|
|
),
|
|
}),
|
|
},
|
|
);
|
|
}
|
|
if (cause.reason === "path" || cause.reason === "format")
|
|
return new AnalysisInputError(OPERATION, { cause }, [
|
|
{
|
|
path: ["path"],
|
|
reason: "invalid_format",
|
|
message: cause.message,
|
|
},
|
|
]);
|
|
}
|
|
if (cause instanceof Error && "code" in cause) {
|
|
if (cause.code === "EACCES" || cause.code === "EPERM")
|
|
return new AnalysisAccessDeniedError(OPERATION, input.path, cause.code, {
|
|
cause,
|
|
});
|
|
if (
|
|
["ENOENT", "ENOTDIR", "ELOOP", "ENAMETOOLONG"].includes(
|
|
String(cause.code),
|
|
)
|
|
)
|
|
return new AnalysisInputError(OPERATION, { cause }, [
|
|
{
|
|
path: ["path"],
|
|
reason: "invalid_value",
|
|
message: `Selected ELF image could not be read (${String(cause.code)}): ${input.path}.`,
|
|
},
|
|
]);
|
|
}
|
|
return new ArtifactOperationError(
|
|
OPERATION,
|
|
cause instanceof ArtifactReaderFailure ? cause.reason : "io",
|
|
undefined,
|
|
cause instanceof Error ? cause.message : "ELF note inspection failed.",
|
|
{ cause },
|
|
);
|
|
};
|
|
|
|
const elfNotesFailure = (
|
|
cause: unknown,
|
|
input: InspectElfNotesInput,
|
|
signal: AbortSignal | undefined,
|
|
) => {
|
|
const primary =
|
|
cause instanceof ArtifactFileCleanupFailure &&
|
|
cause.fileCleanupFailure.outcome.kind === "failed"
|
|
? elfNotesFailurePrimary(cause.fileCleanupFailure.outcome.cause, input)
|
|
: cause instanceof ArtifactFileCleanupFailure && signal?.aborted
|
|
? new AnalysisCancelledError(OPERATION, { cause: signal.reason })
|
|
: elfNotesFailurePrimary(cause, input);
|
|
if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined)
|
|
return primary;
|
|
const cleanup = new ProviderCleanupError(
|
|
ELF_NOTES_PROVIDER.id,
|
|
cause.cleanup.resources,
|
|
{ reason: cause.cleanup.reason },
|
|
{ operation: OPERATION, cause },
|
|
);
|
|
const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION);
|
|
bindArtifactFileCleanup(cause, error);
|
|
return error;
|
|
};
|