Files
rea/src/application/binaryDiagnostics/ElfNotesService.ts
T
Matt Van Horn 2edf3b23c1 feat(elf): inspect PT_NOTE and SHT_NOTE tables (#4837)
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.
2026-10-22 15:02:23 +08:00

128 lines
4.0 KiB
TypeScript

import { isAbsolute } from "node:path";
import type { ExecutionOptions } from "../AnalysisProvider.js";
import type { ElfNotesPort } from "./ElfNotesPort.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import {
AnalysisCancelledError,
AnalysisInputError,
AnalysisOutputError,
} from "../../domain/analysisErrorCore.js";
import { createEvidence, type Evidence } from "../../domain/evidence.js";
import {
inspectElfNotesInputSchema,
elfNotesSchema,
type ElfNotes,
} from "../../domain/native/elfNotes.js";
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
import { BINARY_ARCHITECTURES } from "../../domain/binaryTargetTypes.js";
import { elfArchitecture } from "../../domain/elfFixedHeader.js";
import { err, type Result } from "../../domain/result.js";
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
const OPERATION = "inspect_elf_notes";
/** Shared caller validation, output parsing and Evidence provenance for CLI/MCP. */
export class ElfNotesService {
constructor(readonly provider: ElfNotesPort) {}
close(): Promise<void> {
return this.provider.close?.() ?? Promise.resolve();
}
async inspect(
rawInput: unknown,
options?: ExecutionOptions,
): Promise<Result<Evidence, AnalysisError>> {
if (options?.signal?.aborted)
return err(new AnalysisCancelledError(OPERATION));
const input = inspectElfNotesInputSchema.safeParse(rawInput);
if (!input.success)
return err(
analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, {
cause: input.error,
}),
);
if (!isAbsolute(input.data.path))
return err(
new AnalysisInputError(OPERATION, undefined, [
{
path: ["path"],
reason: "invalid_format",
message: "Expected an absolute filesystem path on this host.",
},
]),
);
const result = await this.provider.inspect(input.data, options);
if (!result.ok) return result;
const report = elfNotesSchema.safeParse(result.value);
if (
!report.success ||
report.data.artifact.path !== input.data.path ||
!validReport(
report.data,
input.data.max_entries,
input.data.max_file_bytes,
)
)
return err(
new AnalysisOutputError(
OPERATION,
"ELF note provider returned malformed output or changed artifact identity.",
),
);
const value = report.data;
const architecture = BINARY_ARCHITECTURES.find(
(supported) => supported === elfArchitecture(value.machine),
);
const locations = [
{ kind: "artifact-path" as const, path: value.artifact.path },
...value.tables.map((table) => ({
kind: "file-offset-range" as const,
start: table.location.offset,
end: table.location.offset + table.location.bytes,
})),
];
return completeProviderEvidence(
createEvidence(
{
path: value.artifact.path,
format: "elf",
sha256: value.artifact.sha256,
...(architecture === undefined ? {} : { architecture }),
},
this.provider.identity,
{
operation: OPERATION,
parameters: input.data,
result: value,
confidence: "observed",
limitations: value.limitations,
locations,
},
),
options?.signal,
);
}
}
const validReport = (
report: ElfNotes,
maxEntries: number,
maxBytes: number,
): boolean => {
const inside = ({ offset, bytes }: { offset: number; bytes: number }) =>
offset <= report.artifact.bytes - bytes;
if (
report.artifact.bytes > maxBytes ||
report.coverage.examined_entries > maxEntries ||
report.entries.length > maxEntries
)
return false;
return (
report.tables.every((table) => inside(table.location)) &&
report.entries.every(
(entry) => inside(entry.location) && inside(entry.descriptor.location),
)
);
};