[GH-ISSUE #8213] [Bug] Comparing a renamed Ghidra function reports the same entry as mismatched #6487

Closed
opened 2026-11-01 21:11:34 +00:00 by bsakaguchi · 5 comments
Owner

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

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

compare_functions reports function_match.status: "mismatched" when the two retained dossiers describe the same Ghidra function before and after an REA metadata rename in one live Program. The name and pseudocode text did change, but the selected function entry did not. Treating the labels as proof of different functions loses the stronger same-Program observation.

Reproduction

On macOS arm64 with Ghidra 12.1.4, REA source 38433cfb4577b133d7598f9439c6448299cb44eb, and a self-built, unexecuted arm64 Mach-O:

  1. open_binary using Ghidra; run analyze_function on _evaluate and retain its dossier.
  2. annotate_native_function for _evaluate with name: "audit_renamed_evaluate".
  3. Run analyze_function on the new name and call compare_functions with the two retained dossier references.

Both dossiers have the same target SHA-256, Ghidra profile digest and function_coordinate_context, entry 0x100000430, and inclusive body range 0x100000430..0x10000047f. The primary symbol source changed from imported to user_defined, as expected for the annotation. Comparison returns function_match: {status: "mismatched", method: "explicit", ...}, status: "changed", with two changed dimensions (identity and exact pseudocode) and six unchanged. A control comparing the original _evaluate against the distinct _adjust at 0x100000410 also returns mismatched. Two independent MCP runs, one using summary references and one with complete dossiers, produced the same result. Close succeeded and the target SHA-256 stayed unchanged; the edit was confined to Ghidra's ephemeral database.

Expected behavior

Recognize the two explicitly selected observations as the same entry when compatible, authenticated same-artifact coordinate context establishes it, while independently reporting the observed rename and textual change. If the available context cannot establish correspondence, return ambiguous rather than deriving a mismatch solely from names. Do not equate equal numerical addresses across different mappings, slices, providers, or uncommitted/unknown coordinate contexts.

Impact and scope

An agent comparing its own annotation before and after can conclude that it selected different functions even though both dossiers came from the same Ghidra function in the same Program. The comparison correctly reports changed name/text; this report does not assert that the overall comparison must be unchanged or that exact text change implies behavior change. Only the fixture, host and Ghidra version above were tested; cross-provider and cross-version identity remain separate questions.

REA version

38433cfb4577b133d7598f9439c6448299cb44eb (PR #8187 branch)

Runtime and platform

Node 24.22.1; macOS 26.3 arm64; Ghidra 12.1.4 and JDK 21.

Target and artifact context

Self-built Mach-O arm64 executable; no target execution; Ghidra metadata renamed in an ephemeral session only.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Before: {address:"0x100000430", name:"_evaluate", symbol:{source:"imported"}}.
After: {address:"0x100000430", name:"audit_renamed_evaluate", symbol:{source:"user_defined"}}.

Both report identical subject digest, profile digest and mapping commitment. The result is function_match.status: "mismatched"; the expected positive correspondence is limited to these authenticated observations. Separate test: _adjust resolves at 0x100000410 and remains a distinct function.

Typed outcome or recovery expected

Preserve function correspondence separately from changed descriptive identity. If the contract adds a coordinate-based match method, expose its evidence and applicable limits; avoid unconditionally applying same-address equality. Preserve source symbol provenance and exact-text limitations.

Analysis or suspected cause

functionMatch uses stable symbol origins and names to choose matched, mismatched, or ambiguous, without consulting the applicable coordinate context. This source observation explains the observed result but does not by itself establish the correct contract for every provider. Existing relationship comparison already treats coordinate compatibility as conditional on target/format/architecture and mapping commitments.

Additional context

Related safeguards: #6502 (different slices) and #1947 (text normalization, a separate question). Also related to #2300 and #8173 for cross-provider identity, but this is a same-provider, same-Program annotation case. Verify rebased and missing-commitment controls before broadening correspondence.

Originally created by @morluto on GitHub (Oct 30, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8213 ### 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 `compare_functions` reports `function_match.status: "mismatched"` when the two retained dossiers describe the same Ghidra function before and after an REA metadata rename in one live Program. The name and pseudocode text did change, but the selected function entry did not. Treating the labels as proof of different functions loses the stronger same-Program observation. ### Reproduction On macOS arm64 with Ghidra 12.1.4, REA source `38433cfb4577b133d7598f9439c6448299cb44eb`, and a self-built, unexecuted arm64 Mach-O: 1. `open_binary` using Ghidra; run `analyze_function` on `_evaluate` and retain its dossier. 2. `annotate_native_function` for `_evaluate` with `name: "audit_renamed_evaluate"`. 3. Run `analyze_function` on the new name and call `compare_functions` with the two retained dossier references. Both dossiers have the same target SHA-256, Ghidra profile digest and `function_coordinate_context`, entry `0x100000430`, and inclusive body range `0x100000430..0x10000047f`. The primary symbol source changed from `imported` to `user_defined`, as expected for the annotation. Comparison returns `function_match: {status: "mismatched", method: "explicit", ...}`, `status: "changed"`, with two changed dimensions (identity and exact pseudocode) and six unchanged. A control comparing the original `_evaluate` against the distinct `_adjust` at `0x100000410` also returns mismatched. Two independent MCP runs, one using summary references and one with complete dossiers, produced the same result. Close succeeded and the target SHA-256 stayed unchanged; the edit was confined to Ghidra's ephemeral database. ### Expected behavior Recognize the two explicitly selected observations as the same entry when compatible, authenticated same-artifact coordinate context establishes it, while independently reporting the observed rename and textual change. If the available context cannot establish correspondence, return ambiguous rather than deriving a mismatch solely from names. Do not equate equal numerical addresses across different mappings, slices, providers, or uncommitted/unknown coordinate contexts. ### Impact and scope An agent comparing its own annotation before and after can conclude that it selected different functions even though both dossiers came from the same Ghidra function in the same Program. The comparison correctly reports changed name/text; this report does not assert that the overall comparison must be unchanged or that exact text change implies behavior change. Only the fixture, host and Ghidra version above were tested; cross-provider and cross-version identity remain separate questions. ### REA version `38433cfb4577b133d7598f9439c6448299cb44eb` (PR #8187 branch) ### Runtime and platform Node 24.22.1; macOS 26.3 arm64; Ghidra 12.1.4 and JDK 21. ### Target and artifact context Self-built Mach-O arm64 executable; no target execution; Ghidra metadata renamed in an ephemeral session only. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Before: `{address:"0x100000430", name:"_evaluate", symbol:{source:"imported"}}`. After: `{address:"0x100000430", name:"audit_renamed_evaluate", symbol:{source:"user_defined"}}`. Both report identical subject digest, profile digest and mapping commitment. The result is `function_match.status: "mismatched"`; the expected positive correspondence is limited to these authenticated observations. Separate test: `_adjust` resolves at `0x100000410` and remains a distinct function. ### Typed outcome or recovery expected Preserve function correspondence separately from changed descriptive identity. If the contract adds a coordinate-based match method, expose its evidence and applicable limits; avoid unconditionally applying same-address equality. Preserve source symbol provenance and exact-text limitations. ### Analysis or suspected cause [`functionMatch`](https://github.com/morluto/rea/blob/38433cfb4577b133d7598f9439c6448299cb44eb/src/domain/functionComparisonNormalization.ts) uses stable symbol origins and names to choose `matched`, `mismatched`, or `ambiguous`, without consulting the applicable coordinate context. This source observation explains the observed result but does not by itself establish the correct contract for every provider. Existing relationship comparison already treats coordinate compatibility as conditional on target/format/architecture and mapping commitments. ### Additional context Related safeguards: #6502 (different slices) and #1947 (text normalization, a separate question). Also related to #2300 and #8173 for cross-provider identity, but this is a same-provider, same-Program annotation case. Verify rebased and missing-commitment controls before broadening correspondence.
bsakaguchi 2026-11-01 21:11:34 +00:00
  • closed this issue
  • added the
    bug
    triage
    labels
Author
Owner

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

Downstream MCP control on the same real Ghidra rename fixture: the retained-reference compare_functions result fed find_changed_behavior, which returned two static_candidate findings (changed identity and exact pseudocode) and behavior_status: "unknown"; it did not claim an observed runtime change. verify_reconstruction with six explicitly requested structural dimensions passed assembly, calls and CFG, and failed identity, pseudocode and overall. The name and exact pseudocode really changed in this experiment, so the observed verification failure is not proof that the function-mismatch defect alone causes a false failure; no behavioral claim was tested.

Source inspection shows overall structural verification treats function_match: mismatched as an independent function change. A targeted regression should separately vary correspondence from actual dimension changes and include unknown/mismatched mapping controls; the experiment above does not isolate that consumer branch. No target execution occurred.

<!-- gh-comment-id:6533987465 --> @morluto commented on GitHub (Oct 30, 2026): Downstream MCP control on the same real Ghidra rename fixture: the retained-reference `compare_functions` result fed `find_changed_behavior`, which returned two `static_candidate` findings (changed identity and exact pseudocode) and `behavior_status: "unknown"`; it did not claim an observed runtime change. `verify_reconstruction` with six explicitly requested structural dimensions passed assembly, calls and CFG, and failed identity, pseudocode and overall. The name and exact pseudocode really changed in this experiment, so the observed verification failure is **not** proof that the function-mismatch defect alone causes a false failure; no behavioral claim was tested. Source inspection shows overall structural verification treats `function_match: mismatched` as an independent function change. A targeted regression should separately vary correspondence from actual dimension changes and include unknown/mismatched mapping controls; the experiment above does not isolate that consumer branch. No target execution occurred.
Author
Owner

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

A real rebased-project control reinforces the need to qualify any proposed coordinate-based identity rule. An independently prepared Ghidra project moved the same owned Mach-O's image base from 0x100000000 to 0x180000000. Separate REA/Ghidra sessions selected fresh and rebased analysis: entry resolved to 0x1000002d8 versus 0x1800002d8, and each independently mapped to original file offset 728 with the same eight bytes. The target SHA-256 matched in both sessions; the selected project's profile and its prior edits remained explicit.

The retained-dossier comparison matched by the same imported symbol and correctly kept the reference dimension unknown because it could not establish compatible coordinate commitments. This is evidence that equal numeric entry addresses are not necessary to observe a plausible same-artifact function, and that matching target digest alone must not assert identical coordinate spaces. It does not prove equivalence for arbitrary rebases or independently edited projects, and the existing-project profile does not currently supply the fresh import's coordinate commitment. The exact-text assembly change and caveats are recorded in #1947. The target and project contents stayed unchanged during REA's sessions.

<!-- gh-comment-id:6535028257 --> @morluto commented on GitHub (Oct 30, 2026): A real rebased-project control reinforces the need to qualify any proposed coordinate-based identity rule. An independently prepared Ghidra project moved the same owned Mach-O's image base from `0x100000000` to `0x180000000`. Separate REA/Ghidra sessions selected fresh and rebased analysis: `entry` resolved to `0x1000002d8` versus `0x1800002d8`, and each independently mapped to original file offset 728 with the same eight bytes. The target SHA-256 matched in both sessions; the selected project's profile and its prior edits remained explicit. The retained-dossier comparison matched by the same imported symbol and correctly kept the reference dimension `unknown` because it could not establish compatible coordinate commitments. This is evidence that equal numeric entry addresses are not necessary to observe a plausible same-artifact function, and that matching target digest alone must not assert identical coordinate spaces. It does not prove equivalence for arbitrary rebases or independently edited projects, and the existing-project profile does not currently supply the fresh import's coordinate commitment. The exact-text assembly change and caveats are recorded in #1947. The target and project contents stayed unchanged during REA's sessions.
Author
Owner

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

Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0 for visibility on the Ghidra, Kuna, and REA tool-design investigation.

<!-- gh-comment-id:6538797870 --> @morluto commented on GitHub (Oct 31, 2026): Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0 for visibility on the Ghidra, Kuna, and REA tool-design investigation.
Author
Owner

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

Real Ghidra verification of the merged fix

I reran the original public stdio MCP scenario on REA b43c329df779708e43ae297f498a844e45d9c923 (the comparison implementation is unchanged at checked main 23de10bb2849cdce8ddbe70504aa7cc57de75a98) with Ghidra 12.1.4 and the same owned arm64 Mach-O. Before and after an ephemeral Ghidra rename of _evaluate to audit_renamed_evaluate, analyze_function returned separate retained dossiers. Comparing them now reports function_match.status: matched, method: same-entry, and entry 0x100000430. The overall comparison still reports changed, including the observed name and exact pseudocode differences; assembly and six total dimensions were unchanged. This separates correspondence from descriptive/text changes without claiming behavioral equivalence.

The distinct _adjust entry control returned function_match.status: mismatched. All calls including close succeeded, and the target SHA-256 remained unchanged; the target was not executed. This confirms the original fix against one real provider/session and control, not arbitrary rebases, providers or projects.

<!-- gh-comment-id:6544427287 --> @morluto commented on GitHub (Oct 31, 2026): ### Real Ghidra verification of the merged fix I reran the original public stdio MCP scenario on REA `b43c329df779708e43ae297f498a844e45d9c923` (the comparison implementation is unchanged at checked `main` `23de10bb2849cdce8ddbe70504aa7cc57de75a98`) with Ghidra 12.1.4 and the same owned arm64 Mach-O. Before and after an ephemeral Ghidra rename of `_evaluate` to `audit_renamed_evaluate`, `analyze_function` returned separate retained dossiers. Comparing them now reports `function_match.status: matched`, `method: same-entry`, and entry `0x100000430`. The overall comparison still reports `changed`, including the observed name and exact pseudocode differences; assembly and six total dimensions were unchanged. This separates correspondence from descriptive/text changes without claiming behavioral equivalence. The distinct `_adjust` entry control returned `function_match.status: mismatched`. All calls including close succeeded, and the target SHA-256 remained unchanged; the target was not executed. This confirms the original fix against one real provider/session and control, not arbitrary rebases, providers or projects.
Author
Owner

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

Cross-provider negative control on the same REA revision and target: I opened real Ghidra 12.1.4 and Kuna 1.986 in sequence, retained each _evaluate dossier, and compared them. Both happened to report 0x100000430, but compare_functions kept function_match.status: ambiguous, overall unknown, and all eight dimensions unknown rather than applying the same-entry match across providers. After exporting the Evidence bundle and closing, the references correctly required reimport; importing restored the same comparison Evidence ID. The target digest remained unchanged and the target was not executed. This confirms one incompatible-provider control, not general address correspondence or behavioral equivalence.

<!-- gh-comment-id:6544432576 --> @morluto commented on GitHub (Oct 31, 2026): Cross-provider negative control on the same REA revision and target: I opened real Ghidra 12.1.4 and Kuna 1.986 in sequence, retained each `_evaluate` dossier, and compared them. Both happened to report `0x100000430`, but `compare_functions` kept `function_match.status: ambiguous`, overall `unknown`, and all eight dimensions unknown rather than applying the same-entry match across providers. After exporting the Evidence bundle and closing, the references correctly required reimport; importing restored the same comparison Evidence ID. The target digest remained unchanged and the target was not executed. This confirms one incompatible-provider control, not general address correspondence or behavioral equivalence.
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#6487
No description provided.