diff --git a/lib/patchelf/alt_saver.rb b/lib/patchelf/alt_saver.rb index 45d94f7..430eee1 100644 --- a/lib/patchelf/alt_saver.rb +++ b/lib/patchelf/alt_saver.rb @@ -21,7 +21,7 @@ module Refinements # @param [Integer] nbytes # @return[void] def fill(char, nbytes) - at_once = Helper::PAGE_SIZE + at_once = Helper.page_size pending = nbytes if pending > at_once @@ -133,6 +133,8 @@ def each_dynamic_tags sec = find_section '.dynamic' return unless sec + return if sec.header.sh_type == ELFTools::Constants::SHT_NOBITS + shdr = sec.header with_buf_at(shdr.sh_offset) do |buf| dyn = ELFTools::Structs::ELF_Dyn.new(elf_class: elf_class, endian: endian) @@ -292,6 +294,11 @@ def add_dt_rpath!(d_tag: nil, d_val: nil) dt_null_idx += 1 end + if dyn_num_bytes.nil? + Logger.error 'no dynamic tags' + return + end + # allot for new dt_runpath shdr_dynamic = find_section('.dynamic').header new_dynamic_data = replace_section '.dynamic', shdr_dynamic.sh_size + dyn_num_bytes @@ -330,7 +337,7 @@ def new_section_idx(old_shndx) end def page_size - Helper::PAGE_SIZE + Helper.page_size(ehdr.e_machine) end def patch_out @@ -528,11 +535,11 @@ def copy_shdrs_to_eof def rewrite_sections_executable sort_shdrs! shdr = start_replacement_shdr - start_offset = shdr.sh_offset - start_addr = shdr.sh_addr + start_offset = shdr.sh_offset.to_i + start_addr = shdr.sh_addr.to_i first_page = start_addr - start_offset - Logger.debug "first reserved offset/addr is 0x#{start_offset.to_i.to_s 16}/0x#{start_addr.to_i.to_s 16}" + Logger.debug "first reserved offset/addr is 0x#{start_offset.to_s 16}/0x#{start_addr.to_s 16}" unless start_addr % page_size == start_offset % page_size raise PatchError, 'start_addr != start_offset (mod PAGE_SIZE)' @@ -542,6 +549,8 @@ def rewrite_sections_executable copy_shdrs_to_eof if ehdr.e_shoff < start_offset + normalize_note_segments + seg_num_bytes = @segments.first.header.num_bytes needed_space = ( ehdr.num_bytes + @@ -556,10 +565,10 @@ def rewrite_sections_executable Logger.debug "needed pages is #{needed_pages}" raise PatchError, 'virtual address space underrun' if needed_pages * page_size > first_page + shift_file(needed_pages, start_offset) + first_page -= needed_pages * page_size start_offset += needed_pages * page_size - - shift_file(needed_pages, first_page) end Logger.debug "needed space is #{needed_space}" @@ -574,27 +583,31 @@ def rewrite_sections_executable end def replace_sections_in_the_way_of_phdr! - pht_size = ehdr.num_bytes + ((@segments.count + 1) * @segments.first.header.num_bytes) + num_notes = @sections.count { |sec| sec.type == ELFTools::Constants::SHT_NOTE } + pht_size = ehdr.num_bytes + ((@segments.count + num_notes + 1) * @segments.first.header.num_bytes) # replace sections that may overlap with expanded program header table @sections.each_with_index do |sec, idx| shdr = sec.header next if idx.zero? || @replaced_sections[sec.name] - break if shdr.sh_addr > pht_size + break if shdr.sh_offset > pht_size replace_section sec.name, shdr.sh_size end end - def seg_end_addr(seg) - phdr = seg.header - Helper.alignup(phdr.p_vaddr + phdr.p_memsz, page_size) - end - def rewrite_sections_library - start_page = seg_end_addr(@segments.max_by(&method(:seg_end_addr))) + start_page = 0 + first_page = 0 + @segments.each do |seg| + phdr = seg.header + this_page = Helper.alignup(phdr.p_vaddr + phdr.p_memsz, page_size) + start_page = [start_page, this_page].max + first_page = phdr.p_vaddr - phdr.p_offset if phdr.p_type == ELFTools::Constants::PT_PHDR + end Logger.debug "Last page is 0x#{start_page.to_s 16}" + Logger.debug "First page is 0x#{first_page.to_s 16}" replace_sections_in_the_way_of_phdr! needed_space = @replaced_sections.sum { |_, str| Helper.alignup(str.size, @section_alignment) } Logger.debug "needed space = #{needed_space}" @@ -622,42 +635,172 @@ def rewrite_sections_library p_align: page_size ) + normalize_note_segments + cur_off = write_replaced_sections start_offset, start_page, start_offset raise PatchError, 'cur_off != start_offset + needed_space' if cur_off != start_offset + needed_space - rewrite_headers ehdr.e_phoff + rewrite_headers(first_page + ehdr.e_phoff) end - def shift_file(extra_pages, start_page) - oldsz = @buffer.size - shift = extra_pages * page_size - buf_grow!(oldsz + shift) - buf_move! shift, 0, oldsz - with_buf_at(ehdr.num_bytes) { |buf| buf.write "\x00" * (shift - ehdr.num_bytes) } + def normalize_note_segments + return if @replaced_sections.none? do |rsec_name, _| + find_section(rsec_name)&.type == ELFTools::Constants::SHT_NOTE + end - ehdr.e_phoff = ehdr.num_bytes - ehdr.e_shoff = ehdr.e_shoff + shift + new_phdrs = [] + + phdrs_by_type(ELFTools::Constants::PT_NOTE) do |phdr| + # Binaries produced by older patchelf versions may contain empty PT_NOTE segments. + next if @sections.none? do |sec| + sec.header.sh_offset >= phdr.p_offset && sec.header.sh_offset < phdr.p_offset + phdr.p_filesz + end + + new_phdrs += normalize_note_segment(phdr) + end + + new_phdrs.each { |phdr| add_segment!(**phdr.snapshot) } + end + + def normalize_note_segment(phdr) + start_off = phdr.p_offset.to_i + curr_off = start_off + end_off = start_off + phdr.p_filesz + + new_phdrs = [] + + while curr_off < end_off + size = 0 + sections_at_aligned_offset(curr_off) do |sec| + next if sec.type != ELFTools::Constants::SHT_NOTE + + size = sec.header.sh_size.to_i + curr_off = sec.header.sh_offset.to_i + break + end + + raise PatchError, 'cannot normalize PT_NOTE segment: non-contiguous SHT_NOTE sections' if size.zero? + + if curr_off + size > end_off + raise PatchError, 'cannot normalize PT_NOTE segment: partially mapped SHT_NOTE section' + end + + new_phdr = ELFTools::Structs::ELF_Phdr[elf_class].new(endian: endian, **phdr.snapshot) + new_phdr.p_offset = curr_off + new_phdr.p_vaddr = phdr.p_vaddr + (curr_off - start_off) + new_phdr.p_paddr = phdr.p_paddr + (curr_off - start_off) + new_phdr.p_filesz = size + new_phdr.p_memsz = size + + if curr_off == start_off + phdr.assign(new_phdr) + else + new_phdrs << new_phdr + end + + curr_off += size + end + + new_phdrs + end + + def sections_at_aligned_offset(offset) + @sections.each do |sec| + shdr = sec.header + + aligned_offset = Helper.alignup(offset, shdr.sh_addralign) + next if shdr.sh_offset != aligned_offset + + yield sec + end + end + + def shift_sections(shift, start_offset) + ehdr.e_shoff += shift if ehdr.e_shoff >= start_offset @sections.each_with_index do |sec, i| next if i.zero? # dont touch NULL section shdr = sec.header + next if shdr.sh_offset < start_offset + shdr.sh_offset += shift end + end - @segments.each do |seg| + def shift_segment_offset(phdr, shift) + phdr.p_offset += shift + phdr.p_align = page_size if phdr.p_align != 0 && (phdr.p_vaddr - phdr.p_offset) % phdr.p_align != 0 + end + + def shift_segment_virtual_address(phdr, shift) + phdr.p_paddr -= shift if phdr.p_paddr > shift + phdr.p_vaddr -= shift if phdr.p_vaddr > shift + end + + # rubocop:disable Metrics/PerceivedComplexity + def shift_segments(shift, start_offset) + split_index = -1 + split_shift = 0 + + @segments.each_with_index do |seg, idx| phdr = seg.header - phdr.p_offset += shift - phdr.p_align = page_size if phdr.p_align != 0 && (phdr.p_vaddr - phdr.p_offset) % phdr.p_align != 0 + p_start = phdr.p_offset + + if p_start <= start_offset && p_start + phdr.p_filesz > start_offset && + phdr.p_type == ELFTools::Constants::PT_LOAD + raise PatchError, "split_index(#{split_index}) != -1" if split_index != -1 + + split_index = idx + split_shift = start_offset - p_start + + phdr.p_offset = start_offset + phdr.p_memsz -= split_shift + phdr.p_filesz -= split_shift + phdr.p_paddr += split_shift + phdr.p_vaddr += split_shift + + p_start = start_offset + end + + if p_start >= start_offset + shift_segment_offset(phdr, shift) + else + shift_segment_virtual_address(phdr, shift) + end end + raise PatchError, "split_index(#{split_index}) == -1" if split_index == -1 + + [split_index, split_shift] + end + # rubocop:enable Metrics/PerceivedComplexity + + def shift_file(extra_pages, start_offset) + raise PatchError, "start_offset(#{start_offset}) < ehdr.num_bytes" if start_offset < ehdr.num_bytes + + oldsz = @buffer.size + raise PatchError, "oldsz <= start_offset(#{start_offset})" if oldsz <= start_offset + + shift = extra_pages * page_size + buf_grow!(oldsz + shift) + buf_move!(start_offset + shift, start_offset, oldsz - start_offset) + with_buf_at(start_offset) { |buf| buf.write "\x00" * shift } + + ehdr.e_phoff = ehdr.num_bytes + + shift_sections(shift, start_offset) + + split_index, split_shift = shift_segments(shift, start_offset) + + split_phdr = @segments[split_index].header add_segment!( p_type: ELFTools::Constants::PT_LOAD, - p_offset: 0, - p_vaddr: start_page, - p_paddr: start_page, - p_filesz: shift, - p_memsz: shift, + p_offset: split_phdr.p_offset - split_shift - shift, + p_vaddr: split_phdr.p_vaddr - split_shift - shift, + p_paddr: split_phdr.p_paddr - split_shift - shift, + p_filesz: split_shift + shift, + p_memsz: split_shift + shift, p_flags: ELFTools::Constants::PF_R | ELFTools::Constants::PF_W, p_align: page_size ) @@ -701,11 +844,20 @@ def sort_shdrs! return if @sections.empty? section_dep_values = collect_section_to_section_refs - shstrtab_name = @sections[ehdr.e_shstrndx].name + shstrtab = @sections[ehdr.e_shstrndx].header @sections.sort! { |me, you| me.header.sh_offset.to_i <=> you.header.sh_offset.to_i } update_section_idx! restore_section_to_section_refs!(section_dep_values) - ehdr.e_shstrndx = find_section_idx shstrtab_name + @sections.each_with_index do |sec, idx| + ehdr.e_shstrndx = idx if sec.header.sh_offset == shstrtab.sh_offset + end + end + + def jmprel_section_name + sec_name = %w[.rel.plt .rela.plt .rela.IA_64.pltoff].find { |s| find_section(s) } + raise PatchError, 'cannot find section corresponding to DT_JMPREL' unless sec_name + + sec_name end # given a +dyn.d_tag+, returns the section name it must be synced to. @@ -720,12 +872,14 @@ def dyn_tag_to_section_name(d_tag) when ELFTools::Constants::DT_HASH '.hash' when ELFTools::Constants::DT_GNU_HASH - '.gnu.hash' - when ELFTools::Constants::DT_JMPREL - sec_name = %w[.rel.plt .rela.plt .rela.IA_64.pltoff].find { |s| find_section(s) } - raise PatchError, 'cannot find section corresponding to DT_JMPREL' unless sec_name + # return nil if not found, patchelf claims no problem in skipping + find_section('.gnu.hash')&.name + when ELFTools::Constants::DT_MIPS_XHASH + return if ehdr.e_machine != ELFTools::Constants::EM_MIPS - sec_name + '.MIPS.xhash' + when ELFTools::Constants::DT_JMPREL + jmprel_section_name when ELFTools::Constants::DT_REL # regarding .rel.got, NixOS/patchelf says # "no idea if this makes sense, but it was needed for some program" @@ -744,9 +898,26 @@ def dyn_tag_to_section_name(d_tag) # updates dyn tags by syncing it with @section values def sync_dyn_tags! + dyn_table_offset = nil each_dynamic_tags do |dyn, buf_off| + dyn_table_offset ||= buf_off + sec_name = dyn_tag_to_section_name(dyn.d_tag) - next unless sec_name + + unless sec_name + if dyn.d_tag == ELFTools::Constants::DT_MIPS_RLD_MAP_REL && ehdr.e_machine == ELFTools::Constants::EM_MIPS + rld_map = find_section('.rld_map') + dyn.d_val = if rld_map + rld_map.header.sh_addr.to_i - (buf_off - dyn_table_offset) - + find_section('.dynamic').header.sh_addr.to_i + else + Logger.warn 'DT_MIPS_RLD_MAP_REL entry is present, but .rld_map section is not' + 0 + end + end + + next + end shdr = find_section(sec_name).header dyn.d_val = dyn.d_tag == ELFTools::Constants::DT_STRSZ ? shdr.sh_size.to_i : shdr.sh_addr.to_i @@ -785,29 +956,46 @@ def phdrs_by_type(seg_type) end end - # To be used when the section header does not exist. - def dummy_shdr - ELFTools::Structs::ELF_Shdr.new(endian: endian, elf_class: elf_class) + # Returns a blank shdr if the section doesn't exist. + def find_or_create_section_header(rsec_name) + shdr = find_section(rsec_name)&.header + shdr ||= ELFTools::Structs::ELF_Shdr.new(endian: endian, elf_class: elf_class) + shdr end - def write_replaced_sections(cur_off, start_addr, start_offset) - sht_no_bits = ELFTools::Constants::SHT_NOBITS - + def overwrite_replaced_sections # the original source says this has to be done separately to # prevent clobbering the previously written section contents. @replaced_sections.each do |rsec_name, _| shdr = find_section(rsec_name)&.header next unless shdr - with_buf_at(shdr.sh_offset) { |b| b.fill('X', shdr.sh_size) } if shdr.sh_type != sht_no_bits + next if shdr.sh_type == ELFTools::Constants::SHT_NOBITS + + with_buf_at(shdr.sh_offset) { |b| b.fill('X', shdr.sh_size) } end + end + + def write_section_aligment(shdr) + return if shdr.sh_type == ELFTools::Constants::SHT_NOTE && shdr.sh_addralign <= @section_alignment + + shdr.sh_addralign = @section_alignment + end + + def section_bounds_within_segment?(s_start, s_end, p_start, p_end) + (s_start >= p_start && s_start < p_end) || (s_end > p_start && s_end <= p_end) + end + + def write_replaced_sections(cur_off, start_addr, start_offset) + overwrite_replaced_sections + + noted_phdrs = Set.new # the sort is necessary, the strategy in ruby and Cpp to iterate map/hash # is different, patchelf v0.10 iterates the replaced_sections sorted by # keys. @replaced_sections.sort.each do |rsec_name, rsec_data| - shdr = find_section(rsec_name)&.header - shdr ||= dummy_shdr + shdr = find_or_create_section_header(rsec_name) Logger.debug <<~DEBUG rewriting section '#{rsec_name}' @@ -817,18 +1005,43 @@ def write_replaced_sections(cur_off, start_addr, start_offset) with_buf_at(cur_off) { |b| b.write rsec_data } + orig_sh_offset = shdr.sh_offset.to_i + orig_sh_size = shdr.sh_size.to_i + shdr.sh_offset = cur_off shdr.sh_addr = start_addr + (cur_off - start_offset) shdr.sh_size = rsec_data.size - shdr.sh_addralign = @section_alignment + + write_section_aligment(shdr) seg_type = { '.interp' => ELFTools::Constants::PT_INTERP, - '.dynamic' => ELFTools::Constants::PT_DYNAMIC + '.dynamic' => ELFTools::Constants::PT_DYNAMIC, + '.MIPS.abiflags' => ELFTools::Constants::PT_MIPS_ABIFLAGS, + '.note.gnu.property' => ELFTools::Constants::PT_GNU_PROPERTY }[rsec_name] phdrs_by_type(seg_type) { |phdr| sync_sec_to_seg(shdr, phdr) } + if shdr.sh_type == ELFTools::Constants::SHT_NOTE + phdrs_by_type(ELFTools::Constants::PT_NOTE) do |phdr, idx| + next if noted_phdrs.include?(idx) + + s_start = orig_sh_offset + s_end = s_start + orig_sh_size + p_start = phdr.p_offset + p_end = p_start + phdr.p_filesz + + next unless section_bounds_within_segment?(s_start, s_end, p_start, p_end) + + raise PatchError, 'unsupported overlap of SHT_NOTE and PT_NOTE' if p_start != s_start || p_end != s_end + + sync_sec_to_seg(shdr, phdr) + + noted_phdrs << idx + end + end + cur_off += Helper.alignup(rsec_data.size, @section_alignment) end @replaced_sections.clear diff --git a/lib/patchelf/helper.rb b/lib/patchelf/helper.rb index 71c1a3e..10b740d 100644 --- a/lib/patchelf/helper.rb +++ b/lib/patchelf/helper.rb @@ -3,9 +3,6 @@ module PatchELF # Helper methods for internal usage. module Helper - # The size of one page. - PAGE_SIZE = 0x1000 - module_function # Color codes for pretty print. @@ -16,6 +13,23 @@ module Helper error: "\e[38;5;196m" # heavy red }.freeze + # The size of one page. + def page_size(e_machine = nil) + # Different architectures have different minimum section alignments. + case e_machine + when ELFTools::Constants::EM_SPARC, + ELFTools::Constants::EM_MIPS, + ELFTools::Constants::EM_PPC, + ELFTools::Constants::EM_PPC64, + ELFTools::Constants::EM_AARCH64, + ELFTools::Constants::EM_TILEGX, + ELFTools::Constants::EM_LOONGARCH + 0x10000 + else + 0x1000 + end + end + # For wrapping string with color codes for prettier inspect. # @param [String] str # Content to colorize. @@ -48,7 +62,7 @@ def color_enabled? # #=> 32 # aligndown(0x10, 0x8) # #=> 16 - def aligndown(val, align = PAGE_SIZE) + def aligndown(val, align = page_size) val - (val & (align - 1)) end @@ -63,7 +77,7 @@ def aligndown(val, align = PAGE_SIZE) # #=> 64 # alignup(0x10, 0x8) # #=> 16 - def alignup(val, align = PAGE_SIZE) + def alignup(val, align = page_size) (val & (align - 1)).zero? ? val : (aligndown(val, align) + align) end end diff --git a/lib/patchelf/mm.rb b/lib/patchelf/mm.rb index b6202cd..ec2b1ea 100644 --- a/lib/patchelf/mm.rb +++ b/lib/patchelf/mm.rb @@ -161,7 +161,7 @@ def shift_attributes seg.header.p_offset += extend_size # We have to change align of LOAD segment since ld.so checks it. - seg.header.p_align = Helper::PAGE_SIZE if seg.is_a?(ELFTools::Segments::LoadSegment) + seg.header.p_align = Helper.page_size if seg.is_a?(ELFTools::Segments::LoadSegment) end @elf.header.e_shoff += extend_size if @elf.header.e_shoff >= threshold diff --git a/spec/files/Makefile b/spec/files/Makefile index 8884a0e..0e56fad 100644 --- a/spec/files/Makefile +++ b/spec/files/Makefile @@ -10,6 +10,8 @@ all: $(SRC) $(GCC) $< -Wl,--enable-new-dtags -Wl,-rpath=/not_exists:/lib:/pusheen/is/fat -o runpath.elf $(GCC) $< -fPIE -pie -m32 -o pie32.elf $(GCC) $< -fPIE -pie -o nosection.elf && ./patch.rb + $(GCC) $< -shared -fPIC -Wl,--disable-new-dtags -Wl,--build-id -o libbuildid.so + $(GCC) contiguous-note-sections.s -shared -pie -nostdlib -T contiguous-note-sections.ld -o contiguous-note-sections.elf clean: $(RM) *.elf *.so diff --git a/spec/files/README.md b/spec/files/README.md index 2d119fe..5d98b7a 100644 --- a/spec/files/README.md +++ b/spec/files/README.md @@ -42,3 +42,44 @@ when `patchelf_compatible: true`, and by default fails with `NotImplementedError ### 2.3) Todo Update `why was this added?` after implementing `new_load_method`(also remove this and the last sentence;) + + +## 3. libbuildid.so + +### 3.1) Source +It is generated by the included Makefile. + +### 3.2) Why was this added? +This is a library with the `.gnu.note.build-id` section. It is used to test note sections are correctly relocated along with note segments. + + +## 4. contiguous-note-sections.elf + +### 4.1) Source +It is generated by the included Makefile, using the included contiguous-note-section.ld and contiguous-note-section.s files. The original source can be found at https://github.com/NixOS/patchelf/tree/0.15.0/tests. + +### 4.2) Why was this added? +This covers an edge case of when there are multiple note sections back-to-back with varying alignments. This test ensures that this does not cause the patcher to error and think there are gaps. + + +## 5. empty-note + +### 5.1) Source +This ELF is hand crafted to contain a PT_NOTE segment that has a non-zero size but does not have any sections within that segment. + +### 5.2) Why was this added? +This tests backwards compatibility with older versions of the patchelf-compatible saver that could produce such binaries. This test ensures that this does not cause the patcher to error. + + +## 6. libbig-dynstr.debug + +### 6.1) Source +This was built from sources at https://github.com/NixOS/patchelf/tree/0.15.0/tests. + +To build, run `./bootstrap.sh && ./configure && make`. The binary can be found in `tests`. + +### 6.2) Why was this added? +This tests a binary that only contains debug data and no dynamic section. + +### 6.3) Todo +Simplify this binary's build procedure and include it in our Makefile. diff --git a/spec/files/contiguous-note-sections.elf b/spec/files/contiguous-note-sections.elf new file mode 100755 index 0000000..a91838a Binary files /dev/null and b/spec/files/contiguous-note-sections.elf differ diff --git a/spec/files/contiguous-note-sections.ld b/spec/files/contiguous-note-sections.ld new file mode 100644 index 0000000..230b8dc --- /dev/null +++ b/spec/files/contiguous-note-sections.ld @@ -0,0 +1,24 @@ +PHDRS +{ + headers PT_PHDR PHDRS ; + notes PT_NOTE; + text PT_LOAD FILEHDR PHDRS ; + data PT_LOAD ; + interp PT_INTERP ; + dynamic PT_DYNAMIC ; +} + +SECTIONS +{ + . = SIZEOF_HEADERS; + . = ALIGN(4); + + .note.my-section0 : { *(.note.my-section0) } :notes :text + .note.my-section1 : { *(.note.my-section1) } :notes :text + + .interp : { *(.interp) } :text :interp + .text : { *(.text) } :text + .rodata : { *(.rodata) } /* defaults to :text */ + + .data : { *(.data) } :data +} diff --git a/spec/files/contiguous-note-sections.s b/spec/files/contiguous-note-sections.s new file mode 100644 index 0000000..fbc0685 --- /dev/null +++ b/spec/files/contiguous-note-sections.s @@ -0,0 +1,23 @@ +/* + * Testcase for error: + * patchelf: cannot normalize PT_NOTE segment: non-contiguous SHT_NOTE sections + */ +.section ".note.my-section0", "a", %note + .align 4 + .long 1f - 0f /* name length (not including padding) */ + .long 3f - 2f /* desc length (not including padding) */ + .long 1 /* type = NT_VERSION */ +0: .asciz "my-version-12345" /* name */ +1: .align 4 +2: .long 1 /* desc - toolchain version number, 32-bit LE */ +3: .align 4 + +.section ".note.my-section1", "a", %note + .align 8 + .long 1f - 0f /* name length (not including padding) */ + .long 3f - 2f /* desc length (not including padding) */ + .long 1 /* type = NT_VERSION */ +0: .asciz "my-version-1" /* name */ +1: .align 4 +2: .long 1 /* desc - toolchain version number, 32-bit LE */ +3: .align 4 diff --git a/spec/files/empty-note b/spec/files/empty-note new file mode 100755 index 0000000..d58435e Binary files /dev/null and b/spec/files/empty-note differ diff --git a/spec/files/libbig-dynstr.debug b/spec/files/libbig-dynstr.debug new file mode 100755 index 0000000..22b6ff0 Binary files /dev/null and b/spec/files/libbig-dynstr.debug differ diff --git a/spec/files/libbuildid.so b/spec/files/libbuildid.so new file mode 100755 index 0000000..ba67146 Binary files /dev/null and b/spec/files/libbuildid.so differ diff --git a/spec/patcher_spec.rb b/spec/patcher_spec.rb index eae8222..28b37e6 100644 --- a/spec/patcher_spec.rb +++ b/spec/patcher_spec.rb @@ -137,6 +137,22 @@ def get_patcher(filename, on_error: :log) expect($CHILD_STATUS.exitstatus).to eq 0 end end + + it 'does not error on contiguous note sections' do + patcher = get_patcher('contiguous-note-sections.elf') + patcher.interpreter = '/lib64/ld-linux-x86-64.so.2' + with_tempfile do |tmp| + expect { patcher.save(tmp, patchelf_compatible: true) }.not_to raise_error + end + end + + it 'does not error on blank note segments' do + patcher = get_patcher('empty-note') + patcher.interpreter = '/lib64/ld-linux-x86-64.so.2' + with_tempfile do |tmp| + expect { patcher.save(tmp, patchelf_compatible: true) }.not_to raise_error + end + end end end @@ -297,6 +313,50 @@ def get_patcher(filename, on_error: :log) expect(saved_patcher.rpath).to eq 'o O' end end + + it 'can patch note segments correctly' do + patcher = get_patcher('libbuildid.so') + + rpath = 'A' * 10_000 + patcher.rpath = rpath + + def note_section_in_segment_count(patcher) + patcher.elf.segments_by_type(:NOTE).sum do |seg| + phdr = seg.header + patcher.elf.sections_by_type(:NOTE).count do |sec| + shdr = sec.header + shdr.sh_offset >= phdr.p_offset && shdr.sh_offset + shdr.sh_size <= phdr.p_offset + phdr.p_filesz + end + end + end + + prev_note_count = note_section_in_segment_count(patcher) + + with_tempfile do |tmp| + patcher.save tmp, patchelf_compatible: true + + saved_patcher = described_class.new(tmp, on_error: :silent) + expect(saved_patcher.runpath).to be_nil + expect(saved_patcher.rpath).to eq rpath + expect(note_section_in_segment_count(saved_patcher)).to eq prev_note_count + end + end + + it 'will not raise a Ruby error on executables without a dynamic section' do + patcher = get_patcher('libbig-dynstr.debug', on_error: :silent) + expect(patcher.runpath).to be_nil + expect(patcher.rpath).to be_nil + + patcher.rpath = 'ABC' + with_tempfile do |tmp| + expect(PatchELF::Logger).to receive(:error).with('no dynamic tags') + patcher.save tmp, patchelf_compatible: true + + saved_patcher = described_class.new(tmp, on_error: :silent) + expect(saved_patcher.runpath).to be_nil + expect(saved_patcher.rpath).to be_nil + end + end end end