Skip to content

Broken path rewrite in binary relocation for custom prefixes #24074

Description

@coffeejunk

brew doctor output

Your system is ready to brew.

Verification

  • I ran brew update twice and am still able to reproduce my issue.
  • I have supplied my brew doctor output above and it seems unrelated to my issue.
  • This issue's title and/or description do not reference a single formula e.g. brew install wget. If they do, open an issue at https://github.com/Homebrew/homebrew-core/issues/new/choose instead.
  • I did not use AI/LLM to create this issue, or I disclosed the tool and model used; I will answer maintainer questions myself without AI/LLM.

brew config output

HOMEBREW_VERSION: 7.0.6
ORIGIN: https://github.com/Homebrew/brew
HEAD: 570982948a8a194f0f42f43f4a5bce2d1c9f64cb
Last commit: 26 hours ago
Branch: stable
Core tap: N/A
Core cask tap: N/A
HOMEBREW_PREFIX: /home/u/.b
HOMEBREW_REPOSITORY: /home/u/.b/Homebrew
HOMEBREW_CELLAR: /home/u/.b/Cellar
HOMEBREW_NO_ANALYTICS: set
HOMEBREW_NO_AUTO_UPDATE: set
HOMEBREW_NO_ENV_HINTS: set
Homebrew Ruby: 4.0.7 => /home/u/.b/Homebrew/Library/Homebrew/vendor/portable-ruby/4.0.7/bin/ruby
CPU: 16-core 64-bit zen4
Clang: N/A
Git: 2.55.0 => /bin/git
Curl: 8.15.0 => /bin/curl
Kernel: Linux 7.2.5-100.fc43.x86_64 x86_64 GNU/Linux
Landlock ABI: 10
OS: Fedora Linux 43 (Container Image)
Host glibc: 2.42
Host libstdc++: 6.0.34
/usr/bin/gcc: 15.3.1
/usr/bin/ruby: N/A
glibc: N/A
gcc@13: N/A
gcc: N/A
xorg: N/A

What were you trying to do (and why)?

What: Use Homebrew on Linux at a per-user prefix (/home/u/.b) instead of /home/linuxbrew/.linuxbrew.

Why: We are building a Linux-based immutable OS and rely on brew for the users' package management. To better support multi-user situations, we install brew per user in ~/.b.

What happened (include all command output)?

I ran into this issue when installing fonts and fontconfig:

$ ~/.b/bin/fc-match sans-serif
Fontconfig error: Cannot load default config file: File not found
DejaVuSans.ttf: "DejaVu Sans" "Book"

$ strace -e trace=access -P /home/linuxbrew/.linuxbrew/etc/fonts/fonts.conf ~/.b/bin/fc-match sans-serif
access("/home/linuxbrew/.linuxbrew/etc/fonts/fonts.conf", R_OK) = -1 ENOENT (No such file or directory)

When Homebrew is installed at a prefix other than /home/linuxbrew/.linuxbrew, it rewrites the build prefix in
the installed files. The rewrite currently only finds the prefix where it is stored as one string. Compiled code sometimes
holds a path in 8-byte pieces spread over several instructions instead, which the rewrite doesn't touch:

$ ruby split-prefix.rb ~/.b/Cellar
avahi/0.9-rc5/sbin/avahi-daemon: /home/li|nuxbrew/|.linuxbr|ew/var/r|un/avahi|-daemon/|socket
avahi/0.9-rc5/sbin/avahi-dnsconfd: /home/li|nuxbrew/|.linuxbr|ew/var/r|un/avahi|-daemon/|socket
fontconfig/2.18.3/lib/libfontconfig.so.1.17.0: /home/li|nuxbrew/|.linuxbr|ew/etc/f|c/fonts
glib/2.90.0/lib/libgio-2.0.so.0.9000.0: /home/li|nuxbrew/|.linuxbr|ew/lib/g|io/modul
glib/2.90.0/lib/libgirepository-2.0.so.0.9000.0: /home/li|nuxbrew/|.linuxbr|ew/lib/g|ireposit|ory-1.0
glib/2.90.0/lib/libglib-2.0.so.0.9000.0: /home/li|nuxbrew/|.linuxbr|ew/Cella|r/glib/2|.90.0/li|charset.|t.alias
gtk+3/3.24.52/lib/libgtk-3.so.0.2420.32: /home/li|nuxbrew/|.linuxbr|ew/Cella|r/gtk+3/|3.24.52/|2/share
gtk+3/3.24.52/lib/libgtk-3.so.0.2420.32: /home/li|nuxbrew/|.linuxbr|ew/Cella|r/libx11|/1.8.13/|share/X1|1/locale
util-linux/2.42.3/lib/libuuid.so.1.3.0: /home/li|nuxbrew/|.linuxbr|ew/Cella|r/util-l|inux/2.4|2.3/var/|run/uuid|d/reques
Script listing paths stored in 8-byte pieces inside machine code `split-prefix.rb`
#!/usr/bin/env ruby
# List ELF files under a directory in which the Homebrew build prefix is
# written 8 bytes at a time by x86-64 `movabs` instructions instead of being
# stored as one string. Prints the file and the pieces the immediates hold.
require "find"

PREFIX = "/home/linuxbrew/.linuxbrew".b
MOVABS = /\x48[\xb8-\xbf](.{8})/mn
CHAIN_START = /\x48[\xb8-\xbf]#{Regexp.escape(PREFIX[0, 8])}/mn

