Skip to content

runtime/linux: make RELRO re-protection failure fatal (part C)#356

Merged
octalide merged 3 commits into
devfrom
feat/347
Jul 3, 2026
Merged

runtime/linux: make RELRO re-protection failure fatal (part C)#356
octalide merged 3 commits into
devfrom
feat/347

Conversation

@octalide

@octalide octalide commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

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 the mprotect result. 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 (matching std.system.os.page_size's AT_PAGESZ precedent, #336/#343) in each case:

  • AT_PAGESZ absent under a live RELRO region (mandatory linux auxv entry missing),
  • RELRO region not congruent with the runtime page (violated max-page layout contract),
  • mprotect failure (cannot restore read-only hardening).

No silent-unhardened path remains: a --pie binary either runs fully hardened or dies loudly naming the invariant.

Acceptance status

  • happy path unchanged — mach build . + mach test . green (620/620) on the CI-seeded released mach
  • negative path probe-verified (forced misalignment → panic on stderr + death by signal) — probe + CI job to follow in this PR
  • happy path on aarch64/riscv64 qemu

CI-seed note

mach-std CI seeds the released mach (v2.13.0), which predates mach#1844/#1845 — its --pie writer emits a 4 KiB-aligned whole-.rodata RELRO, 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

octalide added 3 commits July 3, 2026 03:18
_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.
@octalide

octalide commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator Author

Verification

Happy + negative paths, all three arches, both mach seeds (test/relro/verify.sh):

arch seed happy negative (forced congruence mismatch)
x86_64 native released v2.13.0 exit 42 (hardened) SIGSEGV (exit 139) + panic on stderr
x86_64 native from-source (mach#1845) exit 42 SIGSEGV (exit 139) + message
aarch64 qemu released exit 42 SIGTRAP (exit 133) + message
riscv64 qemu released exit 42 SIGTRAP (exit 133) + message

Negative-path stderr: std.runtime.linux.reloc: PT_GNU_RELRO region not congruent with the runtime page (violated max-page layout contract; see mach#1845).

  • Happy path: a --pie binary with a relocated constant pointer runs fully hardened and returns 42 — _rt_relocate slid the pointer and relro_reprotect restored the region read-only without panicking.
  • Negative path: relro_reprotect called with a region size not a whole number of the runtime page (0x1000 under 0x4000) panics naming the invariant and dies by signal — the exact large-page mismatch that was silently skipped before this change. The forced inputs never reach the mprotect, so no live mapping is touched.
  • mach build . + mach test . green (620/620) on the CI-seeded released mach.

Why the fault probe works under the old seed (the lead's CI-seed concern): it forces relro_reprotect down its congruence branch with direct incongruent args, so it exercises the fatal path independently of the compiler's page alignment. That path is now wired into all three CI jobs (build native + both cross-qemu). The full new-writer layout (64K aarch64 RELRO) is exercised once mach-std's mach pin bumps to the next release.

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.

@octalide octalide merged commit 54eb226 into dev Jul 3, 2026
3 checks passed
@octalide octalide deleted the feat/347 branch July 3, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

runtime/linux: make RELRO re-protection failure fatal once the writer guarantees max-page alignment

1 participant