mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
* refactor: report NativeAOT, Interface Builder and owned-MCP output limits through the shared limit owners * refactor(errors): carry a parser's required value on its limit reference and in adjustable-budget refusals (#11201) * fix(android): keep the JADX output-limit refusal's retained process output * refactor(native): name PE and native-export allowances in the BinaryResourceFailure limit (#11253) PeImportLimitFailure, PeLoadConfigLimitFailure and NativeExportLimitFailure kept a parallel `limit` field beside the LimitRef their BinaryResourceFailure base already carries (named only "structure"). The allowance name now lives in the LimitRef, consumers read it with binaryResourceAllowance against the parser's exported allowance list, and the three subclasses are removed. The local-limit-error-class guard also counts limit subclasses of BinaryResourceFailure.
204 lines
7.0 KiB
TypeScript
204 lines
7.0 KiB
TypeScript
import { parseFailureAnalysisError } from "../../domain/parseFailureAnalysisError.js";
|
|
import {
|
|
projectOwnedArtifactIdentityFailure,
|
|
projectSelectedArtifactFileFailure,
|
|
projectStableArtifactFailure,
|
|
stableArtifactInputFailure,
|
|
projectArtifactCloseFailure,
|
|
} from "../../artifacts/StableArtifactFailure.js";
|
|
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
|
|
import {
|
|
type StableArtifactSnapshot,
|
|
readStableArtifact,
|
|
} from "../../artifacts/readStableArtifact.js";
|
|
import {
|
|
artifactFilePrimaryFacts,
|
|
type ArtifactFilePrimaryFacts,
|
|
} from "../../artifacts/ArtifactFileOperation.js";
|
|
import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
|
import type { ElfNotesPort } from "../../application/binaryDiagnostics/ElfNotesPort.js";
|
|
import type { ExecutionOptions } from "../../application/AnalysisProvider.js";
|
|
import { ArtifactOperationError } from "../../domain/artifactOperationError.js";
|
|
import { 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 { ELF_NOTES_PROVIDER } from "../../domain/investigationProviders.js";
|
|
import { elfNoteSectionNameBudget, 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) {
|
|
throw projectArtifactCloseFailure(
|
|
cause,
|
|
ELF_NOTES_PROVIDER.id,
|
|
OPERATION,
|
|
);
|
|
}
|
|
}
|
|
|
|
async inspect(input: InspectElfNotesInput, options?: ExecutionOptions) {
|
|
let snapshot: StableArtifactSnapshot | undefined;
|
|
try {
|
|
return await this.#resources.run(async () => {
|
|
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, snapshot));
|
|
}
|
|
}
|
|
}
|
|
|
|
const NOTE_TEXT_BUDGET =
|
|
"The ELF note text, hash, and scan budget fixed by the admitted image length plus decoded section names";
|
|
|
|
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 sectionNameBudgetRemediation = (required: bigint): string =>
|
|
required <= BigInt(MAX_INSPECT_ELF_NOTE_FILE_BYTES)
|
|
? `Raise max_file_bytes to at least ${String(required)} bytes to admit the source and decoded ELF section names.`
|
|
: `The source and decoded ELF section names require ${String(required)} bytes, above this operation's ${String(MAX_INSPECT_ELF_NOTE_FILE_BYTES)}-byte maximum; select an image with a smaller section-name table.`;
|
|
|
|
const elfNotesFailurePrimary = (
|
|
facts: ArtifactFilePrimaryFacts,
|
|
input: InspectElfNotesInput,
|
|
) => {
|
|
const { cause, origin } = facts;
|
|
const sectionNameBudget = elfNoteSectionNameBudget(cause);
|
|
if (sectionNameBudget !== undefined)
|
|
return new AnalysisResourceConstraintError(
|
|
OPERATION,
|
|
"memory",
|
|
sectionNameBudget.detail,
|
|
{
|
|
max_file_bytes: sectionNameBudget.selected,
|
|
minimum_required_bytes: sectionNameBudget.required.toString(),
|
|
},
|
|
{
|
|
cause,
|
|
retryable: false,
|
|
remediationAction: sectionNameBudgetRemediation(
|
|
sectionNameBudget.required,
|
|
),
|
|
},
|
|
);
|
|
const formatFailure = parseFailureAnalysisError(OPERATION, input.path, cause);
|
|
if (formatFailure !== undefined) return formatFailure;
|
|
const identityFailure = projectOwnedArtifactIdentityFailure(
|
|
facts,
|
|
OPERATION,
|
|
input.path,
|
|
);
|
|
if (identityFailure !== undefined) return identityFailure;
|
|
if (
|
|
origin === "owned-or-classified" &&
|
|
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 plus decoded section names, 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 stableArtifactInputFailure(cause, OPERATION);
|
|
}
|
|
const fileFailure = projectSelectedArtifactFileFailure(
|
|
cause,
|
|
OPERATION,
|
|
input.path,
|
|
"ELF image",
|
|
);
|
|
if (fileFailure !== undefined) return fileFailure;
|
|
return new ArtifactOperationError(
|
|
OPERATION,
|
|
origin === "owned-or-classified" && cause instanceof ArtifactReaderFailure
|
|
? cause.reason
|
|
: "io",
|
|
undefined,
|
|
cause instanceof Error ? cause.message : "ELF note inspection failed.",
|
|
{ cause },
|
|
);
|
|
};
|
|
|
|
const elfNotesFailure = (
|
|
cause: unknown,
|
|
input: InspectElfNotesInput,
|
|
snapshot?: StableArtifactSnapshot,
|
|
) => {
|
|
const primary = elfNotesFailurePrimary(
|
|
artifactFilePrimaryFacts(cause),
|
|
input,
|
|
);
|
|
return projectStableArtifactFailure(
|
|
cause,
|
|
primary,
|
|
input.path,
|
|
ELF_NOTES_PROVIDER.id,
|
|
OPERATION,
|
|
snapshot,
|
|
);
|
|
};
|