NixOS / patchelf

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

Using patchelf and llvm-strip in sequence corrupts ELF files #507

Open mologie opened 1 year ago

mologie commented 1 year ago

Describe the bug

Running patchelf on any ELF file causes llvm-strip-14 to corrupt it afterwards. (One might argue that this is an llvm-strip bug, and it probably is, but patchelf might be able to produce an ELF file in a format that's still processable with LLVM.)

Affects: All patchelf versions from 0.14 to 0.18 that I tested, LLVM 14 and 15

Steps To Reproduce

tl;dr: Run patchelf --set-rpath '$ORIGIN' on any binary, then run llvm-strip with standard args on it. The output is always corrupted.

Use an Ubuntu 22.04 image and install llvm-14-tools for llvm-strip-14. (llvm-15-tools is similarly affected.)

$ wget https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-x86_64.tar.gz
[...]
$ tar xf patchelf-0.18.0-x86_64.tar.gz
$ export PATH=$PWD/bin:$PATH
$ echo 'int main() { return 0; }' > a.c
$ clang -Os -o a a.c
$ file a
a: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5a1afb6fe3c09ec9355fe3dfe35d4160dadb606d, for GNU/Linux 3.2.0, not stripped
$ ./a
$ llvm-strip --version
llvm-strip, compatible with GNU strip
Ubuntu LLVM version 14.0.0

  Optimized build.
  Default target: x86_64-pc-linux-gnu
  Host CPU: skylake
$ clang --version
Ubuntu clang version 14.0.0-1ubuntu1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$ patchelf --version
patchelf 0.18.0
$ llvm-strip -o a.stripped a
$ ./a.stripped
$ patchelf --set-rpath '$ORIGIN' --output a.patched a
$ ./a.patched
$ llvm-strip -o a.patched.stripped a.patched
$ ldd a.patched.stripped
    not a dynamic executable
$ file a.patched.stripped
a.patched.stripped: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5a1afb6fe3c09ec9355fe3dfe35d4160dadb606d, for GNU/Linux 3.2.0, stripped
$ ./a.patched.stripped
[1]    31807 segmentation fault  ./a.patched.stripped

The a.* binaries are attached: patchelf-strip-corruption.tar.gz

Expected behavior

I expected llvm-strip to work. Specifically, I'd have expected the ldd a.patched.stripped command to report that it's still a dynamic executable, and ./a.patched.stripped should not have segfaulted.

patchelf --version output

patchelf 0.18.0

Additional context

Files in this report are from Ubuntu 22.04 x86_64, but it appears to happen on any OS and with any LLVM distribution. Gave Arch Linux x86_64 and Ubuntu for arm64 a shot. Strip from binutils does not corrupt the ELF files.

HeavenVolkoff commented 1 year ago

Just hit this bug. I have a script that, after building a couple of shared libraries, executes patchelf --set-rpath '$ORIGIN' then llvm-strip-16 -S, and I got that same result as @mologie:

ubuntu@ubuntu:~/Workspace/native-deps/out/lib$ ldd libpdfium.so 
    not a dynamic executable

Inverting the patchelf call with llvm-strip fixed the issue

mhsmith commented 4 weeks ago

I encountered this issue with patchelf 0.18.0 (from Ubuntu 24.04 on GitHub Actions) and llvm-strip 18.0.2 (from the Android NDK version 27.1.12297006).

The error message from Android was very confusing:

dlopen failed: cannot find "ED_set_revocationDate" from verneed[0] in DT_NEEDED list for "/data/app/~~Rr0R8wZnu25DjWkjOaOrRg==/com.chaquo.python.demo3-rax9ByToGYEcHJfwYeyCzg==/base.apk!/lib/arm64-v8a/libcrypto_python.so"

It looks like it's trying to read the "version needs" section but is finding a fragment of a symbol where there should be a library name. But llvm-readelf shows that section to be normal:

Version needs section '.gnu.version_r' contains 2 entries:
 Addr: 0000000000023284  Offset: 0x023284  Link: 26 (.dynstr)
  0x0000: Version: 1  File: libdl.so  Cnt: 1
  0x0020:   Name: LIBC  Flags: none  Version: 7
  0x0010: Version: 1  File: libc.so  Cnt: 1
  0x0030:   Name: LIBC  Flags: none  Version: 6

The offending string appears in the middle of the .dynsym table:

3789: 00000000002e8c78    96 FUNC    GLOBAL DEFAULT    12 X509_REVOKED_set_revocationDate@@OPENSSL_3.0.0

As in the previous comment, calling strip before patchelf fixes the problem. Unfortunately this isn't an adequate workaround on Android, because the app build tool strips libraries automatically, including in my users' apps which I have no control over.