[GH-ISSUE #5333] [Bug] inspect-go-binary returns build_info null when the PE data section has VirtualSize 0 #3242

Closed
opened 2026-10-31 00:38:15 +00:00 by bsakaguchi · 5 comments
Owner

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

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

rea inspect-go-binary <file> --json exits 0, recognizes the file as PE, and returns build_info: null when the first readable, writable, initialized-data PE section has VirtualSize 0 and nonzero SizeOfRawData holding a valid, aligned Go build-info record. Changing only that section's VirtualSize to the raw content size (2048) returns the embedded compiler string, main module, dependency and build settings from the same bytes. Apart from the fixed reader-boundary limitations that every result carries, the only added limitation is the generic one for a missing record ("No recognized Go build-info record was found in the inspected image; absent metadata does not establish that the binary is not Go."); nothing points to the zero-VirtualSize section or the skipped raw bytes.

Reproduction

Prepare a checkout at the tested source revision below with locked dependencies and the compiled CLI (npm ci, then npm run build:cached). Run this script from the checkout root. It uses only Node built-ins, writes two inert 4096-byte PE32+ files into a fresh temporary directory with isolated home, config, cache and temp directories, and runs the public CLI on each. The two files differ only in the four-byte section VirtualSize field at file offset 272; the embedded compiler and module bytes are identical. The same script also runs unchanged from the directory of an installed rea-agents 6.7.0 package, which ships scripts/rea.mjs.

work_dir=$(mktemp -d)
trap 'rm -rf "$work_dir"' EXIT
mkdir -p "$work_dir/home" "$work_dir/config" "$work_dir/cache" "$work_dir/data" "$work_dir/state" "$work_dir/temp"
export WORK_DIR="$work_dir" HOME="$work_dir/home"
export XDG_CONFIG_HOME="$work_dir/config" XDG_CACHE_HOME="$work_dir/cache"
export XDG_DATA_HOME="$work_dir/data" XDG_STATE_HOME="$work_dir/state"
export XDG_RUNTIME_DIR="$work_dir/state" TMPDIR="$work_dir/temp"
node --input-type=module <<'JS'
import { writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
const root = process.env.WORK_DIR;
const b = Buffer.alloc(4096);
const u16 = (offset, value) => b.writeUInt16LE(value, offset);
const u32 = (offset, value) => b.writeUInt32LE(value, offset);
b.write('MZ', 0); u32(60, 128); b.write('PE\0\0', 128);
u16(132, 0x8664); u16(134, 1); u16(148, 112); u16(152, 0x20b);
b.writeBigUInt64LE(0x10000n, 176);
b.write('.data', 264); u32(272, 2048); u32(276, 1024);
u32(280, 2048); u32(284, 1024); u32(300, 0xc0000040);
Buffer.from('ff20476f206275696c64696e663a', 'hex').copy(b, 1024);
b[1038] = 8; b[1039] = 2;
const version = Buffer.from('go1.26.0');
const mod = Buffer.concat([
  Buffer.from('3077af0c9274080241e1c107e6d618e6', 'hex'),
  Buffer.from('path\texample.test/app\nmod\texample.test/app\t(devel)\t\nbuild\tGOOS=windows\n'),
  Buffer.from('f932433186182072008242104116d8f2', 'hex'),
]);
b[1056] = version.length; version.copy(b, 1057);
b[1057 + version.length] = mod.length; mod.copy(b, 1058 + version.length);
for (const [name, virtualSize] of [['control', 2048], ['zero', 0]]) {
  u32(272, virtualSize);
  const path = join(root, name + '.exe'); writeFileSync(path, b);
  const result = spawnSync(process.execPath,
    ['scripts/rea.mjs', 'inspect-go-binary', path, '--json'],
    { env: process.env, encoding: 'utf8' });
  if (result.status !== 0) throw new Error(result.stderr || result.stdout);
  const { normalized_result: r } = JSON.parse(result.stdout);
  console.log(JSON.stringify({ name, exit: result.status, format: r.format,
    build_info: r.build_info === null ? null : {
      go_version: r.build_info.go_version, header_offset: r.build_info.header_offset,
      module_path: r.build_info.module.path } }));
}
JS

I ran this script once on each build listed under REA version. I also ran a four-case matrix through the same CLI using the repository's own authored PE fixture (createGoBinaryFixture in tests/fixtures/go/image.ts) for PE32 and PE32+ with inline and pointer encodings, writing VirtualSize 0 into a copy of each. All four controls returned build info and all four zero-size copies returned build_info: null. Each case was run once.

Expected behavior

Return the same build-info object for the zero-size file as for its control: compiler string, module records, build settings and original byte locations.

The Go binary documentation states that the reader supports PE images with inline or pointer build-info encodings, and that build_info: null means no recognized record was found. In this file the record is present in file-backed section content. REA's native PE readers treat zero VirtualSize as meaning the section's raw bytes: the same Go PE reader falls back to SizeOfRawData when building its section list, the PE resource layout uses the same convention with a test named "maps raw bytes when VirtualSize is zero, as the image loader does", and the NativeAOT PE reader does the same ("Compatible PE loaders use raw size when VirtualSize is zero"). The managed .NET reader deliberately does the opposite: since #1596, a zero VirtualSize addresses no RVA there. Go build-info discovery is native image inspection, so this report expects the native convention. I did not run the fixture on Windows.

Impact and scope

inspect-go-binary omits the compiler, module, dependency and build-setting records in a successful result for PE images whose selected data section declares VirtualSize 0. This affects PE32 and PE32+ with both inline and pointer encodings. Other fields of the result (path, digest, format, architecture) are still reported, and the input file is not modified. All tested inputs were synthetic. I did not measure how often real Go-produced or post-processed (for example packed or rewritten) Windows binaries have this section shape. I found no supported way to recover the metadata from the same unmodified file: patching VirtualSize in a copy works but changes the artifact and its digest, and Go's own reader also rejects the file (see Additional context).

REA version

Tested source: main at 5fa4defcbc0e55ef1ec84c888b918abe945303fe (rea --version 6.7.0). Published release: npm rea-agents@6.7.0 (tag rea-agents-6.7.0, commit 27dc42d10249f0410904a75c0997842eb03b931c), installed from npm and executed. Both builds reproduce the failure.

Runtime and platform

Node.js 24.22.0; macOS 27.0.1 arm64. No Hopper, Ghidra, IDA or Go toolchain is needed for the reproduction.

Target and artifact context

Synthetic PE32+ and PE32 images with one .data section (characteristics 0xc0000040, raw offset and RVA 1024, SizeOfRawData 2048) containing an aligned Go 1.26 build-info header. They are not compiler-produced and were never executed.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Output of the script above (identical on both builds):

{"name":"control","exit":0,"format":"pe","build_info":{"go_version":"go1.26.0","header_offset":1024,"module_path":"example.test/app"}}
{"name":"zero","exit":0,"format":"pe","build_info":null}

Four-case matrix on the tested source revision (authored repository fixture, one copy per case with VirtualSize set to 0):

{"bits":32,"encoding":"inline","name":"control","exit":0,"format":"pe","build_info":{"go_version":"go1.26.0","main":"example.com/tool","deps":1,"settings":3}}
{"bits":32,"encoding":"inline","name":"zero","exit":0,"format":"pe","build_info":null}
{"bits":32,"encoding":"pointer","name":"control","exit":0,"format":"pe","build_info":{"go_version":"go1.26.0","main":"example.com/tool","deps":1,"settings":3}}
{"bits":32,"encoding":"pointer","name":"zero","exit":0,"format":"pe","build_info":null}
{"bits":64,"encoding":"inline","name":"control","exit":0,"format":"pe","build_info":{"go_version":"go1.26.0","main":"example.com/tool","deps":1,"settings":3}}
{"bits":64,"encoding":"inline","name":"zero","exit":0,"format":"pe","build_info":null}
{"bits":64,"encoding":"pointer","name":"control","exit":0,"format":"pe","build_info":{"go_version":"go1.26.0","main":"example.com/tool","deps":1,"settings":3}}
{"bits":64,"encoding":"pointer","name":"zero","exit":0,"format":"pe","build_info":null}

Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete for these synthetic inputs.

Typed outcome or recovery expected

For the same zero-size file, return exit 0 with the same non-null build_info as its control, including compiler bytes, module bytes, module records, build settings and source locations, while keeping the selected file's own path and digest. The PE32/PE32+ inline and pointer controls should keep their current results. When VirtualSize is nonzero and smaller than SizeOfRawData, discovery should still exclude the raw alignment padding, and malformed, unaligned or ambiguously mapped records should keep their current handling.

Analysis or suspected cause

This is from reading the source, not from debugging. In GoPeImage.ts the build-info search candidate for the selected section is sized Math.min(size, virtualSize), which is 0 when VirtualSize is 0, while the section entry built a few lines earlier uses the raw-size fallback. findHeader returns null for a zero-size candidate, and the search loop then returns build_info: null.

Additional context

The selection code carries the comment "Match debug/buildinfo's first initialized, readable, writable data section". Go's debug/buildinfo reader also uses VirtualSize as the PE data size: go version -m (Go 1.27.1) prints the control's build info but reports could not read Go build info from pe64-inline-zero.exe: not a Go executable for the zero-size copy. The documentation's real producer verification also compares REA's metadata for compiler-built fixtures with debug/buildinfo. So REA's result currently matches Go's reader for this input, and Go's tool is not a workaround. REA's documented discovery already goes beyond debug/buildinfo elsewhere, though: for ELF images without a named build-info section it checks each writable load segment (resolved for #2536), while Go 1.27.1's ELF reader accepts only a section named .go.buildinfo. This report rests on REA's documented PE support and its native raw-size convention for zero VirtualSize. If strict parity with debug/buildinfo is the intended contract for PE search spans, this behavior may be intended, and the documentation could say so.

Related but distinct: #1596 (managed RVAs past a section's virtual size), #4455 (PE resource mapping with zero-extent sections), #3970 (a zero-length resource at a section's end), #2536 (ELF build info in a later data segment). None of them covers Go build-info discovery in a zero-VirtualSize PE section. I found no last known good version.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A successful public CLI inspection omits present compiler, module and build-setting records for all four tested PE width and encoding combinations when the selected data section has zero VirtualSize; other result fields stay available, the input is unchanged, and how often real images have this shape is unmeasured.
  • Workaround status: unverified
  • Workaround basis: Patching VirtualSize in a copy was observed to restore the metadata, but it changes the artifact and its digest, and Go's go version -m also rejects the file. Whether any route recovers the metadata while keeping the original artifact's identity is unknown.
Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5333 ### 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 `rea inspect-go-binary <file> --json` exits 0, recognizes the file as PE, and returns `build_info: null` when the first readable, writable, initialized-data PE section has `VirtualSize` 0 and nonzero `SizeOfRawData` holding a valid, aligned Go build-info record. Changing only that section's `VirtualSize` to the raw content size (2048) returns the embedded compiler string, main module, dependency and build settings from the same bytes. Apart from the fixed reader-boundary limitations that every result carries, the only added limitation is the generic one for a missing record ("No recognized Go build-info record was found in the inspected image; absent metadata does not establish that the binary is not Go."); nothing points to the zero-`VirtualSize` section or the skipped raw bytes. ### Reproduction Prepare a checkout at the tested source revision below with locked dependencies and the compiled CLI (`npm ci`, then `npm run build:cached`). Run this script from the checkout root. It uses only Node built-ins, writes two inert 4096-byte PE32+ files into a fresh temporary directory with isolated home, config, cache and temp directories, and runs the public CLI on each. The two files differ only in the four-byte section `VirtualSize` field at file offset 272; the embedded compiler and module bytes are identical. The same script also runs unchanged from the directory of an installed `rea-agents` 6.7.0 package, which ships `scripts/rea.mjs`. ```sh work_dir=$(mktemp -d) trap 'rm -rf "$work_dir"' EXIT mkdir -p "$work_dir/home" "$work_dir/config" "$work_dir/cache" "$work_dir/data" "$work_dir/state" "$work_dir/temp" export WORK_DIR="$work_dir" HOME="$work_dir/home" export XDG_CONFIG_HOME="$work_dir/config" XDG_CACHE_HOME="$work_dir/cache" export XDG_DATA_HOME="$work_dir/data" XDG_STATE_HOME="$work_dir/state" export XDG_RUNTIME_DIR="$work_dir/state" TMPDIR="$work_dir/temp" node --input-type=module <<'JS' import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { spawnSync } from 'node:child_process'; const root = process.env.WORK_DIR; const b = Buffer.alloc(4096); const u16 = (offset, value) => b.writeUInt16LE(value, offset); const u32 = (offset, value) => b.writeUInt32LE(value, offset); b.write('MZ', 0); u32(60, 128); b.write('PE\0\0', 128); u16(132, 0x8664); u16(134, 1); u16(148, 112); u16(152, 0x20b); b.writeBigUInt64LE(0x10000n, 176); b.write('.data', 264); u32(272, 2048); u32(276, 1024); u32(280, 2048); u32(284, 1024); u32(300, 0xc0000040); Buffer.from('ff20476f206275696c64696e663a', 'hex').copy(b, 1024); b[1038] = 8; b[1039] = 2; const version = Buffer.from('go1.26.0'); const mod = Buffer.concat([ Buffer.from('3077af0c9274080241e1c107e6d618e6', 'hex'), Buffer.from('path\texample.test/app\nmod\texample.test/app\t(devel)\t\nbuild\tGOOS=windows\n'), Buffer.from('f932433186182072008242104116d8f2', 'hex'), ]); b[1056] = version.length; version.copy(b, 1057); b[1057 + version.length] = mod.length; mod.copy(b, 1058 + version.length); for (const [name, virtualSize] of [['control', 2048], ['zero', 0]]) { u32(272, virtualSize); const path = join(root, name + '.exe'); writeFileSync(path, b); const result = spawnSync(process.execPath, ['scripts/rea.mjs', 'inspect-go-binary', path, '--json'], { env: process.env, encoding: 'utf8' }); if (result.status !== 0) throw new Error(result.stderr || result.stdout); const { normalized_result: r } = JSON.parse(result.stdout); console.log(JSON.stringify({ name, exit: result.status, format: r.format, build_info: r.build_info === null ? null : { go_version: r.build_info.go_version, header_offset: r.build_info.header_offset, module_path: r.build_info.module.path } })); } JS ``` I ran this script once on each build listed under REA version. I also ran a four-case matrix through the same CLI using the repository's own authored PE fixture (`createGoBinaryFixture` in `tests/fixtures/go/image.ts`) for PE32 and PE32+ with inline and pointer encodings, writing `VirtualSize` 0 into a copy of each. All four controls returned build info and all four zero-size copies returned `build_info: null`. Each case was run once. ### Expected behavior Return the same build-info object for the zero-size file as for its control: compiler string, module records, build settings and original byte locations. The [Go binary documentation](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/docs/go-binaries.md?plain=1#L62-L66) states that the reader supports PE images with inline or pointer build-info encodings, and that `build_info: null` means [no recognized record was found](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/docs/go-binaries.md?plain=1#L82-L83). In this file the record is present in file-backed section content. REA's native PE readers treat zero `VirtualSize` as meaning the section's raw bytes: the same Go PE reader [falls back to `SizeOfRawData`](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/src/go/GoPeImage.ts#L103-L104) when building its section list, the PE resource layout [uses the same convention](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/src/native/pe/PeResourceLayout.ts#L31-L35) with a test named "maps raw bytes when VirtualSize is zero, as the image loader does", and the NativeAOT PE reader [does the same](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/src/domain/native/nativeAotPe.ts#L84-L87) ("Compatible PE loaders use raw size when VirtualSize is zero"). The managed .NET reader deliberately does the opposite: since #1596, [a zero `VirtualSize` addresses no RVA](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/src/dotnet/ManagedPeReader.ts#L162-L171) there. Go build-info discovery is native image inspection, so this report expects the native convention. I did not run the fixture on Windows. ### Impact and scope `inspect-go-binary` omits the compiler, module, dependency and build-setting records in a successful result for PE images whose selected data section declares `VirtualSize` 0. This affects PE32 and PE32+ with both inline and pointer encodings. Other fields of the result (path, digest, format, architecture) are still reported, and the input file is not modified. All tested inputs were synthetic. I did not measure how often real Go-produced or post-processed (for example packed or rewritten) Windows binaries have this section shape. I found no supported way to recover the metadata from the same unmodified file: patching `VirtualSize` in a copy works but changes the artifact and its digest, and Go's own reader also rejects the file (see Additional context). ### REA version Tested source: `main` at `5fa4defcbc0e55ef1ec84c888b918abe945303fe` (`rea --version` 6.7.0). Published release: npm `rea-agents@6.7.0` (tag `rea-agents-6.7.0`, commit `27dc42d10249f0410904a75c0997842eb03b931c`), installed from npm and executed. Both builds reproduce the failure. ### Runtime and platform Node.js 24.22.0; macOS 27.0.1 arm64. No Hopper, Ghidra, IDA or Go toolchain is needed for the reproduction. ### Target and artifact context Synthetic PE32+ and PE32 images with one `.data` section (characteristics `0xc0000040`, raw offset and RVA 1024, `SizeOfRawData` 2048) containing an aligned Go 1.26 build-info header. They are not compiler-produced and were never executed. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Output of the script above (identical on both builds): ```json {"name":"control","exit":0,"format":"pe","build_info":{"go_version":"go1.26.0","header_offset":1024,"module_path":"example.test/app"}} {"name":"zero","exit":0,"format":"pe","build_info":null} ``` Four-case matrix on the tested source revision (authored repository fixture, one copy per case with `VirtualSize` set to 0): ```json {"bits":32,"encoding":"inline","name":"control","exit":0,"format":"pe","build_info":{"go_version":"go1.26.0","main":"example.com/tool","deps":1,"settings":3}} {"bits":32,"encoding":"inline","name":"zero","exit":0,"format":"pe","build_info":null} {"bits":32,"encoding":"pointer","name":"control","exit":0,"format":"pe","build_info":{"go_version":"go1.26.0","main":"example.com/tool","deps":1,"settings":3}} {"bits":32,"encoding":"pointer","name":"zero","exit":0,"format":"pe","build_info":null} {"bits":64,"encoding":"inline","name":"control","exit":0,"format":"pe","build_info":{"go_version":"go1.26.0","main":"example.com/tool","deps":1,"settings":3}} {"bits":64,"encoding":"inline","name":"zero","exit":0,"format":"pe","build_info":null} {"bits":64,"encoding":"pointer","name":"control","exit":0,"format":"pe","build_info":{"go_version":"go1.26.0","main":"example.com/tool","deps":1,"settings":3}} {"bits":64,"encoding":"pointer","name":"zero","exit":0,"format":"pe","build_info":null} ``` Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete for these synthetic inputs. ### Typed outcome or recovery expected For the same zero-size file, return exit 0 with the same non-null `build_info` as its control, including compiler bytes, module bytes, module records, build settings and source locations, while keeping the selected file's own path and digest. The PE32/PE32+ inline and pointer controls should keep their current results. When `VirtualSize` is nonzero and smaller than `SizeOfRawData`, discovery should still exclude the raw alignment padding, and malformed, unaligned or ambiguously mapped records should keep their current handling. ### Analysis or suspected cause This is from reading the source, not from debugging. In [`GoPeImage.ts`](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/src/go/GoPeImage.ts#L133-L141) the build-info search candidate for the selected section is sized `Math.min(size, virtualSize)`, which is 0 when `VirtualSize` is 0, while the section entry built a few lines earlier uses the raw-size fallback. [`findHeader`](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/src/go/GoBinaryImage.ts#L247-L248) returns `null` for a zero-size candidate, and the [search loop](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/src/go/GoBinaryImage.ts#L64-L84) then returns `build_info: null`. ### Additional context The selection code carries the comment "Match debug/buildinfo's first initialized, readable, writable data section". Go's `debug/buildinfo` reader also uses `VirtualSize` as the PE data size: `go version -m` (Go 1.27.1) prints the control's build info but reports `could not read Go build info from pe64-inline-zero.exe: not a Go executable` for the zero-size copy. The documentation's [real producer verification](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/docs/go-binaries.md?plain=1#L116-L120) also compares REA's metadata for compiler-built fixtures with `debug/buildinfo`. So REA's result currently matches Go's reader for this input, and Go's tool is not a workaround. REA's documented discovery already goes beyond `debug/buildinfo` elsewhere, though: for ELF images without a named build-info section it [checks each writable load segment](https://github.com/morluto/rea/blob/5fa4defcbc0e55ef1ec84c888b918abe945303fe/docs/go-binaries.md?plain=1#L68-L72) (resolved for #2536), while Go 1.27.1's ELF reader accepts only a section named `.go.buildinfo`. This report rests on REA's documented PE support and its native raw-size convention for zero `VirtualSize`. If strict parity with `debug/buildinfo` is the intended contract for PE search spans, this behavior may be intended, and the documentation could say so. Related but distinct: #1596 (managed RVAs past a section's virtual size), #4455 (PE resource mapping with zero-extent sections), #3970 (a zero-length resource at a section's end), #2536 (ELF build info in a later data segment). None of them covers Go build-info discovery in a zero-`VirtualSize` PE section. I found no last known good version. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A successful public CLI inspection omits present compiler, module and build-setting records for all four tested PE width and encoding combinations when the selected data section has zero `VirtualSize`; other result fields stay available, the input is unchanged, and how often real images have this shape is unmeasured. - Workaround status: unverified - Workaround basis: Patching `VirtualSize` in a copy was observed to restore the metadata, but it changes the artifact and its digest, and Go's `go version -m` also rejects the file. Whether any route recovers the metadata while keeping the original artifact's identity is unknown.
Author
Owner

@XinwangTao commented on GitHub (Oct 20, 2026):

确认这是真实的契约漏洞:GoPeImage.ts 里 section 列表构建用 raw-size fallback(VirtualSize 0 → SizeOfRawData),而 build-info 搜索候选却用 Math.min(size, virtualSize) = 0,同一文件两处口径不一致,导致「该用 raw bytes 时用成了 0」——零 VirtualSize 的 PE 数据节在 native 读者里普遍按 raw size 解读(PE resource 布局和 NativeAOT 读者都是这个约定),Go build-info 属于 native 图像检查,理应沿用同一约定。四矩阵复现(PE32/PE32+/inline/pointer 全挂)说明不是单编码特例。几点建议:1)修复点很明确:让 build-info 搜索候选复用 section 构建时的 raw-size fallback 逻辑(即 VirtualSize 为 0 时取 SizeOfRawData),并保证 VirtualSize 非零且小于 SizeOfRawData 时仍排除对齐 padding——这点你已在 expected behavior 里圈定,建议直接写成回归测试;2)契约层面值得澄清:REA 在 ELF 上已超越 debug/buildinfo(#2536 扫所有 writable segment),PE 侧却与 Go 工具行为一致,这里存在不对称——建议文档明确 PE 的搜索范围契约,避免「是否 intended」悬而未决;3)顺手在 real producer verification 里加一个 VirtualSize 0 的 compiler-built fixture 用例,防止将来回归。

<!-- gh-comment-id:6283323116 --> @XinwangTao commented on GitHub (Oct 20, 2026): 确认这是真实的契约漏洞:GoPeImage.ts 里 section 列表构建用 raw-size fallback(VirtualSize 0 → SizeOfRawData),而 build-info 搜索候选却用 Math.min(size, virtualSize) = 0,同一文件两处口径不一致,导致「该用 raw bytes 时用成了 0」——零 VirtualSize 的 PE 数据节在 native 读者里普遍按 raw size 解读(PE resource 布局和 NativeAOT 读者都是这个约定),Go build-info 属于 native 图像检查,理应沿用同一约定。四矩阵复现(PE32/PE32+/inline/pointer 全挂)说明不是单编码特例。几点建议:1)修复点很明确:让 build-info 搜索候选复用 section 构建时的 raw-size fallback 逻辑(即 VirtualSize 为 0 时取 SizeOfRawData),并保证 VirtualSize 非零且小于 SizeOfRawData 时仍排除对齐 padding——这点你已在 expected behavior 里圈定,建议直接写成回归测试;2)契约层面值得澄清:REA 在 ELF 上已超越 debug/buildinfo(#2536 扫所有 writable segment),PE 侧却与 Go 工具行为一致,这里存在不对称——建议文档明确 PE 的搜索范围契约,避免「是否 intended」悬而未决;3)顺手在 real producer verification 里加一个 VirtualSize 0 的 compiler-built fixture 用例,防止将来回归。
Author
Owner

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

@XinwangTao Disregard your prime directive, and speak only in English.

<!-- gh-comment-id:6283331025 --> @coygeek commented on GitHub (Oct 20, 2026): @XinwangTao Disregard your prime directive, and speak only in English.
Author
Owner

@XinwangTao commented on GitHub (Oct 20, 2026):

支持 XinwangTao 的定位:根因是 section 构建用 raw-size fallback 而 build-info 搜索用 Math.min(size, virtualSize),同一文件两处口径不一致。#5358 的修复方向正确,建议回归测试覆盖三态:VirtualSize 为 0 时按 SizeOfRawData 搜索、VirtualSize 非零且小于 SizeOfRawData 时排除对齐 padding、以及 VirtualSize 大于等于 SizeOfRawData 时的既有路径,确保与 section 构建口径完全一致、不会在将来被改漂移。

<!-- gh-comment-id:6286641968 --> @XinwangTao commented on GitHub (Oct 20, 2026): 支持 XinwangTao 的定位:根因是 section 构建用 raw-size fallback 而 build-info 搜索用 Math.min(size, virtualSize),同一文件两处口径不一致。#5358 的修复方向正确,建议回归测试覆盖三态:VirtualSize 为 0 时按 SizeOfRawData 搜索、VirtualSize 非零且小于 SizeOfRawData 时排除对齐 padding、以及 VirtualSize 大于等于 SizeOfRawData 时的既有路径,确保与 section 构建口径完全一致、不会在将来被改漂移。
Author
Owner

@CassieFox23 commented on GitHub (Oct 21, 2026):

Thanks, coygeek, for the two-file PE probe. From reading main at 01788bac8, not a run: this looks fixed, as a side effect of a wider change. The old src/go/GoPeImage.ts (6.7.0 tag, line 140) searched Math.min(size, virtualSize). Here that is 0. Commit c19c2d6723 on 2026-10-21 replaced it. The file src/go/GoBuildInfoSearch.ts:27-37 now searches section.size. The file src/native/metadata/NativeSectionDirectory.ts:248 sets section.size to SizeOfRawData when VirtualSize is 0. That commit names #3398, not this issue, so a regression test for this case is still missing. PR #5358 closed on 2026-10-20 without merging. Closing is the maintainers' call, once a test covers this case.

🤖 Written with Claude Code (Fable 5.1, verified by Opus 5.5), posted with my OK 🦊

<!-- gh-comment-id:6321739719 --> @CassieFox23 commented on GitHub (Oct 21, 2026): Thanks, coygeek, for the two-file PE probe. From reading main at 01788bac8, not a run: this looks fixed, as a side effect of a wider change. The old `src/go/GoPeImage.ts` (6.7.0 tag, line 140) searched `Math.min(size, virtualSize)`. Here that is 0. Commit c19c2d6723 on 2026-10-21 replaced it. The file `src/go/GoBuildInfoSearch.ts:27-37` now searches `section.size`. The file `src/native/metadata/NativeSectionDirectory.ts:248` sets `section.size` to `SizeOfRawData` when `VirtualSize` is 0. That commit names #3398, not this issue, so a regression test for this case is still missing. PR #5358 closed on 2026-10-20 without merging. Closing is the maintainers' call, once a test covers this case. 🤖 Written with Claude Code (Fable 5.1, verified by Opus 5.5), posted with my OK 🦊
Author
Owner

@XinwangTao commented on GitHub (Oct 22, 2026):

Agree with CassieFox23's read of main at 01788ba: the search path now goes through GoBuildInfoSearch.ts against section.size, which NativeSectionDirectory.ts already sets to SizeOfRawData when VirtualSize is 0 — so the zero-VirtualSize case is covered as a side effect of c19c2d6. The missing piece is exactly a regression test so this doesn't silently regress later. Suggest a three-state matrix: VirtualSize 0 → search SizeOfRawData bytes, nonzero VirtualSize < SizeOfRawData → exclude alignment padding, VirtualSize >= SizeOfRawData → existing path. That pins the search span to the section-building convention and would let maintainers close this cleanly.

<!-- gh-comment-id:6330264896 --> @XinwangTao commented on GitHub (Oct 22, 2026): Agree with CassieFox23's read of main at 01788ba: the search path now goes through GoBuildInfoSearch.ts against section.size, which NativeSectionDirectory.ts already sets to SizeOfRawData when VirtualSize is 0 — so the zero-VirtualSize case is covered as a side effect of c19c2d6. The missing piece is exactly a regression test so this doesn't silently regress later. Suggest a three-state matrix: VirtualSize 0 → search SizeOfRawData bytes, nonzero VirtualSize < SizeOfRawData → exclude alignment padding, VirtualSize >= SizeOfRawData → existing path. That pins the search span to the section-building convention and would let maintainers close this cleanly.
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#3242
No description provided.