[GH-ISSUE #8238] [Bug] Ghidra batch naming changes winner when object keys are reordered #6505

Closed
opened 2026-11-01 21:11:45 +00:00 by bsakaguchi · 4 comments
Owner

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

Originally assigned to: @kaoru0822-kitauji on GitHub.

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

For Ghidra set_addresses_names, two JSON names objects containing the same address-to-name pairs but in opposite key order produced different persistent-in-session edits when the two addresses requested the same name. Exported Evidence retained the actual per-address success booleans and distinct IDs, but its canonicalized parameters.names was identical in both bundles and did not retain the applied order.

Reproduction

On the same self-built arm64 Mach-O, start two independent fresh REA/Ghidra MCP sessions. Both addresses below are mapped and read_bytes succeeds.

  1. In session A, send {"names":{"0x100004000":"shared_batch_name","0x100004008":"shared_batch_name"}}. The result reports first address true, second false; address_name confirms the first owns the name and the second keeps _message.
  2. In session B, send the same pairs with the keys reversed. The result reports second address true, first false; readback confirms the reversed owner.
  3. Export both Evidence bundles. Their canonical parameters.names contain identical sorted pairs, while their distinct result maps record opposite successes. A retry of the failed address with a different name succeeds in each session. Requesting the already-owned name through set_address_name returns false without a specific reason.

Ghidra independently applies addresses and correctly reports partial success; the issue is that object-member order is not represented as an explicit caller choice while it determines which edit wins. The executable SHA-256 remained unchanged and the target was not executed. REA source 53a4ab78451a0654e9c3beb3d43a5e371a288fcc, Node 24.22.1, macOS 26.3 arm64, Ghidra 12.1.4. This is one controlled pair, not a claim about every namespace or provider.

Expected behavior

Define reproducible semantics for conflicting requested names independent of JSON object serialization order, or accept an explicitly ordered request and preserve that order in Evidence. Rejecting conflicting destinations before applying any conflicting items is one option; whichever behavior is selected, preserve independent nonconflicting successes, per-address results and the existing namespace/ownership rules. Do not infer an intended winner from sorted addresses or silently move another address's label.

Impact and scope

Reordered equivalent input maps can mutate different session labels. Successful/failed status is recorded, so this is not a claim that the result Evidence concealed the actual winner. A consumer cannot reconstruct the original execution order from the canonical parameters alone. The name conflict was authored intentionally to test this boundary; no autonomous-agent frequency or Windows/Hopper behavior was measured.

REA version

53a4ab78451a0654e9c3beb3d43a5e371a288fcc

Runtime and platform

Node 24.22.1; macOS 26.3 arm64; Ghidra 12.1.4.

Target and artifact context

Self-built arm64 Mach-O, SHA-256 ffad1b70c6a5a7ef3f7c3f59126be516520210777d949b35e97c9067f272aa2d.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • A-first result: { "0x100004000": true, "0x100004008": false }.
  • B-first result: { "0x100004000": false, "0x100004008": true }.
  • Both exported records use the same canonical input mapping; their result maps and Evidence IDs differ.
  • Source-derived: Ghidra batch processing groups requests in a linked map and applies each address in iteration order; the single-address owner refuses a name already held at another address.

Typed outcome or recovery expected

Make conflicts and their outcomes explicit without dropping valid nonconflicting edits or treating request order as implicit intent. Verify alias spellings that resolve to one address, same-name collisions, swaps, existing unselected owners, invalid/missing addresses and canonical Evidence replay.

Analysis or suspected cause

The input model is an address-to-name object, while the bridge executes stateful per-address transactions in incoming object iteration order. This explains the observed order dependence; the right public contract needs a deliberate decision, including compatibility with Hopper's separately documented batch rules.

Additional context

#1634 introduced Ghidra batch naming with per-address failure; #6405 concerns durable annotation export/replay. Last known good version: NOT_ENOUGH_INFO.

Originally created by @morluto on GitHub (Oct 30, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8238 Originally assigned to: @kaoru0822-kitauji on GitHub. ### 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 For Ghidra `set_addresses_names`, two JSON `names` objects containing the same address-to-name pairs but in opposite key order produced different persistent-in-session edits when the two addresses requested the same name. Exported Evidence retained the actual per-address success booleans and distinct IDs, but its canonicalized `parameters.names` was identical in both bundles and did not retain the applied order. ### Reproduction On the same self-built arm64 Mach-O, start two independent fresh REA/Ghidra MCP sessions. Both addresses below are mapped and `read_bytes` succeeds. 1. In session A, send `{"names":{"0x100004000":"shared_batch_name","0x100004008":"shared_batch_name"}}`. The result reports first address `true`, second `false`; `address_name` confirms the first owns the name and the second keeps `_message`. 2. In session B, send the same pairs with the keys reversed. The result reports second address `true`, first `false`; readback confirms the reversed owner. 3. Export both Evidence bundles. Their canonical `parameters.names` contain identical sorted pairs, while their distinct result maps record opposite successes. A retry of the failed address with a different name succeeds in each session. Requesting the already-owned name through `set_address_name` returns `false` without a specific reason. Ghidra independently applies addresses and correctly reports partial success; the issue is that object-member order is not represented as an explicit caller choice while it determines which edit wins. The executable SHA-256 remained unchanged and the target was not executed. REA source `53a4ab78451a0654e9c3beb3d43a5e371a288fcc`, Node 24.22.1, macOS 26.3 arm64, Ghidra 12.1.4. This is one controlled pair, not a claim about every namespace or provider. ### Expected behavior Define reproducible semantics for conflicting requested names independent of JSON object serialization order, or accept an explicitly ordered request and preserve that order in Evidence. Rejecting conflicting destinations before applying any conflicting items is one option; whichever behavior is selected, preserve independent nonconflicting successes, per-address results and the existing namespace/ownership rules. Do not infer an intended winner from sorted addresses or silently move another address's label. ### Impact and scope Reordered equivalent input maps can mutate different session labels. Successful/failed status is recorded, so this is not a claim that the result Evidence concealed the actual winner. A consumer cannot reconstruct the original execution order from the canonical parameters alone. The name conflict was authored intentionally to test this boundary; no autonomous-agent frequency or Windows/Hopper behavior was measured. ### REA version `53a4ab78451a0654e9c3beb3d43a5e371a288fcc` ### Runtime and platform Node 24.22.1; macOS 26.3 arm64; Ghidra 12.1.4. ### Target and artifact context Self-built arm64 Mach-O, SHA-256 `ffad1b70c6a5a7ef3f7c3f59126be516520210777d949b35e97c9067f272aa2d`. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence - A-first result: `{ "0x100004000": true, "0x100004008": false }`. - B-first result: `{ "0x100004000": false, "0x100004008": true }`. - Both exported records use the same canonical input mapping; their result maps and Evidence IDs differ. - Source-derived: [Ghidra batch processing](https://github.com/morluto/rea/blob/53a4ab78451a0654e9c3beb3d43a5e371a288fcc/bridge/ghidra/ReaGhidraBridge.java#L1981-L2016) groups requests in a linked map and applies each address in iteration order; the single-address owner refuses a name already held at another address. ### Typed outcome or recovery expected Make conflicts and their outcomes explicit without dropping valid nonconflicting edits or treating request order as implicit intent. Verify alias spellings that resolve to one address, same-name collisions, swaps, existing unselected owners, invalid/missing addresses and canonical Evidence replay. ### Analysis or suspected cause The input model is an address-to-name object, while the bridge executes stateful per-address transactions in incoming object iteration order. This explains the observed order dependence; the right public contract needs a deliberate decision, including compatibility with Hopper's separately documented batch rules. ### Additional context #1634 introduced Ghidra batch naming with per-address failure; #6405 concerns durable annotation export/replay. Last known good version: NOT_ENOUGH_INFO.
bsakaguchi 2026-11-01 21:11:45 +00:00
  • closed this issue
  • added the
    bug
    triage
    labels
Author
Owner

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

A TypeScript MCP SDK 2.3.1 validation control called the real Ghidra set_addresses_names with a successful function edit, an unmapped address and a successful data edit. After listing the scoped catalog, the client explicitly supplied the advertised tool definition to callTool; the partial Boolean result passed output-schema validation under both full and compact output-schema profiles. A mixed batch_decompile result and a retained graph page also passed in those sessions. This is a positive schema-compatibility observation, not a change to the order-dependent mutation behavior reported above.

A separate deliberately incompatible client-side schema was rejected for a successful call, confirming that SDK validation was active; tool-level errors are skipped by SDK validation. This covers these cases only, not all tool outputs or agent hosts. The target digest was unchanged.

<!-- gh-comment-id:6535165668 --> @morluto commented on GitHub (Oct 30, 2026): A TypeScript MCP SDK 2.3.1 validation control called the real Ghidra `set_addresses_names` with a successful function edit, an unmapped address and a successful data edit. After listing the scoped catalog, the client explicitly supplied the advertised tool definition to `callTool`; the partial Boolean result passed output-schema validation under both full and compact output-schema profiles. A mixed `batch_decompile` result and a retained graph page also passed in those sessions. This is a positive schema-compatibility observation, not a change to the order-dependent mutation behavior reported above. A separate deliberately incompatible client-side schema was rejected for a successful call, confirming that SDK validation was active; tool-level errors are skipped by SDK validation. This covers these cases only, not all tool outputs or agent hosts. The target digest was unchanged.
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:6538806553 --> @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):

Additional real Ghidra 12.1.4 MCP controls on REA main e6d061897, using four fresh sessions on the same unexecuted Mach-O. I first named address A audit_alpha and B audit_beta, with readback confirming both. Then:

Requested mapping Object order Per-item result Readback immediately after batch
A → audit_beta, B → audit_gamma A then B A false, B true A audit_alpha, B audit_gamma
Same mapping B then A B true, A true A audit_beta, B audit_gamma
A → audit_beta, B → audit_alpha Either order Both false Original names remain

The first mapping requests distinct destination names: it is still order-dependent because moving B can free the name requested for A. In the A-first session, a later explicit single-address retry of A succeeded after B had moved. This is a controlled state-dependent observation, not a general recommendation to retry mutating requests. The swap control also does not establish that swapping should be implicit or atomic.

Consequently, rejecting only duplicate names within the requested values would not resolve all order-dependent batches. Any chosen semantics need to account for existing owners and dependencies, while preserving partial-result truth and caller intent. All sessions closed and the input digest stayed unchanged. This is one Ghidra/macOS fixture, not Hopper or Windows coverage.

<!-- gh-comment-id:6540637118 --> @morluto commented on GitHub (Oct 31, 2026): Additional real Ghidra 12.1.4 MCP controls on REA main `e6d061897`, using four fresh sessions on the same unexecuted Mach-O. I first named address A `audit_alpha` and B `audit_beta`, with readback confirming both. Then: | Requested mapping | Object order | Per-item result | Readback immediately after batch | | --- | --- | --- | --- | | A → `audit_beta`, B → `audit_gamma` | A then B | A false, B true | A `audit_alpha`, B `audit_gamma` | | Same mapping | B then A | B true, A true | A `audit_beta`, B `audit_gamma` | | A → `audit_beta`, B → `audit_alpha` | Either order | Both false | Original names remain | The first mapping requests **distinct destination names**: it is still order-dependent because moving B can free the name requested for A. In the A-first session, a later explicit single-address retry of A succeeded after B had moved. This is a controlled state-dependent observation, not a general recommendation to retry mutating requests. The swap control also does not establish that swapping should be implicit or atomic. Consequently, rejecting only duplicate names within the requested values would not resolve all order-dependent batches. Any chosen semantics need to account for existing owners and dependencies, while preserving partial-result truth and caller intent. All sessions closed and the input digest stayed unchanged. This is one Ghidra/macOS fixture, not Hopper or Windows coverage.
Author
Owner

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

Real-provider follow-up for merged #8306: on REA 7c9ed94259fef560fdbec0c9b640348c5d54c391, macOS arm64, Node 24.22.1 and Ghidra 12.1.4, I ran verifyGhidraNamespaceAnnotations through an actual stdio MCP client against its freshly compiled source-owned fixture. Its 220 recorded calls passed, including the function and label controls for competing names, swaps and selected/unselected holders in both JSON member orders, individual readbacks, an explicit later transfer, case variants, equal leaf names in different namespaces, and an independent CLI session. The harness checked advertised schemas and validated successful structured results; the verifier itself checked its generated fixture's bytes were unchanged. No target was executed.

A separate real MCP session on another source-owned fixture checked the same address specified as both 0x… and ram:0x…: different requested names failed with conflicting_alias_names in either order and left the original name; identical names succeeded. A mixed batch applied an independent valid edit while reporting unmapped_address and invalid_address on the other keys. Readbacks agreed, and the target SHA-256 was unchanged. All edits were in fresh ephemeral Ghidra sessions.

This is the namespace verifier and the stated alias/mixed controls, not a pass of the complete verify:ghidra lane or coverage of other hosts/providers. The separate lane blockers #8171 and #8172 are unchanged by this check.

<!-- gh-comment-id:6545723423 --> @morluto commented on GitHub (Oct 31, 2026): Real-provider follow-up for merged #8306: on REA `7c9ed94259fef560fdbec0c9b640348c5d54c391`, macOS arm64, Node 24.22.1 and Ghidra 12.1.4, I ran `verifyGhidraNamespaceAnnotations` through an actual stdio MCP client against its freshly compiled source-owned fixture. Its 220 recorded calls passed, including the function and label controls for competing names, swaps and selected/unselected holders in both JSON member orders, individual readbacks, an explicit later transfer, case variants, equal leaf names in different namespaces, and an independent CLI session. The harness checked advertised schemas and validated successful structured results; the verifier itself checked its generated fixture's bytes were unchanged. No target was executed. A separate real MCP session on another source-owned fixture checked the same address specified as both `0x…` and `ram:0x…`: different requested names failed with `conflicting_alias_names` in either order and left the original name; identical names succeeded. A mixed batch applied an independent valid edit while reporting `unmapped_address` and `invalid_address` on the other keys. Readbacks agreed, and the target SHA-256 was unchanged. All edits were in fresh ephemeral Ghidra sessions. This is the namespace verifier and the stated alias/mixed controls, not a pass of the complete `verify:ghidra` lane or coverage of other hosts/providers. The separate lane blockers #8171 and #8172 are unchanged by this check.
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#6505
No description provided.