FS#52637 - mkinitcpio generates incorrect initramfs if ldd does not work
Attached to Project:
Arch Linux
Opened by Adrián Pérez de Castro (aperezdc) - Thursday, 19 January 2017, 17:52 GMT
Last edited by Doug Newgard (Scimmia) - Saturday, 21 January 2017, 00:29 GMT
Opened by Adrián Pérez de Castro (aperezdc) - Thursday, 19 January 2017, 17:52 GMT
Last edited by Doug Newgard (Scimmia) - Saturday, 21 January 2017, 00:29 GMT
|
Details
Description:
For some reason, “ldd” thinks that dynamic binaries are not: % ldd /bin/ls not a dynamic executable This means that “mkinitcpio” is creating unbootable initramfs images, because they are lacking the needed dynamic libraries. Using “readelf” correctly lists the needed ELF shared objects: % readelf -d /usr/bin/ls|grep '(NEEDED)' 0x0000000000000001 (NEEDED) Shared library: [libcap.so.2] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] % Additional info: * mkinitcpio 22-1 * glibc 2.24-2 * gcc-multlib 6.3.1-1 * gcc-libs-multilib 6.3.1-1 Steps to reproduce: 1. Run: % mkinitcpio -p linux 2. Check the output of: % lsinitcpio /boot/initramfs-linux.img | grep 'lib/.*\.so' Possible solution: See the attached patch for “mkinitcpio”. It uses “readelf -d” to figure out the depedencies, which always works: it inspects the ELF headers of the binaries instead of trying to execute them passing funky environment variables and relying on glibc-specific behaviour (see also http://www.catonmat.net/blog/ldd-arbitrary-code-execution/ for more reasons on why “ldd” should be avoided). |
This task depends upon
Full stop. You'll need to figure out why. readelf doesn't always work, because you aren't considering ld.so.conf (and a host of other things, such as multi-arch).
RTLDLIST="/usr/lib/ld-linux.so.2 /usr/lib64/ld-linux-x86-64.so.2 /usr/libx32/ld-linux-x32.so.2"
Which made me think that “ldd” may be trying to run the program through the 32-bit dynamic linker first. So I uninstalled all the 32-bit packages from my system, and switched from “gcc{,-libs}-multilib” to “gcc{,-libs}”... still the same issue. The trace of running “bash -x /usr/bin/ldd /usr/bin/ls” is:
+ TEXTDOMAIN=libc
+ TEXTDOMAINDIR=/usr/share/locale
+ RTLDLIST='/usr/lib/ld-linux.so.2 /usr/lib64/ld-linux-x86-64.so.2 /usr/libx32/ld-linux-x32.so.2'
+ warn=
+ bind_now=
+ verbose=
+ test 1 -gt 0
+ case "$1" in
+ break
+ add_env='LD_TRACE_LOADED_OBJECTS=1 LD_WARN= LD_BIND_NOW='
+ add_env='LD_TRACE_LOADED_OBJECTS=1 LD_WARN= LD_BIND_NOW= LD_LIBRARY_VERSION=$verify_out'
+ add_env='LD_TRACE_LOADED_OBJECTS=1 LD_WARN= LD_BIND_NOW= LD_LIBRARY_VERSION=$verify_out LD_VERBOSE='
+ test '' = yes
+ case $# in
+ single_file=t
+ result=0
+ for file in "$@"
+ test t = t
+ case $file in
+ :
+ test '!' -e /usr/bin/ls
+ test '!' -f /usr/bin/ls
+ test -r /usr/bin/ls
+ test -x /usr/bin/ls
+ RTLD=
+ ret=1
+ for rtld in ${RTLDLIST}
+ test -x /usr/lib/ld-linux.so.2
+ for rtld in ${RTLDLIST}
+ test -x /usr/lib64/ld-linux-x86-64.so.2
+ for rtld in ${RTLDLIST}
+ test -x /usr/libx32/ld-linux-x32.so.2
+ case $ret in
+ nonelf /usr/bin/ls
+ return 1
+ echo ' not a dynamic executable'
not a dynamic executable
+ result=1
+ exit 1
WAT? Well, it doesn't find the dynamic linker as executable. It turns out that “/usr/lib64” is missing, which looks like it should be a symlink to “/usr/lib”. After reinstalling the “filesystem” package with “pacman -S --asdeps filesystem”, things work normally again.
IMHO, the fact that there is a symlink at “/usr/lib64” pointing to the right place *should not* break the system from boot, and “mkinitcpio” should be a bit smarter. Certainly the patch I posted earlier as a possible solution is something I made very quickly to get my system fixed and to provide an example of a possible solution. It ca be improved (e.g. adding multilib support, and using different starting paths depending on the current architecture), and these things would be easy to add and would make mkinitcpio more robust overall. If there are chances that such a patch can end up in mkinitcpio, I can do that myself and re-upload an improved version. WDYT?
The linker isn't actually executing the program (this seems pretty obvious) -- it's reading the headers and doing library resolution, just as your patch attempts with readelf. However, ldd does the full resolution with the lookup to the soname on the filesystem. Your patch is just guessing at the paths and ELF architecture.
> it should at least use canonical paths to the dynamic linkers
So propose that for the glibc -- I agree it's weird that our intepreter is in /lib64 but ldd looks for /usr/lib64. At least then, when you delete /lib64, you'll break everything, not just ldd.
It's unlikely that I want to make such a drastic change in core behavior just to guard against the case where you're deleted files from a base package. I certainly can't accept your patch in its current form.
Strange thought. That symlink isn't going anywhere. It's needed for proprietary blobs which have a fixed linker path of /lib64/ld-linux-x86-64.so, as is common on some other distros (e.g. debian, ubuntu).