[PR #4198] fix(ghidra): prevent loader relocations from constant folding #6120

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

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/4198
Author: @arslanbasharat-o-o
Created: 10/15/2026
Status: 🔄 Open

Base: main ← Head: fix/ghidra-copy-relocation-relro


📝 Commits (10+)

  • 80239d4 fix(ghidra): protect loader relocations from constant folding
  • 9670a68 chore: add reproducible container workflow
  • e6eb71d test(ghidra): cover loader relocation uncertainty
  • d48fc9d fix(docker): harden image runtime and context
  • 63dba3e fix(ghidra): preserve probe assertion failures
  • c56d336 test(ghidra): verify ELF loader relocations
  • 0f3bbfb test(ghidra): validate imported pointer target
  • 8ecee31 docs: preserve canonical README link parity in development setup
  • c96d94b merge: synchronize main into ghidra relocation fix
  • b176ff7 fix(ghidra): handle dynamic ELF relocation reviews

📊 Changes

14 files changed (+908 additions, -10 deletions)

View changed files

📝 .github/workflows/ghidra-bridge-sources.yml (+9 -1)
📝 bridge/ghidra/ReaGhidraBridge.java (+110 -0)
📝 docs/testing.md (+15 -0)
📝 package.json (+1 -0)
📝 scripts/ci/plan.mjs (+4 -1)
📝 scripts/ci/scopes.mjs (+7 -4)
➕ scripts/verify-real-ghidra-loader-relocation.mjs (+227 -0)
📝 src/ghidra/GhidraAnalysisProfile.ts (+2 -0)
📝 src/ghidra/GhidraProvider.behavior.test.ts (+1 -0)
📝 tests/boundary/filesystem/ciChangeScope.test.ts (+20 -4)
📝 tests/conformance/ghidra/README.md (+13 -0)
➕ tests/conformance/ghidra/ReaLoaderRelocationProbe.java (+445 -0)
➕ tests/conformance/ghidra/loader-relocation-provider.c (+9 -0)
➕ tests/conformance/ghidra/loader-relocation.c (+45 -0)

📄 Description

Summary

Fixes #4177.

Ghidra now treats loader-initialized relocation destinations in non-writable loaded memory as uncertain during decompilation. This prevents RELRO copy-relocation values such as stderr from being presented as trustworthy file-backed constants.

Problem and expected behavior

OpenWrt's non-PIE MIPS usign contains a copy-relocated stderr in RELRO. The file stores zero at the destination while the runtime loader supplies the stream pointer. Ghidra could constant-propagate the on-disk zero, making fprintf(stderr, ...) appear to write through a null stream.

A loader-populated destination must not be presented as a known runtime constant. Evidence reports uncertainty only when an instruction reads an address covered by a loader relocation.

Change and scope

  • Mark blocks containing loaded, initialized, non-writable relocation destinations volatile before decompiler initialization, without changing permissions or source FileBytes.
  • Handle ELF ET_DYN PIEs and shared objects even though Ghidra also marks their relocation table relocatable. Keep relocatable object files and core files outside this mitigation.
  • Match Evidence references against the full byte range affected by each relocation and filter out writes/address-only references.
  • Version the Ghidra analysis profile so old snapshots are not reused under changed decompiler semantics.
  • Add a real Linux ELF importer regression. It verifies the ET_DYN case, read/write reference handling, the affected-byte range, and baseline/protected pseudocode. A full-RELRO -fno-plt caller checks that external GOT calls retain the same pseudocode after relocation blocks become volatile.
  • The Buildathon container workflow has been moved into follow-up PR #6912, leaving this PR focused on the Ghidra relocation fix.

Contract and boundary impact

  • Semantic owner and earliest changed stage: Ghidra bridge memory semantics immediately before decompiler initialization.
  • Contribution scope fit: existing-provider bug fix; the Buildathon container workflow is isolated in follow-up PR #6912.
  • CLI and MCP/tool-catalog contract: no schema or operation changes; the analysis profile digest changes intentionally.
  • Provider, bridge, target-format, or platform compatibility: ELF ET_DYN and executable relocations are eligible; ET_REL object files and other Ghidra programs still marked relocatable are skipped.
  • Evidence, artifact, provenance, or reconstruction contract: only read references to bytes affected by a relocation add the loader-value limitation. Source FileBytes remain unchanged.
  • Target selection, authority, upstream behavior, and cleanup details: the fixture is imported into an ephemeral Ghidra project; no target code is executed or modified. The bridge changes only the analysis database.
  • Generated metadata, package, or installation impact: no generated catalogs or package metadata changed.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process. (Ghidra only; no target process.)
  • Inject instrumentation or run scripts/code. (The Ghidra bridge and regression probe run.)
  • Change process memory, registers, files, or configuration. (Ghidra's ephemeral analysis database only.)
  • Contact network services. (The Ghidra analysis and regression lane run locally.)
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: importer-backed Ghidra probe for the dynamic ELF skip guard, relocation ranges, read/write references, full-RELRO GOT pseudocode stability, source bytes, and permissions; the CI scope classifier selects the change-triggered Linux Ghidra lane for the verifier and its fixtures.
  • Base reproduction or other evidence: the prior guard skipped the PR's own PIE fixture because Ghidra marks ET_DYN relocation tables relocatable. The probe now requires an ELF shared-object file type and a relocatable table, then verifies the production bridge makes the destination volatile.
  • User-visible CLI/MCP output: no CLI/MCP response shape changed.
  • Remaining proof gaps: this macOS host has no Ghidra installation or Linux ELF linker, so the native importer/decompiler lane has not run locally. It is wired into the change-selected Linux Ghidra CI lane and must pass with Ghidra 12.1.4 and its native decompiler before merge. GitHub CI and PR title are action_required and need maintainer approval; Greptile passed on the latest head after the test-path selection fix.

For evidence-bearing changes:

  • 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 ci with Node 24.22.1 and npm 11.16.0 — passed.
  • npm run check:fast — passed.
  • npm run format:check — passed.
  • vitest run src/ghidra/GhidraProvider.behavior.test.ts -t "commits exact provider, isolation, and resource semantics" — 1 passed.
  • npm run test:local -- tests/conformance/readmeConsistency.test.ts — 9 passed.
  • npm run test:focused -- tests/boundary/filesystem/ciChangeScope.test.ts — 279 passed, including verifier/fixture lane selection.
  • npm run verify:test-discovery — 2,178 test files discovered exactly once.
  • npm run build:cached — passed.
  • node --check scripts/verify-real-ghidra-loader-relocation.mjs and clang -fsyntax-only -Wall -Wextra tests/conformance/ghidra/loader-relocation.c tests/conformance/ghidra/loader-relocation-provider.c — passed.
  • git diff --check — passed.
  • npm run verify:ghidra:bridge-sources — unavailable because GHIDRA_INSTALL_DIR is not set; the host has no Ghidra installation.

Compatibility, safety, and release

  • Breaking changes or migration steps: none to CLI/MCP inputs or outputs; the profile digest intentionally invalidates stale Ghidra snapshots.
  • Real Hopper/Ghidra, browser, or OS coverage: focused Node/C checks passed; Linux Ghidra importer/decompiler validation remains pending as described above.
  • Package or release metadata impact: no package version or generated metadata change.
  • Security, privacy, process, or containment review: no target execution, injection, or target mutation; the Ghidra analysis database is ephemeral and original source bytes remain unchanged.

Author readiness

Human owner: @arslanbasharat-o-o

  • 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 or an agreed enhancement.
  • Material scope and authority questions are resolved for the final Ghidra-only diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff 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/4198 **Author:** [@arslanbasharat-o-o](https://github.com/arslanbasharat-o-o) **Created:** 10/15/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/ghidra-copy-relocation-relro` --- ### 📝 Commits (10+) - [`80239d4`](https://github.com/morluto/rea/commit/80239d47afa04ed9ac2546b7f90be75ed2f63417) fix(ghidra): protect loader relocations from constant folding - [`9670a68`](https://github.com/morluto/rea/commit/9670a686268d54b6633dcaba407d2c1795f56b66) chore: add reproducible container workflow - [`e6eb71d`](https://github.com/morluto/rea/commit/e6eb71db04a510b01b4121a6c7e1fe6057df667c) test(ghidra): cover loader relocation uncertainty - [`d48fc9d`](https://github.com/morluto/rea/commit/d48fc9dbd340781704ff6bf60c7c8a2385631bad) fix(docker): harden image runtime and context - [`63dba3e`](https://github.com/morluto/rea/commit/63dba3e3c97623180a579d5e27f226eb53746169) fix(ghidra): preserve probe assertion failures - [`c56d336`](https://github.com/morluto/rea/commit/c56d3364edabb2d74064e08056d423bad5e79610) test(ghidra): verify ELF loader relocations - [`0f3bbfb`](https://github.com/morluto/rea/commit/0f3bbfbffcc17222377f448f0b96d323be8e9949) test(ghidra): validate imported pointer target - [`8ecee31`](https://github.com/morluto/rea/commit/8ecee31b2f4752c26c0d79d2a69aed012410b8f7) docs: preserve canonical README link parity in development setup - [`c96d94b`](https://github.com/morluto/rea/commit/c96d94b76d0021129c0044f168dcfc54b79ac359) merge: synchronize main into ghidra relocation fix - [`b176ff7`](https://github.com/morluto/rea/commit/b176ff77504eb7a338a5bcf93b571854d7ae4b26) fix(ghidra): handle dynamic ELF relocation reviews ### 📊 Changes **14 files changed** (+908 additions, -10 deletions) <details> <summary>View changed files</summary> 📝 `.github/workflows/ghidra-bridge-sources.yml` (+9 -1) 📝 `bridge/ghidra/ReaGhidraBridge.java` (+110 -0) 📝 `docs/testing.md` (+15 -0) 📝 `package.json` (+1 -0) 📝 `scripts/ci/plan.mjs` (+4 -1) 📝 `scripts/ci/scopes.mjs` (+7 -4) ➕ `scripts/verify-real-ghidra-loader-relocation.mjs` (+227 -0) 📝 `src/ghidra/GhidraAnalysisProfile.ts` (+2 -0) 📝 `src/ghidra/GhidraProvider.behavior.test.ts` (+1 -0) 📝 `tests/boundary/filesystem/ciChangeScope.test.ts` (+20 -4) 📝 `tests/conformance/ghidra/README.md` (+13 -0) ➕ `tests/conformance/ghidra/ReaLoaderRelocationProbe.java` (+445 -0) ➕ `tests/conformance/ghidra/loader-relocation-provider.c` (+9 -0) ➕ `tests/conformance/ghidra/loader-relocation.c` (+45 -0) </details> ### 📄 Description ## Summary Fixes #4177. Ghidra now treats loader-initialized relocation destinations in non-writable loaded memory as uncertain during decompilation. This prevents RELRO copy-relocation values such as `stderr` from being presented as trustworthy file-backed constants. ## Problem and expected behavior OpenWrt's non-PIE MIPS `usign` contains a copy-relocated `stderr` in RELRO. The file stores zero at the destination while the runtime loader supplies the stream pointer. Ghidra could constant-propagate the on-disk zero, making `fprintf(stderr, ...)` appear to write through a null stream. A loader-populated destination must not be presented as a known runtime constant. Evidence reports uncertainty only when an instruction reads an address covered by a loader relocation. ## Change and scope - Mark blocks containing loaded, initialized, non-writable relocation destinations volatile before decompiler initialization, without changing permissions or source `FileBytes`. - Handle ELF `ET_DYN` PIEs and shared objects even though Ghidra also marks their relocation table relocatable. Keep relocatable object files and core files outside this mitigation. - Match Evidence references against the full byte range affected by each relocation and filter out writes/address-only references. - Version the Ghidra analysis profile so old snapshots are not reused under changed decompiler semantics. - Add a real Linux ELF importer regression. It verifies the `ET_DYN` case, read/write reference handling, the affected-byte range, and baseline/protected pseudocode. A full-RELRO `-fno-plt` caller checks that external GOT calls retain the same pseudocode after relocation blocks become volatile. - The Buildathon container workflow has been moved into [follow-up PR #6912](https://github.com/morluto/rea/pull/6912), leaving this PR focused on the Ghidra relocation fix. ## Contract and boundary impact - Semantic owner and earliest changed stage: Ghidra bridge memory semantics immediately before decompiler initialization. - Contribution scope fit: existing-provider bug fix; the Buildathon container workflow is isolated in [follow-up PR #6912](https://github.com/morluto/rea/pull/6912). - CLI and MCP/tool-catalog contract: no schema or operation changes; the analysis profile digest changes intentionally. - Provider, bridge, target-format, or platform compatibility: ELF `ET_DYN` and executable relocations are eligible; `ET_REL` object files and other Ghidra programs still marked relocatable are skipped. - Evidence, artifact, provenance, or reconstruction contract: only read references to bytes affected by a relocation add the loader-value limitation. Source `FileBytes` remain unchanged. - Target selection, authority, upstream behavior, and cleanup details: the fixture is imported into an ephemeral Ghidra project; no target code is executed or modified. The bridge changes only the analysis database. - Generated metadata, package, or installation impact: no generated catalogs or package metadata changed. Expected effects: - [x] Read existing files or captured data. - [x] Launch or attach to a process. (Ghidra only; no target process.) - [x] Inject instrumentation or run scripts/code. (The Ghidra bridge and regression probe run.) - [x] Change process memory, registers, files, or configuration. (Ghidra's ephemeral analysis database only.) - [x] Contact network services. (The Ghidra analysis and regression lane run locally.) - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: importer-backed Ghidra probe for the dynamic ELF skip guard, relocation ranges, read/write references, full-RELRO GOT pseudocode stability, source bytes, and permissions; the CI scope classifier selects the change-triggered Linux Ghidra lane for the verifier and its fixtures. - Base reproduction or other evidence: the prior guard skipped the PR's own PIE fixture because Ghidra marks `ET_DYN` relocation tables relocatable. The probe now requires an ELF shared-object file type and a relocatable table, then verifies the production bridge makes the destination volatile. - User-visible CLI/MCP output: no CLI/MCP response shape changed. - Remaining proof gaps: this macOS host has no Ghidra installation or Linux ELF linker, so the native importer/decompiler lane has not run locally. It is wired into the change-selected Linux Ghidra CI lane and must pass with Ghidra 12.1.4 and its native decompiler before merge. GitHub `CI` and `PR title` are `action_required` and need maintainer approval; Greptile passed on the latest head after the test-path selection fix. For evidence-bearing changes: - [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 ci` with Node 24.22.1 and npm 11.16.0 — passed. - `npm run check:fast` — passed. - `npm run format:check` — passed. - `vitest run src/ghidra/GhidraProvider.behavior.test.ts -t "commits exact provider, isolation, and resource semantics"` — 1 passed. - `npm run test:local -- tests/conformance/readmeConsistency.test.ts` — 9 passed. - `npm run test:focused -- tests/boundary/filesystem/ciChangeScope.test.ts` — 279 passed, including verifier/fixture lane selection. - `npm run verify:test-discovery` — 2,178 test files discovered exactly once. - `npm run build:cached` — passed. - `node --check scripts/verify-real-ghidra-loader-relocation.mjs` and `clang -fsyntax-only -Wall -Wextra tests/conformance/ghidra/loader-relocation.c tests/conformance/ghidra/loader-relocation-provider.c` — passed. - `git diff --check` — passed. - `npm run verify:ghidra:bridge-sources` — unavailable because `GHIDRA_INSTALL_DIR` is not set; the host has no Ghidra installation. ## Compatibility, safety, and release - Breaking changes or migration steps: none to CLI/MCP inputs or outputs; the profile digest intentionally invalidates stale Ghidra snapshots. - Real Hopper/Ghidra, browser, or OS coverage: focused Node/C checks passed; Linux Ghidra importer/decompiler validation remains pending as described above. - Package or release metadata impact: no package version or generated metadata change. - Security, privacy, process, or containment review: no target execution, injection, or target mutation; the Ghidra analysis database is ephemeral and original source bytes remain unchanged. ## Author readiness Human owner: @arslanbasharat-o-o - [x] 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. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved for the final Ghidra-only diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff 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#6120
No description provided.