NixOS / patchelf

A small utility to modify the dynamic linker and RPATH of ELF executables
GNU General Public License v3.0
3.47k stars 482 forks source link

--add-needed can decrease the address of the first page below ``vm.mmap_min_addr`` #404

Open TheBrokenRail opened 1 year ago

TheBrokenRail commented 1 year ago

Describe the bug

If you run --add-needed on certain executables, it can decrease the address of the first page to below vm.mmap_min_addr, meaning that when it runs, you'll only get Segmentation fault.

Steps To Reproduce

  1. Use an ARM system (the binary this occurs with is ARM)
  2. Run sudo sysctl vm.mmap_min_addr=32768 (Default value on Ubuntu 20.04 ARM64)
  3. Download and extract minecraft-pi.zip
  4. Try running it, you'll probably get a linking error, that's expected because you're probably not on an RPI.
  5. Run patchelf --add-needed libtest.so minecraft-pi
  6. Now try running it! You'll get Segmentation fault (it'll also refuse to core dump). If you run it with /lib/ld-linux-armhf.so.3 directly, you'll get a more detailed error of failed to map segment from shared object.
  7. Now run sudo sysctl vm.mmap_min_addr=0
  8. Try running it one final time! You'll notice it's now back to your regularly scheduled linker error.

Expected behavior

Running --add-needed doesn't mess with the address of the first page.

patchelf --version output

$ ./patchelf --version
patchelf 0.15.0

Additional context

Running strace on /lib/ld-linux-armhf.so.3 with the modified binary gives me:

