[GH-ISSUE #4197] [Bug] Go and Rust binary inspection omit the canonical PowerPC subject architecture #1647

Closed
opened 2026-10-16 21:08:23 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @coygeek on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4197

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

The public inspect-go-binary and inspect-rust-binary commands return subject.architecture: null for accepted ELF32 headers declaring machine 20, EM_PPC. Their reports retain normalized_result.architecture: "ppc", and the owning ELF header classifier recognizes the identical bytes as canonical powerpc. The lost fact is the Evidence subject architecture; the local ppc alias can remain unchanged.

Reproduction

Use the source checkout and toolchain stated under REA version and Runtime and platform, install its locked dependencies, and run npm run build:cached. Run the following block from the repository root. It creates synthetic ELF header fixtures, isolates client home/configuration, and invokes the real public CLI with the bundled readers. The fixtures have no program or section tables and contain no compiler metadata. They are not compiler-built Go or Rust programs.

work_dir="$(mktemp -d)"
trap 'rm -rf "$work_dir"' EXIT
node --input-type=module - "$work_dir" <<'NODE'
import { mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
const work = process.argv[2];
const home = join(work, 'home');
mkdirSync(home);
const env = {
  PATH: process.env.PATH,
  HOME: home,
  XDG_CONFIG_HOME: join(home, 'config'),
  XDG_CACHE_HOME: join(home, 'cache'),
  REA_LOG_LEVEL: 'silent',
};
for (const [name, machine, bits, little] of [
  ['ppc32be', 20, 32, false],
  ['ppc32le', 20, 32, true],
  ['x86', 3, 32, true],
  ['ppc64', 21, 64, false],
]) {
  const bytes = Buffer.alloc(bits === 32 ? 52 : 64);
  bytes.set([0x7f, 0x45, 0x4c, 0x46, bits === 32 ? 1 : 2, little ? 1 : 2, 1]);
  const u16 = little ? 'writeUInt16LE' : 'writeUInt16BE';
  const u32 = little ? 'writeUInt32LE' : 'writeUInt32BE';
  bytes[u16](2, 16);
  bytes[u16](machine, 18);
  bytes[u32](1, 20);
  bytes[u16](bytes.length, bits === 32 ? 40 : 52);
  const path = join(work, `${name}.elf`);
  writeFileSync(path, bytes);
  for (const command of ['inspect-go-binary', 'inspect-rust-binary']) {
    const child = spawnSync(process.execPath, [
      'scripts/rea.mjs', command, path, '--json',
    ], { env, encoding: 'utf8', timeout: 30000 });
    if (child.error || child.status !== 0)
      throw child.error ?? new Error(child.stderr || child.stdout);
    const result = JSON.parse(child.stdout);
    console.log(JSON.stringify({
      command, fixture: name,
      report_architecture: result.normalized_result.architecture,
      subject_architecture: result.subject.architecture,
      bits: result.normalized_result.bits,
      byte_order: result.normalized_result.byte_order,
    }));
  }
}
NODE

Both commands print the following architecture pairs for each fixture: ppc32be and ppc32le return "ppc" / null; the x86 control returns "386" / "x86"; and the ppc64 control returns "ppc64" / null.

Expected behavior

For an accepted ELF declaring EM_PPC, retain the recognized family as subject.architecture: "powerpc". Recognition is independent of missing Go build information or a Rust dependency section, and does not establish compiler identity, ISA/ABI or deep-provider support. Local report and canonical subject names need not match, as the working 386 / x86 control demonstrates. EM_PPC64 must not be relabeled as this family.

The canonical family list, owning ELF classifier, and Evidence subject contract ground this expectation. The PowerPC header checks cover this header layout and both byte orders. The Go observations and missing-metadata contract and Rust observations and missing-section contract permit the fixtures' absent language metadata.

Impact and scope

P2, supported. Both successful inspection commands lose a recognized CPU family in their machine-readable Evidence subjects for the accepted PowerPC headers. Local architecture and other header observations remain accessible. This is a bounded partial-result defect; no severe harm or whole-workflow failure was demonstrated. A subject-restoring workaround is unverified. Reading the retained ppc field lets an analyst identify the family but leaves the emitted subject null.

REA version

Source checkout 62bf09809ef7bb224028916424f8f07652a20f4f, freshly built with npm run build:cached; launcher reports 6.5.0. This is source-checkout verification, not an installed released-package reproduction. Relevant source is unchanged on current main c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb, inspected rather than runtime-reproduced at that revision.

Runtime and platform

Node.js v24.18.0 and npm 11.16.0 on macOS arm64. No Go/Rust compiler, disassembler or external provider was used, and no target code ran. Stdio MCP and other host platforms were not exercised.

Target and artifact context

Synthetic accepted ELF headers with valid magic, class, byte order, header versions and extent. Both ELF32 PowerPC byte orders were checked. Program/section table offsets and counts are zero; the fixtures contain no compiler metadata and are not compiler-built Go or Rust programs.

Interface

CLI

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

Both commands succeeded with exit status 0 and empty stderr for both endian variants of EM_PPC, reporting 32 bits and the declared byte order, but leaving subject.architecture null. The Go result had build_info: null; the Rust result had auditable_metadata: null, consistent with the fixture contents. The same bytes passed the production header classifier as powerpc. Every result passed its canonical public output schema and retained the selected artifact's SHA-256. Schema acceptance therefore does not establish preservation of this known subject fact.

The x86 control succeeded through both commands with normalized_result.architecture: "386" and subject.architecture: "x86". Additional executed amd64 controls returned "amd64" / "x86_64". Executed EM_PPC64, EM_S390, EM_LOONGARCH and unknown-machine controls retained null subjects, and the owning header classifier rejected those families. This report does not request additional family support.

Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete for the stated accepted-header CLI cases. Production factories and bundled readers were used without an injected provider. Absolute-path, report-schema and artifact-identity checks accepted the fixtures. Existing focused checks for binaryTarget.powerpc, GoBinaryService and RustBinaryService passed 14 tests in three files. They do not establish restored behavior.

Typed outcome or recovery expected

Run the same fixtures through both public commands. EM_PPC should yield subject powerpc while retaining artifact identity, declared width, byte order and missing-metadata results. Preserve legitimate local aliases, including 386 with subject x86. Keep EM_PPC64 and other unrecognized canonical families null. No implementation or successful restoration has been tested.

Analysis or suspected cause

The ELF reader produces local ppc for machine 20. The Go subject mapper and Rust subject mapper translate x86 aliases but do not recognize ppc as canonical powerpc. Evidence construction stores the omitted subject architecture as null. This source-derived explanation matches the successful CLI results and preserves freedom to correct the owning representation or translation.

Additional context

This report requests preservation of the already supported EM_PPC family in these metadata results. It does not request new canonical families, Ghidra/Hopper/IDA support or equal local/canonical architecture spellings. Compiler-built PowerPC programs, stdio MCP, other hosts and installed released packages were not verified.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4197 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior The public `inspect-go-binary` and `inspect-rust-binary` commands return `subject.architecture: null` for accepted ELF32 headers declaring machine 20, `EM_PPC`. Their reports retain `normalized_result.architecture: "ppc"`, and the owning ELF header classifier recognizes the identical bytes as canonical `powerpc`. The lost fact is the Evidence subject architecture; the local `ppc` alias can remain unchanged. ### Reproduction Use the source checkout and toolchain stated under REA version and Runtime and platform, install its locked dependencies, and run `npm run build:cached`. Run the following block from the repository root. It creates synthetic ELF header fixtures, isolates client home/configuration, and invokes the real public CLI with the bundled readers. The fixtures have no program or section tables and contain no compiler metadata. They are not compiler-built Go or Rust programs. ```sh work_dir="$(mktemp -d)" trap 'rm -rf "$work_dir"' EXIT node --input-type=module - "$work_dir" <<'NODE' import { mkdirSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { spawnSync } from 'node:child_process'; const work = process.argv[2]; const home = join(work, 'home'); mkdirSync(home); const env = { PATH: process.env.PATH, HOME: home, XDG_CONFIG_HOME: join(home, 'config'), XDG_CACHE_HOME: join(home, 'cache'), REA_LOG_LEVEL: 'silent', }; for (const [name, machine, bits, little] of [ ['ppc32be', 20, 32, false], ['ppc32le', 20, 32, true], ['x86', 3, 32, true], ['ppc64', 21, 64, false], ]) { const bytes = Buffer.alloc(bits === 32 ? 52 : 64); bytes.set([0x7f, 0x45, 0x4c, 0x46, bits === 32 ? 1 : 2, little ? 1 : 2, 1]); const u16 = little ? 'writeUInt16LE' : 'writeUInt16BE'; const u32 = little ? 'writeUInt32LE' : 'writeUInt32BE'; bytes[u16](2, 16); bytes[u16](machine, 18); bytes[u32](1, 20); bytes[u16](bytes.length, bits === 32 ? 40 : 52); const path = join(work, `${name}.elf`); writeFileSync(path, bytes); for (const command of ['inspect-go-binary', 'inspect-rust-binary']) { const child = spawnSync(process.execPath, [ 'scripts/rea.mjs', command, path, '--json', ], { env, encoding: 'utf8', timeout: 30000 }); if (child.error || child.status !== 0) throw child.error ?? new Error(child.stderr || child.stdout); const result = JSON.parse(child.stdout); console.log(JSON.stringify({ command, fixture: name, report_architecture: result.normalized_result.architecture, subject_architecture: result.subject.architecture, bits: result.normalized_result.bits, byte_order: result.normalized_result.byte_order, })); } } NODE ``` Both commands print the following architecture pairs for each fixture: `ppc32be` and `ppc32le` return `"ppc"` / `null`; the `x86` control returns `"386"` / `"x86"`; and the `ppc64` control returns `"ppc64"` / `null`. ### Expected behavior For an accepted ELF declaring `EM_PPC`, retain the recognized family as `subject.architecture: "powerpc"`. Recognition is independent of missing Go build information or a Rust dependency section, and does not establish compiler identity, ISA/ABI or deep-provider support. Local report and canonical subject names need not match, as the working `386` / `x86` control demonstrates. `EM_PPC64` must not be relabeled as this family. The [canonical family list](https://github.com/morluto/rea/blob/c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb/src/domain/binaryTargetTypes.ts#L3-L14), [owning ELF classifier](https://github.com/morluto/rea/blob/c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb/src/domain/binaryTarget.ts#L546-L563), and [Evidence subject contract](https://github.com/morluto/rea/blob/c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb/src/domain/evidence.ts#L76-L85) ground this expectation. The [PowerPC header checks](https://github.com/morluto/rea/blob/c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb/src/domain/binaryTarget.powerpc.test.ts#L4-L58) cover this header layout and both byte orders. The [Go observations and missing-metadata contract](https://github.com/morluto/rea/blob/c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb/docs/go-binaries.md#L23-L80) and [Rust observations and missing-section contract](https://github.com/morluto/rea/blob/c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb/docs/rust-binaries.md#L24-L65) permit the fixtures' absent language metadata. ### Impact and scope P2, supported. Both successful inspection commands lose a recognized CPU family in their machine-readable Evidence subjects for the accepted PowerPC headers. Local architecture and other header observations remain accessible. This is a bounded partial-result defect; no severe harm or whole-workflow failure was demonstrated. A subject-restoring workaround is unverified. Reading the retained `ppc` field lets an analyst identify the family but leaves the emitted subject null. ### REA version Source checkout [62bf09809ef7bb224028916424f8f07652a20f4f](https://github.com/morluto/rea/commit/62bf09809ef7bb224028916424f8f07652a20f4f), freshly built with `npm run build:cached`; launcher reports 6.5.0. This is source-checkout verification, not an installed released-package reproduction. Relevant source is unchanged on current main `c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb`, inspected rather than runtime-reproduced at that revision. ### Runtime and platform Node.js v24.18.0 and npm 11.16.0 on macOS arm64. No Go/Rust compiler, disassembler or external provider was used, and no target code ran. Stdio MCP and other host platforms were not exercised. ### Target and artifact context Synthetic accepted ELF headers with valid magic, class, byte order, header versions and extent. Both ELF32 PowerPC byte orders were checked. Program/section table offsets and counts are zero; the fixtures contain no compiler metadata and are not compiler-built Go or Rust programs. ### Interface CLI ### Earliest failure stage Artifact or evidence registration ### Relevant output or evidence Both commands succeeded with exit status 0 and empty stderr for both endian variants of `EM_PPC`, reporting 32 bits and the declared byte order, but leaving `subject.architecture` null. The Go result had `build_info: null`; the Rust result had `auditable_metadata: null`, consistent with the fixture contents. The same bytes passed the production header classifier as `powerpc`. Every result passed its canonical public output schema and retained the selected artifact's SHA-256. Schema acceptance therefore does not establish preservation of this known subject fact. The x86 control succeeded through both commands with `normalized_result.architecture: "386"` and `subject.architecture: "x86"`. Additional executed amd64 controls returned `"amd64"` / `"x86_64"`. Executed `EM_PPC64`, `EM_S390`, `EM_LOONGARCH` and unknown-machine controls retained null subjects, and the owning header classifier rejected those families. This report does not request additional family support. Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete for the stated accepted-header CLI cases. Production factories and bundled readers were used without an injected provider. Absolute-path, report-schema and artifact-identity checks accepted the fixtures. Existing focused checks for `binaryTarget.powerpc`, `GoBinaryService` and `RustBinaryService` passed 14 tests in three files. They do not establish restored behavior. ### Typed outcome or recovery expected Run the same fixtures through both public commands. `EM_PPC` should yield subject `powerpc` while retaining artifact identity, declared width, byte order and missing-metadata results. Preserve legitimate local aliases, including `386` with subject `x86`. Keep `EM_PPC64` and other unrecognized canonical families null. No implementation or successful restoration has been tested. ### Analysis or suspected cause The [ELF reader](https://github.com/morluto/rea/blob/c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb/src/go/GoElfImage.ts#L327-L342) produces local `ppc` for machine 20. The [Go subject mapper](https://github.com/morluto/rea/blob/c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb/src/application/go/GoBinaryService.ts#L20-L31) and [Rust subject mapper](https://github.com/morluto/rea/blob/c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb/src/application/rust/RustBinaryService.ts#L21-L32) translate x86 aliases but do not recognize `ppc` as canonical `powerpc`. [Evidence construction](https://github.com/morluto/rea/blob/c7f6b8bad1a34d5efb30ff2a017672dea1fcdabb/src/domain/evidence.ts#L387-L400) stores the omitted subject architecture as null. This source-derived explanation matches the successful CLI results and preserves freedom to correct the owning representation or translation. ### Additional context This report requests preservation of the already supported `EM_PPC` family in these metadata results. It does not request new canonical families, Ghidra/Hopper/IDA support or equal local/canonical architecture spellings. Compiler-built PowerPC programs, stdio MCP, other hosts and installed released packages were not verified.
Author
Owner

@kharedevansh11 commented on GitHub (Oct 15, 2026):

Thanks for the precise repro. The root cause is the subject mapper in GoBinaryService.ts and RustBinaryService.ts. It translates 386/amd64 but falls through to an exact canonical match for everything else, so the readers' local ppc alias becomes a null subject.

I've opened #4220, which adds ppc → powerpc to both mappers. The local normalized_result.architecture: "ppc" is unchanged, and ppc64 stays null. With it, your script returns subject.architecture: "powerpc" for both EM_PPC byte orders, and the x86 and ppc64 controls are unchanged.

cc @morluto @kaoru0822-kitauji @N0zoM1z0

<!-- gh-comment-id:6200576607 --> @kharedevansh11 commented on GitHub (Oct 15, 2026): Thanks for the precise repro. The root cause is the subject mapper in `GoBinaryService.ts` and `RustBinaryService.ts`. It translates `386`/`amd64` but falls through to an exact canonical match for everything else, so the readers' local `ppc` alias becomes a null subject. I've opened #4220, which adds `ppc → powerpc` to both mappers. The local `normalized_result.architecture: "ppc"` is unchanged, and `ppc64` stays null. With it, your script returns `subject.architecture: "powerpc"` for both EM_PPC byte orders, and the x86 and ppc64 controls are unchanged. cc @morluto @kaoru0822-kitauji @N0zoM1z0
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

Go and Rust Evidence construction now share binaryMetadataArchitecture, which maps local ppc to canonical powerpc and leaves unsupported ppc64 unmatched. The production-provider/service regression checks both EM_PPC byte orders, x86/amd64 controls and EM_PPC64, retaining local architecture, bits, byte_order and artifact digest. This covers both consumers and the reported canonical-subject loss without changing local aliases.

Implementation: binaryMetadataArchitecture.ts, GoBinaryService.ts.

The relevant regression checks in goRustSubjectArchitecture.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380783408 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. Go and Rust Evidence construction now share binaryMetadataArchitecture, which maps local ppc to canonical powerpc and leaves unsupported ppc64 unmatched. The production-provider/service regression checks both EM_PPC byte orders, x86/amd64 controls and EM_PPC64, retaining local architecture, bits, byte_order and artifact digest. This covers both consumers and the reported canonical-subject loss without changing local aliases. Implementation: [binaryMetadataArchitecture.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/binaryMetadataArchitecture.ts#L10), [GoBinaryService.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/go/GoBinaryService.ts#L71). The relevant regression checks in [goRustSubjectArchitecture.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/goRustSubjectArchitecture.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1647
No description provided.