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 +4 -4
- data/README.md +1 -1
- data/lib/patchelf/alt_saver.rb +280 -59
- data/lib/patchelf/cli.rb +1 -1
- data/lib/patchelf/exceptions.rb +2 -0
- data/lib/patchelf/helper.rb +19 -5
- data/lib/patchelf/mm.rb +2 -2
- data/lib/patchelf/patcher.rb +1 -1
- data/lib/patchelf/saver.rb +4 -4
- data/lib/patchelf/version.rb +1 -1
- metadata +18 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 275f2e51c3ce1332b2069f3d9bb058351c1b0953df6bb5aad9d34f1af3adb9a4
|
4
|
+
data.tar.gz: a4f2c422bac7e669601a4c29e30046cdaa2076f9937d767152bd5f08920abdf4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97c66ab530c8f00f95c67dbeb07dfcc426c786c0d90b4a85cede69dc205283beb3d9d1c393b910b1ff88312bf80d040e6a2dccd5ed35300665015fcc3e3ce7e7
|
7
|
+
data.tar.gz: 9d09373c7ef6d6aa3f7a3541afefd3486b9e9906091a98277141fa25decbd32f0eee3e214ebd97e7f8e56c89cd8646a7c4b3ccde3a48151b345c9ebc170c5173
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[![Build Status](https://
|
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)
|
data/lib/patchelf/alt_saver.rb
CHANGED
@@ -8,10 +8,10 @@ require 'fileutils'
|
|
8
8
|
|
9
9
|
require 'patchelf/helper'
|
10
10
|
|
11
|
-
|
11
|
+
# :nodoc:
|
12
12
|
module PatchELF
|
13
13
|
# TODO: refactor buf_* methods here
|
14
|
-
# TODO: move all refinements into a
|
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
|
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]
|
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
|
-
|
239
|
+
resolved_rpath_dyn = dyn_tags.values_at(:runpath, :rpath).compact.first
|
238
240
|
|
239
241
|
old_rpath = ''
|
240
242
|
rpath_off = nil
|
241
|
-
|
242
|
-
rpath_off = shdr_dynstr.sh_offset +
|
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
|
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]
|
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.
|
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
|
-
|
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.
|
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 =
|
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
|
633
|
-
|
634
|
-
|
635
|
-
|
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
|
-
|
640
|
-
|
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
|
-
|
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
|
652
|
-
|
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:
|
658
|
-
p_vaddr:
|
659
|
-
p_paddr:
|
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
|
-
|
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
|
-
|
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
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
788
|
-
|
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
|
-
|
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)
|
794
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
data/lib/patchelf/exceptions.rb
CHANGED
@@ -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
|
data/lib/patchelf/helper.rb
CHANGED
@@ -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 =
|
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 =
|
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
|
-
#
|
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
|
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
|
data/lib/patchelf/patcher.rb
CHANGED
@@ -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 =
|
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)
|
data/lib/patchelf/saver.rb
CHANGED
@@ -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]
|
57
|
-
old_interp = @elf.segment_by_type(:interp).interp_name
|
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
|
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
|
209
|
+
idx = strtab_string.index("#{str}\x00")
|
210
210
|
# Request string is already exist
|
211
211
|
return yield idx if idx
|
212
212
|
|
data/lib/patchelf/version.rb
CHANGED
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.
|
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:
|
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.
|
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.
|
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
|
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
|
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: '
|
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: '
|
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.
|
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.
|
159
|
+
rubygems_version: 3.1.6
|
153
160
|
signing_key:
|
154
161
|
specification_version: 4
|
155
162
|
summary: patchelf
|