[GH-ISSUE #3204] [Bug] Ghidra instruction incoming references include synthetic entry-point metadata #1141

Closed
opened 2026-10-15 18:31:27 +00:00 by bsakaguchi · 2 comments
Owner

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

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

Source inspection establishes that Ghidra-backed inspect_native_instruction collects synthetic entry-point references into incoming_references, although the field's documented contract excludes them. The separate xrefs collector filters that reference class from the same incoming ReferenceManager query.

This is a static-proof-confirmed defect. The real CLI attempt stopped at provider admission with provider_unavailable and Ghidra diagnostic not_configured because GHIDRA_INSTALL_DIR was unset. It did not reproduce the real-provider output. A focused fake-backed MCP test observed that the production server/provider-parser path preserves both a supplied genuine READ reference and an illustrative entry-reference-shaped item. That test establishes downstream propagation, not Ghidra's actual reference creation, source-address spelling or reference-type spelling.

Reproduction

Use a clean checkout at the revision below on supported macOS or Linux. Configure Ghidra 12.1.x and its documented Java/native-decompiler prerequisites, with GHIDRA_INSTALL_DIR pointing to the extracted installation. These are the complete real-provider reproduction steps; verification did not complete them because Ghidra was not configured.

  1. Install the locked dependencies and build the CLI.

    npm ci
    npm run build:cached
    
  2. Create a one-byte x86-64 RET image and isolated CLI state directories.

    mkdir -p repro-entry-point/home/config repro-entry-point/home/cache repro-entry-point/home/data
    node --input-type=module -e 'import {writeFileSync} from "node:fs"; writeFileSync("repro-entry-point/entry.bin", Buffer.from([0xc3]));'
    
  3. Inspect the selected entry address. The raw-binary import maps file offset zero at 0x1000 and seeds the entry function there. Wait for normal completed auto-analysis.

    env HOME="$PWD/repro-entry-point/home" \
      XDG_CONFIG_HOME="$PWD/repro-entry-point/home/config" \
      XDG_CACHE_HOME="$PWD/repro-entry-point/home/cache" \
      XDG_DATA_HOME="$PWD/repro-entry-point/home/data" \
      node scripts/rea.mjs inspect-native-instruction repro-entry-point/entry.bin 0x1000 \
      --target-format raw-binary --architecture x86_64 --entry-point 0x1000 \
      --provider ghidra --format json
    
  4. Query incoming source addresses for the same target and address.

    env HOME="$PWD/repro-entry-point/home" \
      XDG_CONFIG_HOME="$PWD/repro-entry-point/home/config" \
      XDG_CACHE_HOME="$PWD/repro-entry-point/home/cache" \
      XDG_DATA_HOME="$PWD/repro-entry-point/home/data" \
      node scripts/rea.mjs xrefs repro-entry-point/entry.bin 0x1000 \
      --target-format raw-binary --architecture x86_64 --entry-point 0x1000 \
      --provider ghidra --format json
    
  5. Compare incoming_references with the xrefs source-address list. Source inspection establishes that a synthetic entry-point reference supplied by Ghidra's ReferenceManager reaches the instruction array, while xrefs omits it. Do not assume that the synthetic reference originates at 0x1000 or is a self-reference. Neither its concrete source address nor its type spelling was established by the verifier.

Expected behavior

src/domain/native/nativeInstruction.ts:118-124 defines incoming_references as static references to the exact queried code or data address, excluding synthetic entry-point references. An empty array means the provider examined the address and found no eligible references; null or omission means unavailable. docs/ghidra-provider.md:149 also promises omission of synthetic entry-point references without actionable memory sources.

Eligible references should retain their source and target addresses, reference type, operand index, access facts and provenance. docs/raw-binary.md:32-52 documents the supported raw architecture, entry and loader mapping used above.

Impact and scope

Non-actionable synthetic metadata can reach callers as incoming-reference evidence at registered entry-point addresses, making the instruction result disagree with xrefs. The supported impact is P2. The defect affects this reference class; verification did not establish severe harm, a general core-workflow failure or a security-boundary crossing.

Callers that need genuine incoming source addresses can use xrefs, whose collector explicitly filters entry-point references. It does not return the full typed reference objects, so it does not replace every use of incoming_references.

REA version

6.4.0 (main at 5830088520)

Runtime and platform

Node.js v26.10.0; macOS arm64. No Ghidra version was observed because the provider was not configured. The documented real-provider reproduction requires Ghidra 12.1.x.

Target and artifact context

An inline-created, one-byte raw x86-64 image containing c3, mapped at 0x1000 with the same address registered as its entry point. Normal completed Ghidra auto-analysis, no language override and no document selector are the intended conditions. The verifier's passing MCP test used the repository's existing fake Ghidra integration rather than a live engine.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Source-derived, bridge/ghidra/ReaGhidraBridge.java:1340-1344:
ReferenceIterator incomingIterator = currentProgram.getReferenceManager().getReferencesTo(address);
while (incomingIterator.hasNext()) {
    monitor.checkCancelled();
    addReference(incoming, incomingIterator.next());
}

Source-derived xrefs guard, same file:1400-1403:
Reference reference = references.next();
if (reference.isEntryPointReference()) {
    continue;
}

Observed real-provider attempt: exit 1; provider_unavailable; Ghidra not_configured;
GHIDRA_INSTALL_DIR is not set.

Observed focused fake-backed MCP probe: 1 file, 1 test passed.
MCP preserves both supplied incoming references; xrefs preserves the supplied genuine-only list.

Typed outcome or recovery expected

Return the typed incoming_references array with only eligible static references, preserving every genuine reference's complete facts. Exclude synthetic entry-point metadata without changing the meaning of outgoing references or bare-address xrefs. Keep the existing distinction between an examined empty set and unavailable coverage.

For the entry fixture, exclude the synthetic reference and agree with xrefs on eligible incoming source addresses. A proposed restoration control is a raw image containing e8 01 00 00 00 c3 c3, mapped at 0x1000: the direct CALL at 0x1000 targets the RET at 0x1006, so inspecting 0x1006 must preserve that genuine call reference. This control was not executed with Ghidra during verification.

Analysis or suspected cause

The following reasoning is source-derived at the stated baseline:

  • src/cli/coreNativeCommands.ts:224-253 dispatches the public instruction command. src/ghidra/GhidraAnalysisProfile.ts:106-159, src/ghidra/GhidraProviderClient.ts:599-607, src/ghidra/GhidraLauncher.ts:647-681, and bridge/ghidra/ReaGhidraPrepareRaw.java:29-47 connect the supported raw import to addEntryPoint, disassembly and entry-function creation before analysis.
  • bridge/ghidra/ReaGhidraBridge.java:425-452 dispatches instruction inspection after completed analysis. Its incoming collector at :1338-1349 passes every reference to addReference; that helper at :3694-3701 deduplicates without an entry-point eligibility check. Sorting does not remove the reference class.
  • bridge/ghidra/ReaGhidraBridge.java:1230-1240 serializes the reference, and :4443-4455 serializes addresses without rejecting synthetic entry-point sources. In contrast, xrefs at :1392-1412 and function-reference scans at :3663-3691 explicitly reject isEntryPointReference().
  • src/ghidra/GhidraFunctionValues.ts:406 and :445-496 validate and preserve the result; src/ghidra/GhidraProviderClient.ts:855-862 and :950-961 deliver it as analysis evidence. The fake-backed MCP test confirms this downstream preservation only.
  • scripts/lib/real-ghidra-compact-references.mjs:153-160 explains the synthetic-entry distinction, and :180-199 requires incoming-reference source-address parity with xrefs. These assertions were inspected, not executed.

Additional context

Related closed feature issue #1245 and merged PR #1715 introduced typed incoming references; this report concerns the remaining violation of their synthetic-entry exclusion contract rather than a request to add that feature.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3204 ### 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 Source inspection establishes that Ghidra-backed `inspect_native_instruction` collects synthetic entry-point references into `incoming_references`, although the field's documented contract excludes them. The separate `xrefs` collector filters that reference class from the same incoming ReferenceManager query. This is a static-proof-confirmed defect. The real CLI attempt stopped at provider admission with `provider_unavailable` and Ghidra diagnostic `not_configured` because `GHIDRA_INSTALL_DIR` was unset. It did not reproduce the real-provider output. A focused fake-backed MCP test observed that the production server/provider-parser path preserves both a supplied genuine READ reference and an illustrative entry-reference-shaped item. That test establishes downstream propagation, not Ghidra's actual reference creation, source-address spelling or reference-type spelling. ### Reproduction Use a clean checkout at the revision below on supported macOS or Linux. Configure Ghidra 12.1.x and its documented Java/native-decompiler prerequisites, with `GHIDRA_INSTALL_DIR` pointing to the extracted installation. These are the complete real-provider reproduction steps; verification did not complete them because Ghidra was not configured. 1. Install the locked dependencies and build the CLI. ```sh npm ci npm run build:cached ``` 2. Create a one-byte x86-64 RET image and isolated CLI state directories. ```sh mkdir -p repro-entry-point/home/config repro-entry-point/home/cache repro-entry-point/home/data node --input-type=module -e 'import {writeFileSync} from "node:fs"; writeFileSync("repro-entry-point/entry.bin", Buffer.from([0xc3]));' ``` 3. Inspect the selected entry address. The raw-binary import maps file offset zero at `0x1000` and seeds the entry function there. Wait for normal completed auto-analysis. ```sh env HOME="$PWD/repro-entry-point/home" \ XDG_CONFIG_HOME="$PWD/repro-entry-point/home/config" \ XDG_CACHE_HOME="$PWD/repro-entry-point/home/cache" \ XDG_DATA_HOME="$PWD/repro-entry-point/home/data" \ node scripts/rea.mjs inspect-native-instruction repro-entry-point/entry.bin 0x1000 \ --target-format raw-binary --architecture x86_64 --entry-point 0x1000 \ --provider ghidra --format json ``` 4. Query incoming source addresses for the same target and address. ```sh env HOME="$PWD/repro-entry-point/home" \ XDG_CONFIG_HOME="$PWD/repro-entry-point/home/config" \ XDG_CACHE_HOME="$PWD/repro-entry-point/home/cache" \ XDG_DATA_HOME="$PWD/repro-entry-point/home/data" \ node scripts/rea.mjs xrefs repro-entry-point/entry.bin 0x1000 \ --target-format raw-binary --architecture x86_64 --entry-point 0x1000 \ --provider ghidra --format json ``` 5. Compare `incoming_references` with the `xrefs` source-address list. Source inspection establishes that a synthetic entry-point reference supplied by Ghidra's ReferenceManager reaches the instruction array, while `xrefs` omits it. Do not assume that the synthetic reference originates at `0x1000` or is a self-reference. Neither its concrete source address nor its type spelling was established by the verifier. ### Expected behavior `src/domain/native/nativeInstruction.ts:118-124` defines `incoming_references` as static references to the exact queried code or data address, excluding synthetic entry-point references. An empty array means the provider examined the address and found no eligible references; null or omission means unavailable. `docs/ghidra-provider.md:149` also promises omission of synthetic entry-point references without actionable memory sources. Eligible references should retain their source and target addresses, reference type, operand index, access facts and provenance. `docs/raw-binary.md:32-52` documents the supported raw architecture, entry and loader mapping used above. ### Impact and scope Non-actionable synthetic metadata can reach callers as incoming-reference evidence at registered entry-point addresses, making the instruction result disagree with `xrefs`. The supported impact is P2. The defect affects this reference class; verification did not establish severe harm, a general core-workflow failure or a security-boundary crossing. Callers that need genuine incoming source addresses can use `xrefs`, whose collector explicitly filters entry-point references. It does not return the full typed reference objects, so it does not replace every use of `incoming_references`. ### REA version 6.4.0 (main at 58300885207133422af235e0f2b10658cf958b03) ### Runtime and platform Node.js v26.10.0; macOS arm64. No Ghidra version was observed because the provider was not configured. The documented real-provider reproduction requires Ghidra 12.1.x. ### Target and artifact context An inline-created, one-byte raw x86-64 image containing `c3`, mapped at `0x1000` with the same address registered as its entry point. Normal completed Ghidra auto-analysis, no language override and no document selector are the intended conditions. The verifier's passing MCP test used the repository's existing fake Ghidra integration rather than a live engine. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence ```text Source-derived, bridge/ghidra/ReaGhidraBridge.java:1340-1344: ReferenceIterator incomingIterator = currentProgram.getReferenceManager().getReferencesTo(address); while (incomingIterator.hasNext()) { monitor.checkCancelled(); addReference(incoming, incomingIterator.next()); } Source-derived xrefs guard, same file:1400-1403: Reference reference = references.next(); if (reference.isEntryPointReference()) { continue; } Observed real-provider attempt: exit 1; provider_unavailable; Ghidra not_configured; GHIDRA_INSTALL_DIR is not set. Observed focused fake-backed MCP probe: 1 file, 1 test passed. MCP preserves both supplied incoming references; xrefs preserves the supplied genuine-only list. ``` ### Typed outcome or recovery expected Return the typed `incoming_references` array with only eligible static references, preserving every genuine reference's complete facts. Exclude synthetic entry-point metadata without changing the meaning of outgoing `references` or bare-address `xrefs`. Keep the existing distinction between an examined empty set and unavailable coverage. For the entry fixture, exclude the synthetic reference and agree with `xrefs` on eligible incoming source addresses. A proposed restoration control is a raw image containing `e8 01 00 00 00 c3 c3`, mapped at `0x1000`: the direct CALL at `0x1000` targets the RET at `0x1006`, so inspecting `0x1006` must preserve that genuine call reference. This control was not executed with Ghidra during verification. ### Analysis or suspected cause The following reasoning is source-derived at the stated baseline: - `src/cli/coreNativeCommands.ts:224-253` dispatches the public instruction command. `src/ghidra/GhidraAnalysisProfile.ts:106-159`, `src/ghidra/GhidraProviderClient.ts:599-607`, `src/ghidra/GhidraLauncher.ts:647-681`, and `bridge/ghidra/ReaGhidraPrepareRaw.java:29-47` connect the supported raw import to `addEntryPoint`, disassembly and entry-function creation before analysis. - `bridge/ghidra/ReaGhidraBridge.java:425-452` dispatches instruction inspection after completed analysis. Its incoming collector at `:1338-1349` passes every reference to `addReference`; that helper at `:3694-3701` deduplicates without an entry-point eligibility check. Sorting does not remove the reference class. - `bridge/ghidra/ReaGhidraBridge.java:1230-1240` serializes the reference, and `:4443-4455` serializes addresses without rejecting synthetic entry-point sources. In contrast, `xrefs` at `:1392-1412` and function-reference scans at `:3663-3691` explicitly reject `isEntryPointReference()`. - `src/ghidra/GhidraFunctionValues.ts:406` and `:445-496` validate and preserve the result; `src/ghidra/GhidraProviderClient.ts:855-862` and `:950-961` deliver it as analysis evidence. The fake-backed MCP test confirms this downstream preservation only. - `scripts/lib/real-ghidra-compact-references.mjs:153-160` explains the synthetic-entry distinction, and `:180-199` requires incoming-reference source-address parity with `xrefs`. These assertions were inspected, not executed. ### Additional context Related closed feature issue #1245 and merged PR #1715 introduced typed incoming references; this report concerns the remaining violation of their synthetic-entry exclusion contract rather than a request to add that feature.
bsakaguchi 2026-10-15 18:31:27 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@N0zoM1z0 commented on GitHub (Oct 14, 2026):

Real Windows Ghidra 12.1.4 exposes a synthetic PE entry-point source in inspect_native_instruction.incoming_references although xrefs, procedure_references and function dossiers exclude entry-point references. This makes the compact incoming projection disagree with the address list and presents Entry%20Point:0x0 as an incoming source.

Reproduced on main 500e32399 with the original scripts/verify-real-ghidra-windows.mjs and both source-owned x64/x86 EXE fixtures (ordinary user, two logical CPUs, Java 768 MiB, 3 GiB Job; no removed checks). Both fail the existing independent compact-reference parity assertion. x64 expected header sources 0x1400000a8, 0x1400000ac, 0x140000194; actual prepends Entry%20Point:0x0. x86 likewise adds that synthetic source before 0x4000a8, 0x4000ac, 0x400184. Natural failures, stopReason null, all owned processes settled. DLL controls pass their complete workflows.

The producing boundary is the exact incoming collector in bridge/ghidra/ReaGhidraBridge.java, which feeds every Reference directly to addReference. xrefs and visitReferences already omit isEntryPointReference(). The shared reference eligibility rule should own this policy; preserve real header DATA references and original reference metadata, order, coverage and unavailable-vs-empty semantics. Do not filter address display strings downstream or weaken the real parity oracle. Check affected full/compact/call-target consumers and snapshot freshness.

Additional validation proposed: actual x64/x86 EXE reference workflows through CLI/MCP, unchanged source bytes and cleanup, retained DLL controls; record completed task elapsed/CPU/peak and output size. No target execution or mutation needed.

Full local receipts are retained under /tmp/rea-windows-e2e-20261013/results/issue2903-main500; these paths are local evidence, not publicly downloadable artifacts.

<!-- gh-comment-id:6165689142 --> @N0zoM1z0 commented on GitHub (Oct 14, 2026): Real Windows Ghidra 12.1.4 exposes a synthetic PE entry-point source in `inspect_native_instruction.incoming_references` although `xrefs`, `procedure_references` and function dossiers exclude entry-point references. This makes the compact incoming projection disagree with the address list and presents `Entry%20Point:0x0` as an incoming source. Reproduced on main `500e32399` with the original `scripts/verify-real-ghidra-windows.mjs` and both source-owned x64/x86 EXE fixtures (ordinary user, two logical CPUs, Java 768 MiB, 3 GiB Job; no removed checks). Both fail the existing independent compact-reference parity assertion. x64 expected header sources `0x1400000a8`, `0x1400000ac`, `0x140000194`; actual prepends `Entry%20Point:0x0`. x86 likewise adds that synthetic source before `0x4000a8`, `0x4000ac`, `0x400184`. Natural failures, stopReason null, all owned processes settled. DLL controls pass their complete workflows. The producing boundary is the exact incoming collector in `bridge/ghidra/ReaGhidraBridge.java`, which feeds every Reference directly to `addReference`. `xrefs` and `visitReferences` already omit `isEntryPointReference()`. The shared reference eligibility rule should own this policy; preserve real header DATA references and original reference metadata, order, coverage and unavailable-vs-empty semantics. Do not filter address display strings downstream or weaken the real parity oracle. Check affected full/compact/call-target consumers and snapshot freshness. Additional validation proposed: actual x64/x86 EXE reference workflows through CLI/MCP, unchanged source bytes and cleanup, retained DLL controls; record completed task elapsed/CPU/peak and output size. No target execution or mutation needed. Full local receipts are retained under `/tmp/rea-windows-e2e-20261013/results/issue2903-main500`; these paths are local evidence, not publicly downloadable artifacts.
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Delivered on main in fffb07419.

Instruction incoming-reference projection admitted synthetic entry-point references. The Ghidra producer now uses the existing entry-point-reference filter consistently.

Validation: Source inspection of the shared bridge filter and complete qualification. No real Ghidra replay of the original target was performed; a proposed source-regex test was removed because it did not establish engine behavior. Full exact-revision qualification passed.

<!-- gh-comment-id:6177641742 --> @morluto commented on GitHub (Oct 14, 2026): Delivered on main in [fffb07419](https://github.com/morluto/rea/commit/fffb0741936067530d1ce9777461cd53e87398c2). Instruction incoming-reference projection admitted synthetic entry-point references. The Ghidra producer now uses the existing entry-point-reference filter consistently. Validation: Source inspection of the shared bridge filter and complete qualification. No real Ghidra replay of the original target was performed; a proposed source-regex test was removed because it did not establish engine behavior. Full exact-revision [qualification](https://github.com/morluto/rea/actions/runs/38629313022) passed.
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#1141
No description provided.