patchelf 1.5.2 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f11434b437c5d8623134c6c1fe78332b8cbc1251bfd1b0549a560fef7160cf1e
4
- data.tar.gz: 742bade914a5ad4e8b1523ad3d1aea9b2a801f89365e087cbc2d7fed0bfe4cba
3
+ metadata.gz: 64cae3b6070bf26b6716a82886fcb183f20213bf0c396501141e93c1e7951ffa
4
+ data.tar.gz: 49793e2b2bf154c0d4ff70f1533130495a0161db418248fc39105997159c59c8
5
5
  SHA512:
6
- metadata.gz: a476877469a6480b277ac318e703eb8d5a5c373b47b095b5c215327207e52b024fc8c38093ef3974fb022f206078c4ae20548d97d861bcc3bcae3e5a0b6727c6
7
- data.tar.gz: 4acd3a4e025dec84cefe1b45d09f6aa5425618a12ac3f5bb94a60a3aa9f04a9e5fba68cba8f988be6866a615744ae1f03c3a944812a12513bb4612ade87aa721
6
+ metadata.gz: d6b6dc9f7caa90f2abe18013a778e4ede2206f848e377e3cfce635b83ffc75b4dbc66c9b318899040bffa9553e8f0820cab95f92af445adc783efbbe47e0395f
7
+ data.tar.gz: b9566cc7487e8225e07f5ea7a29698e1d280969fde9f93ef0c0b1cd08d11595e92243883ed6123ef3918c1776003939c16a49d89aed6fc5da7a685e92a4a6a5a
data/README.md CHANGED
@@ -1,11 +1,10 @@
1
1
  [![Downloads](https://img.shields.io/endpoint?url=https://gem-badge-h3lg.onrender.com/downloads/patchelf)](https://rubygems.org/gems/patchelf)
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/patchelf.svg)](https://badge.fury.io/rb/patchelf)
4
- [![Build Status](https://github.com/david942j/patchelf.rb/workflows/build/badge.svg)](https://github.com/david942j/patchelf.rb/actions)
5
- [![Maintainability](https://qlty.sh/gh/david942j/projects/patchelf.rb/maintainability.svg)](https://qlty.sh/gh/david942j/projects/patchelf.rb)
6
- [![Code Coverage](https://qlty.sh/gh/david942j/projects/patchelf.rb/coverage.svg)](https://qlty.sh/gh/david942j/projects/patchelf.rb)
7
- [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/github/david942j/patchelf.rb/master)
8
- [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
4
+ [![CI](https://github.com/Homebrew/patchelf.rb/actions/workflows/tests.yml/badge.svg)](https://github.com/Homebrew/patchelf.rb/actions/workflows/tests.yml)
5
+ [![Coverage Status](https://codecov.io/gh/Homebrew/patchelf.rb/graph/badge.svg)](https://codecov.io/gh/Homebrew/patchelf.rb)
6
+ [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/patchelf)
7
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://choosealicense.com/licenses/mit/)
9
8
 
10
9
  # patchelf.rb
11
10
 
@@ -136,3 +135,16 @@ patcher.save('ls.patch')
136
135
  ## Environment
137
136
 
138
137
  patchelf.rb is implemented in pure Ruby, so it should work in all environments include Linux, macOS, and Windows!
138
+
139
+ ## Project Authors
140
+
141
+ The git history remains the definitive record of all contributions.
142
+
143
+ ### Original Author and Project Founder
144
+
145
+ * david942j (@david942j)
146
+
147
+ ### Maintainers
148
+
149
+ * Homebrew maintainers
150
+ * david942j (@david942j)
@@ -58,7 +58,7 @@ module PatchELF
58
58
  @out_file = out_file
59
59
  @set = set
60
60
 
61
- f = File.open(in_file, 'rb')
61
+ f = File.open(in_file, 'rb') # rubocop:disable Style/FileOpen
62
62
  # the +@buffer+ and +@elf+ both could work on same +StringIO+ stream,
63
63
  # the updating of @buffer in place blocks us from looking up old values.
64
64
  # TODO: cache the values needed later, use same stream for +@buffer+ and +@elf+.
@@ -130,14 +130,12 @@ module PatchELF
130
130
  find_section '.dynstr'
131
131
  end
132
132
 
133
- # yields dynamic tag, and offset in buffer
133
+ # Yields dynamic tag, and its offset in @buffer.
134
+ # @yieldparam [ELFTools::Structs::ELF_Dyn] dyn
135
+ # @yieldparam [Integer] offset The offset of this dynamic tag within +@buffer+.
134
136
  def each_dynamic_tags
135
- return unless block_given?
136
-
137
- sec = find_section '.dynamic'
138
- return unless sec
139
-
140
- return if sec.header.sh_type == ELFTools::Constants::SHT_NOBITS
137
+ sec = find_section('.dynamic')
138
+ return if sec.nil? || sec.header.sh_type == ELFTools::Constants::SHT_NOBITS
141
139
 
142
140
  shdr = sec.header
143
141
  with_buf_at(shdr.sh_offset) do |buf|
@@ -149,8 +147,8 @@ module PatchELF
149
147
  break if dyn.d_tag == ELFTools::Constants::DT_NULL
150
148
 
151
149
  yield dyn, buf_dyn_offset
152
- # there's a possibility for caller to modify @buffer.pos, seek to avoid such issues
153
- buf.seek buf_dyn_offset + dyn.num_bytes
150
+ # It's possible the caller may modify @buffer.pos, seek to ensure it points to the next tag.
151
+ buf.seek(buf_dyn_offset + dyn.num_bytes)
154
152
  end
155
153
  end
156
154
  end
@@ -675,18 +673,13 @@ module PatchELF
675
673
  new_phdrs = []
676
674
 
677
675
  while curr_off < end_off
678
- size = 0
679
- sections_at_aligned_offset(curr_off) do |sec|
680
- next if sec.type != ELFTools::Constants::SHT_NOTE
681
-
682
- size = sec.header.sh_size.to_i
683
- curr_off = sec.header.sh_offset.to_i
684
- break
685
- end
676
+ section = sections_at_aligned_offset(curr_off).find { |sec| sec.type == ELFTools::Constants::SHT_NOTE }
677
+ raise PatchError, 'cannot normalize PT_NOTE segment: non-contiguous SHT_NOTE sections' if section.nil?
686
678
 
687
- raise PatchError, 'cannot normalize PT_NOTE segment: non-contiguous SHT_NOTE sections' if size.zero?
679
+ size = section.header.sh_size.to_i
680
+ curr_off = section.header.sh_offset.to_i
688
681
 
689
- if curr_off + size > end_off
682
+ if size.zero? || curr_off + size > end_off
690
683
  raise PatchError, 'cannot normalize PT_NOTE segment: partially mapped SHT_NOTE section'
691
684
  end
692
685
 
@@ -710,6 +703,8 @@ module PatchELF
710
703
  end
711
704
 
712
705
  def sections_at_aligned_offset(offset)
706
+ return to_enum(__method__, offset) unless block_given?
707
+
713
708
  @sections.each do |sec|
714
709
  shdr = sec.header
715
710
 
@@ -743,7 +738,6 @@ module PatchELF
743
738
  phdr.p_vaddr -= shift if phdr.p_vaddr > shift
744
739
  end
745
740
 
746
- # rubocop:disable Metrics/PerceivedComplexity
747
741
  def shift_segments(shift, start_offset)
748
742
  split_index = -1
749
743
  split_shift = 0
@@ -752,9 +746,8 @@ module PatchELF
752
746
  phdr = seg.header
753
747
  p_start = phdr.p_offset
754
748
 
755
- if p_start <= start_offset && p_start + phdr.p_filesz > start_offset &&
756
- phdr.p_type == ELFTools::Constants::PT_LOAD
757
- raise PatchError, "split_index(#{split_index}) != -1" if split_index != -1
749
+ if (p_start...(p_start + phdr.p_filesz)).cover?(start_offset) && phdr.p_type == ELFTools::Constants::PT_LOAD
750
+ raise PatchError, 'PT_LOAD segments overlapped, unable to shift segments' if split_index != -1
758
751
 
759
752
  split_index = idx
760
753
  split_shift = start_offset - p_start
@@ -775,11 +768,10 @@ module PatchELF
775
768
  end
776
769
  end
777
770
 
778
- raise PatchError, "split_index(#{split_index}) == -1" if split_index == -1
771
+ raise PatchError, "No PT_LOAD found covers offset 0x#{start_offset.to_s(16)}" if split_index == -1
779
772
 
780
773
  [split_index, split_shift]
781
774
  end
782
- # rubocop:enable Metrics/PerceivedComplexity
783
775
 
784
776
  def shift_file(extra_pages, start_offset, extra_bytes)
785
777
  raise PatchError, "start_offset(#{start_offset}) < ehdr.num_bytes" if start_offset < ehdr.num_bytes
@@ -865,9 +857,9 @@ module PatchELF
865
857
  sec_name
866
858
  end
867
859
 
868
- # given a +dyn.d_tag+, returns the section name it must be synced to.
869
- # it may return nil, when given tag maps to no section,
870
- # or when its okay to skip if section is not found.
860
+ # Given a +dyn.d_tag+, returns the section name it must be synced to.
861
+ # Returns +nil+ when given tag maps to no section, or when its okay to skip if section is not found.
862
+ # @return [String?]
871
863
  def dyn_tag_to_section_name(d_tag)
872
864
  case d_tag
873
865
  when ELFTools::Constants::DT_STRTAB, ELFTools::Constants::DT_STRSZ
@@ -901,32 +893,37 @@ module PatchELF
901
893
  end
902
894
  end
903
895
 
896
+ # @return [ELFTools::Structs::ELF_Shdr?]
897
+ def dyn_tag_to_shdr(d_tag)
898
+ sec_name = dyn_tag_to_section_name(d_tag)
899
+ return if sec_name.nil?
900
+
901
+ find_section(sec_name)&.header
902
+ end
903
+
904
904
  # updates dyn tags by syncing it with @section values
905
905
  def sync_dyn_tags!
906
+ # Position of the fist dynamic tag.
906
907
  dyn_table_offset = nil
907
908
  each_dynamic_tags do |dyn, buf_off|
908
909
  dyn_table_offset ||= buf_off
909
910
 
910
- sec_name = dyn_tag_to_section_name(dyn.d_tag)
911
-
912
- unless sec_name
913
- if dyn.d_tag == ELFTools::Constants::DT_MIPS_RLD_MAP_REL && ehdr.e_machine == ELFTools::Constants::EM_MIPS
914
- rld_map = find_section('.rld_map')
915
- dyn.d_val = if rld_map
916
- rld_map.header.sh_addr.to_i - (buf_off - dyn_table_offset) -
917
- find_section('.dynamic').header.sh_addr.to_i
918
- else
919
- Logger.warn 'DT_MIPS_RLD_MAP_REL entry is present, but .rld_map section is not'
920
- 0
921
- end
922
- end
911
+ if dyn.d_tag == ELFTools::Constants::DT_MIPS_RLD_MAP_REL
912
+ rld_map = find_section('.rld_map')
913
+ dyn.d_val = if rld_map
914
+ rld_map.header.sh_addr.to_i - (buf_off - dyn_table_offset) -
915
+ find_section('.dynamic').header.sh_addr.to_i
916
+ else
917
+ Logger.warn 'DT_MIPS_RLD_MAP_REL entry is present, but .rld_map section is not'
918
+ 0
919
+ end
920
+ else
921
+ shdr = dyn_tag_to_shdr(dyn.d_tag)
922
+ next if shdr.nil?
923
923
 
924
- next
924
+ dyn.d_val = dyn.d_tag == ELFTools::Constants::DT_STRSZ ? shdr.sh_size.to_i : shdr.sh_addr.to_i
925
925
  end
926
926
 
927
- shdr = find_section(sec_name).header
928
- dyn.d_val = dyn.d_tag == ELFTools::Constants::DT_STRSZ ? shdr.sh_size.to_i : shdr.sh_addr.to_i
929
-
930
927
  with_buf_at(buf_off) { |wbuf| dyn.write(wbuf) }
931
928
  end
932
929
  end
@@ -945,6 +942,8 @@ module PatchELF
945
942
  nil
946
943
  end
947
944
 
945
+ # Some sections have their corresponding segment.
946
+ # Use this utility when a section is being patched so its segment should be updated with the same values.
948
947
  def sync_sec_to_seg(shdr, phdr)
949
948
  phdr.p_offset = shdr.sh_offset.to_i
950
949
  phdr.p_vaddr = phdr.p_paddr = shdr.sh_addr.to_i
@@ -962,6 +961,8 @@ module PatchELF
962
961
  end
963
962
 
964
963
  # Returns a blank shdr if the section doesn't exist.
964
+ #
965
+ # @return [ELFTools::Structs::ELF_Shdr]
965
966
  def find_or_create_section_header(rsec_name)
966
967
  shdr = find_section(rsec_name)&.header
967
968
  shdr ||= ELFTools::Structs::ELF_Shdr.new(endian: endian, elf_class: elf_class)
@@ -991,11 +992,54 @@ module PatchELF
991
992
  (s_start >= p_start && s_start < p_end) || (s_end > p_start && s_end <= p_end)
992
993
  end
993
994
 
995
+ # Used when patching sections.
996
+ # For sections with a correspondence segment, update the segment values.
997
+ # @return [void]
998
+ def section_sync_correspondence_segment(sec_name, shdr)
999
+ seg_type = {
1000
+ '.interp' => ELFTools::Constants::PT_INTERP,
1001
+ '.dynamic' => ELFTools::Constants::PT_DYNAMIC,
1002
+ '.MIPS.abiflags' => ELFTools::Constants::PT_MIPS_ABIFLAGS,
1003
+ '.note.gnu.property' => ELFTools::Constants::PT_GNU_PROPERTY
1004
+ }[sec_name]
1005
+ return if seg_type.nil?
1006
+
1007
+ phdrs_by_type(seg_type) { |phdr| sync_sec_to_seg(shdr, phdr) }
1008
+ end
1009
+
1010
+ # Similar to +section_sync_correspondence_segment+ but dedicate for note sections as it is allowed to have multiple
1011
+ # note segments.
1012
+ # This function searches all note segments and only sync the one matched with the section values before patching.
1013
+ #
1014
+ # NOTE: This function is no-op if +shdr+ does not have section type +ELFTools::Constants::SHT_NOTE+.
1015
+ #
1016
+ # @param [Integer] orig_sh_offset The original section offset value.
1017
+ # @param [Integer] orig_sh_size The original section size value.
1018
+ # @param [ELFTools::Structs::ELF_Shdr] shdr The section header with values after patched.
1019
+ #
1020
+ # @return [void]
1021
+ def sync_note_segment(orig_sh_offset, orig_sh_size, shdr)
1022
+ return if shdr.sh_type != ELFTools::Constants::SHT_NOTE
1023
+
1024
+ phdrs_by_type(ELFTools::Constants::PT_NOTE) do |phdr|
1025
+ s_start = orig_sh_offset
1026
+ s_end = s_start + orig_sh_size
1027
+ p_start = phdr.p_offset
1028
+ p_end = p_start + phdr.p_filesz
1029
+
1030
+ # Skip if no overlap.
1031
+ next unless section_bounds_within_segment?(s_start, s_end, p_start, p_end)
1032
+
1033
+ # Only support exact matches.
1034
+ raise PatchError, 'unsupported overlap of SHT_NOTE and PT_NOTE' unless [p_start, p_end] == [s_start, s_end]
1035
+
1036
+ sync_sec_to_seg(shdr, phdr)
1037
+ end
1038
+ end
1039
+
994
1040
  def write_replaced_sections(cur_off, start_addr, start_offset)
995
1041
  overwrite_replaced_sections
996
1042
 
997
- noted_phdrs = Set.new
998
-
999
1043
  # the sort is necessary, the strategy in ruby and Cpp to iterate map/hash
1000
1044
  # is different, patchelf v0.10 iterates the replaced_sections sorted by
1001
1045
  # keys.
@@ -1018,34 +1062,8 @@ module PatchELF
1018
1062
  shdr.sh_size = rsec_data.size
1019
1063
 
1020
1064
  write_section_alignment(shdr)
1021
-
1022
- seg_type = {
1023
- '.interp' => ELFTools::Constants::PT_INTERP,
1024
- '.dynamic' => ELFTools::Constants::PT_DYNAMIC,
1025
- '.MIPS.abiflags' => ELFTools::Constants::PT_MIPS_ABIFLAGS,
1026
- '.note.gnu.property' => ELFTools::Constants::PT_GNU_PROPERTY
1027
- }[rsec_name]
1028
-
1029
- phdrs_by_type(seg_type) { |phdr| sync_sec_to_seg(shdr, phdr) }
1030
-
1031
- if shdr.sh_type == ELFTools::Constants::SHT_NOTE
1032
- phdrs_by_type(ELFTools::Constants::PT_NOTE) do |phdr, idx|
1033
- next if noted_phdrs.include?(idx)
1034
-
1035
- s_start = orig_sh_offset
1036
- s_end = s_start + orig_sh_size
1037
- p_start = phdr.p_offset
1038
- p_end = p_start + phdr.p_filesz
1039
-
1040
- next unless section_bounds_within_segment?(s_start, s_end, p_start, p_end)
1041
-
1042
- raise PatchError, 'unsupported overlap of SHT_NOTE and PT_NOTE' if p_start != s_start || p_end != s_end
1043
-
1044
- sync_sec_to_seg(shdr, phdr)
1045
-
1046
- noted_phdrs << idx
1047
- end
1048
- end
1065
+ section_sync_correspondence_segment(rsec_name, shdr)
1066
+ sync_note_segment(orig_sh_offset, orig_sh_size, shdr)
1049
1067
 
1050
1068
  cur_off += Helper.alignup(rsec_data.size, @section_alignment)
1051
1069
  end
data/lib/patchelf/cli.rb CHANGED
@@ -9,7 +9,7 @@ module PatchELF
9
9
  # For command line interface to parsing arguments.
10
10
  module CLI
11
11
  # Name of binary.
12
- SCRIPT_NAME = 'patchelf.rb'.freeze
12
+ SCRIPT_NAME = 'patchelf.rb'
13
13
  # CLI usage string.
14
14
  USAGE = format('Usage: %s <commands> FILENAME [OUTPUT_FILE]', SCRIPT_NAME).freeze
15
15
 
@@ -29,7 +29,7 @@ module PatchELF
29
29
  # :silent = ignore the errors
30
30
  def initialize(filename, on_error: :log, logging: true)
31
31
  @in_file = filename
32
- f = File.open(filename)
32
+ f = File.open(filename) # rubocop:disable Style/FileOpen
33
33
  @elf = ELFTools::ELFFile.new(f)
34
34
  @set = {}
35
35
  @rpath_sym = :runpath
@@ -32,7 +32,7 @@ module PatchELF
32
32
  @set = set
33
33
  # [{Integer => String}]
34
34
  @inline_patch = {}
35
- f = File.open(in_file)
35
+ f = File.open(in_file) # rubocop:disable Style/FileOpen
36
36
  @elf = ELFTools::ELFFile.new(f)
37
37
  @mm = PatchELF::MM.new(@elf)
38
38
  @strtab_extend_requests = []
@@ -2,5 +2,5 @@
2
2
 
3
3
  module PatchELF
4
4
  # Current gem version.
5
- VERSION = '1.5.2'.freeze
5
+ VERSION = '1.6.2'
6
6
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patchelf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 1.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-11-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: elftools
@@ -93,6 +93,20 @@ dependencies:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0.22'
96
+ - !ruby/object:Gem::Dependency
97
+ name: simplecov-cobertura
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '4'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '4'
96
110
  - !ruby/object:Gem::Dependency
97
111
  name: tty-platform
98
112
  requirement: !ruby/object:Gem::Requirement
@@ -144,11 +158,14 @@ files:
144
158
  - lib/patchelf/patcher.rb
145
159
  - lib/patchelf/saver.rb
146
160
  - lib/patchelf/version.rb
147
- homepage: https://github.com/david942j/patchelf.rb
161
+ homepage: https://github.com/Homebrew/patchelf.rb
148
162
  licenses:
149
163
  - MIT
150
164
  metadata:
151
165
  rubygems_mfa_required: 'true'
166
+ source_code_uri: https://github.com/Homebrew/patchelf.rb
167
+ bug_tracker_uri: https://github.com/Homebrew/patchelf.rb/issues
168
+ funding_uri: https://github.com/sponsors/Homebrew
152
169
  rdoc_options: []
153
170
  require_paths:
154
171
  - lib
@@ -156,14 +173,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
156
173
  requirements:
157
174
  - - ">="
158
175
  - !ruby/object:Gem::Version
159
- version: '3.1'
176
+ version: '3.3'
160
177
  required_rubygems_version: !ruby/object:Gem::Requirement
161
178
  requirements:
162
179
  - - ">="
163
180
  - !ruby/object:Gem::Version
164
181
  version: '0'
165
182
  requirements: []
166
- rubygems_version: 3.6.2
183
+ rubygems_version: 4.0.16
167
184
  specification_version: 4
168
185
  summary: patchelf
169
186
  test_files: []