[GH-ISSUE #3961] [Bug] Exact Mach-O ABI-prefixed export matches are classified as inferred decorated candidates #1568

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

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

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 verify-managed-native-boundaries CLI reports a declared P/Invoke entry point as an inferred decorated-name candidate when its matching Mach-O export carries the leading ABI underscore. With declared open_native, matching module identity, and no_mangle: true, _open_native produces an inferred item and zero verified links. Changing only the export spelling to open_native produces a verified item. The retained match is useful, but the returned status, confidence, counts, and limitation misclassify the declaration link.

Reproduction

Use the baseline checkout with its installed locked dependencies and prebuilt runtime. Run the following from the repository root. It creates authenticated synthetic Evidence from the shipped contract example and invokes the public CLI for the failing case and two controls. It does not inspect a real binary or invoke a live provider.

repro_dir=$(mktemp -d)
node --input-type=module - "$repro_dir" <<'NODE'
import { mkdirSync, writeFileSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { MANAGED_NATIVE_VERIFICATION_EXAMPLE as example }
  from './dist/contracts/managed/managedWorkflowExamples.js';
import { createEvidence } from './dist/domain/evidence.js';
const scratch = process.argv[2];
mkdirSync(`${scratch}/home`);
for (const [label, name] of [
  ['plain', 'open_native'], ['abi', '_open_native'],
  ['double', '__open_native'],
]) {
  const input = structuredClone(example);
  const native = input.native_observations[0];
  const result = {
    ...native.normalized_result,
    exports: {
      ...native.normalized_result.exports,
      items: [{ name, address: '0x1000', weak: false,
        reexport: false, source: 'nm' }],
      total: 1,
    },
  };
  input.native_observations = [createEvidence({
    path: native.subject.local_path,
    sha256: native.subject.digest.sha256,
    format: native.subject.format,
    architecture: native.subject.architecture,
  }, native.provider, {
    operation: native.operation, parameters: native.parameters,
    result, rawResult: null, limitations: native.limitations,
  })];
  const path = `${scratch}/${label}.json`;
  writeFileSync(path, JSON.stringify(input));
  const child = spawnSync(process.execPath, [
    'scripts/rea.mjs', 'verify-managed-native-boundaries',
    path, '--format', 'json',
  ], { encoding: 'utf8', env: {
    PATH: process.env.PATH, HOME: `${scratch}/home`,
  } });
  if (child.status !== 0) throw new Error(child.stderr || child.stdout);
  const output = JSON.parse(child.stdout).normalized_result;
  const item = output.pinvoke_imports[0];
  console.log(JSON.stringify({ label, summary: output.summary,
    status: item.status, basis: item.basis,
    confidence: item.confidence, limitations: item.limitations }));
}
NODE

The managed declaration remains open_native in nativehelper.dll, with no_mangle: true. Each native observation preserves the example's matching module identity, and its Evidence ID is recomputed after changing the export spelling.

Expected behavior

The documented workflow compares declared module and entry point identities with native observations to establish declaration links. The repository treats one leading Mach-O underscore as an ABI prefix, while preserving a further C identifier underscore. With the same declared symbol identity and a matching module, the ABI-prefixed case should produce an item with status: "verified", basis: "exact-export-name", confidence: "observed", and summary verified 1, inferred 0. It should preserve the raw native spelling and provenance. This verifies an observed declaration link only; the outer workflow Evidence can remain analyst inference and does not prove runtime loading, binding, marshaling, or execution.

Impact and scope

The observed valid ABI-normalized match receives incorrect per-import status and confidence, summary counts, and limitation text. The failure is bounded to the declaration-link classification; the candidate and provenance remain available. Severe harm and blocked core workflows were not established.

Workaround: The CLI retains the declared import, matching module identity, and raw native candidate, so an analyst can manually recognize the ABI-prefix equality. This preserves access to the underlying evidence but does not correct the machine-readable status; altering authenticated source Evidence is unnecessary.

Assessment: P2.

REA version

rea 6.5.0 at source commit 4692bb3330

Runtime and platform

Node 24.18.0; macOS arm64

Target and artifact context

Synthetic authenticated managed-boundary and Mach-O export Evidence derived from the shipped contract example. No real binary inspection or CLR execution was performed.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed two CLI runs for each spelling, including a rerun of the exact reproduction block above; all exited 0 with empty stderr and returned the same classification in both attempts. The plain control returned verified / exact-export-name / observed, summary verified 1 and inferred 0, with no item limitations. The abi case returned inferred / decorated-name-candidate / inferred, summary verified 0 and inferred 1, while retaining _open_native as its matching candidate. Its item limitation was:

Native symbol matched a decorated candidate rather than the exact declared import name.

The double control returned unresolved / no-native-candidate / unknown, with summary unresolved 1 and no candidates. The existing focused conformance suite passed all 12 tests; this does not establish that the missing positive ABI-prefix classification is correct.

Typed outcome or recovery expected

Rerun the same authenticated inputs. The ABI-prefixed export should produce a verified item with observed confidence, the exact-export basis, verified 1 and inferred 0, and no decorated-candidate limitation. Preserve _open_native in the returned native observation and retain its Evidence links. The plain-name control should remain verified, and the double-underscore control should remain unresolved so restoration does not erase a C identifier underscore. Keep the workflow's limits on runtime claims. These are acceptance criteria; no fix was applied or observed during verification.

Additional context

Freshness checked against origin/main at 22ec8bdb2f. The cited failing implementation files are unchanged from the reproduced baseline; this is a source freshness check, not a runtime rerun on that revision.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3961 ### 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 `verify-managed-native-boundaries` CLI reports a declared P/Invoke entry point as an inferred decorated-name candidate when its matching Mach-O export carries the leading ABI underscore. With declared `open_native`, matching module identity, and `no_mangle: true`, `_open_native` produces an inferred item and zero verified links. Changing only the export spelling to `open_native` produces a verified item. The retained match is useful, but the returned status, confidence, counts, and limitation misclassify the declaration link. ### Reproduction Use the baseline checkout with its installed locked dependencies and prebuilt runtime. Run the following from the repository root. It creates authenticated synthetic Evidence from the shipped contract example and invokes the public CLI for the failing case and two controls. It does not inspect a real binary or invoke a live provider. ```sh repro_dir=$(mktemp -d) node --input-type=module - "$repro_dir" <<'NODE' import { mkdirSync, writeFileSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; import { MANAGED_NATIVE_VERIFICATION_EXAMPLE as example } from './dist/contracts/managed/managedWorkflowExamples.js'; import { createEvidence } from './dist/domain/evidence.js'; const scratch = process.argv[2]; mkdirSync(`${scratch}/home`); for (const [label, name] of [ ['plain', 'open_native'], ['abi', '_open_native'], ['double', '__open_native'], ]) { const input = structuredClone(example); const native = input.native_observations[0]; const result = { ...native.normalized_result, exports: { ...native.normalized_result.exports, items: [{ name, address: '0x1000', weak: false, reexport: false, source: 'nm' }], total: 1, }, }; input.native_observations = [createEvidence({ path: native.subject.local_path, sha256: native.subject.digest.sha256, format: native.subject.format, architecture: native.subject.architecture, }, native.provider, { operation: native.operation, parameters: native.parameters, result, rawResult: null, limitations: native.limitations, })]; const path = `${scratch}/${label}.json`; writeFileSync(path, JSON.stringify(input)); const child = spawnSync(process.execPath, [ 'scripts/rea.mjs', 'verify-managed-native-boundaries', path, '--format', 'json', ], { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: `${scratch}/home`, } }); if (child.status !== 0) throw new Error(child.stderr || child.stdout); const output = JSON.parse(child.stdout).normalized_result; const item = output.pinvoke_imports[0]; console.log(JSON.stringify({ label, summary: output.summary, status: item.status, basis: item.basis, confidence: item.confidence, limitations: item.limitations })); } NODE ``` The managed declaration remains `open_native` in `nativehelper.dll`, with `no_mangle: true`. Each native observation preserves the example's matching module identity, and its Evidence ID is recomputed after changing the export spelling. ### Expected behavior The documented workflow compares declared module and entry point identities with native observations to establish declaration links. The repository treats one leading Mach-O underscore as an ABI prefix, while preserving a further C identifier underscore. With the same declared symbol identity and a matching module, the ABI-prefixed case should produce an item with `status: "verified"`, `basis: "exact-export-name"`, `confidence: "observed"`, and summary verified 1, inferred 0. It should preserve the raw native spelling and provenance. This verifies an observed declaration link only; the outer workflow Evidence can remain analyst inference and does not prove runtime loading, binding, marshaling, or execution. ### Impact and scope The observed valid ABI-normalized match receives incorrect per-import status and confidence, summary counts, and limitation text. The failure is bounded to the declaration-link classification; the candidate and provenance remain available. Severe harm and blocked core workflows were not established. Workaround: The CLI retains the declared import, matching module identity, and raw native candidate, so an analyst can manually recognize the ABI-prefix equality. This preserves access to the underlying evidence but does not correct the machine-readable status; altering authenticated source Evidence is unnecessary. Assessment: P2. ### REA version rea 6.5.0 at source commit 4692bb33300c7cb77b095b6cc0c33da861c1b860 ### Runtime and platform Node 24.18.0; macOS arm64 ### Target and artifact context Synthetic authenticated managed-boundary and Mach-O export Evidence derived from the shipped contract example. No real binary inspection or CLR execution was performed. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Observed two CLI runs for each spelling, including a rerun of the exact reproduction block above; all exited 0 with empty stderr and returned the same classification in both attempts. The `plain` control returned `verified` / `exact-export-name` / `observed`, summary verified 1 and inferred 0, with no item limitations. The `abi` case returned `inferred` / `decorated-name-candidate` / `inferred`, summary verified 0 and inferred 1, while retaining `_open_native` as its matching candidate. Its item limitation was: ```text Native symbol matched a decorated candidate rather than the exact declared import name. ``` The `double` control returned `unresolved` / `no-native-candidate` / `unknown`, with summary unresolved 1 and no candidates. The existing focused conformance suite passed all 12 tests; this does not establish that the missing positive ABI-prefix classification is correct. - Expected source: [docs/managed-code-analysis.md:368-375](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/docs/managed-code-analysis.md#L368), "Managed/native boundary rules"; [src/domain/managed/managedNativeVerificationSchemas.ts:54-69](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/domain/managed/managedNativeVerificationSchemas.ts#L54) defines the exact and decorated result variants. [tests/conformance/providers/managed/managedNativeVerification.test.ts:380-430](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/tests/conformance/providers/managed/managedNativeVerification.test.ts#L380) establishes that exactly one leading Mach-O underscore belongs to the ABI rather than the C identifier. - Failure source: Executed CLI results in Actual behavior. Source-derived explanation at [src/domain/managed/managedNativeVerificationMatch.ts:103-107,181-210,219-240](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/domain/managed/managedNativeVerificationMatch.ts#L103): candidate collection applies ABI normalization, but the verified selector compares the raw export name; a matching module then yields the inferred decorated branch. - The valid synthetic input derives from [src/contracts/managed/managedWorkflowExamples.ts:314-318](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/contracts/managed/managedWorkflowExamples.ts#L314). The CLI reaches the shared application workflow through [src/cli/managedCommands.ts:191-216](https://github.com/morluto/rea/blob/4692bb33300c7cb77b095b6cc0c33da861c1b860/src/cli/managedCommands.ts#L191); no test-only entry point is required. ### Typed outcome or recovery expected Rerun the same authenticated inputs. The ABI-prefixed export should produce a verified item with observed confidence, the exact-export basis, verified 1 and inferred 0, and no decorated-candidate limitation. Preserve `_open_native` in the returned native observation and retain its Evidence links. The plain-name control should remain verified, and the double-underscore control should remain unresolved so restoration does not erase a C identifier underscore. Keep the workflow's limits on runtime claims. These are acceptance criteria; no fix was applied or observed during verification. ### Additional context Freshness checked against origin/main at 22ec8bdb2f7d4626587f23b1eed4a207df600386. The cited failing implementation files are unchanged from the reproduced baseline; this is a source freshness check, not a runtime rerun on that revision.
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#1568
No description provided.