Files
rea/src/rust/RustBinaryProvider.ts
T
morluto 99d2d7ad22 fix(parsers): retain independent facts and boundary identity
Share bounded ELF section-name decoding across native and note readers; retain read-only ranges and coverage reasons when names are unknown. Preserve ELF symbol-version bytes, dyld-bound method uncertainty, partial gRPC-Web trailers, and per-parent model identity checks. Compare GPU signatures as bytes and require a PTX module header. Share selected-path errno classification across EVM and BEAM.

Validation: 397 shared-parser tests passed (one existing skip), 5 MCP schema tests passed, 14 EVM error-mapping tests passed, and 23 protobuf CLI/MCP matrix cases passed across focused runs. Build, typecheck/lint, docs, formatting, and 2134-file discovery passed. Full EVM provider workflow remains locally unverified on Darwin arm64; format controls are synthetic.

Fixes #6738
Fixes #6737
Fixes #6745
Fixes #6740
Fixes #6749
Fixes #6747
Fixes #6739
2026-10-23 22:18:43 +08:00

356 lines
12 KiB
TypeScript

import { readStableArtifact } from "../artifacts/readStableArtifact.js";
import {
artifactFilePrimaryFailure,
artifactFileCancellationCause,
assertArtifactReadActive,
bindArtifactFileCleanup,
} from "../artifacts/ArtifactFileOperation.js";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import type { ExecutionOptions } from "../application/AnalysisProvider.js";
import type { RustBinaryPort } from "../application/rust/RustBinaryPort.js";
import { AnalysisError } from "../domain/analysisErrorBase.js";
import {
AnalysisAccessDeniedError,
AnalysisArtifactChangedError,
AnalysisCancelledError,
AnalysisInputError,
AnalysisOutputError,
AnalysisResourceConstraintError,
AnalysisUnsupportedTargetError,
} from "../domain/analysisErrorCore.js";
import {
RUST_AUDITABLE_FORMAT_VERSIONS,
rustBinarySchema,
type InspectRustBinaryInput,
type RustBinary,
} from "../domain/rust/rustBinary.js";
import { fixedAllowanceOptions } from "../domain/fixedAllowanceRemediation.js";
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
import { err, ok, type Result } from "../domain/result.js";
import {
readRustBinaryImage,
type RustBinaryImage,
} from "./RustBinaryImage.js";
import { requireSelectedNativeImage } from "../native/metadata/MachoSliceSelection.js";
import {
RUST_BINARY_INPUT_BYTES,
RUST_OUTPUT_BYTES,
RustAuditableFormatFailure,
RustAuditableResourceFailure,
} from "./RustAuditableReader.js";
/** Bundled reader identity. Embedded package versions are separate observed facts. */
export const RUST_BINARY_PROVIDER_IDENTITY = {
id: "rust-auditable",
name: "REA Rust dependency reader",
version: "1",
} as const;
const OPERATION = "inspect_rust_binary";
/** Stable in-process dependency inspection without subprocesses, temporary files, or a Rust toolchain. */
export class RustBinaryProvider implements RustBinaryPort {
readonly identity = RUST_BINARY_PROVIDER_IDENTITY;
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(
RUST_BINARY_PROVIDER_IDENTITY.id,
cause.cleanup.resources,
{ reason: cause.cleanup.reason },
{ cause, operation: OPERATION },
);
bindArtifactFileCleanup(cause, error);
throw error;
}
}
/** Read one dependency section and any inferred rustc commit from the selected file. */
async inspect(
input: InspectRustBinaryInput,
options?: ExecutionOptions,
): Promise<Result<RustBinary, AnalysisError>> {
try {
return await this.#resources.run(async () => {
const snapshot = await readStableArtifact(
input.path,
RUST_BINARY_INPUT_BYTES,
options?.signal,
undefined,
this.#resources,
);
assertArtifactReadActive(options?.signal);
const selected = requireSelectedNativeImage(
OPERATION,
input.path,
snapshot.bytes,
input.architecture,
);
const image = inFile(
readRustBinaryImage(selected.bytes),
selected.fileOffset,
);
const report = {
format: image.format,
architecture: image.architecture,
bits: image.bits,
byte_order: image.byte_order,
auditable_metadata: image.auditable_metadata,
rustc_commit: image.rustc_commit,
artifact: {
path: input.path,
sha256: snapshot.sha256,
bytes: snapshot.bytes.length,
},
...(selected.selection === null
? {}
: { selected_slice: selected.selection }),
limitations: limitationsFor(image),
};
if (
Buffer.byteLength(JSON.stringify(report), "utf8") > RUST_OUTPUT_BYTES
)
throw outputLimit(input.path);
const parsed = rustBinarySchema.safeParse(report);
if (!parsed.success)
throw new AnalysisOutputError(
OPERATION,
"Rust reader produced an invalid dependency report.",
{ cause: parsed.error },
);
assertArtifactReadActive(options?.signal);
return ok(parsed.data);
});
} catch (cause: unknown) {
return err(failureFor(cause, input.path));
}
}
}
/** Locate image-relative section and rustc-path offsets in the selected file. */
const inFile = (image: RustBinaryImage, fileOffset: number): RustBinaryImage =>
fileOffset === 0
? image
: {
...image,
auditable_metadata:
image.auditable_metadata === null
? null
: {
...image.auditable_metadata,
file_offset: image.auditable_metadata.file_offset + fileOffset,
},
rustc_commit:
image.rustc_commit === null
? null
: {
...image.rustc_commit,
hashes: image.rustc_commit.hashes.map((entry) => ({
...entry,
file_offset: entry.file_offset + fileOffset,
})),
},
};
const outputLimit = (path: string) =>
new AnalysisResourceConstraintError(
OPERATION,
"transport",
`Complete Rust metadata for ${path} exceeds the inline output budget; no partial success is returned.`,
{ maximum_output_bytes: RUST_OUTPUT_BYTES },
fixedAllowanceOptions("The Rust complete-report allowance", "report"),
);
const limitationsFor = (
image: ReturnType<typeof readRustBinaryImage>,
): string[] => [
"Embedded dependency metadata is file content. It does not authenticate package names, versions, sources, or the compiler that produced the binary.",
"Only the .dep-v0 dependency section and read-only compiler path strings are inspected. Functions, types, and runtime behavior are not recovered.",
"Resource guards bound the selected file to 256 MiB, a dependency section to 1 MiB, inflated JSON to the smaller of 8 MiB and 64 times the section size, and the complete report to 8 MiB. Package and dependency counts are admitted against that output budget before schema cloning, graph objects, or reverse-edge allocation.",
...(image.section_names_limitation === undefined
? []
: [image.section_names_limitation]),
...(image.auditable_metadata === null &&
image.section_names_limitation === undefined
? [
"No .dep-v0 section was found. Absence of .dep-v0 does not mean the binary has no dependencies.",
]
: []),
...(image.auditable_metadata === null &&
image.section_names_limitation !== undefined
? [
"The presence of .dep-v0 could not be determined because ELF section names are unavailable.",
]
: []),
...unknownFormatLimitation(image.auditable_metadata?.format_version),
...(image.auditable_metadata?.packages.some((pkg) => pkg.kind === null)
? [
"An omitted package kind is left null. The embedded format treats an omitted kind as runtime, and that default was not stored in the section.",
]
: []),
...(image.auditable_metadata !== null &&
image.auditable_metadata.cyclic_dependency_indices.length > 0
? [
"Dependency indices contain a cycle. The cycle is reported and was not traversed.",
]
: []),
...rustcLimitation(image.rustc_commit),
];
const unknownFormatLimitation = (
format: number | null | undefined,
): string[] => {
if (format === null || format === undefined) return [];
let preceding = 0;
for (const known of RUST_AUDITABLE_FORMAT_VERSIONS) {
if (known === format) return [];
if (known < format) preceding = known;
}
return [
`cargo-auditable format revision ${format} is unknown. Its schema directs readers to interpret it using the highest known preceding revision (${preceding}); this specific revision has not been independently verified.`,
];
};
const rustcLimitation = (
commit: ReturnType<typeof readRustBinaryImage>["rustc_commit"],
): string[] => {
if (commit === null)
return [
"No /rustc/<40-hex>/ path was found in read-only data, so a rustc commit hash is unknown.",
];
if (commit.hashes.length === 1)
return [
"The rustc commit hash is inferred from a /rustc/<40-hex>/ path in read-only data. It does not by itself prove which compiler produced the binary.",
];
return [
"Read-only data contains more than one /rustc/<40-hex>/ path, so a single rustc commit is unknown.",
];
};
const failureForPrimary = (cause: unknown, path: string): AnalysisError => {
if (cause instanceof AnalysisError) return cause;
if (cause instanceof RustAuditableResourceFailure)
return new AnalysisResourceConstraintError(
OPERATION,
cause.constraint.resource,
`Selected artifact ${path}: ${cause.message}`,
cause.constraint.reported_limits,
{
cause,
...fixedAllowanceOptions(
cause.constraint.resource === "capacity"
? "The rustc commit-path inventory"
: cause.constraint.resource === "transport"
? "The Rust complete-report allowance"
: "This Rust inspection allowance",
cause.constraint.resource === "capacity"
? "count"
: cause.constraint.resource === "transport"
? "report"
: "parser",
),
},
);
if (cause instanceof RustAuditableFormatFailure)
return cause.kind === "unsupported"
? new AnalysisUnsupportedTargetError(OPERATION, path, cause.message, {
cause,
})
: new AnalysisInputError(OPERATION, { cause }, [
{
path: ["path"],
reason: "invalid_format",
message: `Selected artifact ${path}: ${cause.message}`,
},
]);
return artifactFailure(cause, path);
};
const failureFor = (cause: unknown, path: string): AnalysisError => {
const primary = failureForPrimary(artifactFilePrimaryFailure(cause), path);
if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined)
return primary;
const cleanup = new ProviderCleanupError(
RUST_BINARY_PROVIDER_IDENTITY.id,
cause.cleanup.resources,
{ reason: cause.cleanup.reason },
{ cause, operation: OPERATION },
);
const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION);
bindArtifactFileCleanup(cause, error);
return error;
};
const artifactFailure = (cause: unknown, path: string): AnalysisError => {
if (cause instanceof ArtifactReaderFailure)
return artifactReaderFailure(cause, path);
if (cause instanceof Error && "code" in cause) {
if (cause.code === "EACCES" || cause.code === "EPERM")
return new AnalysisAccessDeniedError(OPERATION, 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 artifact could not be read (${String(cause.code)}): ${path}.`,
},
]);
}
return new ProviderAdapterError(RUST_BINARY_PROVIDER_IDENTITY.id, OPERATION, {
cause,
diagnostics: {
path,
reason: cause instanceof Error ? cause.message : String(cause),
},
});
};
const artifactReaderFailure = (
cause: ArtifactReaderFailure,
path: string,
): AnalysisError => {
if (cause.reason === "integrity")
return new AnalysisArtifactChangedError(OPERATION, path, cause.message, {
cause,
});
if (cause.reason === "cancelled")
return new AnalysisCancelledError(OPERATION, {
cause: artifactFileCancellationCause(cause),
});
if (cause.reason === "limit")
return new AnalysisResourceConstraintError(
OPERATION,
"file-size",
cause.message,
{ maximum_input_bytes: RUST_BINARY_INPUT_BYTES },
{
cause,
...fixedAllowanceOptions("the fixed Rust input allowance", "input"),
},
);
if (cause.reason === "path")
return new AnalysisInputError(OPERATION, { cause }, [
{ path: ["path"], reason: "invalid_format", message: cause.message },
]);
return new ProviderAdapterError(RUST_BINARY_PROVIDER_IDENTITY.id, OPERATION, {
cause,
diagnostics: { path, reason: cause.message },
});
};