Skip to content

Commit

Permalink
Bugfixes for NixOS/patchelf-compatible saver (#39)
Browse files Browse the repository at this point in the history
* Bugfixes for NixOS/patchelf-compatible saver

* Fix handling of NOTE sections - NixOS/patchelf@43a3348 and subsequent bugfix commits
* Fix default page size on some architectures - NixOS/patchelf@0470d69
* Fix corruption if sh_offset and sh_address differs - NixOS/patchelf@83aa89a
* Fix corruption with multiple shstrtab sections - NixOS/patchelf@a89d508
* Fix corruption due to incorrect PT_PHDR virtaddr - NixOS/patchelf@4efbce4
* Fix error patching binaries without .gnu.hash - NixOS/patchelf@57fe1d3
* Fix error using debug-only stripped binaries - NixOS/patchelf@64fe89b

* Skip blank PT_NOTE segments - Bo98/patchelf@1097751
* Sync .note.gnu.property to PT_GNU_PROPERTY - Bo98/patchelf@de79f1a
* Rework file shifting to avoid sections crossing multiple segments - Bo98/patchelf@109b771

* Support LoongArch - NixOS/patchelf@4b87d4f
* Support special MIPS sections - NixOS/patchelf@b240bb8, NixOS/patchelf@bf73d6e, NixOS/patchelf@820da7b

* Add various tests for alt_saver fixes
  • Loading branch information
Bo98 authored Oct 28, 2022
1 parent a4c0873 commit e002a96
Show file tree
Hide file tree
Showing 12 changed files with 434 additions and 57 deletions.
315 changes: 264 additions & 51 deletions lib/patchelf/alt_saver.rb

Large diffs are not rendered by default.

24 changes: 19 additions & 5 deletions lib/patchelf/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/patchelf/mm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions spec/files/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
41 changes: 41 additions & 0 deletions spec/files/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Binary file added spec/files/contiguous-note-sections.elf
Binary file not shown.
24 changes: 24 additions & 0 deletions spec/files/contiguous-note-sections.ld
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions spec/files/contiguous-note-sections.s
Original file line number Diff line number Diff line change
@@ -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
Binary file added spec/files/empty-note
Binary file not shown.
Binary file added spec/files/libbig-dynstr.debug
Binary file not shown.
Binary file added spec/files/libbuildid.so
Binary file not shown.
60 changes: 60 additions & 0 deletions spec/patcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down

0 comments on commit e002a96

Please sign in to comment.