Skip to content

Harden build prefix relocation of poured bottles - #23670

Merged
MikeMcQuaid merged 1 commit into
mainfrom
relocate-build-prefix-hardening
Aug 27, 2026
Merged

Harden build prefix relocation of poured bottles#23670
MikeMcQuaid merged 1 commit into
mainfrom
relocate-build-prefix-hardening

Conversation

@MikeMcQuaid

Copy link
Copy Markdown
Member
  • 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 three 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.
  • 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 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.
  • 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


  • Have you followed our Contributing guidelines?
  • Have you checked for other open Pull Requests for the same change?
  • Have you explained what your changes do? Performance claims (e.g. "this is faster") must include brew benchmark results.
  • Have you explained why you'd like these changes included, not just what they do?
  • For bug fixes, have you given step-by-step brew commands to reproduce the bug?
  • Have you written new tests (excluding integration tests)? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) locally?

  • I did not use AI/LLM to create this PR, or I disclosed the tool/model below and reviewed its output; I did not attribute commits to AI and will answer maintainer questions and review comments myself without AI/LLM.

Claude with Fable at High effort, with local review and testing.


Copilot AI lite review requested due to automatic review settings August 27, 2026 08:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Library/Homebrew/keg_relocate.rb Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread Library/Homebrew/keg_relocate.rb Outdated
Comment thread Library/Homebrew/keg_relocate.rb Outdated
@MikeMcQuaid
MikeMcQuaid force-pushed the relocate-build-prefix-hardening branch from 895d928 to f72656f Compare August 27, 2026 10:31
@MikeMcQuaid
MikeMcQuaid added this pull request to the merge queue 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
MikeMcQuaid removed this pull request from the merge queue due to a manual request Aug 27, 2026
@MikeMcQuaid
MikeMcQuaid force-pushed the relocate-build-prefix-hardening branch from f72656f to cbbaa10 Compare August 27, 2026 17:53
@MikeMcQuaid
MikeMcQuaid added this pull request to the merge queue Aug 27, 2026
Merged via the queue into main with commit f38f1b0 Aug 27, 2026
47 checks passed
@MikeMcQuaid
MikeMcQuaid deleted the relocate-build-prefix-hardening branch August 27, 2026 18:51
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.

3 participants