diff --git a/Library/Homebrew/os/linux/elf.rb b/Library/Homebrew/os/linux/elf.rb index 8eeae1fc5e..9376bdf047 100644 --- a/Library/Homebrew/os/linux/elf.rb +++ b/Library/Homebrew/os/linux/elf.rb @@ -149,6 +149,7 @@ module ELFShim # Search for dependencies in the runpath/rpath first local_paths&.each do |local_path| + local_path = OS::Linux::Elf.expand_elf_dst(local_path, "ORIGIN", path.parent) candidate = Pathname(local_path)/basename return candidate if candidate.exist? && candidate.elf? end @@ -221,3 +222,37 @@ module ELFShim metadata.dylibs end end + +module OS + module Linux + # Helper functions for working with ELF objects. + # + # @api private + module Elf + sig { params(str: String, ref: String, repl: T.any(String, Pathname)).returns(String) } + def self.expand_elf_dst(str, ref, repl) + # ELF gABI rules for DSTs: + # - Longest possible sequence using the rules (greedy). + # - Must start with a $ (enforced by caller). + # - Must follow $ with one underscore or ASCII [A-Za-z] (caller + # follows these rules for REF) or '{' (start curly quoted name). + # - Must follow first two characters with zero or more [A-Za-z0-9_] + # (enforced by caller) or '}' (end curly quoted name). + # (from https://github.com/bminor/glibc/blob/41903cb6f460d62ba6dd2f4883116e2a624ee6f8/elf/dl-load.c#L182-L228) + + # In addition to capturing a token, also attempt to capture opening/closing braces and check that they are not + # mismatched before expanding. + str.gsub(/\$({?)([a-zA-Z_][a-zA-Z0-9_]*)(}?)/) do |orig_str| + has_opening_brace = ::Regexp.last_match(1).present? + matched_text = ::Regexp.last_match(2) + has_closing_brace = ::Regexp.last_match(3).present? + if (matched_text == ref) && (has_opening_brace == has_closing_brace) + repl + else + orig_str + end + end + end + end + end +end diff --git a/Library/Homebrew/test/os/linux/elf_spec.rb b/Library/Homebrew/test/os/linux/elf_spec.rb new file mode 100644 index 0000000000..6bcf15f0a9 --- /dev/null +++ b/Library/Homebrew/test/os/linux/elf_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +RSpec.describe OS::Linux::Elf do + describe "::expand_elf_dst" do + it "expands tokens that are not wrapped in curly braces" do + str = "$ORIGIN/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "/opt/homebrew/bin/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + end + + it "expands tokens that are wrapped in curly braces" do + str = "${ORIGIN}/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "/opt/homebrew/bin/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + + str = "${ORIGIN}new/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "/opt/homebrew/binnew/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + end + + it "expands multiple occurrences of token" do + str = "${ORIGIN}/../..$ORIGIN/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "/opt/homebrew/bin/../../opt/homebrew/bin/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + end + + it "rejects and passes through tokens containing additional characters" do + str = "$ORIGINAL/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "$ORIGINAL/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + + str = "$ORIGIN_/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "$ORIGIN_/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + + str = "$ORIGIN_STORY/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "$ORIGIN_STORY/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + + str = "${ORIGINAL}/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "${ORIGINAL}/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + + str = "${ORIGIN_}/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "${ORIGIN_}/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + + str = "${ORIGIN_STORY}/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "${ORIGIN_STORY}/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + end + + it "rejects and passes through tokens with mismatched curly braces" do + str = "${ORIGIN/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "${ORIGIN/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + + str = "$ORIGIN}/../lib" + ref = "ORIGIN" + repl = "/opt/homebrew/bin" + expected = "$ORIGIN}/../lib" + expect(described_class.expand_elf_dst(str, ref, repl)).to eq(expected) + end + end +end