[GH-ISSUE #4370] [Bug] A method ending in volatile. receives a complete decoded-CIL fingerprint #1727

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

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

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

SOURCE-ONLY verification: the source inspector inspectManagedMembersBytes reports a synthetic method containing only FE 13 (volatile.) as present, with issue: null, complete coverage, and a normalized IL hash. The prefix has no following instruction to modify. The source-boundary check reproduced once on each tested revision; no compiled CLI, MCP, installed npm package, provider, or .NET target was executed.

Reproduction

Prerequisites: Node 24.18.0 and npm 11.16.0, matching the repository pins. Prepare the source and locked dependencies:

git clone https://github.com/morluto/rea.git rea
cd rea
git checkout 8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd
npm ci

Save this complete script as ../repro.mts:

import assert from "node:assert/strict";
import { resolve } from "node:path";
import { pathToFileURL } from "node:url";
const root = process.env.REA_CHECKOUT;
if (!root) throw new Error("Set REA_CHECKOUT to the source checkout");
const load = (file: string) => import(pathToFileURL(resolve(root, file)).href);
const { inspectManagedMembersBytes } = await load("src/dotnet/ManagedMemberInspector.ts");
const { buildManagedPeFixture, managedPeFixtureTarget } = await load("src/dotnet/ManagedPe.fixture.ts");
for (const [name, il, status] of [
  ["nop-ret", [0x00, 0x2a], "present"],
  ["truncated-opcode", [0xfe], "malformed"],
  ["dangling-volatile", [0xfe, 0x13], "present"],
] as const) {
  const bytes = buildManagedPeFixture({
    ilBody: Buffer.from([(il.length << 2) | 2, ...il]),
  });
  const result = inspectManagedMembersBytes(bytes, managedPeFixtureTarget(bytes));
  const body = result.methods[0]!.body;
  assert.equal(body.status, status); // Assert the observed behavior, including the bug.
  assert.equal(body.issue === null, status === "present");
  assert.equal(body.normalized_il_sha256 !== null, status === "present");
  console.log(JSON.stringify({ name, status: body.status, issue: body.issue,
    normalized_il_sha256: body.normalized_il_sha256, coverage: result.coverage.state }));
}

From the checkout, run:

REA_CHECKOUT="$PWD" ./node_modules/.bin/tsx ../repro.mts

The script was executed against current main and latest published release source with prepared locked dependencies. It asserts observed behavior, including the incorrect present result. The failing fixture's method bytes are 0A FE 13: a tiny header declaring two IL bytes followed by volatile.. The existing builder supplies intact surrounding PE/CLI metadata. Neither fixture is loaded or executed as .NET code.

Expected behavior

An unfinished prefix chain should produce a malformed body, a meaningful issue, and no normalized IL hash. Microsoft's volatile. documentation requires the prefix to immediately precede a supported memory-access instruction. REA's fingerprint contract exposes the normalized field only for an entirely decoded, present CIL stream and excludes malformed bodies. This report concerns a missing instruction after a prefix, not general stack/type verification or validation of every prefix/opcode combination.

Impact and scope

Observed impact is incorrect completeness and fingerprint evidence for malformed method bodies. Ordinary and truncated-opcode controls behave as expected. No real assembly occurrence, downstream reconstruction acceptance, exception-region misclassification, or general CIL stack/type validation was established.

REA version

Current main source 8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd; latest published release 6.5.0 source 3b79af28e80785afce9f5998d8f03289402a1847. Both package manifests report 6.5.0; both source checks returned the result below.

Runtime and platform

Node v24.18.0; npm 11.16.0; Darwin arm64.

Target and artifact context

Synthetic PE/CLI method bytes with otherwise intact fixture metadata. No external target or provider is required.

Interface

Other

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Exact stdout from the script, identical on both revisions; both processes exited 0:

{"name":"nop-ret","status":"present","issue":null,"normalized_il_sha256":"5e5fad7741cb44bca3a4f045546b7449990da343f612f5f34c0ca30e9eee0636","coverage":"complete"}
{"name":"truncated-opcode","status":"malformed","issue":"Two-byte CIL opcode at IL offset 0 leaves method body","normalized_il_sha256":null,"coverage":"partial"}
{"name":"dangling-volatile","status":"present","issue":null,"normalized_il_sha256":"49bc9d95b5e2864f83b9eb13a65f02c237127e3923ed1f294dedf4bce6c63b63","coverage":"complete"}

Typed outcome or recovery expected

For the terminal-prefix case, require body.status: "malformed", a non-null issue identifying the unfinished prefix chain, normalized_il_sha256: null, and partial CIL scan coverage while retaining already decoded evidence. The nop-ret control must remain present with a hash and complete coverage; the truncated-opcode control must remain malformed with a null hash and partial coverage.

Analysis or suspected cause

Source inspection shows that decodeInstructions records pending prefixStart but never checks it at the end of the stream. methodBody maps the issue-free decode to present, emits a hash, and marks scanning complete. This source-derived explanation matches the executed inspector result.

Additional context

Related #2677 concerns diagnostic wording for already-malformed truncated operands. #2241 adds the supported no. prefix and explicitly leaves prefix/opcode combination validation out of its scope. #297 addresses instruction-limit truncation. None covers success for a terminal complete prefix opcode. No real assembly occurrence, downstream reconstruction acceptance, exception-region misclassification, or general CIL stack/type validation was established.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The source inspector marks an unfinished prefix stream complete and emits a normalized hash, creating incorrect managed analysis evidence. Broader operational harm was not established.
  • Workaround status: unverified
  • Workaround basis: No executed caller-visible workaround restored correct completeness and hash reporting while preserving inspection of the malformed body.
Originally created by @coygeek on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4370 ### 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 SOURCE-ONLY verification: the source inspector `inspectManagedMembersBytes` reports a synthetic method containing only `FE 13` (`volatile.`) as `present`, with `issue: null`, complete coverage, and a normalized IL hash. The prefix has no following instruction to modify. The source-boundary check reproduced once on each tested revision; no compiled CLI, MCP, installed npm package, provider, or .NET target was executed. ### Reproduction Prerequisites: Node 24.18.0 and npm 11.16.0, matching the repository pins. Prepare the source and locked dependencies: ```sh git clone https://github.com/morluto/rea.git rea cd rea git checkout 8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd npm ci ``` Save this complete script as `../repro.mts`: ```ts import assert from "node:assert/strict"; import { resolve } from "node:path"; import { pathToFileURL } from "node:url"; const root = process.env.REA_CHECKOUT; if (!root) throw new Error("Set REA_CHECKOUT to the source checkout"); const load = (file: string) => import(pathToFileURL(resolve(root, file)).href); const { inspectManagedMembersBytes } = await load("src/dotnet/ManagedMemberInspector.ts"); const { buildManagedPeFixture, managedPeFixtureTarget } = await load("src/dotnet/ManagedPe.fixture.ts"); for (const [name, il, status] of [ ["nop-ret", [0x00, 0x2a], "present"], ["truncated-opcode", [0xfe], "malformed"], ["dangling-volatile", [0xfe, 0x13], "present"], ] as const) { const bytes = buildManagedPeFixture({ ilBody: Buffer.from([(il.length << 2) | 2, ...il]), }); const result = inspectManagedMembersBytes(bytes, managedPeFixtureTarget(bytes)); const body = result.methods[0]!.body; assert.equal(body.status, status); // Assert the observed behavior, including the bug. assert.equal(body.issue === null, status === "present"); assert.equal(body.normalized_il_sha256 !== null, status === "present"); console.log(JSON.stringify({ name, status: body.status, issue: body.issue, normalized_il_sha256: body.normalized_il_sha256, coverage: result.coverage.state })); } ``` From the checkout, run: ```sh REA_CHECKOUT="$PWD" ./node_modules/.bin/tsx ../repro.mts ``` The script was executed against current main and latest published release source with prepared locked dependencies. It asserts observed behavior, including the incorrect `present` result. The failing fixture's method bytes are `0A FE 13`: a tiny header declaring two IL bytes followed by `volatile.`. The existing builder supplies intact surrounding PE/CLI metadata. Neither fixture is loaded or executed as .NET code. ### Expected behavior An unfinished prefix chain should produce a malformed body, a meaningful issue, and no normalized IL hash. Microsoft's [volatile. documentation](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.volatile?view=net-10.0) requires the prefix to immediately precede a supported memory-access instruction. REA's [fingerprint contract](https://github.com/morluto/rea/blob/8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd/docs/managed-code-analysis.md#decoded-cil-fingerprint) exposes the normalized field only for an entirely decoded, present CIL stream and excludes malformed bodies. This report concerns a missing instruction after a prefix, not general stack/type verification or validation of every prefix/opcode combination. ### Impact and scope Observed impact is incorrect completeness and fingerprint evidence for malformed method bodies. Ordinary and truncated-opcode controls behave as expected. No real assembly occurrence, downstream reconstruction acceptance, exception-region misclassification, or general CIL stack/type validation was established. ### REA version Current main source `8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd`; latest published release 6.5.0 source `3b79af28e80785afce9f5998d8f03289402a1847`. Both package manifests report 6.5.0; both source checks returned the result below. ### Runtime and platform Node v24.18.0; npm 11.16.0; Darwin arm64. ### Target and artifact context Synthetic PE/CLI method bytes with otherwise intact fixture metadata. No external target or provider is required. ### Interface Other ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Exact stdout from the script, identical on both revisions; both processes exited 0: ```jsonl {"name":"nop-ret","status":"present","issue":null,"normalized_il_sha256":"5e5fad7741cb44bca3a4f045546b7449990da343f612f5f34c0ca30e9eee0636","coverage":"complete"} {"name":"truncated-opcode","status":"malformed","issue":"Two-byte CIL opcode at IL offset 0 leaves method body","normalized_il_sha256":null,"coverage":"partial"} {"name":"dangling-volatile","status":"present","issue":null,"normalized_il_sha256":"49bc9d95b5e2864f83b9eb13a65f02c237127e3923ed1f294dedf4bce6c63b63","coverage":"complete"} ``` ### Typed outcome or recovery expected For the terminal-prefix case, require `body.status: "malformed"`, a non-null issue identifying the unfinished prefix chain, `normalized_il_sha256: null`, and partial CIL scan coverage while retaining already decoded evidence. The `nop-ret` control must remain present with a hash and complete coverage; the truncated-opcode control must remain malformed with a null hash and partial coverage. ### Analysis or suspected cause Source inspection shows that [decodeInstructions](https://github.com/morluto/rea/blob/8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd/src/dotnet/ManagedMemberInstructionDecoder.ts#L438-L500) records pending `prefixStart` but never checks it at the end of the stream. [methodBody](https://github.com/morluto/rea/blob/8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd/src/dotnet/ManagedMethodBodyReader.ts#L114-L198) maps the issue-free decode to `present`, emits a hash, and marks scanning complete. This source-derived explanation matches the executed inspector result. ### Additional context Related [#2677](https://github.com/morluto/rea/issues/2677) concerns diagnostic wording for already-malformed truncated operands. [#2241](https://github.com/morluto/rea/pull/2241) adds the supported `no.` prefix and explicitly leaves prefix/opcode combination validation out of its scope. [#297](https://github.com/morluto/rea/pull/297) addresses instruction-limit truncation. None covers success for a terminal complete prefix opcode. No real assembly occurrence, downstream reconstruction acceptance, exception-region misclassification, or general CIL stack/type validation was established. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The source inspector marks an unfinished prefix stream complete and emits a normalized hash, creating incorrect managed analysis evidence. Broader operational harm was not established. - Workaround status: unverified - Workaround basis: No executed caller-visible workaround restored correct completeness and hash reporting while preserving inspection of the malformed body.
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#1727
No description provided.