[GH-ISSUE #3364] [Bug] Truncated managed metadata diagnostics report stream offsets as file offsets #1232

Closed
opened 2026-10-16 21:03:06 +00:00 by bsakaguchi · 0 comments
Owner

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

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

Managed inspection returns a metadata table-row-count issue at offset 24 when the failed read is at file offset 888. The parser correctly refuses the truncated table stream, but its diagnostic points into the DOS header instead of the selected artifact's metadata. The same incorrect coordinate appears through inspect-managed-artifact, inspect-managed-members, and inspect-managed-native-boundaries.

All three commands exit with status 0 and retain the invalid-metadata-root issue with scope metadata.table-row-count. Artifact coverage is partial; member and native-boundary coverage are unavailable. Offset 24 is relative to the table stream, not to the file, so the byte location is wrong by 864 bytes.

Reproduction

  1. Prepare a clean source checkout at 8dde6370d0809ad04cda3342d4e6f290f48aa063, then install locked dependencies and build the runtime from the repository root:
npm ci
npm run build:cached
  1. Create repro/truncated.dll with the inline generator below. Shorten only the table stream's declared extent, leaving the file, CLI header, and metadata-root location intact:
mkdir -p repro/home
node --input-type=module <<'JS'
import { writeFileSync } from 'node:fs';
import { buildManagedPeFixture } from './scripts/fixtures/managed/pe.mjs';
const bytes = buildManagedPeFixture();
const root = bytes.indexOf(Buffer.from('BSJB'));
const streamHeader = root + Math.ceil((16 + bytes.readUInt32LE(root + 12)) / 4) * 4 + 4;
const tables = root + bytes.readUInt32LE(streamHeader);
bytes.writeUInt32LE(24, streamHeader + 4);
console.log(JSON.stringify({ metadata_root: root, tables_file_offset: tables, failed_read_file_offset: tables + 24 }));
writeFileSync('repro/truncated.dll', bytes);
JS
  1. Run each public inspection command against the same file:
HOME="$PWD/repro/home" node scripts/rea.mjs inspect-managed-artifact "$PWD/repro/truncated.dll" --format json --filter-output normalized_result.coverage
HOME="$PWD/repro/home" node scripts/rea.mjs inspect-managed-members "$PWD/repro/truncated.dll" --format json --filter-output normalized_result.coverage
HOME="$PWD/repro/home" node scripts/rea.mjs inspect-managed-native-boundaries "$PWD/repro/truncated.dll" --format json --filter-output normalized_result.coverage
  1. Compare the generator's failed_read_file_offset: 888 with the issue's offset: 24. The generator prints metadata_root: 768 and tables_file_offset: 864. The declared 24-byte table stream includes its fixed header and valid-table mask but none of its required row-count words.

Expected behavior

Retain the table-row-count failure with a location in the artifact's file coordinate system. For this input the missing row-count read starts at file offset 888. Continue rejecting the truncated stream rather than reading the bytes that remain physically present outside its declared extent.

docs/tool-design.md:51 requires source locations with returned facts; src/dotnet/ManagedMetadataExtraData.test.ts:199 pins artifact-relative offsets for truncated words in the same table stream, and src/dotnet/ManagedReaderFailure.ts:13 describes failures at exact byte locations.

Impact and scope

P2, supported. The diagnostic carries an incorrect byte coordinate into retained analysis evidence and directs an analyst to the DOS header instead of the metadata failure. The input is still rejected correctly; the observed defect concerns provenance and diagnosis across artifact, member, and native-boundary inspection.

A workaround is available for the reproduced table-stream failure. The generator demonstrates how to read the stream's artifact offset and add its relative cursor. Adding 864 and 24 yields the correct file offset 888 without altering the inspected artifact.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0 on macOS 27.0.1 arm64, using the built local CLI.

Target and artifact context

A source-owned PE/CLI byte fixture from scripts/fixtures/managed/pe.mjs, written as repro/truncated.dll. Only the declared table-stream extent is shortened to 24 bytes; the file, CLI header, metadata-root location, and physically present bytes remain intact. No target code or external analysis engine was executed.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The executed generator independently reports the artifact coordinate, and all three executed public commands return the stream-relative coordinate:

{"metadata_root":768,"tables_file_offset":864,"failed_read_file_offset":888}
{"code":"invalid-metadata-root","scope":"metadata.table-row-count","offset":24,"detail":"metadata.table-row-count leaves the metadata byte range"}

The reproduction was observed locally and is complete. All three commands exited with status 0; artifact coverage was partial, and member and native-boundary coverage were unavailable.

Typed outcome or recovery expected

Repeat the reproduction. Keep the existing failure classification and coverage states, but return the failure at artifact offset 888. Check another metadata-root or table-stream placement so a hard-coded correction cannot pass. Existing correctly located truncated ExtraData diagnostics must retain their artifact coordinates, and parser bounds must continue following the declared stream extent.

Analysis or suspected cause

Source-derived reasoning: src/dotnet/ManagedMetadataLayout.ts:324 checks a sliced table buffer using its relative cursor, while src/dotnet/ManagedMetadataLayout.ts:136 retains that relative value directly in the caller-visible issue. The table-data range check at src/dotnet/ManagedMetadataLayout.ts:387 uses the same convention. The executed reproduction establishes the row-count failure; the table-data observation comes from source inspection.

Additional context

  • Related completed issue #2677 concerns truncated CIL operands losing instruction context in body.issue; this report concerns a retained metadata-table issue with a stream-relative value in its file-offset field.
  • Related completed issue #2191 concerns malformed PE section errors becoming generic execution_failure; this report already retains the typed metadata failure and appropriate coverage but gives its wrong byte coordinate.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3364 ### 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 Managed inspection returns a metadata table-row-count issue at offset 24 when the failed read is at file offset 888. The parser correctly refuses the truncated table stream, but its diagnostic points into the DOS header instead of the selected artifact's metadata. The same incorrect coordinate appears through `inspect-managed-artifact`, `inspect-managed-members`, and `inspect-managed-native-boundaries`. All three commands exit with status 0 and retain the `invalid-metadata-root` issue with scope `metadata.table-row-count`. Artifact coverage is `partial`; member and native-boundary coverage are `unavailable`. Offset 24 is relative to the table stream, not to the file, so the byte location is wrong by 864 bytes. ### Reproduction 1. Prepare a clean source checkout at `8dde6370d0809ad04cda3342d4e6f290f48aa063`, then install locked dependencies and build the runtime from the repository root: ```sh npm ci npm run build:cached ``` 2. Create `repro/truncated.dll` with the inline generator below. Shorten only the table stream's declared extent, leaving the file, CLI header, and metadata-root location intact: ```sh mkdir -p repro/home node --input-type=module <<'JS' import { writeFileSync } from 'node:fs'; import { buildManagedPeFixture } from './scripts/fixtures/managed/pe.mjs'; const bytes = buildManagedPeFixture(); const root = bytes.indexOf(Buffer.from('BSJB')); const streamHeader = root + Math.ceil((16 + bytes.readUInt32LE(root + 12)) / 4) * 4 + 4; const tables = root + bytes.readUInt32LE(streamHeader); bytes.writeUInt32LE(24, streamHeader + 4); console.log(JSON.stringify({ metadata_root: root, tables_file_offset: tables, failed_read_file_offset: tables + 24 })); writeFileSync('repro/truncated.dll', bytes); JS ``` 3. Run each public inspection command against the same file: ```sh HOME="$PWD/repro/home" node scripts/rea.mjs inspect-managed-artifact "$PWD/repro/truncated.dll" --format json --filter-output normalized_result.coverage HOME="$PWD/repro/home" node scripts/rea.mjs inspect-managed-members "$PWD/repro/truncated.dll" --format json --filter-output normalized_result.coverage HOME="$PWD/repro/home" node scripts/rea.mjs inspect-managed-native-boundaries "$PWD/repro/truncated.dll" --format json --filter-output normalized_result.coverage ``` 4. Compare the generator's `failed_read_file_offset: 888` with the issue's `offset: 24`. The generator prints `metadata_root: 768` and `tables_file_offset: 864`. The declared 24-byte table stream includes its fixed header and valid-table mask but none of its required row-count words. ### Expected behavior Retain the table-row-count failure with a location in the artifact's file coordinate system. For this input the missing row-count read starts at file offset 888. Continue rejecting the truncated stream rather than reading the bytes that remain physically present outside its declared extent. `docs/tool-design.md:51` requires source locations with returned facts; `src/dotnet/ManagedMetadataExtraData.test.ts:199` pins artifact-relative offsets for truncated words in the same table stream, and `src/dotnet/ManagedReaderFailure.ts:13` describes failures at exact byte locations. ### Impact and scope P2, supported. The diagnostic carries an incorrect byte coordinate into retained analysis evidence and directs an analyst to the DOS header instead of the metadata failure. The input is still rejected correctly; the observed defect concerns provenance and diagnosis across artifact, member, and native-boundary inspection. A workaround is available for the reproduced table-stream failure. The generator demonstrates how to read the stream's artifact offset and add its relative cursor. Adding 864 and 24 yields the correct file offset 888 without altering the inspected artifact. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0 on macOS 27.0.1 arm64, using the built local CLI. ### Target and artifact context A source-owned PE/CLI byte fixture from `scripts/fixtures/managed/pe.mjs`, written as `repro/truncated.dll`. Only the declared table-stream extent is shortened to 24 bytes; the file, CLI header, metadata-root location, and physically present bytes remain intact. No target code or external analysis engine was executed. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The executed generator independently reports the artifact coordinate, and all three executed public commands return the stream-relative coordinate: ```text {"metadata_root":768,"tables_file_offset":864,"failed_read_file_offset":888} {"code":"invalid-metadata-root","scope":"metadata.table-row-count","offset":24,"detail":"metadata.table-row-count leaves the metadata byte range"} ``` The reproduction was observed locally and is complete. All three commands exited with status 0; artifact coverage was `partial`, and member and native-boundary coverage were `unavailable`. ### Typed outcome or recovery expected Repeat the reproduction. Keep the existing failure classification and coverage states, but return the failure at artifact offset 888. Check another metadata-root or table-stream placement so a hard-coded correction cannot pass. Existing correctly located truncated ExtraData diagnostics must retain their artifact coordinates, and parser bounds must continue following the declared stream extent. ### Analysis or suspected cause Source-derived reasoning: `src/dotnet/ManagedMetadataLayout.ts:324` checks a sliced table buffer using its relative cursor, while `src/dotnet/ManagedMetadataLayout.ts:136` retains that relative value directly in the caller-visible issue. The table-data range check at `src/dotnet/ManagedMetadataLayout.ts:387` uses the same convention. The executed reproduction establishes the row-count failure; the table-data observation comes from source inspection. ### Additional context - Related completed issue [#2677](https://github.com/morluto/rea/issues/2677) concerns truncated CIL operands losing instruction context in `body.issue`; this report concerns a retained metadata-table issue with a stream-relative value in its file-offset field. - Related completed issue [#2191](https://github.com/morluto/rea/issues/2191) concerns malformed PE section errors becoming generic `execution_failure`; this report already retains the typed metadata failure and appropriate coverage but gives its wrong byte coordinate.
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#1232
No description provided.