fix(build): put the whole GOT inside the RELRO window on ELF targets - #1914
Open
DeusData wants to merge 1 commit into
Open
fix(build): put the whole GOT inside the RELRO window on ELF targets#1914DeusData wants to merge 1 commit into
DeusData 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.