Skip to content

fix(runtime/linux): re-protect the RELRO segment's mapped extent, not p_memsz (mach#1885)#360

Merged
octalide merged 1 commit into
devfrom
fix/1885
Jul 3, 2026
Merged

fix(runtime/linux): re-protect the RELRO segment's mapped extent, not p_memsz (mach#1885)#360
octalide merged 1 commit into
devfrom
fix/1885

Conversation

@octalide

@octalide octalide commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes the critical arm64 startup crash briar-systems/mach#1885. Std side of the fix; the mach writer side (honest PT_GNU_RELRO p_memsz) follows per the release/pin choreography.

Root cause (errno evidence, native arm64)

On a native 4 KiB-page aarch64 kernel, --pie binaries died at startup: mprotect of the PT_GNU_RELRO region failed, SIGTRAP (exit 133). The ELF writer rounds PT_GNU_RELRO p_memsz up to the IMAGE max-page (64 KiB on aarch64, mach#1845), but the kernel maps the tiny .data.rel.ro segment rounded to the RUNTIME page (4 KiB). The re-protect then mprotected 64 KiB starting at a segment only one 4 KiB page is mapped for → spanned the unmapped gap up to the next 64K-aligned segment → ENOMEM. mach-std#347 made that fatal.

Evidence (GitHub arm64 runner, 6.17.0-azure, PAGE_SIZE 4096): readelf shows .data.rel.ro LOAD MemSiz 0x10 but GNU_RELRO MemSiz 0x10000; strace shows mprotect(…, 65536, …) = -1 ENOMEM.

Fix

_rt_relocate now takes the re-protect extent from the RELRO segment's backing PT_LOAD (its actual mapped size), and relro_reprotect rounds that up to AT_PAGESZ — the pages the kernel actually mapped. The rounding lives against the runtime page, not the build-time max-page. PT_GNU_RELRO covers exactly the .data.rel.ro load segment in mach's layout, so that segment's p_memsz is the authoritative region size; the runtime does not rely on the writer's over-rounded p_memsz, so this repairs already-released v2.14.0 binaries on rebuild (no new mach release needed).

#347's fatal semantics stay: an absent AT_PAGESZ, a region with no backing segment, a misaligned start, or an mprotect failure each panic naming the invariant — no silent-unhardened path.

Verification

  • Native arm64 (scratch CI, released mach + this branch): probe now EXIT=42 (hardened), mprotect(…, 4096, …) = 0 — was 65536 = -1 ENOMEM / exit 133.
  • mach build . + mach test . green (620/620); test/relro/verify.sh green (happy exit 42; fault forces a misaligned start → panic + death by signal). Cross-arch qemu green.
  • Note: qemu-user does NOT reproduce the native bug; the definitive proof is mach's int linux-arm64 (native) once this is pinned.

🤖 Generated with Claude Code

… p_memsz

arm64 --pie binaries died at startup on native 4K/16K-page kernels:
`mprotect of the PT_GNU_RELRO region failed`, SIGTRAP (mach#1885). The ELF
writer rounds PT_GNU_RELRO p_memsz up to the IMAGE max-page (64 KiB on aarch64,
mach#1845), but the kernel maps the tiny .data.rel.ro segment rounded to the
RUNTIME page; on a runtime page smaller than the image max-page the re-protect
spanned the unmapped tail and faulted ENOMEM. mach-std#347 made that fatal.

Take the extent from the RELRO segment's backing PT_LOAD (its actual mapped
size) instead of the writer's rounded p_memsz, and round it up to AT_PAGESZ in
relro_reprotect - the pages the kernel actually mapped. The rounding now lives
against the runtime page, not the build-time max-page. #347's fatal semantics
stay: AT_PAGESZ absence, a region with no backing segment, a misaligned start,
or an mprotect failure each panic naming the invariant.

Update the negative-path probe to force a misaligned start (the congruence gate
is gone).
@octalide

octalide commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator Author

Verified on native arm64 (scratch CI, GitHub 4 KiB-page aarch64 runner)

Runtime fix alone repairs already-released binaries — probe built with the RELEASED mach (unchanged 64K writer) + this branch:

  • before: mprotect(…, 65536, …) = -1 ENOMEM, exit 133.
  • after: mprotect(…, 4096, PROT_READ) = 0, EXIT=42 (hardened). readelf still shows the old writer's GNU_RELRO MemSiz 0x10000, but the runtime uses the .data.rel.ro LOAD's actual 16-byte extent → rounds to the 4K runtime page → succeeds.

Full fix (this + the mach writer PR mach#1888) — probe built with both:

  • readelf GNU_RELRO MemSiz 0x10 (honest, == the .data.rel.ro LOAD), mprotect(…, 4096, …) = 0, EXIT=42.

CI green (build + cross-arm64 + cross-riscv64, incl. the relro probe). This PR is the crash fix and is sufficient on its own to make mach's int linux-arm64 (native) green once it reaches mach-std main (the int cases track branch/main); mach#1888 then makes the writer's declared extent match.

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.

1 participant