patchelf
patchelf copied to clipboard
--add-needed can decrease the address of the first page below ``vm.mmap_min_addr``
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
- Use an ARM system (the binary this occurs with is ARM)
- Run
sudo sysctl vm.mmap_min_addr=32768(Default value on Ubuntu 20.04 ARM64) - Download and extract minecraft-pi.zip
- Try running it, you'll probably get a linking error, that's expected because you're probably not on an RPI.
- Run
patchelf --add-needed libtest.so minecraft-pi - 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.3directly, you'll get a more detailed error offailed to map segment from shared object. - Now run
sudo sysctl vm.mmap_min_addr=0 - 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.
Shifting sections to lower addresses is indeed unintended. Maybe some rounding error?
Could you test if this is still reproducible after https://github.com/NixOS/patchelf/pull/415 being merged?
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.