fix(build): put the whole GOT inside the RELRO window on ELF targets - #1914
fix(build): put the whole GOT inside the RELRO window on ELF targets#1914DeusData wants to merge 1 commit into
Conversation
Adds -Wl,-z,relro and -Wl,-z,now to ELF_HARDENING_FLAGS, and asserts the
resulting property on the produced artifact rather than on the compiler's
willingness to accept a flag.
The shipped Linux binaries measurably lacked the property. Built the way
.github/workflows/_build.yml builds them (STATIC=1, gcc 13.3 / GNU ld 2.42,
Ubuntu 24.04 aarch64), PT_GNU_RELRO ends at 0x11DA0000 in both cases:
without -z now: .got 0x11d9ec20+0x13c8, .got.plt 0x11d9ffe8+0x40
-> .got.plt ends at 0x11DA0028, 40 bytes PAST the window.
Those GOT slots stayed writable for the whole process
lifetime, in every Linux binary shipped to date.
with -z now: .got.plt folds into .got, the whole GOT sits inside the
window and is re-mapped read-only after startup.
Both flags are free at runtime for a static binary -- everything resolves at
link time, so there is no lazy binding left to pay for. -z relro is already
this toolchain's default and changes nothing here; it is named anyway so the
property stops depending on one distro's spec file, since the musl/portable
and glibc-floor images are different toolchains and a default is not a
guarantee.
A1c/A1d in scripts/ci/check-binary-composition.sh assert the OUTCOME on the
binary via readelf, for the same reason A1 exists beside the .note.GNU-stack
annotation: a flag the linker accepts is not evidence the artifact gained
anything. PR #1138 demonstrated that directly -- its -pie was silently
discarded under -static, and its -D_FORTIFY_SOURCE=2 silently DOWNGRADED
Ubuntu's default of 3, both while every compile probe passed.
The readers use awk rather than a short-circuiting reader on purpose: a
reader that exits at the first match leaves the upstream taking EPIPE, and
under `set -o pipefail` the satisfied case is reported as the failing one.
Deliberately NOT taken from #1138: -D_FORTIFY_SOURCE=2 (a downgrade on the
release platform), -fPIE/-pie (inert under STATIC=1), -fstack-protector-strong
(real but unmeasured cost, and #1138 injected it into SQLite, tree-sitter and
mimalloc -- the indexing hot loop), and the $(shell) probe machinery (~10
compiler spawns per Makefile parse, making the binary a function of the
builder's toolchain).
Co-authored-by: PR9000 <119280965+PR9000@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
The log opens with
So the gate as written applies a release-binary property to a packaging fixture. Two ways out:
(1) is the smaller change; (2) is the one that will not quietly rot. Worth deciding deliberately rather than reaching for whichever makes the leg green. Reproduced on the second run, so this is deterministic, not flake: run |
Distilled from #1138 with
Co-authored-by:credit to @PR9000. Takes the two flags worth taking, leaves the rest, and asserts the result on the artifact rather than on the compiler accepting a flag.Every Linux binary we have shipped had part of the GOT left writable
Measured on a binary built the way
.github/workflows/_build.ymlbuilds the Linux release (STATIC=1, gcc 13.3 / GNU ld 2.42, Ubuntu 24.04 aarch64).PT_GNU_RELROends at0x11DA0000in both cases:Both flags are free at runtime here — a static binary resolves everything at link time, so there is no lazy binding left to pay for.
-z relrois already this toolchain's default and changes nothing on it; it is named anyway so the property stops depending on one distro's spec file, since the musl/portable and glibc-floor images are different toolchains and a default is not a guarantee.The assertions are the point
A1c-relroandA1d-bind-nowincheck-binary-composition.shread the produced binary withreadelfand assert the outcome — the same reasonA1exists beside the.note.GNU-stackannotation.#1138 is the direct evidence for why that matters. Two of its flags passed every compile probe and did nothing, or worse:
-piewas silently discarded under-static. Probe passed, flag added, artifact unchanged (Type: EXEC, no PIE).-D_FORTIFY_SOURCE=2silently DOWNGRADED Ubuntu 24.04's default of3, with no diagnostic, on the exact platform that builds the shipped Linux binaries. A hardening flag that weakened hardening.A probe answers "does this flag compile", never "did the binary gain anything".
The readers use
awkrather than a short-circuiting reader deliberately: a reader that exits at the first match leaves the upstream takingEPIPE, and underset -o pipefailthe satisfied case gets reported as the failing one. That is the same defect fixed in #1879 earlier today, avoided here by construction.Deliberately not taken from #1138
-D_FORTIFY_SOURCE=2-fPIE/-pieSTATIC=1, which is how Linux release binaries are built. Adding it would be false assurance-fstack-protector-strong$(shell)probe machinerymakeincludingcleanand-n), and it makes the produced binary a function of whichever toolchain the builder happens to have/homealias are both better than the versions in that PRNo new opt-in knob: main already applies
ELF_HARDENING_FLAGSunconditionally on ELF and ships that way, so two more free linker flags join the existing set rather than growing aPRODUCTION_HARDENING-style variable.Honest limit: macOS cannot show ELF program headers, so the RELRO/BIND_NOW outcome is verified by the measurement above and by the new assertions running in CI's Linux legs — not on this machine.