| 
									
										
										
										
											2023-03-21 21:42:51 -07:00
										 |  |  | # typed: true | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-24 02:23:13 +08:00
										 |  |  | require "os/linux/ld" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  | # {Pathname} extension for dealing with ELF files. | 
					
						
							| 
									
										
										
										
											2018-10-18 21:42:43 -04:00
										 |  |  | # @see https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  | module ELFShim | 
					
						
							|  |  |  |   MAGIC_NUMBER_OFFSET = 0
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :MAGIC_NUMBER_OFFSET | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  |   MAGIC_NUMBER_ASCII = "\x7fELF" | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :MAGIC_NUMBER_ASCII | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   OS_ABI_OFFSET = 0x07
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :OS_ABI_OFFSET | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   OS_ABI_SYSTEM_V = 0
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :OS_ABI_SYSTEM_V | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   OS_ABI_LINUX = 3
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :OS_ABI_LINUX | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   TYPE_OFFSET = 0x10
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :TYPE_OFFSET | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   TYPE_EXECUTABLE = 2
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :TYPE_EXECUTABLE | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   TYPE_SHARED = 3
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :TYPE_SHARED | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   ARCHITECTURE_OFFSET = 0x12
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :ARCHITECTURE_OFFSET | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   ARCHITECTURE_I386 = 0x3
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :ARCHITECTURE_I386 | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   ARCHITECTURE_POWERPC = 0x14
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :ARCHITECTURE_POWERPC | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   ARCHITECTURE_ARM = 0x28
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :ARCHITECTURE_ARM | 
					
						
							| 
									
										
										
										
											2021-07-18 16:55:57 +08:00
										 |  |  |   ARCHITECTURE_X86_64 = 0x3E
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :ARCHITECTURE_X86_64 | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   ARCHITECTURE_AARCH64 = 0xB7
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :ARCHITECTURE_AARCH64 | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   def read_uint8(offset) | 
					
						
							| 
									
										
										
										
											2019-10-13 10:13:42 +01:00
										 |  |  |     read(1, offset).unpack1("C") | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def read_uint16(offset) | 
					
						
							| 
									
										
										
										
											2019-10-13 10:13:42 +01:00
										 |  |  |     read(2, offset).unpack1("v") | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def elf? | 
					
						
							|  |  |  |     return @elf if defined? @elf | 
					
						
							| 
									
										
										
										
											2023-04-18 15:06:50 -07:00
										 |  |  |     return @elf = false if read(MAGIC_NUMBER_ASCII.size, MAGIC_NUMBER_OFFSET) != MAGIC_NUMBER_ASCII | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Check that this ELF file is for Linux or System V. | 
					
						
							|  |  |  |     # OS_ABI is often set to 0 (System V), regardless of the target platform. | 
					
						
							|  |  |  |     @elf = [OS_ABI_LINUX, OS_ABI_SYSTEM_V].include? read_uint8(OS_ABI_OFFSET) | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def arch | 
					
						
							|  |  |  |     return :dunno unless elf? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @arch ||= case read_uint16(ARCHITECTURE_OFFSET) | 
					
						
							|  |  |  |     when ARCHITECTURE_I386 then :i386 | 
					
						
							|  |  |  |     when ARCHITECTURE_X86_64 then :x86_64 | 
					
						
							|  |  |  |     when ARCHITECTURE_POWERPC then :powerpc | 
					
						
							|  |  |  |     when ARCHITECTURE_ARM then :arm | 
					
						
							|  |  |  |     when ARCHITECTURE_AARCH64 then :arm64 | 
					
						
							|  |  |  |     else :dunno | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def elf_type | 
					
						
							|  |  |  |     return :dunno unless elf? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @elf_type ||= case read_uint16(TYPE_OFFSET) | 
					
						
							|  |  |  |     when TYPE_EXECUTABLE then :executable | 
					
						
							|  |  |  |     when TYPE_SHARED then :dylib | 
					
						
							|  |  |  |     else :dunno | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def dylib? | 
					
						
							|  |  |  |     elf_type == :dylib | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def binary_executable? | 
					
						
							|  |  |  |     elf_type == :executable | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-17 08:14:44 -08:00
										 |  |  |   # The runtime search path, such as: | 
					
						
							|  |  |  |   # "/lib:/usr/lib:/usr/local/lib" | 
					
						
							| 
									
										
										
										
											2020-07-10 06:31:31 +05:30
										 |  |  |   def rpath | 
					
						
							|  |  |  |     return @rpath if defined? @rpath | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 06:41:51 +05:30
										 |  |  |     @rpath = rpath_using_patchelf_rb | 
					
						
							| 
									
										
										
										
											2020-07-10 06:31:31 +05:30
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-17 08:14:44 -08:00
										 |  |  |   # An array of runtime search path entries, such as: | 
					
						
							|  |  |  |   # ["/lib", "/usr/lib", "/usr/local/lib"] | 
					
						
							|  |  |  |   def rpaths | 
					
						
							| 
									
										
										
										
											2022-08-26 22:29:55 +08:00
										 |  |  |     Array(rpath&.split(":")) | 
					
						
							| 
									
										
										
										
											2021-12-17 08:14:44 -08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-23 03:18:29 +05:30
										 |  |  |   def interpreter | 
					
						
							|  |  |  |     return @interpreter if defined? @interpreter | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 06:41:51 +05:30
										 |  |  |     @interpreter = patchelf_patcher.interpreter | 
					
						
							| 
									
										
										
										
											2019-07-18 15:22:09 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-05 20:32:37 +05:30
										 |  |  |   def patch!(interpreter: nil, rpath: nil) | 
					
						
							|  |  |  |     return if interpreter.blank? && rpath.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-19 15:15:13 +01:00
										 |  |  |     save_using_patchelf_rb interpreter, rpath | 
					
						
							| 
									
										
										
										
											2020-08-05 20:32:37 +05:30
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   def dynamic_elf? | 
					
						
							|  |  |  |     return @dynamic_elf if defined? @dynamic_elf | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 06:41:51 +05:30
										 |  |  |     @dynamic_elf = patchelf_patcher.elf.segment_by_type(:DYNAMIC).present? | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   # Helper class for reading metadata from an ELF file. | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   class Metadata | 
					
						
							|  |  |  |     attr_reader :path, :dylib_id, :dylibs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def initialize(path) | 
					
						
							|  |  |  |       @path = path | 
					
						
							|  |  |  |       @dylibs = [] | 
					
						
							|  |  |  |       @dylib_id, needed = needed_libraries path | 
					
						
							|  |  |  |       return if needed.empty? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-24 02:23:13 +08:00
										 |  |  |       @dylibs = needed.map { |lib| find_full_lib_path(lib).to_s } | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def needed_libraries(path) | 
					
						
							| 
									
										
										
										
											2020-07-26 04:43:30 +05:30
										 |  |  |       return [nil, []] unless path.dynamic_elf? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 06:41:51 +05:30
										 |  |  |       needed_libraries_using_patchelf_rb path | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-02 18:36:13 +05:30
										 |  |  |     def needed_libraries_using_patchelf_rb(path) | 
					
						
							|  |  |  |       patcher = path.patchelf_patcher | 
					
						
							| 
									
										
										
										
											2020-07-10 06:31:31 +05:30
										 |  |  |       [patcher.soname, patcher.needed] | 
					
						
							| 
									
										
										
										
											2020-06-02 18:36:13 +05:30
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2024-04-24 02:23:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def find_full_lib_path(basename) | 
					
						
							|  |  |  |       local_paths = (path.patchelf_patcher.runpath || path.patchelf_patcher.rpath)&.split(":") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Search for dependencies in the runpath/rpath first | 
					
						
							|  |  |  |       local_paths&.each do |local_path| | 
					
						
							| 
									
										
										
										
											2024-04-24 23:56:55 -04:00
										 |  |  |         local_path = OS::Linux::Elf.expand_elf_dst(local_path, "ORIGIN", path.parent) | 
					
						
							| 
									
										
										
										
											2024-04-24 02:23:13 +08:00
										 |  |  |         candidate = Pathname(local_path)/basename | 
					
						
							|  |  |  |         return candidate if candidate.exist? && candidate.elf? | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Check if DF_1_NODEFLIB is set | 
					
						
							|  |  |  |       dt_flags_1 = path.patchelf_patcher.elf.segment_by_type(:dynamic)&.tag_by_type(:flags_1) | 
					
						
							|  |  |  |       nodeflib_flag = if dt_flags_1.nil? | 
					
						
							|  |  |  |         false | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         dt_flags_1.value & ELFTools::Constants::DF::DF_1_NODEFLIB != 0
 | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       linker_library_paths = OS::Linux::Ld.library_paths | 
					
						
							|  |  |  |       linker_system_dirs = OS::Linux::Ld.system_dirs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # If DF_1_NODEFLIB is set, exclude any library paths that are subdirectories | 
					
						
							|  |  |  |       # of the system dirs | 
					
						
							|  |  |  |       if nodeflib_flag | 
					
						
							|  |  |  |         linker_library_paths = linker_library_paths.reject do |lib_path| | 
					
						
							|  |  |  |           linker_system_dirs.any? { |system_dir| Utils::Path.child_of? system_dir, lib_path } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # If not found, search recursively in the paths listed in ld.so.conf (skipping | 
					
						
							|  |  |  |       # paths that are subdirectories of the system dirs if DF_1_NODEFLIB is set) | 
					
						
							|  |  |  |       linker_library_paths.each do |linker_library_path| | 
					
						
							|  |  |  |         candidate = Pathname(linker_library_path)/basename | 
					
						
							|  |  |  |         return candidate if candidate.exist? && candidate.elf? | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # If not found, search in the system dirs, unless DF_1_NODEFLIB is set | 
					
						
							|  |  |  |       unless nodeflib_flag | 
					
						
							|  |  |  |         linker_system_dirs.each do |linker_system_dir| | 
					
						
							|  |  |  |           candidate = Pathname(linker_system_dir)/basename | 
					
						
							|  |  |  |           return candidate if candidate.exist? && candidate.elf? | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       basename | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private_constant :Metadata | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-05 20:32:37 +05:30
										 |  |  |   def save_using_patchelf_rb(new_interpreter, new_rpath) | 
					
						
							|  |  |  |     patcher = patchelf_patcher | 
					
						
							|  |  |  |     patcher.interpreter = new_interpreter if new_interpreter.present? | 
					
						
							|  |  |  |     patcher.rpath = new_rpath if new_rpath.present? | 
					
						
							|  |  |  |     patcher.save(patchelf_compatible: true) | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-10 06:31:31 +05:30
										 |  |  |   def rpath_using_patchelf_rb | 
					
						
							|  |  |  |     patchelf_patcher.runpath || patchelf_patcher.rpath | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-02 18:36:13 +05:30
										 |  |  |   def patchelf_patcher | 
					
						
							| 
									
										
										
										
											2020-07-28 23:09:13 +05:30
										 |  |  |     require "patchelf" | 
					
						
							| 
									
										
										
										
											2020-08-05 20:32:37 +05:30
										 |  |  |     @patchelf_patcher ||= ::PatchELF::Patcher.new to_s, on_error: :silent | 
					
						
							| 
									
										
										
										
											2020-06-02 18:36:13 +05:30
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  |   def metadata | 
					
						
							|  |  |  |     @metadata ||= Metadata.new(self) | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2020-08-25 00:11:59 +02:00
										 |  |  |   private :metadata | 
					
						
							| 
									
										
										
										
											2017-12-01 16:29:50 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   def dylib_id | 
					
						
							|  |  |  |     metadata.dylib_id | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def dynamically_linked_libraries(*) | 
					
						
							|  |  |  |     metadata.dylibs | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end | 
					
						
							| 
									
										
										
										
											2024-04-24 23:56:55 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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 |