[GH-ISSUE #1238] fix(ghidra): real verifier rejects a valid zero-operand ENDBR64 entry #436

Closed
opened 2026-10-15 18:25:03 +00:00 by bsakaguchi · 0 comments
Owner

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

Summary

The real Ghidra verifier rejects its own correctly decoded native fixture when the compiler inserts ENDBR64 at the selected function entry. The instruction has no explicit operands, but verifyDebugFunctionOperations requires a nonempty operand array and exits with Structured native instruction facts drifted. The same instruction succeeds through the public CLI and agrees with GNU objdump. This blocks the real-provider verification lane on the tested supported Linux toolchain without establishing a provider decoding failure.

Runtime or environment

Tested release: rea-agents@6.1.0, tag rea-agents-6.1.0, commit ae9aaee16b9c738d761d3938a3b34fdf37e581b5. Host: Ubuntu 24.04.5 LTS, Linux x64. Development tools: Node.js v24.18.0, npm 11.16.0, cc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0, GNU objdump 2.42. Provider: the unchanged official Ghidra distribution 12.1.4, with its published archive digest verified, and OpenJDK 21.0.12.1+1-1-24.04.4-Ubuntu. The headless heap was 1536M. cc -Q --help=common reported -fcf-protection=full by default. The verifier's native fixture flags were -O0 -g -fno-inline -fno-pie -no-pie.

Steps to reproduce

  1. Use the release checkout and toolchain stated above, with GHIDRA_INSTALL_DIR selecting the extracted official provider and JAVA_HOME selecting the full JDK. Run npm ci and npm run build:cached.
  2. Run node scripts/rea.mjs doctor --provider ghidra --json; the scoped checks pass. Run npm run verify:ghidra without replacing REA_CC. The verifier compiles the repository-owned fixture tests/conformance/ghidra/inventory.c and fails at its instruction-facts check.
  3. Independently regenerate and inspect that same real native fixture from the checkout root:
fixture_root="$(mktemp -d)"
cc -O0 -g -fno-inline -fno-pie -no-pie tests/conformance/ghidra/inventory.c -o "$fixture_root/inventory"
objdump -d --disassemble=rea_ghidra_inventory_entry "$fixture_root/inventory"
address="0x$(nm "$fixture_root/inventory" | awk '$3 == "rea_ghidra_inventory_entry" {print $1}')"
node scripts/rea.mjs inspect-native-instruction "$fixture_root/inventory" "$address" --provider ghidra --json

The fixture is built from the repository's C source; no external target binary or private application is required. The observed reproduction was not minimized beyond these existing fixture inputs.

Expected behavior

The real-provider lane should accept a correctly decoded supported native instruction with no explicit operands. src/domain/native/nativeInstruction.ts:34 defines operands as an array without a minimum length, and the native inspection contract preserves the instruction's observed bytes, length and operand facts. docs/testing.md identifies verify:ghidra as the real Ghidra verification lane; the selected compiler output is a valid host-native fixture for that lane.

Actual behavior

The original verifier exited 1 with:

Error: Structured native instruction facts drifted
    at verifyDebugFunctionOperations (.../scripts/verify-real-ghidra-function.mjs:98:11)

The public inspect-native-instruction CLI exited 0 and returned these relevant normalized facts:

{"address":"0x401538","status":"decoded","bytes":"f30f1efa","length":4,"mnemonic":"ENDBR64","raw_disassembly":"ENDBR64","operands":[]}

GNU objdump independently reported 401538: f3 0f 1e fa endbr64, followed by 40153c: 55 push %rbp. A diagnostic rerun added only the decoded instruction and selected entry to the existing error message; it kept every assertion and reproduced the same failed condition. The diagnostic edit was then restored. A subsequent run of the unchanged verifier through a compiler wrapper adding -fcf-protection=none passed this first check and reached a separate instruction-boundary assertion, so that control is not a full-lane success or an established complete workaround.

Evidence

  • Expected source: docs/testing.md, real Ghidra lane description; src/domain/native/nativeInstruction.ts:17-34, provider-decoded instruction contract and operand-array schema, in the tested release.
  • Failure source: Executed npm run verify:ghidra and its error at scripts/verify-real-ghidra-function.mjs:98; independent regenerated-fixture objdump output and successful public CLI inspection shown above.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The failing condition at scripts/verify-real-ghidra-function.mjs:89-98 includes instruction.operands.length === 0 regardless of the decoded mnemonic or actual operand count. Provider version and installation checks passed before this failure.

Restoration check

With the same CET-enabled fixture and supported provider, the real verifier must accept the correctly decoded ENDBR64 result without inventing operands or discarding its byte and length checks. A genuine instruction with explicit operands must retain its observed operand facts, and malformed or nondecoded instruction results must still fail the relevant verification checks. Verify this specific failed contract independently from any later verifier failures; advancing past this check alone does not establish a complete real-provider lane pass.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A documented real-provider verification command aborts on its valid native fixture under the tested supported Linux compiler defaults. The provider's public inspection result is correct, so the observed impact is failed verification rather than incorrect analysis or modified target bytes.
  • Workaround status: unverified
  • Workaround basis: Disabling CET only in a caller-selected fixture compiler bypassed this check but then hit a separate verifier assertion. No complete successful unmodified verification run was established, and direct CLI inspection does not replace the complete real-provider lane.
Originally created by @coygeek on GitHub (Oct 9, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1238 ## Summary The real Ghidra verifier rejects its own correctly decoded native fixture when the compiler inserts `ENDBR64` at the selected function entry. The instruction has no explicit operands, but `verifyDebugFunctionOperations` requires a nonempty operand array and exits with `Structured native instruction facts drifted`. The same instruction succeeds through the public CLI and agrees with GNU objdump. This blocks the real-provider verification lane on the tested supported Linux toolchain without establishing a provider decoding failure. ## Runtime or environment Tested release: `rea-agents@6.1.0`, tag `rea-agents-6.1.0`, commit `ae9aaee16b9c738d761d3938a3b34fdf37e581b5`. Host: Ubuntu 24.04.5 LTS, Linux x64. Development tools: Node.js `v24.18.0`, npm `11.16.0`, `cc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0`, GNU objdump `2.42`. Provider: the unchanged official Ghidra distribution `12.1.4`, with its published archive digest verified, and OpenJDK `21.0.12.1+1-1-24.04.4-Ubuntu`. The headless heap was `1536M`. `cc -Q --help=common` reported `-fcf-protection=full` by default. The verifier's native fixture flags were `-O0 -g -fno-inline -fno-pie -no-pie`. ## Steps to reproduce 1. Use the release checkout and toolchain stated above, with `GHIDRA_INSTALL_DIR` selecting the extracted official provider and `JAVA_HOME` selecting the full JDK. Run `npm ci` and `npm run build:cached`. 2. Run `node scripts/rea.mjs doctor --provider ghidra --json`; the scoped checks pass. Run `npm run verify:ghidra` without replacing `REA_CC`. The verifier compiles the repository-owned fixture `tests/conformance/ghidra/inventory.c` and fails at its instruction-facts check. 3. Independently regenerate and inspect that same real native fixture from the checkout root: ```sh fixture_root="$(mktemp -d)" cc -O0 -g -fno-inline -fno-pie -no-pie tests/conformance/ghidra/inventory.c -o "$fixture_root/inventory" objdump -d --disassemble=rea_ghidra_inventory_entry "$fixture_root/inventory" address="0x$(nm "$fixture_root/inventory" | awk '$3 == "rea_ghidra_inventory_entry" {print $1}')" node scripts/rea.mjs inspect-native-instruction "$fixture_root/inventory" "$address" --provider ghidra --json ``` The fixture is built from the repository's C source; no external target binary or private application is required. The observed reproduction was not minimized beyond these existing fixture inputs. ## Expected behavior The real-provider lane should accept a correctly decoded supported native instruction with no explicit operands. `src/domain/native/nativeInstruction.ts:34` defines `operands` as an array without a minimum length, and the native inspection contract preserves the instruction's observed bytes, length and operand facts. `docs/testing.md` identifies `verify:ghidra` as the real Ghidra verification lane; the selected compiler output is a valid host-native fixture for that lane. ## Actual behavior The original verifier exited `1` with: ```text Error: Structured native instruction facts drifted at verifyDebugFunctionOperations (.../scripts/verify-real-ghidra-function.mjs:98:11) ``` The public `inspect-native-instruction` CLI exited `0` and returned these relevant normalized facts: ```json {"address":"0x401538","status":"decoded","bytes":"f30f1efa","length":4,"mnemonic":"ENDBR64","raw_disassembly":"ENDBR64","operands":[]} ``` GNU objdump independently reported `401538: f3 0f 1e fa endbr64`, followed by `40153c: 55 push %rbp`. A diagnostic rerun added only the decoded instruction and selected entry to the existing error message; it kept every assertion and reproduced the same failed condition. The diagnostic edit was then restored. A subsequent run of the unchanged verifier through a compiler wrapper adding `-fcf-protection=none` passed this first check and reached a separate instruction-boundary assertion, so that control is not a full-lane success or an established complete workaround. ## Evidence - Expected source: `docs/testing.md`, real Ghidra lane description; `src/domain/native/nativeInstruction.ts:17-34`, provider-decoded instruction contract and operand-array schema, in the tested release. - Failure source: Executed `npm run verify:ghidra` and its error at `scripts/verify-real-ghidra-function.mjs:98`; independent regenerated-fixture objdump output and successful public CLI inspection shown above. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The failing condition at `scripts/verify-real-ghidra-function.mjs:89-98` includes `instruction.operands.length === 0` regardless of the decoded mnemonic or actual operand count. Provider version and installation checks passed before this failure. ## Restoration check With the same CET-enabled fixture and supported provider, the real verifier must accept the correctly decoded `ENDBR64` result without inventing operands or discarding its byte and length checks. A genuine instruction with explicit operands must retain its observed operand facts, and malformed or nondecoded instruction results must still fail the relevant verification checks. Verify this specific failed contract independently from any later verifier failures; advancing past this check alone does not establish a complete real-provider lane pass. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A documented real-provider verification command aborts on its valid native fixture under the tested supported Linux compiler defaults. The provider's public inspection result is correct, so the observed impact is failed verification rather than incorrect analysis or modified target bytes. - Workaround status: unverified - Workaround basis: Disabling CET only in a caller-selected fixture compiler bypassed this check but then hit a separate verifier assertion. No complete successful unmodified verification run was established, and direct CLI inspection does not replace the complete real-provider lane.
bsakaguchi 2026-10-15 18:25:03 +00:00
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#436
No description provided.