[GH-ISSUE #6501] [Bug] compare_functions cannot normalize supported address-space coordinates #3896

Closed
opened 2026-10-31 00:45:02 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

  • I searched open issues for related reports. Closed issues were not searched.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

Public MCP compare_functions accepts valid code:0x1000 coordinates, then returns comments, strings_names, and cfg as unknown even when the two supplied observations are identical in all three facets and explicitly declare those facets available. Removing only the code: coordinate prefix makes all three dimensions, and the overall comparison, unchanged.

This was executed with synthetic provider observations created by production createEvidence, through production createServer and an in-memory MCP client. A real Ghidra session was not run.

Reproduction

From the built checkout with locked dependencies installed, run the following with the repository-pinned Node.js. The executed probe contained these two cases plus two unrelated reference-identity cases; the excerpt retains the executed cases without changing their inputs. No second replay after drafting is claimed.

node --input-type=module <<'JS'
const repo=process.cwd();
const load=(path)=>import(`${repo}/dist/${path}.js`);
const {Client,InMemoryTransport}=await import(`${repo}/node_modules/@modelcontextprotocol/client/dist/index.mjs`);
const {BinarySession}=await load('application/binary/BinarySession');
const {SessionProviderRouter}=await load('application/binary/SessionProviderRouter');
const {AnalysisProviderRegistry}=await load('application/binary/AnalysisProviderRegistry');
const {createServer}=await load('server/createServer');
const {createEvidence}=await load('domain/evidence');
const {FUNCTION_COMPARISON_EXAMPLE}=await load('contracts/functionComparisonExample');
const session=new BinarySession(SessionProviderRouter.selectable(new AnalysisProviderRegistry([]),[]));
const server=createServer({kind:'session',session},{environment:{}});
const client=new Client({name:'native-coordinate-probe',version:'1'});
const [ct,st]=InMemoryTransport.createLinkedPair();
await server.connect(st);await client.connect(ct);
const base=structuredClone(FUNCTION_COMPARISON_EXAMPLE.left.normalized_result);
function observe({space='',architecture='arm64',callee='helper',digest='1',withReferences=false}) {
 const d=structuredClone(base);const at=n=>`${space}0x${n.toString(16)}`;
 d.procedure.address=at(0x1000);
 d.comments=[{address:at(0x1000),kind:'comment',text:'entry'}];
 d.referenced_strings=[{address:at(0x2000),source_address:at(0x1000),value:'hello'}];
 d.basic_blocks=[{start:at(0x1000),end:at(0x1004),successors:[]}];
 if(withReferences) {
  const p=(address,name)=>({address,name,classification:null,body:{available:false,reason:'fixture'}});
  d.outgoing_references=[{source_address:at(0x1000),target_address:at(0x2000),source_procedure:p(at(0x1000),'main'),target_procedure:p(at(0x2000),callee),kind:{available:true,provenance:'ghidra-reference-manager',type:'READ',flow:false,call:false,jump:false,data:true,read:true,write:false,indirect:false,computed:false,conditional:false,terminal:false,primary:true,operand_index:0,external:false}}];
 }
 return createEvidence({path:'/tmp/public-fixture',sha256:digest.repeat(64),format:'mach-o-universal',architecture},{id:'fixture',name:'Synthetic provider observations',version:'1'},{operation:'analyze_function',parameters:{procedure:'main'},result:d,confidence:'derived',authority:'shipped-artifact',relationshipCoverage:{status:'complete'}});
}
const cases=[
 ['plain-control',observe({}),observe({digest:'2'})],
 ['address-space',observe({space:'code:'}),observe({space:'code:',digest:'2'})],
];
try {
 for(const [name,left,right]of cases) {
  const result=await client.callTool({name:'compare_functions',arguments:{left,right}});

  const r=result.structuredContent?.normalized_result;
  console.log(JSON.stringify({name,isError:result.isError??false,subject_architectures:[left.subject.architecture,right.subject.architecture],status:r?.status,dimensions:r?.dimensions?.map(({dimension,status,limitations})=>({dimension,status,limitations})),error:r?undefined:result}));
 }
}finally{await client.close();await server.close();await session.close();}
JS

Each case ran once. Both MCP calls succeeded; the prefix-only difference changed the resulting facet statuses.

Expected behavior

Normalize coordinates whose relevant addresses all belong to the same named address space, preserving that space identity. Equal available CFG sizes/successors, comments at equal offsets, and strings at equal source offsets should remain comparable. This does not request address normalization for opaque pseudocode or assembly text.

docs/native-investigation.md:50 explicitly supports address-space coordinates; src/domain/native/nativeAddress.ts:3 defines their canonical representation, and src/ghidra/GhidraFunctionValues.ts:293 accepts those coordinates throughout function dossiers. docs/mcp-contracts.md:1139 describes comparison of observed block sizes and successor relationships.

Impact and scope

P2: supported native coordinate forms lose three existing comparison facets and force the overall result to unknown. The executed sample consists of one synthetic named-space pair and its plain-address control; behavior of an actual provider session was not measured. Analysts can inspect retained coordinates and compare offsets manually without discarding address-space identity. Stripping space names from stored Evidence is not a safe workaround.

REA version

Source revision 4c547008c53ee9f0ff2368e7d874a2c3347aa235, package version 6.7.0. This is a built main checkout; the published 6.7.0 release was not separately replayed.

Runtime and platform

Node.js v24.18.0; Linux x86_64; repository-locked MCP client 2.3.1. No real native analysis provider was launched.

Target and artifact context

Authenticated synthetic analyze_function Evidence for arm64 mach-o-universal subjects. The fixture has one four-byte CFG block, one comment at the entry, and one string reference from the entry; all coordinates have a common code: space in the failing case. The target format is incidental to the provider-neutral coordinate parser. No real Mach-O file or provider analysis is claimed.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed result projection:

plain-control: isError=false; status=unchanged
  comments=unchanged; strings_names=unchanged; cfg=unchanged
address-space: isError=false; status=unknown
  comments=unknown: Comment locations could not be normalized relative to the function.
  strings_names=unknown: Reference source locations could not be normalized to function offsets.
  cfg=unknown: CFG addresses could not be normalized to local block indices.

The MCP tool authenticates both supplied records before comparison at src/server/registerFunctionComparisonTool.ts:48. Source-derived cause: src/domain/functionComparisonNormalization.ts:208 only parses /^0x[0-9a-f]+$/i, unlike the canonical native address parser. Both src/domain/functionComparisonNormalization.ts:37 and src/domain/functionComparisonNormalization.ts:211 use that restricted parser. The former feeds CFG comparison and the latter feeds comment/string offsets.

Typed outcome or recovery expected

The three facets should report unchanged for this equal, fully available, same-space fixture. Coordinates from different or unresolved spaces must retain uncertainty. Existing exact pseudocode/assembly comparison semantics should be preserved.

Analysis or suspected cause

The shared normalization parser predates the address-space-aware native coordinate representation. This explains all three observed symptoms as one failed coordinate-normalization contract. The same helper also feeds structural candidates in native function matching; that downstream effect is source-derived and was not executed here.

Additional context

Open-issue searches: repo:morluto/rea is:issue is:open "compare_functions", "compare_functions" "space", and "compare" "address". Reviewed #1947: it requests an additional normalized view of opaque assembly/pseudocode text, whereas this report concerns existing structured normalization. #5535 concerns Hopper's generated EntryPoint name in calls/references. No matching open report was found. Closed issues were not searched. No source edits, fix, or restoration run were performed.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A public MCP fixture using supported coordinates loses three available structural comparison facets; the equivalent plain-address control compares successfully.
  • Workaround status: available
  • Workaround basis: The complete source Evidence retains each space and numeric coordinate, allowing an analyst to compare these bounded facts manually; automatic comparison remains impaired.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6501 ### Before submitting - [x] I searched open issues for related reports. Closed issues were not searched. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior Public MCP `compare_functions` accepts valid `code:0x1000` coordinates, then returns `comments`, `strings_names`, and `cfg` as `unknown` even when the two supplied observations are identical in all three facets and explicitly declare those facets available. Removing only the `code:` coordinate prefix makes all three dimensions, and the overall comparison, `unchanged`. This was executed with synthetic provider observations created by production `createEvidence`, through production `createServer` and an in-memory MCP client. A real Ghidra session was not run. ### Reproduction From the built checkout with locked dependencies installed, run the following with the repository-pinned Node.js. The executed probe contained these two cases plus two unrelated reference-identity cases; the excerpt retains the executed cases without changing their inputs. No second replay after drafting is claimed. ```sh node --input-type=module <<'JS' const repo=process.cwd(); const load=(path)=>import(`${repo}/dist/${path}.js`); const {Client,InMemoryTransport}=await import(`${repo}/node_modules/@modelcontextprotocol/client/dist/index.mjs`); const {BinarySession}=await load('application/binary/BinarySession'); const {SessionProviderRouter}=await load('application/binary/SessionProviderRouter'); const {AnalysisProviderRegistry}=await load('application/binary/AnalysisProviderRegistry'); const {createServer}=await load('server/createServer'); const {createEvidence}=await load('domain/evidence'); const {FUNCTION_COMPARISON_EXAMPLE}=await load('contracts/functionComparisonExample'); const session=new BinarySession(SessionProviderRouter.selectable(new AnalysisProviderRegistry([]),[])); const server=createServer({kind:'session',session},{environment:{}}); const client=new Client({name:'native-coordinate-probe',version:'1'}); const [ct,st]=InMemoryTransport.createLinkedPair(); await server.connect(st);await client.connect(ct); const base=structuredClone(FUNCTION_COMPARISON_EXAMPLE.left.normalized_result); function observe({space='',architecture='arm64',callee='helper',digest='1',withReferences=false}) { const d=structuredClone(base);const at=n=>`${space}0x${n.toString(16)}`; d.procedure.address=at(0x1000); d.comments=[{address:at(0x1000),kind:'comment',text:'entry'}]; d.referenced_strings=[{address:at(0x2000),source_address:at(0x1000),value:'hello'}]; d.basic_blocks=[{start:at(0x1000),end:at(0x1004),successors:[]}]; if(withReferences) { const p=(address,name)=>({address,name,classification:null,body:{available:false,reason:'fixture'}}); d.outgoing_references=[{source_address:at(0x1000),target_address:at(0x2000),source_procedure:p(at(0x1000),'main'),target_procedure:p(at(0x2000),callee),kind:{available:true,provenance:'ghidra-reference-manager',type:'READ',flow:false,call:false,jump:false,data:true,read:true,write:false,indirect:false,computed:false,conditional:false,terminal:false,primary:true,operand_index:0,external:false}}]; } return createEvidence({path:'/tmp/public-fixture',sha256:digest.repeat(64),format:'mach-o-universal',architecture},{id:'fixture',name:'Synthetic provider observations',version:'1'},{operation:'analyze_function',parameters:{procedure:'main'},result:d,confidence:'derived',authority:'shipped-artifact',relationshipCoverage:{status:'complete'}}); } const cases=[ ['plain-control',observe({}),observe({digest:'2'})], ['address-space',observe({space:'code:'}),observe({space:'code:',digest:'2'})], ]; try { for(const [name,left,right]of cases) { const result=await client.callTool({name:'compare_functions',arguments:{left,right}}); const r=result.structuredContent?.normalized_result; console.log(JSON.stringify({name,isError:result.isError??false,subject_architectures:[left.subject.architecture,right.subject.architecture],status:r?.status,dimensions:r?.dimensions?.map(({dimension,status,limitations})=>({dimension,status,limitations})),error:r?undefined:result})); } }finally{await client.close();await server.close();await session.close();} JS ``` Each case ran once. Both MCP calls succeeded; the prefix-only difference changed the resulting facet statuses. ### Expected behavior Normalize coordinates whose relevant addresses all belong to the same named address space, preserving that space identity. Equal available CFG sizes/successors, comments at equal offsets, and strings at equal source offsets should remain comparable. This does not request address normalization for opaque pseudocode or assembly text. [docs/native-investigation.md:50](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/docs/native-investigation.md#L50) explicitly supports address-space coordinates; [src/domain/native/nativeAddress.ts:3](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/domain/native/nativeAddress.ts#L3) defines their canonical representation, and [src/ghidra/GhidraFunctionValues.ts:293](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/ghidra/GhidraFunctionValues.ts#L293) accepts those coordinates throughout function dossiers. [docs/mcp-contracts.md:1139](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/docs/mcp-contracts.md#L1139) describes comparison of observed block sizes and successor relationships. ### Impact and scope P2: supported native coordinate forms lose three existing comparison facets and force the overall result to unknown. The executed sample consists of one synthetic named-space pair and its plain-address control; behavior of an actual provider session was not measured. Analysts can inspect retained coordinates and compare offsets manually without discarding address-space identity. Stripping space names from stored Evidence is not a safe workaround. ### REA version Source revision `4c547008c53ee9f0ff2368e7d874a2c3347aa235`, package version `6.7.0`. This is a built main checkout; the published 6.7.0 release was not separately replayed. ### Runtime and platform Node.js `v24.18.0`; Linux x86_64; repository-locked MCP client `2.3.1`. No real native analysis provider was launched. ### Target and artifact context Authenticated synthetic `analyze_function` Evidence for arm64 `mach-o-universal` subjects. The fixture has one four-byte CFG block, one comment at the entry, and one string reference from the entry; all coordinates have a common `code:` space in the failing case. The target format is incidental to the provider-neutral coordinate parser. No real Mach-O file or provider analysis is claimed. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Observed result projection: ```text plain-control: isError=false; status=unchanged comments=unchanged; strings_names=unchanged; cfg=unchanged address-space: isError=false; status=unknown comments=unknown: Comment locations could not be normalized relative to the function. strings_names=unknown: Reference source locations could not be normalized to function offsets. cfg=unknown: CFG addresses could not be normalized to local block indices. ``` The MCP tool authenticates both supplied records before comparison at [src/server/registerFunctionComparisonTool.ts:48](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/server/registerFunctionComparisonTool.ts#L48). Source-derived cause: [src/domain/functionComparisonNormalization.ts:208](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/domain/functionComparisonNormalization.ts#L208) only parses `/^0x[0-9a-f]+$/i`, unlike the canonical native address parser. Both [src/domain/functionComparisonNormalization.ts:37](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/domain/functionComparisonNormalization.ts#L37) and [src/domain/functionComparisonNormalization.ts:211](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/domain/functionComparisonNormalization.ts#L211) use that restricted parser. The former feeds CFG comparison and the latter feeds comment/string offsets. ### Typed outcome or recovery expected The three facets should report `unchanged` for this equal, fully available, same-space fixture. Coordinates from different or unresolved spaces must retain uncertainty. Existing exact pseudocode/assembly comparison semantics should be preserved. ### Analysis or suspected cause The shared normalization parser predates the address-space-aware native coordinate representation. This explains all three observed symptoms as one failed coordinate-normalization contract. The same helper also feeds structural candidates in native function matching; that downstream effect is source-derived and was not executed here. ### Additional context Open-issue searches: `repo:morluto/rea is:issue is:open "compare_functions"`, `"compare_functions" "space"`, and `"compare" "address"`. Reviewed #1947: it requests an additional normalized view of opaque assembly/pseudocode text, whereas this report concerns existing structured normalization. #5535 concerns Hopper's generated EntryPoint name in calls/references. No matching open report was found. Closed issues were not searched. No source edits, fix, or restoration run were performed. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A public MCP fixture using supported coordinates loses three available structural comparison facets; the equivalent plain-address control compares successfully. - Workaround status: available - Workaround basis: The complete source Evidence retains each space and numeric coordinate, allowing an analyst to compare these bounded facts manually; automatic comparison remains impaired.
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#3896
No description provided.