Conversation
_rt_relocate's PT_GNU_RELRO re-protection was best-effort: it skipped a region not congruent with the runtime page (the documented large-page degradation) and ignored the mprotect result. Now that the ELF writer aligns every segment to the target's max page (>= any supported kernel page, briar-systems/mach#1845), the region is always a whole number of runtime pages, so neither escape is legitimate - an absent AT_PAGESZ, a congruence mismatch, or a failed mprotect is a broken environment or a violated layout contract, and running unhardened would silently mask it. Extract the step into relro_reprotect, which panics naming the violated invariant in each case - the glibc stance, matching std.system.os.page_size's AT_PAGESZ precedent (#336/#343). No silent-unhardened path remains: a --pie binary either runs fully hardened or dies loudly.
Add test/relro: a --pie happy-path probe (a relocated constant pointer, run hardened, returns 42) and a fault probe that forces relro_reprotect down its congruence-invariant branch and asserts the panic message on stderr plus death by signal. verify.sh drives both, defaulting to native x86_64 and accepting a target + qemu runner for the cross arches. The fault probe calls relro_reprotect with forced incongruent args, so it exercises the fatal branch independently of the compiler's page alignment - CI covers it under the released mach seed (whose writer predates the max-page layout). Wired into the build job (native) and both cross-qemu jobs.
VerificationHappy + negative paths, all three arches, both mach seeds (
Negative-path stderr:
Why the fault probe works under the old seed (the lead's CI-seed concern): it forces Acceptance: no silent-unhardened path remains; negative path probe-verified (panic on stderr + death by signal, mirroring #343); happy path unchanged on x86_64 native + aarch64/riscv64 qemu. |
Closes #347. Completes the RELRO end-state trio (with mach#1844 + mach#1845, both merged).
What
_rt_relocate's PT_GNU_RELRO re-protection was best-effort — it skipped a region not congruent with the runtime page (the documented large-page degradation) and ignored themprotectresult. Now that the ELF writer aligns every segment to the target's max page (≥ any supported kernel page, mach#1845), the region is always a whole number of runtime pages, so neither escape is legitimate.Extracted the step into
relro_reprotect, which adopts the glibc stance — panic naming the violated invariant (matchingstd.system.os.page_size's AT_PAGESZ precedent, #336/#343) in each case:mprotectfailure (cannot restore read-only hardening).No silent-unhardened path remains: a
--piebinary either runs fully hardened or dies loudly naming the invariant.Acceptance status
mach build .+mach test .green (620/620) on the CI-seeded released machCI-seed note
mach-std CI seeds the released mach (v2.13.0), which predates mach#1844/#1845 — its
--piewriter emits a 4 KiB-aligned whole-.rodataRELRO, which stays congruent on the 4 KiB CI runners (happy path). The new fatal branches are exercised by a direct-call probe (forced incongruent args), which is compiler-independent, so CI covers them without the new writer. The full new-writer layout is exercised once mach-std's pin bumps to the next mach release.🤖 Generated with Claude Code