From f975977b2251a3e5d61ef30949d665b703a548ab Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Sat, 24 Oct 2026 16:46:52 +0100 Subject: [PATCH] Preserve UTF-8 paths in ELF loader strings - Validate ELF loader strings as UTF-8 before checking paths, so valid non-ASCII references reach bottle relocation detection. - Keep rejecting invalid byte sequences and cover loader paths and the binary relocation metadata produced by `brew bottle`. --- Library/Homebrew/keg_relocate.rb | 5 ++-- Library/Homebrew/test/dev-cmd/bottle_spec.rb | 10 ++++++- .../test/keg_relocate/elf_checker_spec.rb | 27 +++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Library/Homebrew/keg_relocate.rb b/Library/Homebrew/keg_relocate.rb index 40c672176d..1f0cb6a045 100644 --- a/Library/Homebrew/keg_relocate.rb +++ b/Library/Homebrew/keg_relocate.rb @@ -742,9 +742,10 @@ class Keg end strings.filter_map do |offset, match| - next unless match.ascii_only? + match.force_encoding(Encoding::UTF_8) + next unless match.valid_encoding? - [offset.to_s(16), match.force_encoding(Encoding::UTF_8)] + [offset.to_s(16), match] end ensure stream.close diff --git a/Library/Homebrew/test/dev-cmd/bottle_spec.rb b/Library/Homebrew/test/dev-cmd/bottle_spec.rb index 52ba439697..0e438bbf67 100644 --- a/Library/Homebrew/test/dev-cmd/bottle_spec.rb +++ b/Library/Homebrew/test/dev-cmd/bottle_spec.rb @@ -111,6 +111,14 @@ RSpec.describe Homebrew::DevCmd::Bottle do (formula.libexec/"raw-prefix").binwrite( "\0#{Array.new(Homebrew::DevCmd::Bottle::MAXIMUM_STRING_MATCHES + 1, formula.libexec.to_s).join("\0")}\0", ) + require "patchelf" + library = formula.libexec/"José/libhello.so.0" + library.dirname.mkpath + FileUtils.cp TEST_FIXTURE_DIR/"elf/libhello.so.0", library + FileUtils.cp TEST_FIXTURE_DIR/"elf/hello", formula.libexec/"elf-prefix" + patcher = PatchELF::Patcher.new((formula.libexec/"elf-prefix").to_s, on_error: :silent) + patcher.add_needed(library.to_s.b) + patcher.save stderr = if !Sandbox.available? "Warning: Sandbox unavailable: processing downloaded files without sandboxing!\n" elsif Sandbox.nested_sandbox? @@ -140,7 +148,7 @@ RSpec.describe Homebrew::DevCmd::Bottle do expect(tag.fetch("tab")).to include( "changed_files" => be_an(Array), "linkage_files" => be_an(Array), - "binary_relocation_files" => include("libexec/raw-prefix"), + "binary_relocation_files" => include("libexec/raw-prefix", "libexec/elf-prefix"), "built_prefix" => HOMEBREW_PREFIX.to_s, ) binary_relocation_diagnostics = tag.fetch("binary_relocation_diagnostics") diff --git a/Library/Homebrew/test/keg_relocate/elf_checker_spec.rb b/Library/Homebrew/test/keg_relocate/elf_checker_spec.rb index 5dd138617d..9480cbbbb8 100644 --- a/Library/Homebrew/test/keg_relocate/elf_checker_spec.rb +++ b/Library/Homebrew/test/keg_relocate/elf_checker_spec.rb @@ -34,6 +34,24 @@ RSpec.describe Keg do expect(described_class.text_matches_in_file(file, dir.to_s, [], [], nil).size).to eq 1 end + it "keeps referenced paths containing non-ASCII characters" do + rpath = dir/"José" + rpath.mkpath + patch_rpath rpath.to_s + + expect(described_class.text_matches_in_file(file, dir.to_s, [], [], nil).map(&:first)).to eq [rpath.to_s] + end + + it "drops referenced paths containing invalid UTF-8" do + rpath = "#{dir}/invalid-\xFF".b + patch_rpath rpath + + candidates = [] + described_class.each_candidate_string(file, dir.to_s) { |candidate| candidates << candidate.last.b } + + expect(candidates).not_to include(rpath) + end + it "drops dead loader strings nothing references any more" do # Shrinking the RPATH leaves an in-place `X` run inside `.dynstr`; # writing a prefix string over it recreates the dead bytes a moved or @@ -96,6 +114,15 @@ RSpec.describe Keg do expect(described_class.text_matches_in_file(file, dir.to_s, [], [], nil).size).to eq 1 end + it "keeps an interpreter containing non-ASCII characters" do + interpreter = dir/"ld-José.so" + FileUtils.touch interpreter + patch_rpath "#{Keg::PREFIX_PLACEHOLDER}/lib" + patch_interpreter interpreter.to_s.b + + expect(described_class.text_matches_in_file(file, dir.to_s, [], [], nil).map(&:first)).to eq [interpreter.to_s] + end + it "drops old interpreter bytes the loader no longer uses" do patch_rpath "#{Keg::PREFIX_PLACEHOLDER}/lib" # Growing the interpreter moves it; shrinking it back leaves an