Merge pull request #20408 from Homebrew/fd-leak-fix

os/linux/elf: fix file descriptor leak
This commit is contained in:
Mike McQuaid 2025-08-11 12:10:16 +00:00 committed by GitHub
commit 97c0bc7d0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -52,7 +52,6 @@ module ELFShim
@interpreter = T.let(nil, T.nilable(String)) @interpreter = T.let(nil, T.nilable(String))
@dynamic_elf = T.let(nil, T.nilable(T::Boolean)) @dynamic_elf = T.let(nil, T.nilable(T::Boolean))
@metadata = T.let(nil, T.nilable(Metadata)) @metadata = T.let(nil, T.nilable(Metadata))
@patchelf_patcher = nil
super super
end end
@ -123,7 +122,7 @@ module ELFShim
# "/lib:/usr/lib:/usr/local/lib" # "/lib:/usr/lib:/usr/local/lib"
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def rpath def rpath
@rpath ||= rpath_using_patchelf_rb metadata.rpath
end end
# An array of runtime search path entries, such as: # An array of runtime search path entries, such as:
@ -134,7 +133,7 @@ module ELFShim
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def interpreter def interpreter
@interpreter ||= patchelf_patcher.interpreter metadata.interpreter
end end
def patch!(interpreter: nil, rpath: nil) def patch!(interpreter: nil, rpath: nil)
@ -149,11 +148,12 @@ module ELFShim
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def dynamic_elf? def dynamic_elf?
@dynamic_elf ||= elf_parser.segment_by_type(:DYNAMIC).present? metadata.dynamic_elf?
end end
sig { returns(T::Array[String]) }
def section_names def section_names
@section_names ||= elf_parser.sections.map(&:name).compact_blank metadata.section_names
end end
# Helper class for reading metadata from an ELF file. # Helper class for reading metadata from an ELF file.
@ -164,35 +164,53 @@ module ELFShim
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
attr_reader :dylib_id attr_reader :dylib_id
sig { returns(T::Boolean) }
def dynamic_elf?
@dynamic_elf
end
sig { returns(T.nilable(String)) }
attr_reader :interpreter
sig { returns(T.nilable(String)) }
attr_reader :rpath
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
attr_reader :dylibs attr_reader :section_names
sig { params(path: ELFShim).void } sig { params(path: ELFShim).void }
def initialize(path) def initialize(path)
@path = T.let(path, ELFShim) require "patchelf"
@dylibs = T.let([], T::Array[String]) patcher = path.patchelf_patcher
@dylib_id = T.let(nil, T.nilable(String))
@dylib_id, needed = needed_libraries path
@dylibs = needed.map { |lib| find_full_lib_path(lib).to_s } if needed.present?
@metadata = T.let(nil, T.nilable(T::Hash[String, T.untyped])) @path = T.let(path, ELFShim)
@dylibs = T.let(nil, T.nilable(T::Array[String]))
@dylib_id = T.let(nil, T.nilable(String))
dynamic_segment = patcher.elf.segment_by_type(:dynamic)
@dynamic_elf = dynamic_segment.present?
@dylib_id, @needed = if @dynamic_elf
[patcher.soname, patcher.needed]
else
[nil, []]
end
@interpreter = patcher.interpreter
@rpath = patcher.runpath || patcher.rpath
@section_names = patcher.elf.sections.map(&:name).compact_blank
@dt_flags_1 = dynamic_segment&.tag_by_type(:flags_1)&.value
end
sig { returns(T::Array[String]) }
def dylibs
@dylibs ||= @needed.map { |lib| find_full_lib_path(lib).to_s }
end end
private private
def needed_libraries(path)
return [nil, []] unless path.dynamic_elf?
needed_libraries_using_patchelf_rb path
end
def needed_libraries_using_patchelf_rb(path)
patcher = path.patchelf_patcher
[patcher.soname, patcher.needed]
end
def find_full_lib_path(basename) def find_full_lib_path(basename)
local_paths = (path.patchelf_patcher.runpath || path.patchelf_patcher.rpath)&.split(":") local_paths = rpath&.split(":")
# Search for dependencies in the runpath/rpath first # Search for dependencies in the runpath/rpath first
local_paths&.each do |local_path| local_paths&.each do |local_path|
@ -202,11 +220,10 @@ module ELFShim
end end
# Check if DF_1_NODEFLIB is set # Check if DF_1_NODEFLIB is set
dt_flags_1 = path.elf_parser.segment_by_type(:dynamic)&.tag_by_type(:flags_1) nodeflib_flag = if @dt_flags_1.nil?
nodeflib_flag = if dt_flags_1.nil?
false false
else else
dt_flags_1.value & ELFTools::Constants::DF::DF_1_NODEFLIB != 0 @dt_flags_1 & ELFTools::Constants::DF::DF_1_NODEFLIB != 0
end end
linker_library_paths = OS::Linux::Ld.library_paths linker_library_paths = OS::Linux::Ld.library_paths
@ -247,13 +264,12 @@ module ELFShim
patcher.save(patchelf_compatible: true) patcher.save(patchelf_compatible: true)
end end
def rpath_using_patchelf_rb # Don't cache the patcher; it keeps the ELF file open so long as it is alive.
patchelf_patcher.runpath || patchelf_patcher.rpath # Instead, for read-only access to the ELF file's metadata, fetch it and cache
end # it with {Metadata}.
def patchelf_patcher def patchelf_patcher
require "patchelf" require "patchelf"
@patchelf_patcher ||= ::PatchELF::Patcher.new to_s, on_error: :silent ::PatchELF::Patcher.new to_s, on_error: :silent
end end
sig { returns(Metadata) } sig { returns(Metadata) }