[PR #3029] fix(ghidra): recover registered Borland SEH handler gaps #5568

Open
opened 2026-10-31 01:07:38 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/3029
Author: @matbrgz
Created: 10/13/2026
Status: 🔄 Open

Base: main ← Head: fix/ghidra-delphi-seh-regions


📝 Commits (7)

  • c872381 fix(ghidra): recover registered Borland SEH handler gaps
  • 46d7013 fix(ghidra): verify handler recovery cleanup on updated upstream
  • 25736d7 fix(ghidra): gate SEH continuations and preserve boundary evidence
  • 299f82d Merge remote-tracking branch 'upstream/main' into fix/ghidra-delphi-seh-regions
  • 495089d fix(ghidra): bound SEH recovery and preserve containment provenance
  • 4917e8d fix(ghidra): retain instruction-level SEH boundaries and observations
  • b361a3d fix(ghidra): discard speculative SEH units and retain source provenance

📊 Changes

28 files changed (+1590 additions, -55 deletions)

View changed files

➕ bridge/ghidra/ReaGhidraBorlandSeh.java (+312 -0)
📝 bridge/ghidra/ReaGhidraBridge.java (+77 -20)
📝 docs/ghidra-provider.md (+26 -0)
➕ docs/specs/ghidra/borland-seh/plan.md (+134 -0)
➕ docs/specs/ghidra/borland-seh/spec.md (+46 -0)
➕ docs/specs/ghidra/borland-seh/tasks.md (+46 -0)
📝 package.json (+2 -0)
➕ scripts/lib/borland-seh-fixture.mjs (+61 -0)
➕ scripts/verify-real-ghidra-borland-seh.mjs (+379 -0)
📝 src/domain/flutter/flutterBuildAnalysis.ts (+1 -1)
📝 src/ghidra/GhidraAnalysisProfile.ts (+10 -1)
📝 src/ghidra/GhidraBridgeSources.ts (+1 -0)
📝 src/ghidra/GhidraClient.ts (+19 -5)
📝 src/ghidra/GhidraFunctionValues.test.ts (+24 -0)
📝 src/ghidra/GhidraFunctionValues.ts (+3 -0)
📝 src/ghidra/GhidraInventoryValues.test.ts (+105 -8)
📝 src/ghidra/GhidraInventoryValues.ts (+19 -0)
📝 src/ghidra/GhidraProvider.behavior.test.ts (+30 -0)
📝 src/ghidra/GhidraSnapshotCache.test.ts (+2 -2)
📝 src/server/http/NodeHttpRequestOwner.ts (+2 -3)

...and 8 more files

📄 Description

Summary

Refs #2926. Recover registered Delphi/Borland PE32 SEH handler gaps in Ghidra's ephemeral function body, making handler addresses, calls and strings visible through existing CLI/MCP function queries. Known unrecovered registrations remain explicit limitations with partial callee coverage.

Problem and expected behavior

An exception handler entered through the Win32 SEH chain can lie between two observed function ranges without ordinary control-flow references. Recovery is bounded to unowned gaps and requires a supplied/imported Borland RTL symbol before interpreting bytes after a non-fallthrough jump. An arbitrary executable jump target leaves an explicit omission, never an inferred catch body. Related omission-reporting work: #2981.

Change and scope

One Ghidra-owned Java producer recognizes exact decoded zero-offset FS registration patterns, recovers only unowned executable gaps, respects defined data/independent functions, traverses a continuation justified by a supplied/imported RTL identity, and refreshes references using the caller-enabled Ghidra analyzers. Existing queries consume the corrected FunctionManager body. Diagnostics enter main's shared function-boundary producer, including procedure references. SEH omissions augment its coverage without replacing unresolved/indirect-call diagnostics. Analysis profile invalidation is limited to PE32/x86.

Real validation exposed an ownership-token race during JVM exit. After a validated session acknowledges shutdown, close can wait for the retained original child and retry full owned-group verification once. Child exit alone never authorizes deletion; unresolved cleanup retains files and supervision. Startup failures and forced cancellation retain their existing behavior.

Contract and boundary impact

  • Semantic owner: Ghidra function-body and reference producer before decompiler initialization; provider boundary parses coverage once.
  • Contribution scope: ordinary provider fix within existing POSIX ephemeral-database authority; no new target execution/provider/protection path, so an additional scope decision is not applicable.
  • CLI/MCP: existing function operations and containment Evidence limitations; optional relationship_coverage on producer callee results, preserving Evidence limitations.
  • Compatibility: PE32 x86 only; Windows P0 observes registrations without body mutation. Existing names/signatures and independent owners are preserved.
  • Evidence/provenance: registration observation is not proof of runtime execution; recognized omissions remain visible.
  • Target/cleanup: executable bytes unchanged; own bounded gaps only; group identity must be verified before runtime removal.
  • Package: ship the helper beside the bridge and require it in packed-bridge verification; no binaries/projects/build outputs committed.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.

These are the owned Ghidra process, bridge scripts and ephemeral analysis database. The analyzed program is never executed; its bytes are never modified.

Evidence and regression coverage

  • Source-owned PE32 fixture verified through real Ghidra CLI/MCP, with a real producer probe for nonzero FS offsets, independently owned handlers and observe-only behavior.
  • The pre-review public reproduction used the old permissive jump heuristic. It has not been rerun with the stricter RTL-identity gate and is not proof of the current branch's recovery on that executable.
  • Typed malformed-coverage tests and provider projection regressions preserve limitations and locations. Client tests cover successful retry and retained files when group verification remains incomplete.
  • No actual Windows host verification; observe-only policy was exercised on POSIX. General register dataflow, other prologues, outside-span recovery and guessed RTL names remain outside scope.

Instruction inspection and call-target resolution now retain the same producer-owned SEH provenance as containment and function views. Reused path destinations must be decoded instruction starts, including already-owned handler entries. Recognized terminal flows remain static Ghidra classifications, with an explicit non-return caveat. The tar-listing fixture consumes the canonical packaged-source manifest rather than an obsolete duplicated source list.

Omitted or cancelled traversal clears only its newly decoded units and generated references, preserving pre-existing listing/references and earlier successful candidates. Procedure selectors and byte-match owners retain their SEH observations; callers, incoming procedure references, xrefs, instruction inspection and dossiers retain provenance from returned source addresses. Observations are deduplicated and unrelated queries do not inherit another function's SEH facts.

Representative output: a recognized handler jump without a supplied RTL identity retains the original function body, reports the omitted continuation in procedure_references limitations, and returns procedure_callees coverage partial alongside its existing indirect-call diagnostics.

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run check — passed.
  • npm run verify:package — not completed on the current review revision. One attempt exceeded the packaged MCP startup probe's 5-second timeout; the next completed installation, then failed in the update fixture with ERR_HTTP_HEADERS_SENT at scripts/verify-package-update.mjs:29 while proxying registry responses. The preceding review revision passed full package verification; this is not a claim that the current full-package gate passed. Follow-up: #6038.
  • Latest focused suites — 238 passed across function/inventory contracts, snapshot admission, MCP Evidence, client lifecycle and bridge-source packaging on upstream ccc7da309. Both failing CI tests used stale observation-profile expectations; the snapshot test now explicitly rejects prior v5 semantics. Earlier revision: 122 passed, including the tar-listing regression.
  • node scripts/verify-real-ghidra-borland-seh.mjs after build — passed on Ghidra 12.1.4/JDK 21, darwin/arm64. Covers omitted/cancelled speculative-unit and generated-reference cleanup, preserved pre-existing listing/user references and earlier successful recovery; procedure-address selection, byte-match ownership, reverse callers/references/xrefs and incoming instruction/dossier provenance; unrelated-query controls; and the prior RTL, budget, ownership, containment and instruction-boundary cases. Target unchanged, runtime empty and process lineage positively verified empty.
  • npm run docs:check — passed; the old #3022 drift is resolved in main.

No controlled before/after speed benchmark, live model token usage, CPU measurement or process-family peak-memory comparison was performed for this recovery pass; no performance gain is claimed.

Compatibility, safety, and release

Normalized public result shapes remain unchanged. The internal bridge now requires observation envelopes for procedure_address, search_bytes and xrefs. POSIX PE32/x86 recovery profile v5 includes speculative-unit cleanup; shared function/address observation profile v6 invalidates older results for every format. Existing Windows P0 read-only policy remains authoritative. Recovery has one analysis-wide admission budget (16,384 visits, 64 KiB newly decoded instruction bytes, 4,096 pending addresses), restricts each disassembly request to at most 15 x86 bytes, and stops at unresolved flow, ownership and data boundaries; known omissions are retained. Specifications: docs/specs/ghidra/borland-seh/.

Author readiness

Human owner: @matbrgz

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem.
  • Material scope and authority questions are resolved.
  • The PR has one focused outcome and a conventional title.
  • Related issue and alternative PR are linked.
  • Tests cover changed behavior and meaningful failure paths.
  • Owning docs, contracts, and package metadata are updated.
  • Representative user-visible output is included.
  • Final diff reviewed for secrets, unrelated cleanup and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/3029 **Author:** [@matbrgz](https://github.com/matbrgz) **Created:** 10/13/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/ghidra-delphi-seh-regions` --- ### 📝 Commits (7) - [`c872381`](https://github.com/morluto/rea/commit/c8723811f05f6e6747f71db79025f5b4675beb0c) fix(ghidra): recover registered Borland SEH handler gaps - [`46d7013`](https://github.com/morluto/rea/commit/46d70133e94447a6ef3b6d72a03ded4e0d01cbb8) fix(ghidra): verify handler recovery cleanup on updated upstream - [`25736d7`](https://github.com/morluto/rea/commit/25736d74faf2b5d8b10a724d7362926ff96ab1de) fix(ghidra): gate SEH continuations and preserve boundary evidence - [`299f82d`](https://github.com/morluto/rea/commit/299f82dc2ca9a32643ae09a5745c1a7c21ae12b5) Merge remote-tracking branch 'upstream/main' into fix/ghidra-delphi-seh-regions - [`495089d`](https://github.com/morluto/rea/commit/495089d1b6c90e769d4691dfa85f1c9ece863bab) fix(ghidra): bound SEH recovery and preserve containment provenance - [`4917e8d`](https://github.com/morluto/rea/commit/4917e8ded62138894f41045c601a5927984a2460) fix(ghidra): retain instruction-level SEH boundaries and observations - [`b361a3d`](https://github.com/morluto/rea/commit/b361a3dd2eb530a9dfebbf7592f6514df842d730) fix(ghidra): discard speculative SEH units and retain source provenance ### 📊 Changes **28 files changed** (+1590 additions, -55 deletions) <details> <summary>View changed files</summary> ➕ `bridge/ghidra/ReaGhidraBorlandSeh.java` (+312 -0) 📝 `bridge/ghidra/ReaGhidraBridge.java` (+77 -20) 📝 `docs/ghidra-provider.md` (+26 -0) ➕ `docs/specs/ghidra/borland-seh/plan.md` (+134 -0) ➕ `docs/specs/ghidra/borland-seh/spec.md` (+46 -0) ➕ `docs/specs/ghidra/borland-seh/tasks.md` (+46 -0) 📝 `package.json` (+2 -0) ➕ `scripts/lib/borland-seh-fixture.mjs` (+61 -0) ➕ `scripts/verify-real-ghidra-borland-seh.mjs` (+379 -0) 📝 `src/domain/flutter/flutterBuildAnalysis.ts` (+1 -1) 📝 `src/ghidra/GhidraAnalysisProfile.ts` (+10 -1) 📝 `src/ghidra/GhidraBridgeSources.ts` (+1 -0) 📝 `src/ghidra/GhidraClient.ts` (+19 -5) 📝 `src/ghidra/GhidraFunctionValues.test.ts` (+24 -0) 📝 `src/ghidra/GhidraFunctionValues.ts` (+3 -0) 📝 `src/ghidra/GhidraInventoryValues.test.ts` (+105 -8) 📝 `src/ghidra/GhidraInventoryValues.ts` (+19 -0) 📝 `src/ghidra/GhidraProvider.behavior.test.ts` (+30 -0) 📝 `src/ghidra/GhidraSnapshotCache.test.ts` (+2 -2) 📝 `src/server/http/NodeHttpRequestOwner.ts` (+2 -3) _...and 8 more files_ </details> ### 📄 Description ## Summary Refs #2926. Recover registered Delphi/Borland PE32 SEH handler gaps in Ghidra's ephemeral function body, making handler addresses, calls and strings visible through existing CLI/MCP function queries. Known unrecovered registrations remain explicit limitations with partial callee coverage. ## Problem and expected behavior An exception handler entered through the Win32 SEH chain can lie between two observed function ranges without ordinary control-flow references. Recovery is bounded to unowned gaps and requires a supplied/imported Borland RTL symbol before interpreting bytes after a non-fallthrough jump. An arbitrary executable jump target leaves an explicit omission, never an inferred catch body. Related omission-reporting work: #2981. ## Change and scope One Ghidra-owned Java producer recognizes exact decoded zero-offset FS registration patterns, recovers only unowned executable gaps, respects defined data/independent functions, traverses a continuation justified by a supplied/imported RTL identity, and refreshes references using the caller-enabled Ghidra analyzers. Existing queries consume the corrected FunctionManager body. Diagnostics enter main's shared function-boundary producer, including procedure references. SEH omissions augment its coverage without replacing unresolved/indirect-call diagnostics. Analysis profile invalidation is limited to PE32/x86. Real validation exposed an ownership-token race during JVM exit. After a validated session acknowledges shutdown, close can wait for the retained original child and retry full owned-group verification once. Child exit alone never authorizes deletion; unresolved cleanup retains files and supervision. Startup failures and forced cancellation retain their existing behavior. ## Contract and boundary impact - Semantic owner: Ghidra function-body and reference producer before decompiler initialization; provider boundary parses coverage once. - Contribution scope: ordinary provider fix within existing POSIX ephemeral-database authority; no new target execution/provider/protection path, so an additional scope decision is not applicable. - CLI/MCP: existing function operations and containment Evidence limitations; optional `relationship_coverage` on producer callee results, preserving Evidence limitations. - Compatibility: PE32 x86 only; Windows P0 observes registrations without body mutation. Existing names/signatures and independent owners are preserved. - Evidence/provenance: registration observation is not proof of runtime execution; recognized omissions remain visible. - Target/cleanup: executable bytes unchanged; own bounded gaps only; group identity must be verified before runtime removal. - Package: ship the helper beside the bridge and require it in packed-bridge verification; no binaries/projects/build outputs committed. Expected effects: - [x] Read existing files or captured data. - [x] Launch or attach to a process. - [x] Inject instrumentation or run scripts/code. - [x] Change process memory, registers, files, or configuration. These are the owned Ghidra process, bridge scripts and ephemeral analysis database. The analyzed program is never executed; its bytes are never modified. ## Evidence and regression coverage - Source-owned PE32 fixture verified through real Ghidra CLI/MCP, with a real producer probe for nonzero FS offsets, independently owned handlers and observe-only behavior. - The pre-review public reproduction used the old permissive jump heuristic. It has not been rerun with the stricter RTL-identity gate and is not proof of the current branch's recovery on that executable. - Typed malformed-coverage tests and provider projection regressions preserve limitations and locations. Client tests cover successful retry and retained files when group verification remains incomplete. - No actual Windows host verification; observe-only policy was exercised on POSIX. General register dataflow, other prologues, outside-span recovery and guessed RTL names remain outside scope. Instruction inspection and call-target resolution now retain the same producer-owned SEH provenance as containment and function views. Reused path destinations must be decoded instruction starts, including already-owned handler entries. Recognized terminal flows remain static Ghidra classifications, with an explicit non-return caveat. The tar-listing fixture consumes the canonical packaged-source manifest rather than an obsolete duplicated source list. Omitted or cancelled traversal clears only its newly decoded units and generated references, preserving pre-existing listing/references and earlier successful candidates. Procedure selectors and byte-match owners retain their SEH observations; callers, incoming procedure references, xrefs, instruction inspection and dossiers retain provenance from returned source addresses. Observations are deduplicated and unrelated queries do not inherit another function's SEH facts. Representative output: a recognized handler jump without a supplied RTL identity retains the original function body, reports the omitted continuation in `procedure_references` limitations, and returns `procedure_callees` coverage `partial` alongside its existing indirect-call diagnostics. - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run check` — passed. - `npm run verify:package` — **not completed on the current review revision**. One attempt exceeded the packaged MCP startup probe's 5-second timeout; the next completed installation, then failed in the update fixture with `ERR_HTTP_HEADERS_SENT` at `scripts/verify-package-update.mjs:29` while proxying registry responses. The preceding review revision passed full package verification; this is not a claim that the current full-package gate passed. Follow-up: [#6038](https://github.com/morluto/rea/issues/6038). - Latest focused suites — **238 passed** across function/inventory contracts, snapshot admission, MCP Evidence, client lifecycle and bridge-source packaging on upstream `ccc7da309`. Both failing CI tests used stale observation-profile expectations; the snapshot test now explicitly rejects prior v5 semantics. Earlier revision: 122 passed, including the tar-listing regression. - `node scripts/verify-real-ghidra-borland-seh.mjs` after build — passed on Ghidra 12.1.4/JDK 21, darwin/arm64. Covers omitted/cancelled speculative-unit and generated-reference cleanup, preserved pre-existing listing/user references and earlier successful recovery; procedure-address selection, byte-match ownership, reverse callers/references/xrefs and incoming instruction/dossier provenance; unrelated-query controls; and the prior RTL, budget, ownership, containment and instruction-boundary cases. Target unchanged, runtime empty and process lineage positively verified empty. - `npm run docs:check` — passed; the old #3022 drift is resolved in main. No controlled before/after speed benchmark, live model token usage, CPU measurement or process-family peak-memory comparison was performed for this recovery pass; no performance gain is claimed. ## Compatibility, safety, and release Normalized public result shapes remain unchanged. The internal bridge now requires observation envelopes for procedure_address, search_bytes and xrefs. POSIX PE32/x86 recovery profile v5 includes speculative-unit cleanup; shared function/address observation profile v6 invalidates older results for every format. Existing Windows P0 read-only policy remains authoritative. Recovery has one analysis-wide admission budget (16,384 visits, 64 KiB newly decoded instruction bytes, 4,096 pending addresses), restricts each disassembly request to at most 15 x86 bytes, and stops at unresolved flow, ownership and data boundaries; known omissions are retained. Specifications: `docs/specs/ghidra/borland-seh/`. ## Author readiness Human owner: @matbrgz - [ ] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [ ] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [ ] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem. - [x] Material scope and authority questions are resolved. - [x] The PR has one focused outcome and a conventional title. - [x] Related issue and alternative PR are linked. - [x] Tests cover changed behavior and meaningful failure paths. - [x] Owning docs, contracts, and package metadata are updated. - [x] Representative user-visible output is included. - [x] Final diff reviewed for secrets, unrelated cleanup and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
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#5568
No description provided.