diff --git a/src/native/metadata/ElfImage.ts b/src/native/metadata/ElfImage.ts index 1fba9c8277..5e1ec28571 100644 --- a/src/native/metadata/ElfImage.ts +++ b/src/native/metadata/ElfImage.ts @@ -8,6 +8,7 @@ import { type LoadRegion, type ZeroFillMapping, } from "./BinaryContainer.js"; +import { elfArchitecture } from "../../domain/elfFixedHeader.js"; import { readNativeDirectoryFromBuffer } from "./NativeSectionDirectory.js"; /** Read ELF sections and file-backed program mappings without interpreting language metadata. */ @@ -105,17 +106,27 @@ export const readElfImage = (bytes: Buffer): BinaryContainer => { /** Map ELF machine identifiers to stable architecture names. */ export const architectureForElfMachine = (machine: number): string => { - const names = new Map([ - [3, "386"], - [8, "mips"], - [20, "ppc"], - [21, "ppc64"], - [22, "s390"], - [40, "arm"], - [62, "amd64"], - [183, "arm64"], - [243, "riscv"], - [258, "loongarch"], - ]); - return names.get(machine) ?? `elf-machine-${machine}`; + const architecture = elfArchitecture(machine); + if (architecture !== undefined) { + switch (architecture) { + case "x86": + return "386"; + case "x86_64": + return "amd64"; + case "powerpc": + return "ppc"; + default: + return architecture; + } + } + switch (machine) { + case 21: + return "ppc64"; + case 22: + return "s390"; + case 258: + return "loongarch"; + default: + return `elf-machine-${machine}`; + } }; diff --git a/tests/acceptance/nativeElfArchitecture.test.ts b/tests/acceptance/nativeElfArchitecture.test.ts new file mode 100644 index 0000000000..22c91e2ff6 --- /dev/null +++ b/tests/acceptance/nativeElfArchitecture.test.ts @@ -0,0 +1,92 @@ +import { goBinarySchema } from "../../src/domain/go/goBinary.js"; +import { rustBinarySchema } from "../../src/domain/rust/rustBinary.js"; +import { expect } from "vitest"; +import { toolContract } from "../../src/contracts/toolContracts.js"; +import { createGoBinaryFixture } from "../fixtures/go/image.js"; +import { createRustImage } from "../fixtures/rust/image.js"; +import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js"; +import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js"; +import { join } from "node:path"; +import { writeFile } from "node:fs/promises"; +import { cliTest } from "../support/cli/cliFixture.js"; + +cliTest( + "preserves classifier-known Hexagon ELF identity through Go and Rust CLI/MCP", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-elf-machine-identity-"); + const { client, call } = await connectLocalToolsMcp(); + for (const language of ["go", "rust"] as const) { + const operation = + language === "go" ? "inspect_go_binary" : "inspect_rust_binary"; + const command = + language === "go" ? "inspect-go-binary" : "inspect-rust-binary"; + for (const item of [ + { machine: 164, architecture: "hexagon", subject: "hexagon" }, + { machine: 62, architecture: "amd64", subject: "x86_64" }, + ]) { + const bytes = + language === "go" + ? createGoBinaryFixture({ omitBuildInfo: true }).bytes + : createRustImage({ + format: "elf", + omitSection: true, + includeRustc: true, + }); + bytes.writeUInt16LE(item.machine, 18); + const path = join(root, `${language}-${item.machine}.elf`); + await writeFile(path, bytes); + + const cliResponse = await cli.run({ + arguments: [command, path, "--json"], + }); + expect( + cliResponse.exitCode, + `${cliResponse.stderr}\n${JSON.stringify(cliResponse.json)}`, + ).toBe(0); + const cliEvidence = toolContract(operation).outputSchema.parse( + cliResponse.json, + ); + const cliReport = + language === "go" + ? goBinarySchema.parse(cliEvidence.normalized_result) + : rustBinarySchema.parse(cliEvidence.normalized_result); + expect(cliReport.architecture).toBe(item.architecture); + expect(cliEvidence.subject?.architecture).toBe(item.subject); + + const mcpResponse = await call(operation, { path }); + expect(mcpResponse.isError, JSON.stringify(mcpResponse)).not.toBe(true); + const mcpEvidence = toolContract(operation).outputSchema.parse( + mcpResponse.structuredContent, + ); + const mcpReport = + language === "go" + ? goBinarySchema.parse(mcpEvidence.normalized_result) + : rustBinarySchema.parse(mcpEvidence.normalized_result); + expect(mcpReport.architecture).toBe(item.architecture); + expect(mcpEvidence.subject?.architecture).toBe(item.subject); + } + + const malformed = + language === "go" + ? createGoBinaryFixture({ omitBuildInfo: true }).bytes + : createRustImage({ + format: "elf", + omitSection: true, + includeRustc: true, + }); + malformed[0] = 0; + const path = join(root, `${language}-malformed.elf`); + await writeFile(path, malformed); + const cliFailure = await cli.run({ + arguments: [command, path, "--json"], + }); + expect(cliFailure.exitCode).not.toBe(0); + expect(cliFailure.json).not.toHaveProperty("normalized_result"); + const mcpFailure = await call(operation, { path }); + expect(mcpFailure.isError).toBe(true); + expect(mcpFailure.structuredContent).toBeUndefined(); + } + await client.ping(); + }, + 30000, +); diff --git a/tests/boundary/go/goBinaryImage.test.ts b/tests/boundary/go/goBinaryImage.test.ts index ad1b0c8271..bcaa8c619d 100644 --- a/tests/boundary/go/goBinaryImage.test.ts +++ b/tests/boundary/go/goBinaryImage.test.ts @@ -17,6 +17,16 @@ import { GO_MODULE_TEXT, } from "../../fixtures/go/image.js"; +it("uses the shared ELF classifier for Go image architecture", () => { + const fixture = createGoBinaryFixture({ omitBuildInfo: true }); + fixture.bytes.writeUInt16LE(164, 18); // ELF EM_HEXAGON. + expect(readGoBinaryImage(fixture.bytes)).toMatchObject({ + format: "elf", + architecture: "hexagon", + build_info: null, + }); +}); + it("preserves exact Mach-O protection masks for Go fallback selection", () => { const { bytes } = createGoBinaryFixture({ format: "macho", diff --git a/tests/boundary/rust/rustBinaryImage.test.ts b/tests/boundary/rust/rustBinaryImage.test.ts index 354ab2f72a..718237cbf4 100644 --- a/tests/boundary/rust/rustBinaryImage.test.ts +++ b/tests/boundary/rust/rustBinaryImage.test.ts @@ -19,6 +19,7 @@ const packages = [ source: "crates.io", kind: null, dependencies: [], + features: [], root: false, }, { @@ -27,6 +28,7 @@ const packages = [ source: "local", kind: null, dependencies: [0], + features: [], root: true, }, ]; @@ -87,6 +89,20 @@ it("preserves the arm64_32 Mach-O CPU family when dependency metadata is absent" }); }); +it("uses the shared ELF classifier for Rust image architecture", () => { + const bytes = createRustImage({ + format: "elf", + omitSection: true, + includeRustc: true, + }); + bytes.writeUInt16LE(164, 18); // ELF EM_HEXAGON. + expect(readRustBinaryImage(bytes)).toMatchObject({ + format: "elf", + architecture: "hexagon", + auditable_metadata: null, + }); +}); + it("ignores PE file-alignment padding after the dependency content", () => { const bytes = createRustImage({ format: "pe", includeRustc: false }); const payloadBytes = rustDependencyPayload().length;