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
Task Type Bug Report
Category Arch Projects
Status Closed
Assigned To No-one
Architecture x86_64
Severity High
Priority Normal
Reported Version
Due in Version Undecided
Due Date Undecided
Percent Complete 100%
Votes 0
Private No

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

Closed by  Doug Newgard (Scimmia)
Saturday, 21 January 2017, 00:29 GMT
Reason for closing:  Not a bug
Comment by Doug Newgard (Scimmia) - Thursday, 19 January 2017, 18:06 GMT
So why is your ldd broken?
Comment by Dave Reisner (falconindy) - Thursday, 19 January 2017, 19:52 GMT
> For some reason, “ldd” thinks that dynamic binaries are not:

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).
Comment by Adrián Pérez de Castro (aperezdc) - Friday, 20 January 2017, 12:40 GMT
Well, I haven't done anything out of the ordinary, as far as I can tell (except for being in “testing” and doing regular updates every few days), so I cannot see how things could have gone wrong with “ldd”. I have checked the “ldd” script, it reads:

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.
Comment by Adrián Pérez de Castro (aperezdc) - Friday, 20 January 2017, 12:46 GMT
I still don't know how did the symlink disappear, but it is quite shitty that “ldd” has a hardcoded list of paths to the dynamic linker which contains paths that are not canonical paths. Not to mention that is still über-shitty that “ldd” relies on running executables instead of inspecting their headers.

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?
Comment by Adrián Pérez de Castro (aperezdc) - Friday, 20 January 2017, 12:56 GMT
BTW, on the contrary, I would argue that “readelf” will always work *predictably*, even if by itself does not work “exactly like ldd does” (which is not its intention, hence the wrapper script I sketched in my patch). Given how things have gone badly here, it is certainly clear that “ldd” does not precisely work predictably: it should at least use canonical paths to the dynamic linkers, or even better read the path to the dynamic linker from the “INTERP” tag of the ELF header.
Comment by Dave Reisner (falconindy) - Friday, 20 January 2017, 13:35 GMT
> Not to mention that is still über-shitty that “ldd” relies on running executables instead of inspecting their headers.
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.
Comment by Oscar Garcia (ogarcia) - Friday, 20 January 2017, 14:15 GMT
I think that the patch not is bad idea, cause removes the /lib64 requeriment that, I think, may be deprecated in any moment.
Comment by Dave Reisner (falconindy) - Friday, 20 January 2017, 14:28 GMT
> I think, may be deprecated in any moment.
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).

Loading...