mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
* fix(ghidra): prevent loader relocations from constant folding Ghidra imports loader relocation destinations with their file bytes. A destination in a non-writable loaded block can be populated by the runtime loader instead, for example a copy-relocated musl `stderr` in RELRO, so the decompiler folded the on-disk zero and showed `fprintf(NULL, ...)`. Before initializing the decompiler, mark each loaded, initialized, non-writable block containing a relocation destination volatile in the ephemeral database, and add a limitation to procedures that read such a destination. ELF shared objects and PIEs are included; relocatable objects and core files are not. Permissions and source FileBytes are unchanged. Sessions without database-mutation authority (Windows P0 and existing caller projects) record the limitation but do not mark blocks volatile, and the analysis profile versions the two behaviors separately. A real Linux Ghidra ELF probe runs in the Ghidra bridge CI lane. Fixes #4177 Squashed from #4198 and rebased onto main. Rebase adaptations: the read-only gate, the verifier renamed to verify-ghidra-loader-relocation so it stays out of the manual-verifier glob, docs moved to docs/testing-lanes.md and docs/providers/ghidra-provider.md. Co-Authored-By: gensokyo-dev <gensokyo-dev@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ghidra): await installation inspection in the relocation verifier inspectGhidraInstallation became async on main after #4198's base, so the verifier asserted on a pending Promise. Also wait for provider output to close before reading it, like the sibling Ghidra verifiers, and keep the workflow reference inside the docs site. Co-Authored-By: gensokyo-dev <gensokyo-dev@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ghidra): address relocation review findings - Version the existing-project analysis profile with the read-only relocation semantics, since those sessions now add the limitation. - Inspect the installation through the verifier helper that owns the file-reader resources. - Build the fixture with -fPIC: with -fPIE, GCC 12 emits a copy relocation of rea_external_value into writable .bss, so the probe's GOT check could not hold. - Run probe cleanup through withVerifierCleanup so a cleanup failure does not hide the probe failure, and keep the workspace when owned Ghidra cleanup is incomplete. - Link the workflow with a full GitHub URL, per the docs guide. Co-Authored-By: gensokyo-dev <gensokyo-dev@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ghidra): reach the external data through the GOT with a weak extern Building the whole fixture with -fPIC also turned the reader's direct load of rea_loader_pointer into a GOT-relaxed lea plus two loads, and the real-Ghidra lane then reported that the baseline no longer folds the file constant. Keep -fPIE and declare only rea_external_value weak: GCC and clang must reach a possibly absent symbol through a GOT entry, which lands as R_X86_64_GLOB_DAT inside GNU_RELRO (checked with readelf for GCC 12.2 and clang/lld). Co-Authored-By: gensokyo-dev <gensokyo-dev@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ghidra): protect only relocations Ghidra could not apply Running the real-Ghidra lane locally (Ghidra 12.1.4, JDK 21, GCC 12) showed that marking every non-writable block that holds a relocation volatile also marks full-RELRO .got. External calls then decompile as opaque pointer calls (`(*pcVar2)(param_1)` through `PTR_rea_external_...` instead of `rea_external(param_1)`), which the probe's GOT-stability check caught. Ghidra applies GLOB_DAT and RELATIVE relocations, so those bytes already hold the loader's value and folding them is correct. The #4177 case is a copy relocation, which Ghidra imports as UNSUPPORTED with the file's zero in place. Protect only destinations whose relocation status is not APPLIED or APPLIED_OTHER. The fixture now reads a provider `const` object that the linker copies into .data.rel.ro, the #4177 shape. The probe requires that relocation to be unapplied and requires applied GOT relocations to stay non-volatile. Ghidra 12.1.4 already shows this x86-64 read by name without protection, so the probe records whether the baseline folds and compares before and after only when it does. The profile value becomes volatile-unapplied-relocation-blocks-v1. Co-Authored-By: gensokyo-dev <gensokyo-dev@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Arslan Basharat <arslanbasharat414@gmail.com> Co-authored-by: gensokyo-dev <gensokyo-dev@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
2.1 KiB
C
59 lines
2.1 KiB
C
/* Source-owned Linux ELF fixture for the Ghidra loader-relocation boundary. */
|
|
typedef unsigned int rea_loader_word;
|
|
|
|
/* This file-backed value is the constant the unprotected decompiler may fold. */
|
|
__attribute__((used, visibility("default")))
|
|
const rea_loader_word rea_loader_value = 0x11111111u;
|
|
|
|
#if defined(__ELF__)
|
|
#define REA_LOADER_POINTER_SECTION ".data.rel.ro"
|
|
#elif defined(__APPLE__)
|
|
#define REA_LOADER_POINTER_SECTION "__DATA_CONST,__const"
|
|
#else
|
|
#error "The loader-relocation fixture requires an ELF or Mach-O compiler"
|
|
#endif
|
|
|
|
/* The loader fills this pointer before its RELRO block is sealed read-only. */
|
|
__attribute__((used, visibility("default"), section(REA_LOADER_POINTER_SECTION)))
|
|
const rea_loader_word *volatile const rea_loader_pointer = &rea_loader_value;
|
|
|
|
/* A const object defined by the provider. In a PIE, GCC and the linker copy
|
|
* it into RELRO with a copy relocation that Ghidra cannot apply, so the file
|
|
* holds zero where the loader writes the provider's value: the #4177 shape. */
|
|
extern const rea_loader_word rea_external_const;
|
|
|
|
__attribute__((noinline, used, visibility("default")))
|
|
rea_loader_word rea_loader_reader(void) {
|
|
return rea_external_const;
|
|
}
|
|
|
|
__attribute__((noinline, used, visibility("default")))
|
|
void rea_loader_writer(void) {
|
|
*(volatile rea_loader_word *)(void *)&rea_external_const = 0;
|
|
}
|
|
|
|
/* An applied relocation in the same RELRO segment; it is not protected. */
|
|
__attribute__((noinline, used, visibility("default")))
|
|
rea_loader_word rea_loader_pointer_reader(void) {
|
|
return *rea_loader_pointer;
|
|
}
|
|
|
|
extern rea_loader_word rea_external(int value);
|
|
/* Weak so -fPIE code reads it through a RELRO GOT entry. A strong reference
|
|
* lets GCC use a copy relocation into writable .bss instead. */
|
|
extern rea_loader_word rea_external_value __attribute__((weak));
|
|
|
|
__attribute__((noinline, used, visibility("default")))
|
|
rea_loader_word rea_loader_got_caller(rea_loader_word value) {
|
|
return rea_external((int)value) + rea_external_value;
|
|
}
|
|
|
|
__attribute__((used, visibility("default"))) void rea_loader_start(void) {
|
|
volatile rea_loader_word result = rea_loader_reader();
|
|
(void)result;
|
|
for (;;) {
|
|
}
|
|
}
|
|
|
|
#undef REA_LOADER_POINTER_SECTION
|