Custom Libc

Problem: I want to run a binary that depends on a more recent libc than I have installed.

The binary is closed-source and cannot be recompiled. The test binary is /bin/sleep and the more recent libc is extracted in /opt/libc/.

Set LD_LIBRARY_PATH

$ LD_LIBRARY_PATH=/opt/libc/lib/x86_64-linux-gnu:/opt/libc/usr/lib/x86_64-linux-gnu /bin/sleep 10000
Segmentation fault (core dumped)

Doesn’t work, because loader and libc version must match.

Execute with ld loader

/opt/libc/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 --library-path /opt/libc/lib/x86_64-linux-gnu:/opt/libc/usr/lib/x86_64-linux-gnu /bin/sleep 10000

Works, but:

  • ps ax contains ld-linux and library-path. Not a problem, just ugly
  • /proc/$PID/exe is not a symlink to /bin/sleep, but to /opt/.../ld-2.31.so!​ Problematic if own binary path is used for something, e.g. to look up path to other files.

Change ELF interpreter

Each ELF binary has a dependency on an “ELF interpreter”:

$ ldd /bin/sleep|grep ld
/lib64/ld-linux-x86-64.so.2 (0x00007fab02051000)

Or like this:

$ patchelf --print-interpreter /bin/sleep
/lib64/ld-linux-x86-64.so.2

Patchelf can change the interpreter:

$ patchelf --set-interpreter /opt/libc/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 sleep

But, calling patched “sleep” crashes … version mismatch!

Set LD_LIBRARY_PATH?

$ LD_LIBRARY_PATH=/opt/libc/lib/x86_64-linux-gnu:/opt/libc/usr/lib/x86_64-linux-gnu /tmp/sleep

Works, but hmm.​ Patchelf can also set rpath

$ patchelf --set-rpath /opt/libc/lib/x86_64-linux-gnu:/opt/libc/usr/lib/x86_64-linux-gnu sleep

Now this binary uses the new libc and can just be executed​.

Not setting LD_LIBRARY_PATH has another advantage: This variable doesn’t get inherited to forked processes. Depending on what my binary forks we are back to segmentation faults, because of a mismatch of C libs.

More trouble

Some binaries need libc-related libs, especially librt: in this case we are back to setting LD_LIBRARY_PATH and potential trouble with fork+exec. One way out is to add a shim-script. Lets call this binary: started-by-sleep (uses standard libc).

  • Rename the original binary, e.g. started-by-sleep -> started-by-sleep.orig
  • Create a patchelf’d bash (see above) and place it at e.g. /opt/libc/bin/bash
  • Create a shell script started-by-sleep with this content:
#!/opt/libc/bin/bash

unset LD_LIBRARY_PATH
exec started-by-sleep.orig "$@"

Even more trouble

Mixing binaries with different libc at run time usually works, but if they use shared memory to communicate there might again be trouble. Then even binaries that still only need the standard libc might needed to be treated as well.

Minimal Chroot

The classic way is of course a chroot, but

  • I just need a more recent libc and maybe libstc++, nothing else
  • To transparently run it, a lot of bind mounts are needed to mount the existing libs
  • Not really tried :)

Full Chroot

With a full chroot: debootstrap a full debian/ubuntu (or whatever is needed), bind mount /home and a few others and be done with it. (Containers would also work, of course).

Downside here: it needs a lot of disk space which is very scarce in my scenario.

Summary

For any normal system first consider updating libc, if this is not an option, use a full chroot. Going with patchelf is hacky and painful; don’t do it unless disk space is the limiting factor and not the time you may waste on it.