patchelf 1.3.0 → 1.4.0

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: 8182cf70ee88eceed07b85e8edda947e647842fb0e1767b36c0bfc61651ae9a6
4
- data.tar.gz: a739c96f21f48a4ae7036ba184e46e3ca6a6d3c1cc79215b0e8e4a5b4b970562
3
+ metadata.gz: 275f2e51c3ce1332b2069f3d9bb058351c1b0953df6bb5aad9d34f1af3adb9a4
4
+ data.tar.gz: a4f2c422bac7e669601a4c29e30046cdaa2076f9937d767152bd5f08920abdf4
5
5
  SHA512:
6
- metadata.gz: ed2e115d7925aef19b3b192f0d9fe70744bd267fe3ef1e9b4245340e741bd6a3cf62bb82d6a847edd8c99878dfaf2e64b50e82ff1049be1f8e89881279fbd682
7
- data.tar.gz: 02bfde530e73b448ec73932a1be1dd378bb44d74235fa9ab9096bf3c45607f0194ccfe3979857e4b4389004ee1cef8d4852b687fd571c4d4cfb291e57c576f8a
6
+ metadata.gz: 97c66ab530c8f00f95c67dbeb07dfcc426c786c0d90b4a85cede69dc205283beb3d9d1c393b910b1ff88312bf80d040e6a2dccd5ed35300665015fcc3e3ce7e7
7
+ data.tar.gz: 9d09373c7ef6d6aa3f7a3541afefd3486b9e9906091a98277141fa25decbd32f0eee3e214ebd97e7f8e56c89cd8646a7c4b3ccde3a48151b345c9ebc170c5173
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://travis-ci.org/david942j/patchelf.rb.svg?branch=master)](https://travis-ci.org/david942j/patchelf.rb)
1
+ [![Build Status](https://github.com/david942j/patchelf.rb/workflows/build/badge.svg)](https://github.com/david942j/patchelf.rb/actions)
2
2
  [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=david942j/patchelf.rb)](https://dependabot.com)
3
3
  [![Code Climate](https://codeclimate.com/github/david942j/patchelf.rb/badges/gpa.svg)](https://codeclimate.com/github/david942j/patchelf.rb)
4
4
  [![Issue Count](https://codeclimate.com/github/david942j/patchelf.rb/badges/issue_count.svg)](https://codeclimate.com/github/david942j/patchelf.rb)
@@ -8,10 +8,10 @@ require 'fileutils'
8
8
 
9
9
  require 'patchelf/helper'
10
10
 
11
- #:nodoc:
11
+ # :nodoc:
12
12
  module PatchELF
13
13
  # TODO: refactor buf_* methods here
14
- # TODO: move all refinements into a seperate file / helper file.
14
+ # TODO: move all refinements into a separate file / helper file.
15
15
  # refinements for cleaner syntax / speed / memory optimizations
16
16
  module Refinements
17
17
  refine StringIO do
@@ -21,7 +21,7 @@ module PatchELF
21
21
  # @param [Integer] nbytes
22
22
  # @return[void]
23
23
  def fill(char, nbytes)
24
- at_once = Helper::PAGE_SIZE
24
+ at_once = Helper.page_size
25
25
  pending = nbytes
26
26
 
27
27
  if pending > at_once
@@ -133,6 +133,8 @@ module PatchELF
133
133
  sec = find_section '.dynamic'
134
134
  return unless sec
135
135
 
136
+ return if sec.header.sh_type == ELFTools::Constants::SHT_NOBITS
137
+
136
138
  shdr = sec.header
137
139
  with_buf_at(shdr.sh_offset) do |buf|
138
140
  dyn = ELFTools::Structs::ELF_Dyn.new(elf_class: elf_class, endian: endian)
@@ -171,7 +173,7 @@ module PatchELF
171
173
  end
172
174
 
173
175
  def modify_interpreter
174
- @replaced_sections['.interp'] = @set[:interpreter] + "\x00"
176
+ @replaced_sections['.interp'] = "#{@set[:interpreter]}\x00"
175
177
  end
176
178
 
177
179
  def modify_needed
@@ -234,14 +236,13 @@ module PatchELF
234
236
  dyn_tags = collect_runpath_tags
235
237
  resolve_rpath_tag_conflict(dyn_tags, force_rpath: force_rpath)
236
238
  # (:runpath, :rpath) order_matters.
237
- resolved_rpath_dyns = dyn_tags.values_at(:runpath, :rpath).compact
239
+ resolved_rpath_dyn = dyn_tags.values_at(:runpath, :rpath).compact.first
238
240
 
239
241
  old_rpath = ''
240
242
  rpath_off = nil
241
- resolved_rpath_dyns.each do |dyn|
242
- rpath_off = shdr_dynstr.sh_offset + dyn[:header].d_val
243
+ if resolved_rpath_dyn
244
+ rpath_off = shdr_dynstr.sh_offset + resolved_rpath_dyn[:header].d_val
243
245
  old_rpath = buf_cstr(rpath_off)
244
- break
245
246
  end
246
247
  return if old_rpath == new_rpath
247
248
 
@@ -293,6 +294,11 @@ module PatchELF
293
294
  dt_null_idx += 1
294
295
  end
295
296
 
297
+ if dyn_num_bytes.nil?
298
+ Logger.error 'no dynamic tags'
299
+ return
300
+ end
301
+
296
302
  # allot for new dt_runpath
297
303
  shdr_dynamic = find_section('.dynamic').header
298
304
  new_dynamic_data = replace_section '.dynamic', shdr_dynamic.sh_size + dyn_num_bytes
@@ -331,7 +337,7 @@ module PatchELF
331
337
  end
332
338
 
333
339
  def page_size
334
- Helper::PAGE_SIZE
340
+ Helper.page_size(ehdr.e_machine)
335
341
  end
336
342
 
337
343
  def patch_out
@@ -359,7 +365,7 @@ module PatchELF
359
365
  elsif data.size < size
360
366
  data.ljust(size, "\x00")
361
367
  else
362
- data[0...size] + "\x00"
368
+ "#{data[0...size]}\x00"
363
369
  end
364
370
  @replaced_sections[section_name] = rep_data
365
371
  end
@@ -512,7 +518,7 @@ module PatchELF
512
518
  def copy_shdrs_to_eof
513
519
  shoff_new = @buffer.size
514
520
  # honestly idk why `ehdr.e_shoff` is considered when we are only moving shdrs.
515
- sh_size = ehdr.e_shoff + ehdr.e_shnum * ehdr.e_shentsize
521
+ sh_size = ehdr.e_shoff + (ehdr.e_shnum * ehdr.e_shentsize)
516
522
  buf_grow! @buffer.size + sh_size
517
523
  ehdr.e_shoff = shoff_new
518
524
  raise PatchError, 'ehdr.e_shnum != @sections.size' if ehdr.e_shnum != @sections.size
@@ -529,11 +535,11 @@ module PatchELF
529
535
  def rewrite_sections_executable
530
536
  sort_shdrs!
531
537
  shdr = start_replacement_shdr
532
- start_offset = shdr.sh_offset
533
- start_addr = shdr.sh_addr
538
+ start_offset = shdr.sh_offset.to_i
539
+ start_addr = shdr.sh_addr.to_i
534
540
  first_page = start_addr - start_offset
535
541
 
536
- Logger.debug "first reserved offset/addr is 0x#{start_offset.to_i.to_s 16}/0x#{start_addr.to_i.to_s 16}"
542
+ Logger.debug "first reserved offset/addr is 0x#{start_offset.to_s 16}/0x#{start_addr.to_s 16}"
537
543
 
538
544
  unless start_addr % page_size == start_offset % page_size
539
545
  raise PatchError, 'start_addr != start_offset (mod PAGE_SIZE)'
@@ -543,6 +549,8 @@ module PatchELF
543
549
 
544
550
  copy_shdrs_to_eof if ehdr.e_shoff < start_offset
545
551
 
552
+ normalize_note_segments
553
+
546
554
  seg_num_bytes = @segments.first.header.num_bytes
547
555
  needed_space = (
548
556
  ehdr.num_bytes +
@@ -557,10 +565,10 @@ module PatchELF
557
565
  Logger.debug "needed pages is #{needed_pages}"
558
566
  raise PatchError, 'virtual address space underrun' if needed_pages * page_size > first_page
559
567
 
568
+ shift_file(needed_pages, start_offset)
569
+
560
570
  first_page -= needed_pages * page_size
561
571
  start_offset += needed_pages * page_size
562
-
563
- shift_file(needed_pages, first_page)
564
572
  end
565
573
  Logger.debug "needed space is #{needed_space}"
566
574
 
@@ -575,27 +583,31 @@ module PatchELF
575
583
  end
576
584
 
577
585
  def replace_sections_in_the_way_of_phdr!
578
- pht_size = ehdr.num_bytes + (@segments.count + 1) * @segments.first.header.num_bytes
586
+ num_notes = @sections.count { |sec| sec.type == ELFTools::Constants::SHT_NOTE }
587
+ pht_size = ehdr.num_bytes + ((@segments.count + num_notes + 1) * @segments.first.header.num_bytes)
579
588
 
580
589
  # replace sections that may overlap with expanded program header table
581
590
  @sections.each_with_index do |sec, idx|
582
591
  shdr = sec.header
583
592
  next if idx.zero? || @replaced_sections[sec.name]
584
- break if shdr.sh_addr > pht_size
593
+ break if shdr.sh_offset > pht_size
585
594
 
586
595
  replace_section sec.name, shdr.sh_size
587
596
  end
588
597
  end
589
598
 
590
- def seg_end_addr(seg)
591
- phdr = seg.header
592
- Helper.alignup(phdr.p_vaddr + phdr.p_memsz, page_size)
593
- end
594
-
595
599
  def rewrite_sections_library
596
- start_page = seg_end_addr(@segments.max_by(&method(:seg_end_addr)))
600
+ start_page = 0
601
+ first_page = 0
602
+ @segments.each do |seg|
603
+ phdr = seg.header
604
+ this_page = Helper.alignup(phdr.p_vaddr + phdr.p_memsz, page_size)
605
+ start_page = [start_page, this_page].max
606
+ first_page = phdr.p_vaddr - phdr.p_offset if phdr.p_type == ELFTools::Constants::PT_PHDR
607
+ end
597
608
 
598
609
  Logger.debug "Last page is 0x#{start_page.to_s 16}"
610
+ Logger.debug "First page is 0x#{first_page.to_s 16}"
599
611
  replace_sections_in_the_way_of_phdr!
600
612
  needed_space = @replaced_sections.sum { |_, str| Helper.alignup(str.size, @section_alignment) }
601
613
  Logger.debug "needed space = #{needed_space}"
@@ -623,42 +635,172 @@ module PatchELF
623
635
  p_align: page_size
624
636
  )
625
637
 
638
+ normalize_note_segments
639
+
626
640
  cur_off = write_replaced_sections start_offset, start_page, start_offset
627
641
  raise PatchError, 'cur_off != start_offset + needed_space' if cur_off != start_offset + needed_space
628
642
 
629
- rewrite_headers ehdr.e_phoff
643
+ rewrite_headers(first_page + ehdr.e_phoff)
630
644
  end
631
645
 
632
- def shift_file(extra_pages, start_page)
633
- oldsz = @buffer.size
634
- shift = extra_pages * page_size
635
- buf_grow!(oldsz + shift)
636
- buf_move! shift, 0, oldsz
637
- with_buf_at(ehdr.num_bytes) { |buf| buf.write "\x00" * (shift - ehdr.num_bytes) }
646
+ def normalize_note_segments
647
+ return if @replaced_sections.none? do |rsec_name, _|
648
+ find_section(rsec_name)&.type == ELFTools::Constants::SHT_NOTE
649
+ end
638
650
 
639
- ehdr.e_phoff = ehdr.num_bytes
640
- ehdr.e_shoff = ehdr.e_shoff + shift
651
+ new_phdrs = []
652
+
653
+ phdrs_by_type(ELFTools::Constants::PT_NOTE) do |phdr|
654
+ # Binaries produced by older patchelf versions may contain empty PT_NOTE segments.
655
+ next if @sections.none? do |sec|
656
+ sec.header.sh_offset >= phdr.p_offset && sec.header.sh_offset < phdr.p_offset + phdr.p_filesz
657
+ end
658
+
659
+ new_phdrs += normalize_note_segment(phdr)
660
+ end
661
+
662
+ new_phdrs.each { |phdr| add_segment!(**phdr.snapshot) }
663
+ end
664
+
665
+ def normalize_note_segment(phdr)
666
+ start_off = phdr.p_offset.to_i
667
+ curr_off = start_off
668
+ end_off = start_off + phdr.p_filesz
669
+
670
+ new_phdrs = []
671
+
672
+ while curr_off < end_off
673
+ size = 0
674
+ sections_at_aligned_offset(curr_off) do |sec|
675
+ next if sec.type != ELFTools::Constants::SHT_NOTE
676
+
677
+ size = sec.header.sh_size.to_i
678
+ curr_off = sec.header.sh_offset.to_i
679
+ break
680
+ end
681
+
682
+ raise PatchError, 'cannot normalize PT_NOTE segment: non-contiguous SHT_NOTE sections' if size.zero?
683
+
684
+ if curr_off + size > end_off
685
+ raise PatchError, 'cannot normalize PT_NOTE segment: partially mapped SHT_NOTE section'
686
+ end
687
+
688
+ new_phdr = ELFTools::Structs::ELF_Phdr[elf_class].new(endian: endian, **phdr.snapshot)
689
+ new_phdr.p_offset = curr_off
690
+ new_phdr.p_vaddr = phdr.p_vaddr + (curr_off - start_off)
691
+ new_phdr.p_paddr = phdr.p_paddr + (curr_off - start_off)
692
+ new_phdr.p_filesz = size
693
+ new_phdr.p_memsz = size
694
+
695
+ if curr_off == start_off
696
+ phdr.assign(new_phdr)
697
+ else
698
+ new_phdrs << new_phdr
699
+ end
700
+
701
+ curr_off += size
702
+ end
703
+
704
+ new_phdrs
705
+ end
706
+
707
+ def sections_at_aligned_offset(offset)
708
+ @sections.each do |sec|
709
+ shdr = sec.header
710
+
711
+ aligned_offset = Helper.alignup(offset, shdr.sh_addralign)
712
+ next if shdr.sh_offset != aligned_offset
713
+
714
+ yield sec
715
+ end
716
+ end
717
+
718
+ def shift_sections(shift, start_offset)
719
+ ehdr.e_shoff += shift if ehdr.e_shoff >= start_offset
641
720
 
642
721
  @sections.each_with_index do |sec, i|
643
722
  next if i.zero? # dont touch NULL section
644
723
 
645
724
  shdr = sec.header
725
+ next if shdr.sh_offset < start_offset
726
+
646
727
  shdr.sh_offset += shift
647
728
  end
729
+ end
648
730
 
649
- @segments.each do |seg|
731
+ def shift_segment_offset(phdr, shift)
732
+ phdr.p_offset += shift
733
+ phdr.p_align = page_size if phdr.p_align != 0 && (phdr.p_vaddr - phdr.p_offset) % phdr.p_align != 0
734
+ end
735
+
736
+ def shift_segment_virtual_address(phdr, shift)
737
+ phdr.p_paddr -= shift if phdr.p_paddr > shift
738
+ phdr.p_vaddr -= shift if phdr.p_vaddr > shift
739
+ end
740
+
741
+ # rubocop:disable Metrics/PerceivedComplexity
742
+ def shift_segments(shift, start_offset)
743
+ split_index = -1
744
+ split_shift = 0
745
+
746
+ @segments.each_with_index do |seg, idx|
650
747
  phdr = seg.header
651
- phdr.p_offset += shift
652
- phdr.p_align = page_size if phdr.p_align != 0 && (phdr.p_vaddr - phdr.p_offset) % phdr.p_align != 0
748
+ p_start = phdr.p_offset
749
+
750
+ if p_start <= start_offset && p_start + phdr.p_filesz > start_offset &&
751
+ phdr.p_type == ELFTools::Constants::PT_LOAD
752
+ raise PatchError, "split_index(#{split_index}) != -1" if split_index != -1
753
+
754
+ split_index = idx
755
+ split_shift = start_offset - p_start
756
+
757
+ phdr.p_offset = start_offset
758
+ phdr.p_memsz -= split_shift
759
+ phdr.p_filesz -= split_shift
760
+ phdr.p_paddr += split_shift
761
+ phdr.p_vaddr += split_shift
762
+
763
+ p_start = start_offset
764
+ end
765
+
766
+ if p_start >= start_offset
767
+ shift_segment_offset(phdr, shift)
768
+ else
769
+ shift_segment_virtual_address(phdr, shift)
770
+ end
653
771
  end
654
772
 
773
+ raise PatchError, "split_index(#{split_index}) == -1" if split_index == -1
774
+
775
+ [split_index, split_shift]
776
+ end
777
+ # rubocop:enable Metrics/PerceivedComplexity
778
+
779
+ def shift_file(extra_pages, start_offset)
780
+ raise PatchError, "start_offset(#{start_offset}) < ehdr.num_bytes" if start_offset < ehdr.num_bytes
781
+
782
+ oldsz = @buffer.size
783
+ raise PatchError, "oldsz <= start_offset(#{start_offset})" if oldsz <= start_offset
784
+
785
+ shift = extra_pages * page_size
786
+ buf_grow!(oldsz + shift)
787
+ buf_move!(start_offset + shift, start_offset, oldsz - start_offset)
788
+ with_buf_at(start_offset) { |buf| buf.write "\x00" * shift }
789
+
790
+ ehdr.e_phoff = ehdr.num_bytes
791
+
792
+ shift_sections(shift, start_offset)
793
+
794
+ split_index, split_shift = shift_segments(shift, start_offset)
795
+
796
+ split_phdr = @segments[split_index].header
655
797
  add_segment!(
656
798
  p_type: ELFTools::Constants::PT_LOAD,
657
- p_offset: 0,
658
- p_vaddr: start_page,
659
- p_paddr: start_page,
660
- p_filesz: shift,
661
- p_memsz: shift,
799
+ p_offset: split_phdr.p_offset - split_shift - shift,
800
+ p_vaddr: split_phdr.p_vaddr - split_shift - shift,
801
+ p_paddr: split_phdr.p_paddr - split_shift - shift,
802
+ p_filesz: split_shift + shift,
803
+ p_memsz: split_shift + shift,
662
804
  p_flags: ELFTools::Constants::PF_R | ELFTools::Constants::PF_W,
663
805
  p_align: page_size
664
806
  )
@@ -699,12 +841,23 @@ module PatchELF
699
841
  end
700
842
 
701
843
  def sort_shdrs!
844
+ return if @sections.empty?
845
+
702
846
  section_dep_values = collect_section_to_section_refs
703
- shstrtab_name = @sections[ehdr.e_shstrndx].name
847
+ shstrtab = @sections[ehdr.e_shstrndx].header
704
848
  @sections.sort! { |me, you| me.header.sh_offset.to_i <=> you.header.sh_offset.to_i }
705
849
  update_section_idx!
706
850
  restore_section_to_section_refs!(section_dep_values)
707
- ehdr.e_shstrndx = find_section_idx shstrtab_name
851
+ @sections.each_with_index do |sec, idx|
852
+ ehdr.e_shstrndx = idx if sec.header.sh_offset == shstrtab.sh_offset
853
+ end
854
+ end
855
+
856
+ def jmprel_section_name
857
+ sec_name = %w[.rel.plt .rela.plt .rela.IA_64.pltoff].find { |s| find_section(s) }
858
+ raise PatchError, 'cannot find section corresponding to DT_JMPREL' unless sec_name
859
+
860
+ sec_name
708
861
  end
709
862
 
710
863
  # given a +dyn.d_tag+, returns the section name it must be synced to.
@@ -719,12 +872,14 @@ module PatchELF
719
872
  when ELFTools::Constants::DT_HASH
720
873
  '.hash'
721
874
  when ELFTools::Constants::DT_GNU_HASH
722
- '.gnu.hash'
723
- when ELFTools::Constants::DT_JMPREL
724
- sec_name = %w[.rel.plt .rela.plt .rela.IA_64.pltoff].find { |s| find_section(s) }
725
- raise PatchError, 'cannot find section corresponding to DT_JMPREL' unless sec_name
875
+ # return nil if not found, patchelf claims no problem in skipping
876
+ find_section('.gnu.hash')&.name
877
+ when ELFTools::Constants::DT_MIPS_XHASH
878
+ return if ehdr.e_machine != ELFTools::Constants::EM_MIPS
726
879
 
727
- sec_name
880
+ '.MIPS.xhash'
881
+ when ELFTools::Constants::DT_JMPREL
882
+ jmprel_section_name
728
883
  when ELFTools::Constants::DT_REL
729
884
  # regarding .rel.got, NixOS/patchelf says
730
885
  # "no idea if this makes sense, but it was needed for some program"
@@ -743,9 +898,26 @@ module PatchELF
743
898
 
744
899
  # updates dyn tags by syncing it with @section values
745
900
  def sync_dyn_tags!
901
+ dyn_table_offset = nil
746
902
  each_dynamic_tags do |dyn, buf_off|
903
+ dyn_table_offset ||= buf_off
904
+
747
905
  sec_name = dyn_tag_to_section_name(dyn.d_tag)
748
- next unless sec_name
906
+
907
+ unless sec_name
908
+ if dyn.d_tag == ELFTools::Constants::DT_MIPS_RLD_MAP_REL && ehdr.e_machine == ELFTools::Constants::EM_MIPS
909
+ rld_map = find_section('.rld_map')
910
+ dyn.d_val = if rld_map
911
+ rld_map.header.sh_addr.to_i - (buf_off - dyn_table_offset) -
912
+ find_section('.dynamic').header.sh_addr.to_i
913
+ else
914
+ Logger.warn 'DT_MIPS_RLD_MAP_REL entry is present, but .rld_map section is not'
915
+ 0
916
+ end
917
+ end
918
+
919
+ next
920
+ end
749
921
 
750
922
  shdr = find_section(sec_name).header
751
923
  dyn.d_val = dyn.d_tag == ELFTools::Constants::DT_STRSZ ? shdr.sh_size.to_i : shdr.sh_addr.to_i
@@ -784,22 +956,46 @@ module PatchELF
784
956
  end
785
957
  end
786
958
 
787
- def write_replaced_sections(cur_off, start_addr, start_offset)
788
- sht_no_bits = ELFTools::Constants::SHT_NOBITS
959
+ # Returns a blank shdr if the section doesn't exist.
960
+ def find_or_create_section_header(rsec_name)
961
+ shdr = find_section(rsec_name)&.header
962
+ shdr ||= ELFTools::Structs::ELF_Shdr.new(endian: endian, elf_class: elf_class)
963
+ shdr
964
+ end
789
965
 
790
- # the original source says this has to be done seperately to
966
+ def overwrite_replaced_sections
967
+ # the original source says this has to be done separately to
791
968
  # prevent clobbering the previously written section contents.
792
969
  @replaced_sections.each do |rsec_name, _|
793
- shdr = find_section(rsec_name).header
794
- with_buf_at(shdr.sh_offset) { |b| b.fill('X', shdr.sh_size) } if shdr.sh_type != sht_no_bits
970
+ shdr = find_section(rsec_name)&.header
971
+ next unless shdr
972
+
973
+ next if shdr.sh_type == ELFTools::Constants::SHT_NOBITS
974
+
975
+ with_buf_at(shdr.sh_offset) { |b| b.fill('X', shdr.sh_size) }
795
976
  end
977
+ end
978
+
979
+ def write_section_aligment(shdr)
980
+ return if shdr.sh_type == ELFTools::Constants::SHT_NOTE && shdr.sh_addralign <= @section_alignment
981
+
982
+ shdr.sh_addralign = @section_alignment
983
+ end
984
+
985
+ def section_bounds_within_segment?(s_start, s_end, p_start, p_end)
986
+ (s_start >= p_start && s_start < p_end) || (s_end > p_start && s_end <= p_end)
987
+ end
988
+
989
+ def write_replaced_sections(cur_off, start_addr, start_offset)
990
+ overwrite_replaced_sections
991
+
992
+ noted_phdrs = Set.new
796
993
 
797
994
  # the sort is necessary, the strategy in ruby and Cpp to iterate map/hash
798
995
  # is different, patchelf v0.10 iterates the replaced_sections sorted by
799
996
  # keys.
800
997
  @replaced_sections.sort.each do |rsec_name, rsec_data|
801
- section = find_section(rsec_name)
802
- shdr = section.header
998
+ shdr = find_or_create_section_header(rsec_name)
803
999
 
804
1000
  Logger.debug <<~DEBUG
805
1001
  rewriting section '#{rsec_name}'
@@ -809,18 +1005,43 @@ module PatchELF
809
1005
 
810
1006
  with_buf_at(cur_off) { |b| b.write rsec_data }
811
1007
 
1008
+ orig_sh_offset = shdr.sh_offset.to_i
1009
+ orig_sh_size = shdr.sh_size.to_i
1010
+
812
1011
  shdr.sh_offset = cur_off
813
1012
  shdr.sh_addr = start_addr + (cur_off - start_offset)
814
1013
  shdr.sh_size = rsec_data.size
815
- shdr.sh_addralign = @section_alignment
1014
+
1015
+ write_section_aligment(shdr)
816
1016
 
817
1017
  seg_type = {
818
1018
  '.interp' => ELFTools::Constants::PT_INTERP,
819
- '.dynamic' => ELFTools::Constants::PT_DYNAMIC
820
- }[section.name]
1019
+ '.dynamic' => ELFTools::Constants::PT_DYNAMIC,
1020
+ '.MIPS.abiflags' => ELFTools::Constants::PT_MIPS_ABIFLAGS,
1021
+ '.note.gnu.property' => ELFTools::Constants::PT_GNU_PROPERTY
1022
+ }[rsec_name]
821
1023
 
822
1024
  phdrs_by_type(seg_type) { |phdr| sync_sec_to_seg(shdr, phdr) }
823
1025
 
1026
+ if shdr.sh_type == ELFTools::Constants::SHT_NOTE
1027
+ phdrs_by_type(ELFTools::Constants::PT_NOTE) do |phdr, idx|
1028
+ next if noted_phdrs.include?(idx)
1029
+
1030
+ s_start = orig_sh_offset
1031
+ s_end = s_start + orig_sh_size
1032
+ p_start = phdr.p_offset
1033
+ p_end = p_start + phdr.p_filesz
1034
+
1035
+ next unless section_bounds_within_segment?(s_start, s_end, p_start, p_end)
1036
+
1037
+ raise PatchError, 'unsupported overlap of SHT_NOTE and PT_NOTE' if p_start != s_start || p_end != s_end
1038
+
1039
+ sync_sec_to_seg(shdr, phdr)
1040
+
1041
+ noted_phdrs << idx
1042
+ end
1043
+ end
1044
+
824
1045
  cur_off += Helper.alignup(rsec_data.size, @section_alignment)
825
1046
  end
826
1047
  @replaced_sections.clear
data/lib/patchelf/cli.rb CHANGED
@@ -139,7 +139,7 @@ module PatchELF
139
139
  @options[:set][:soname] = soname
140
140
  end
141
141
 
142
- opts.on('--version', 'Show current gem\'s version.') {}
142
+ opts.on('--version', 'Show current gem\'s version.')
143
143
  end
144
144
  end
145
145
 
@@ -6,8 +6,10 @@ require 'elftools/exceptions'
6
6
  module PatchELF
7
7
  # Raised on an error during ELF modification.
8
8
  class PatchError < ELFTools::ELFError; end
9
+
9
10
  # Raised when Dynamic Tag is missing
10
11
  class MissingTagError < PatchError; end
12
+
11
13
  # Raised on missing Program Header(segment)
12
14
  class MissingSegmentError < PatchError; end
13
15
  end
@@ -3,9 +3,6 @@
3
3
  module PatchELF
4
4
  # Helper methods for internal usage.
5
5
  module Helper
6
- # The size of one page.
7
- PAGE_SIZE = 0x1000
8
-
9
6
  module_function
10
7
 
11
8
  # Color codes for pretty print.
@@ -16,6 +13,23 @@ module PatchELF
16
13
  error: "\e[38;5;196m" # heavy red
17
14
  }.freeze
18
15
 
16
+ # The size of one page.
17
+ def page_size(e_machine = nil)
18
+ # Different architectures have different minimum section alignments.
19
+ case e_machine
20
+ when ELFTools::Constants::EM_SPARC,
21
+ ELFTools::Constants::EM_MIPS,
22
+ ELFTools::Constants::EM_PPC,
23
+ ELFTools::Constants::EM_PPC64,
24
+ ELFTools::Constants::EM_AARCH64,
25
+ ELFTools::Constants::EM_TILEGX,
26
+ ELFTools::Constants::EM_LOONGARCH
27
+ 0x10000
28
+ else
29
+ 0x1000
30
+ end
31
+ end
32
+
19
33
  # For wrapping string with color codes for prettier inspect.
20
34
  # @param [String] str
21
35
  # Content to colorize.
@@ -48,7 +62,7 @@ module PatchELF
48
62
  # #=> 32
49
63
  # aligndown(0x10, 0x8)
50
64
  # #=> 16
51
- def aligndown(val, align = PAGE_SIZE)
65
+ def aligndown(val, align = page_size)
52
66
  val - (val & (align - 1))
53
67
  end
54
68
 
@@ -63,7 +77,7 @@ module PatchELF
63
77
  # #=> 64
64
78
  # alignup(0x10, 0x8)
65
79
  # #=> 16
66
- def alignup(val, align = PAGE_SIZE)
80
+ def alignup(val, align = page_size)
67
81
  (val & (align - 1)).zero? ? val : (aligndown(val, align) + align)
68
82
  end
69
83
  end
data/lib/patchelf/mm.rb CHANGED
@@ -115,7 +115,7 @@ module PatchELF
115
115
  # prefer backward than forward
116
116
  return extend_backward(loads[idx - 1]) if writable?(loads[idx - 1])
117
117
 
118
- # note: loads[idx].file_head has been changed in shift_attributes
118
+ # NOTE: loads[idx].file_head has been changed in shift_attributes
119
119
  extend_forward(loads[idx], @extend_size)
120
120
  end
121
121
 
@@ -161,7 +161,7 @@ module PatchELF
161
161
 
162
162
  seg.header.p_offset += extend_size
163
163
  # We have to change align of LOAD segment since ld.so checks it.
164
- seg.header.p_align = Helper::PAGE_SIZE if seg.is_a?(ELFTools::Segments::LoadSegment)
164
+ seg.header.p_align = Helper.page_size if seg.is_a?(ELFTools::Segments::LoadSegment)
165
165
  end
166
166
 
167
167
  @elf.header.e_shoff += extend_size if @elf.header.e_shoff >= threshold
@@ -30,7 +30,7 @@ module PatchELF
30
30
  @elf = ELFTools::ELFFile.new(File.open(filename))
31
31
  @set = {}
32
32
  @rpath_sym = :runpath
33
- @on_error = !logging ? :exception : on_error
33
+ @on_error = logging ? on_error : :exception
34
34
 
35
35
  on_error_syms = %i[exception log silent]
36
36
  raise ArgumentError, "on_error must be one of #{on_error_syms}" unless on_error_syms.include?(@on_error)
@@ -53,8 +53,8 @@ module PatchELF
53
53
  def patch_interpreter
54
54
  return if @set[:interpreter].nil?
55
55
 
56
- new_interp = @set[:interpreter] + "\x00"
57
- old_interp = @elf.segment_by_type(:interp).interp_name + "\x00"
56
+ new_interp = "#{@set[:interpreter]}\x00"
57
+ old_interp = "#{@elf.segment_by_type(:interp).interp_name}\x00"
58
58
  return if old_interp == new_interp
59
59
 
60
60
  # These headers must be found here but not in the proc.
@@ -184,7 +184,7 @@ module PatchELF
184
184
  need_size = strtab_string.size + @strtab_extend_requests.reduce(0) { |sum, (str, _)| sum + str.size + 1 }
185
185
  dynstr = section_header('.dynstr')
186
186
  @mm.malloc(need_size) do |off, vaddr|
187
- new_str = strtab_string + @strtab_extend_requests.map(&:first).join("\x00") + "\x00"
187
+ new_str = "#{strtab_string}#{@strtab_extend_requests.map(&:first).join("\x00")}\x00"
188
188
  inline_patch(off, new_str)
189
189
  cur = strtab_string.size
190
190
  @strtab_extend_requests.each do |str, block|
@@ -206,7 +206,7 @@ module PatchELF
206
206
  # @yieldparam [Integer] idx
207
207
  # @yieldreturn [void]
208
208
  def reg_str_table(str, &block)
209
- idx = strtab_string.index(str + "\x00")
209
+ idx = strtab_string.index("#{str}\x00")
210
210
  # Request string is already exist
211
211
  return yield idx if idx
212
212
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module PatchELF
4
4
  # Current gem version.
5
- VERSION = '1.3.0'.freeze
5
+ VERSION = '1.4.0'.freeze
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patchelf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-27 00:00:00.000000000 Z
11
+ date: 2022-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: elftools
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.1.3
19
+ version: '1.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.1.3
26
+ version: '1.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,28 +44,28 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '3.8'
47
+ version: '3'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '3.8'
54
+ version: '3'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.62'
61
+ version: '1'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.62'
68
+ version: '1'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: simplecov
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -73,6 +73,9 @@ dependencies:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0.17'
76
+ - - "<"
77
+ - !ruby/object:Gem::Version
78
+ version: '0.18'
76
79
  type: :development
77
80
  prerelease: false
78
81
  version_requirements: !ruby/object:Gem::Requirement
@@ -80,6 +83,9 @@ dependencies:
80
83
  - - "~>"
81
84
  - !ruby/object:Gem::Version
82
85
  version: '0.17'
86
+ - - "<"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.18'
83
89
  - !ruby/object:Gem::Dependency
84
90
  name: tty-platform
85
91
  requirement: !ruby/object:Gem::Requirement
@@ -133,7 +139,8 @@ files:
133
139
  homepage: https://github.com/david942j/patchelf.rb
134
140
  licenses:
135
141
  - MIT
136
- metadata: {}
142
+ metadata:
143
+ rubygems_mfa_required: 'true'
137
144
  post_install_message:
138
145
  rdoc_options: []
139
146
  require_paths:
@@ -142,14 +149,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
149
  requirements:
143
150
  - - ">="
144
151
  - !ruby/object:Gem::Version
145
- version: '2.3'
152
+ version: '2.6'
146
153
  required_rubygems_version: !ruby/object:Gem::Requirement
147
154
  requirements:
148
155
  - - ">="
149
156
  - !ruby/object:Gem::Version
150
157
  version: '0'
151
158
  requirements: []
152
- rubygems_version: 3.1.2
159
+ rubygems_version: 3.1.6
153
160
  signing_key:
154
161
  specification_version: 4
155
162
  summary: patchelf