execve("./squashfs-root/lib/minecraft-pi-reborn-server/sysroot/lib/ld-linux-armhf.so.3", ["./squashfs-root/lib/minecraft-pi"..., "./mcpi3"], 0xffffed661ca8 /* 24 vars */strace: [ Process PID=8203 runs in 32 bit mode. ]
strace: WARNING: Proper structure decoding for this personality is not supported, please consider building strace with mpers support enabled.
) = 0
brk(NULL)                               = 0x1dda000
openat(AT_FDCWD, "./mcpi3", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\2\0(\0\1\0\0\0\234\33\1\0004\0\0\0"..., 512) = 512
pread64(3, "\4\0\0\0\20\0\0\0\1\0\0\0GNU\0\0\0\0\0\2\0\0\0\6\0\0\0\32\0\0\0", 32, 16848) = 32
getcwd("/home/server/server", 128)      = 20
mmap2(0x7000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = -1 EPERM (Operation not permitted)
close(3)                                = 0
writev(2, [{iov_base="./mcpi3", iov_len=7}, {iov_base=": ", iov_len=2}, {iov_base="error while loading shared libra"..., iov_len=36}, {iov_base=": ", iov_len=2}, {iov_base="./mcpi3", iov_len=7}, {iov_base=": ", iov_len=2}, {iov_base="failed to map segment from share"..., iov_len=40}, {iov_base="", iov_len=0}, {iov_base="", iov_len=0}, {iov_base="\n", iov_len=1}], 10./mcpi3: error while loading shared libraries: ./mcpi3: failed to map segment from shared object
) = 97
exit_group(127)                         = ?
+++ exited with 127 +++

And running strace on /lib/ld-linux-armhf.so.3 with the original binary and filtering for mmap2 gives me:

mmap2(0x8000, 1200128, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0x8000
mmap2(0x135000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x125000) = 0x135000
mmap2(0x137000, 322528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x137000
mmap2(NULL, 27653, PROT_READ, MAP_PRIVATE, 3, 0) = 0xf7921000

I'm not entirely sure that "the address of the first page" is the right terminology. I chose it because it's what PatchELF called 0x8000 (on the original) and 0x7000 (on the modified) in the debug info. And those happen to be the same addresses that either succeed (the original) or fail (the modified) to map.

Here's the debug info when modifying the binary:

$ patchelf --debug --add-needed libtest.so minecraft-pi
patching ELF file 'minecraft-pi'
DT_NULL index is 34
replacing section '.dynamic' with size 328
replacing section '.dynstr' with size 6619
this is an executable
using replaced section '.dynstr'
using replaced section '.dynamic'
last replaced is 24
looking at section '.interp'
replacing section '.interp' which is in the way
looking at section '.note.ABI-tag'
replacing section '.note.ABI-tag' which is in the way
looking at section '.note.gnu.build-id'
replacing section '.note.gnu.build-id' which is in the way
looking at section '.hash'
replacing section '.hash' which is in the way
looking at section '.gnu.hash'
replacing section '.gnu.hash' which is in the way
looking at section '.dynsym'
replacing section '.dynsym' which is in the way
looking at section '.dynstr'
looking at section '.gnu.version'
first reserved offset/addr is 0x4498/0xc498
first page is 0x8000
needed space is 17932
needed space is 17964
needed pages is 1
changing alignment of program header 3 from 32768 to 4096
changing alignment of program header 4 from 32768 to 4096
clearing first 21284 bytes
rewriting section '.dynamic' from offset 0x126638 (size 320) to offset 0x174 (size 328)
rewriting section '.dynstr' from offset 0x3ac8 (size 6607) to offset 0x2bc (size 6619)
rewriting section '.dynsym' from offset 0x2578 (size 5456) to offset 0x1c98 (size 5456)
rewriting section '.gnu.hash' from offset 0x1b0c (size 2668) to offset 0x31e8 (size 2668)
rewriting section '.hash' from offset 0x1194 (size 2424) to offset 0x3c54 (size 2424)
rewriting section '.interp' from offset 0x1134 (size 25) to offset 0x45cc (size 25)
rewriting section '.note.ABI-tag' from offset 0x1150 (size 32) to offset 0x45e8 (size 32)
rewriting section '.note.gnu.build-id' from offset 0x1170 (size 36) to offset 0x4608 (size 36)
rewriting symbol table section 3
writing minecraft-pi

And here's the log when running it a second time:

$ patchelf --debug --add-needed libtest.so minecraft-pi
patching ELF file 'minecraft-pi'
DT_NULL index is 35
replacing section '.dynamic' with size 336
replacing section '.dynstr' with size 6631
this is an executable
using replaced section '.dynamic'
using replaced section '.dynstr'
last replaced is 2
looking at section '.dynamic'
looking at section '.dynstr'
first reserved offset/addr is 0x1c98/0x8c98
first page is 0x7000
needed space is 7340
needed space is 7372
needed pages is 1
clearing first 11012 bytes
rewriting section '.dynamic' from offset 0x1174 (size 328) to offset 0x194 (size 336)
rewriting section '.dynstr' from offset 0x12bc (size 6619) to offset 0x2e4 (size 6631)
rewriting symbol table section 3
writing minecraft-pi

Notice that the "first page" value has decreased by 0x1000.

Mic92 commented 1 year ago

Shifting sections to lower addresses is indeed unintended. Maybe some rounding error?

Mic92 commented 1 year ago

Could you test if this is still reproducible after https://github.com/NixOS/patchelf/pull/415 being merged?

TheBrokenRail commented 1 year ago

Sorry for the very late response, but I tested the latest master, and the issue seems to still be occurring.

For instance:

# readelf of the unmodified binary
$ readelf -lw minecraft-pi-old

Elf file type is EXEC (Executable file)
Entry point 0x12360
There are 8 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x11c2ec 0x001242ec 0x001242ec 0x085e0 0x085e0 R   0x4
  PHDR           0x000034 0x00008034 0x00008034 0x00100 0x00100 R E 0x4
  INTERP         0x000134 0x00008134 0x00008134 0x00019 0x00019 R   0x1
      [Requesting program interpreter: /lib/ld-linux-armhf.so.3]
  LOAD           0x000000 0x00008000 0x00008000 0x1248d0 0x1248d0 R E 0x8000
  LOAD           0x125000 0x00135000 0x00135000 0x0138c 0x50be0 RW  0x8000
  DYNAMIC        0x125638 0x00135638 0x00135638 0x00140 0x00140 RW  0x4
  NOTE           0x000150 0x00008150 0x00008150 0x00044 0x00044 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

 Section to Segment mapping:
  Segment Sections...
   00     .ARM.exidx 
   01     
   02     .interp 
   03     .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.extab .ARM.exidx .eh_frame 
   04     .init_array .fini_array .jcr .data.rel.ro .dynamic .got .data .bss 
   05     .dynamic 
   06     .note.ABI-tag .note.gnu.build-id 
   07     
Contents of the .eh_frame section:

00000000 ZERO terminator

# readelf of the modified binary
$ readelf -lw minecraft-pi

Elf file type is EXEC (Executable file)
Entry point 0x12360
There are 10 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00007034 0x00007034 0x00140 0x00140 R E 0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
  LOAD           0x000000 0x00007000 0x00007000 0x05498 0x05498 RW  0x1000
  INTERP         0x000174 0x00007174 0x00007174 0x00019 0x00019 R   0x1
      [Requesting program interpreter: /lib/ld-linux-armhf.so.3]
  NOTE           0x000190 0x00007190 0x00007190 0x00020 0x00020 R   0x4
  NOTE           0x0001b0 0x000071b0 0x000071b0 0x00024 0x00024 R   0x4
  DYNAMIC        0x0044e4 0x0000b4e4 0x0000b4e4 0x00148 0x00148 RW  0x4
  LOAD           0x005498 0x0000c498 0x0000c498 0x120438 0x120438 R E 0x1000
  EXIDX          0x11d2ec 0x001242ec 0x001242ec 0x085e0 0x085e0 R   0x4
  LOAD           0x126000 0x00135000 0x00135000 0x0138c 0x50be0 RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00     
   01     
   02     .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .dynamic 
   03     .interp 
   04     .note.ABI-tag 
   05     .note.gnu.build-id 
   06     .dynamic 
   07     .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.extab .ARM.exidx .eh_frame 
   08     .ARM.exidx 
   09     .init_array .fini_array .jcr .data.rel.ro .got .data .bss 
Contents of the .eh_frame section:

00000000 ZERO terminator

Note the the virtual address of the first LOAD segment still decreased.