Harden build prefix relocation of poured bottles - #23670
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens Keg#relocate_build_prefix (used when pouring pinned bottles) by fixing correctness issues in binary prefix patching and adding targeted regression tests, including Mach-O load-command patching and ELF dynamic string-table edge cases.
Changes:
- Refuse longer replacement prefixes up front to avoid partial rewrites and mid-stream size mismatch failures.
- Patch hardlinked files by inode group (patch once, then re-link) and re-sign only the patched inode representatives.
- Preserve suffix-merged ELF string-table interior references by padding with extra path separators instead of trailing NULs when needed, and add specs covering these cases.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| Library/Homebrew/test/os/mac/keg_spec.rb | Adds a macOS spec ensuring Mach-O load-command strings are correctly patched by relocate_build_prefix. |
| Library/Homebrew/test/keg_relocate/binary_relocation_spec.rb | Adds regression tests for multi-occurrence replacement, hardlinks, sharball skipping, longer-prefix refusal, re-signing, and ELF suffix-merge handling. |
| Library/Homebrew/plans/relocatable-bottles.md | Documents the separator-padding behavior for suffix-merged ELF dynamic string-table references and removes the now-implemented hardening plan item. |
| Library/Homebrew/keg_relocate.rb | Implements the relocation hardening: up-front longer-prefix refusal, inode-group patching + re-linking, ELF interior-reference detection, and length-preserving replacement helper. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
MikeMcQuaid
force-pushed
the
relocate-build-prefix-hardening
branch
from
August 27, 2026 10:31
895d928 to
f72656f
Compare
p-linnane
approved these changes
Aug 27, 2026
- `Keg#relocate_build_prefix` NUL-pads raw prefix strings in pinned bottles at pour time but had no tests beyond one happy path; the hardening cases in the plan each get a spec, and several found bugs. - Patching wrote the file atomically, giving it a new inode, so any hardlinked names kept the unpatched content. Files are now grouped by inode, patched once and re-linked afterwards, as text relocation already does. Bottle metadata records only one name per inode, so when a recorded file has other links they are found with a walk, which only happens in that case. - Paths recorded in bottle metadata are resolved inside the keg only; absolute paths or `..` segments from a mirror cannot make relocation touch files outside it. - A prefix longer than the bottled one failed midway with a size mismatch after earlier files had already been rewritten; it is now refused up front. - Linkers suffix-merge strings, so a dynamic string table entry can be referenced from its interior (`libfoo.so` inside `/old/prefix/libfoo.so`); trailing NUL padding shifted such references onto garbage. Strings with interior loader references now have every occurrence padded with extra path separators, which path resolution ignores, keeping each reference's offset valid; an occurrence that is not a path under the prefix falls back to NUL padding for the whole string so nothing is left partly relocated. Other strings keep NUL padding. - Also covered: several occurrences per string and several strings per file, the sharball skip, re-signing of patched files and prefix strings inside Mach-O load commands. This change is part of [`plans/relocatable-bottles.md`](https://github.com/Homebrew/brew/blob/HEAD/Library/Homebrew/plans/relocatable-bottles.md)
MikeMcQuaid
force-pushed
the
relocate-build-prefix-hardening
branch
from
August 27, 2026 17:53
f72656f to
cbbaa10
Compare
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.
Keg#relocate_build_prefixNUL-pads raw prefix strings in pinned bottles at pour time but had no tests beyond one happy path; the hardening cases in the plan each get a spec, and three found bugs.libfoo.soinside/old/prefix/libfoo.so); trailing NUL padding shifted such references onto garbage. Strings with interior loader references are now padded with extra path separators after the prefix, which path resolution ignores, keeping every reference's offset valid. Other strings keep NUL padding.This change is part of
plans/relocatable-bottles.mdbrew benchmarkresults.brewcommands to reproduce the bug?brew lgtm(style, typechecking and tests) locally?Claude with Fable at High effort, with local review and testing.