[GH-ISSUE #6831] [Bug] Go legacy pointer metadata ignores the file-backed PE header mapping #4174

Closed
opened 2026-10-31 00:47:26 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

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

Area

Native analysis or decompilation

Observed behavior

The bundled Go service returns AnalysisInputError / invalid_format when the compiler string contents or its string descriptor live in the PE image's declared file-backed header range. Keeping those same values in .data succeeds. The shared container reader reports the header mapping, and the shared mapper resolves the affected offsets correctly when that mapping is included.

Reproduction

With this source checkout's locked dependencies available, save the following script outside the checkout as /tmp/rea-go-repro.mts. From the checkout root run:

PATH="/usr/local/bin:$PATH" node_modules/.bin/tsx /tmp/rea-go-repro.mts /tmp/rea-go-repro-fixtures

Each case in this exact script was executed once. It calls the production createGoBinaryService() composition shared by CLI/MCP; neither adapter transport was invoked. The repository's authored fixture supplies inert bytes. No target was loaded or executed.

import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
const load = (p) => import(pathToFileURL(join(process.cwd(), p)).href);
const { createGoBinaryFixture } = await load('tests/fixtures/go/image.ts');
const { createGoBinaryService } = await load('src/composition/go.ts');
const { readBinaryContainer } = await load('src/native/metadata/readBinaryContainer.ts');
const { mappedFileOffset } = await load('src/native/metadata/BinaryContainer.ts');
const directory = process.argv[2]; await mkdir(directory, { recursive: true });
const { bytes: control } = createGoBinaryFixture({ format: 'pe', encoding: 'pointer', moduleText: 'path\tdemo\n' });
control.writeUInt16LE(0x22, 150);
control.writeUInt32LE(4096, 184); control.writeUInt32LE(512, 188);
control.writeUInt32LE(8192, 208); control.writeUInt32LE(1024, 212);
control.writeUInt32LE(4096, 276);
control.writeBigUInt64LE(0x11200n, 1040); control.writeBigUInt64LE(0x11210n, 1048);
control.writeBigUInt64LE(0x11400n, 1536); control.writeBigUInt64LE(0x11500n, 1552);
const content = Buffer.from(control); control.copy(content, 512, 2048, 2056);
content.writeBigUInt64LE(0x10200n, 1536);
const header = Buffer.from(control); control.copy(header, 528, 1536, 1552);
header.writeBigUInt64LE(0x10210n, 1040);
const service = createGoBinaryService();
try {
  for (const [name, bytes, address, size] of [['section-control', control, 0x11400n, 8], ['header-content', content, 0x10200n, 8], ['header-descriptor', header, 0x10210n, 16]]) {
    const image = readBinaryContainer(bytes);
    const mapped = mappedFileOffset([image.pe_header_mapping, ...image.mappings], address, size, image.zero_fills);
    const path = join(directory, name + '.exe'); await writeFile(path, bytes);
    const result = await service.inspect({ path });
    console.log(JSON.stringify({ name, independently_mapped_offset: mapped, ok: result.ok,
      ...(result.ok ? { go_version: result.value.normalized_result.build_info.go_version } : {
        error: result.error._tag, reason: result.error.issues[0].reason,
        message: result.error.issues[0].message.split(': ').slice(1).join(': ') }) }));
  }
} finally { await service.close(); }

Expected behavior

Follow the compiler-string pointer to file offset 512, or its descriptor to file offset 528, within the declared SizeOfHeaders = 1024 range, and return go1.26.0. The container contract explicitly identifies this as file-backed memory mapped at the image base, and the native export consumer includes it when resolving PE virtual addresses. Go inspection documents pointer-backed strings without a section-only restriction.

Impact and scope

Observed: complete Go metadata inspection is rejected for two layouts whose required bytes are present in a declared image-header mapping. This is a bounded compatibility/result defect; the original file is unchanged. Compiler-produced occurrence, Windows loading, Go's standard reader, PE32, inline encoding, CLI/MCP transports and other platforms were not tested. The provisional assessment retains the uncertainty about real-world reach and whether this layout is intended to be supported.

REA version

Source revision 9066444f6b8e9ffe1607a9b02cde3895a37db431, main; package.json reports 6.7.0. This is a source-service reproduction, not verification of an installed npm package.

Runtime and platform

Measured Node.js v26.12.1, Linux 7.2.8-9-omarchy x86_64 (process.arch: x64), locked tsx 4.23.15. No Go compiler, Erlang emulator, disassembler or device was used.

Target and artifact context

Synthetic 4096-byte PE32+ amd64, little endian; historical eight-byte pointer encoding. ImageBase = 0x10000, SizeOfHeaders = 1024, SectionAlignment = 4096, FileAlignment = 512, SizeOfImage = 8192; .data is file offset 1024, RVA 4096. The recognized build-info record stays in .data. Variants move only the version content to header offset 512 or its 16-byte descriptor to offset 528, updating the corresponding pointer. Neither overlaps the PE structural headers or section table.

Interface

Other

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Actual stdout from the script above; the error message is the production error's issue message with only its selected-path prefix omitted:

{"name":"section-control","independently_mapped_offset":2048,"ok":true,"go_version":"go1.26.0"}
{"name":"header-content","independently_mapped_offset":512,"ok":false,"error":"AnalysisInputError","reason":"invalid_format","message":"Virtual address or string range is not file mapped"}
{"name":"header-descriptor","independently_mapped_offset":528,"ok":false,"error":"AnalysisInputError","reason":"invalid_format","message":"Virtual address or string range is not file mapped"}

Evidence provenance: observed service results and synthetic file mappings; source-derived diagnosis. Local verification: reproduced. Reproduction completeness: complete for the stated source-service cases. Public CLI, stdio MCP, installed release, other hosts and real compiler-produced occurrences were not tested. NOT_ENOUGH_INFO: real-world frequency and a last-known-good version are unknown.

Typed outcome or recovery expected

Recover the same compiler/module bytes and original file offsets from the unchanged artifact. Keep failures for genuinely unmapped, contradictory or zero-filled nonempty string ranges. No fix or restored implementation was tested.

Analysis or suspected cause

readPeImage builds pe_header_mapping and returns it separately from section mappings. readPointerString destructures and uses only section mappings for both descriptor and content. The executed independent mapping check uses the same mappedFileOffset helper with the existing header mapping added; it returns offsets 512 and 528 without modifying production code. This is source-derived diagnosis supported by the service results.

Additional context

Open-and-closed searches included Go/pointer/header, Go/headers, and SizeOfHeaders; no matching report was found. Open #5333 concerns zero VirtualSize discovery; all cases here have nonzero virtual size and the same recognized record. Closed #4750 and #4763 concern NativeAOT header extent/ownership, not the Go consumer omitting an existing header mapping.

Triage assessment

  • Impact level: P2
  • Assessment status: provisional
  • Impact basis: The executed production service rejects two synthetic PE layouts with independently resolved, file-backed compiler data; the section-only control succeeds. Real producer occurrence and intended support for header-backed Go strings remain unverified.
  • Workaround status: unverified
  • Workaround basis: The synthetic controls establish recovery after changing layout in a copy, which changes artifact identity. No alternate route recovering the affected unchanged artifact was tested.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6831 ## 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 Native analysis or decompilation ## Observed behavior The bundled Go service returns `AnalysisInputError` / `invalid_format` when the compiler string contents or its string descriptor live in the PE image's declared file-backed header range. Keeping those same values in `.data` succeeds. The shared container reader reports the header mapping, and the shared mapper resolves the affected offsets correctly when that mapping is included. ## Reproduction With this source checkout's locked dependencies available, save the following script outside the checkout as `/tmp/rea-go-repro.mts`. From the checkout root run: ```sh PATH="/usr/local/bin:$PATH" node_modules/.bin/tsx /tmp/rea-go-repro.mts /tmp/rea-go-repro-fixtures ``` Each case in this exact script was executed once. It calls the production `createGoBinaryService()` composition shared by CLI/MCP; neither adapter transport was invoked. The repository's authored fixture supplies inert bytes. No target was loaded or executed. ```ts import { mkdir, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; const load = (p) => import(pathToFileURL(join(process.cwd(), p)).href); const { createGoBinaryFixture } = await load('tests/fixtures/go/image.ts'); const { createGoBinaryService } = await load('src/composition/go.ts'); const { readBinaryContainer } = await load('src/native/metadata/readBinaryContainer.ts'); const { mappedFileOffset } = await load('src/native/metadata/BinaryContainer.ts'); const directory = process.argv[2]; await mkdir(directory, { recursive: true }); const { bytes: control } = createGoBinaryFixture({ format: 'pe', encoding: 'pointer', moduleText: 'path\tdemo\n' }); control.writeUInt16LE(0x22, 150); control.writeUInt32LE(4096, 184); control.writeUInt32LE(512, 188); control.writeUInt32LE(8192, 208); control.writeUInt32LE(1024, 212); control.writeUInt32LE(4096, 276); control.writeBigUInt64LE(0x11200n, 1040); control.writeBigUInt64LE(0x11210n, 1048); control.writeBigUInt64LE(0x11400n, 1536); control.writeBigUInt64LE(0x11500n, 1552); const content = Buffer.from(control); control.copy(content, 512, 2048, 2056); content.writeBigUInt64LE(0x10200n, 1536); const header = Buffer.from(control); control.copy(header, 528, 1536, 1552); header.writeBigUInt64LE(0x10210n, 1040); const service = createGoBinaryService(); try { for (const [name, bytes, address, size] of [['section-control', control, 0x11400n, 8], ['header-content', content, 0x10200n, 8], ['header-descriptor', header, 0x10210n, 16]]) { const image = readBinaryContainer(bytes); const mapped = mappedFileOffset([image.pe_header_mapping, ...image.mappings], address, size, image.zero_fills); const path = join(directory, name + '.exe'); await writeFile(path, bytes); const result = await service.inspect({ path }); console.log(JSON.stringify({ name, independently_mapped_offset: mapped, ok: result.ok, ...(result.ok ? { go_version: result.value.normalized_result.build_info.go_version } : { error: result.error._tag, reason: result.error.issues[0].reason, message: result.error.issues[0].message.split(': ').slice(1).join(': ') }) })); } } finally { await service.close(); } ``` ## Expected behavior Follow the compiler-string pointer to file offset 512, or its descriptor to file offset 528, within the declared `SizeOfHeaders = 1024` range, and return `go1.26.0`. [The container contract](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/src/native/metadata/BinaryContainer.ts#L99-L105) explicitly identifies this as file-backed memory mapped at the image base, and [the native export consumer](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/src/native/exports/NativeExportParser.ts#L110-L117) includes it when resolving PE virtual addresses. [Go inspection documents pointer-backed strings](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/docs/go-binaries.md#L112-L117) without a section-only restriction. ## Impact and scope Observed: complete Go metadata inspection is rejected for two layouts whose required bytes are present in a declared image-header mapping. This is a bounded compatibility/result defect; the original file is unchanged. Compiler-produced occurrence, Windows loading, Go's standard reader, PE32, inline encoding, CLI/MCP transports and other platforms were not tested. The provisional assessment retains the uncertainty about real-world reach and whether this layout is intended to be supported. ## REA version Source revision [9066444f6b8e9ffe1607a9b02cde3895a37db431](https://github.com/morluto/rea/commit/9066444f6b8e9ffe1607a9b02cde3895a37db431), main; `package.json` reports 6.7.0. This is a source-service reproduction, not verification of an installed npm package. ## Runtime and platform Measured Node.js v26.12.1, Linux 7.2.8-9-omarchy x86_64 (`process.arch: x64`), locked tsx 4.23.15. No Go compiler, Erlang emulator, disassembler or device was used. ## Target and artifact context Synthetic 4096-byte PE32+ amd64, little endian; historical eight-byte pointer encoding. `ImageBase = 0x10000`, `SizeOfHeaders = 1024`, `SectionAlignment = 4096`, `FileAlignment = 512`, `SizeOfImage = 8192`; `.data` is file offset 1024, RVA 4096. The recognized build-info record stays in `.data`. Variants move only the version content to header offset 512 or its 16-byte descriptor to offset 528, updating the corresponding pointer. Neither overlaps the PE structural headers or section table. ## Interface Other ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence Actual stdout from the script above; the error message is the production error's issue message with only its selected-path prefix omitted: ```jsonl {"name":"section-control","independently_mapped_offset":2048,"ok":true,"go_version":"go1.26.0"} {"name":"header-content","independently_mapped_offset":512,"ok":false,"error":"AnalysisInputError","reason":"invalid_format","message":"Virtual address or string range is not file mapped"} {"name":"header-descriptor","independently_mapped_offset":528,"ok":false,"error":"AnalysisInputError","reason":"invalid_format","message":"Virtual address or string range is not file mapped"} ``` Evidence provenance: observed service results and synthetic file mappings; source-derived diagnosis. Local verification: reproduced. Reproduction completeness: complete for the stated source-service cases. Public CLI, stdio MCP, installed release, other hosts and real compiler-produced occurrences were not tested. NOT_ENOUGH_INFO: real-world frequency and a last-known-good version are unknown. ## Typed outcome or recovery expected Recover the same compiler/module bytes and original file offsets from the unchanged artifact. Keep failures for genuinely unmapped, contradictory or zero-filled nonempty string ranges. No fix or restored implementation was tested. ## Analysis or suspected cause [readPeImage](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/src/native/metadata/PeImage.ts#L92-L97) builds `pe_header_mapping` and [returns it separately](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/src/native/metadata/PeImage.ts#L164-L174) from section `mappings`. [readPointerString](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/src/go/GoBinaryImage.ts#L347-L373) destructures and uses only section `mappings` for both descriptor and content. The executed independent mapping check uses the same `mappedFileOffset` helper with the existing header mapping added; it returns offsets 512 and 528 without modifying production code. This is source-derived diagnosis supported by the service results. ## Additional context Open-and-closed searches included Go/pointer/header, Go/headers, and SizeOfHeaders; no matching report was found. Open [#5333](https://github.com/morluto/rea/issues/5333) concerns zero `VirtualSize` discovery; all cases here have nonzero virtual size and the same recognized record. Closed [#4750](https://github.com/morluto/rea/issues/4750) and [#4763](https://github.com/morluto/rea/issues/4763) concern NativeAOT header extent/ownership, not the Go consumer omitting an existing header mapping. ## Triage assessment - Impact level: P2 - Assessment status: provisional - Impact basis: The executed production service rejects two synthetic PE layouts with independently resolved, file-backed compiler data; the section-only control succeeds. Real producer occurrence and intended support for header-backed Go strings remain unverified. - Workaround status: unverified - Workaround basis: The synthetic controls establish recovery after changing layout in a copy, which changes artifact identity. No alternate route recovering the affected unchanged artifact was tested.
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#4174
No description provided.