root = ARGV.fetch(0)
Find.find(root) do |path|
  next if File.symlink?(path) || !File.file?(path)

  data = File.binread(path)
  next unless data.start_with?("\x7fELF".b)

  data.to_enum(:scan, CHAIN_START).map { Regexp.last_match.begin(0) }.each do |start|
    pieces = []
    pos = start
    while (m = MOVABS.match(data, pos)) && m.begin(0) < pos + 40
      pieces << m[1]
      pos = m.end(0)
    end
    next unless pieces.join.start_with?(PREFIX)

    puts "#{path.delete_prefix("#{root}/")}: #{pieces.join("|")}"
  end
end

What did you expect to happen?

fc-match reads /home/u/.b/etc/fonts/fonts.conf and prints no error, as it does with FONTCONFIG_PATH=~/.b/etc/fonts set:

$ FONTCONFIG_PATH=~/.b/etc/fonts ~/.b/bin/fc-match sans-serif
DejaVuSans.ttf: "DejaVu Sans" "Book"

Step-by-step reproduction instructions (running brew commands)

NONINTERACTIVE=1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -- --path="$HOME/.b"
export PATH="$HOME/.b/bin:$PATH"
brew install fontconfig
fc-match sans-serif


Full repro in containers:


*Ubuntu 24.04*


podman run --rm ubuntu:24.04 bash -c "$(cat <<'EOF'
set -e
apt-get update -qq >/dev/null
apt-get install -y -qq build-essential procps curl file git strace >/dev/null 2>&1
useradd -m u
su - u -s /bin/bash <<'USER'
export PATH=$HOME/.b/bin:$PATH HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_ANALYTICS=1 HOMEBREW_NO_ENV_HINTS=1
NONINTERACTIVE=1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -- --path="$HOME/.b" >/dev/null 2>&1
set -x
brew update </dev/null
brew update </dev/null
brew install fontconfig </dev/null 2>&1 | grep Pouring.fontconfig
brew doctor
ls -l ~/.b/etc/fonts/fonts.conf
fc-match sans-serif
strace -e trace=access -P /home/linuxbrew/.linuxbrew/etc/fonts/fonts.conf fc-match sans-serif
FONTCONFIG_PATH=~/.b/etc/fonts fc-match sans-serif
USER
EOF
)"


Output:


+ brew update
==> Updating Homebrew...
Already up-to-date.
+ brew update
==> Updating Homebrew...
Already up-to-date.
+ brew install fontconfig
+ grep Pouring.fontconfig
==> Pouring fontconfig--2.18.3.x86_64_linux.bottle.tar.gz
+ brew doctor
Your system is ready to brew.
+ ls -l /home/u/.b/etc/fonts/fonts.conf
-rw-r--r--. 1 u u 2710 Sep 22 18:08 /home/u/.b/etc/fonts/fonts.conf
+ fc-match sans-serif
Fontconfig error: Cannot load default config file: File not found
DejaVuSans.ttf: "DejaVu Sans" "Book"
+ strace -e trace=access -P /home/linuxbrew/.linuxbrew/etc/fonts/fonts.conf fc-match sans-serif
access("/home/linuxbrew/.linuxbrew/etc/fonts/fonts.conf", R_OK) = -1 ENOENT (No such file or directory)
Fontconfig error: Cannot load default config file: File not found
DejaVuSans.ttf: "DejaVu Sans" "Book"
+++ exited with 0 +++
+ FONTCONFIG_PATH=/home/u/.b/etc/fonts
+ fc-match sans-serif
DejaVuSans.ttf: "DejaVu Sans" "Book"


*Fedora 43*


podman run --rm fedora:43 bash -c "$(cat <<'EOF'
set -e
dnf install -y -q gcc gcc-c++ make procps-ng curl file git strace dejavu-sans-fonts >/dev/null 2>&1
useradd -m u
su - u -s /bin/bash <<'USER'
export PATH=$HOME/.b/bin:$PATH HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_ANALYTICS=1 HOMEBREW_NO_ENV_HINTS=1
NONINTERACTIVE=1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -- --path="$HOME/.b" >/dev/null 2>&1
set -x
brew update </dev/null
brew update </dev/null
brew install fontconfig </dev/null 2>&1 | grep Pouring.fontconfig
brew doctor
ls -l ~/.b/etc/fonts/fonts.conf
fc-match sans-serif
strace -e trace=access -P /home/linuxbrew/.linuxbrew/etc/fonts/fonts.conf fc-match sans-serif
FONTCONFIG_PATH=~/.b/etc/fonts fc-match sans-serif
USER
EOF
)"


Output:


+ brew update
==> Updating Homebrew...
Already up-to-date.
+ brew update
==> Updating Homebrew...
Already up-to-date.
+ brew install fontconfig
+ grep Pouring.fontconfig
==> Pouring fontconfig--2.18.3.x86_64_linux.bottle.tar.gz
+ brew doctor
Your system is ready to brew.
+ ls -l /home/u/.b/etc/fonts/fonts.conf
-rw-r--r--. 1 u u 2710 Sep 22 18:09 /home/u/.b/etc/fonts/fonts.conf
+ fc-match sans-serif
Fontconfig error: Cannot load default config file: File not found
DejaVuSans.ttf: "DejaVu Sans" "Book"
+ strace -e trace=access -P /home/linuxbrew/.linuxbrew/etc/fonts/fonts.conf fc-match sans-serif
access("/home/linuxbrew/.linuxbrew/etc/fonts/fonts.conf", R_OK) = -1 ENOENT (No such file or directory)
Fontconfig error: Cannot load default config file: File not found
DejaVuSans.ttf: "DejaVu Sans" "Book"
+++ exited with 0 +++
+ FONTCONFIG_PATH=/home/u/.b/etc/fonts
+ fc-match sans-serif
DejaVuSans.ttf: "DejaVu Sans" "Book"


Debugged with the help of Claude Code (Fable 5.1)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions