patchelf 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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