libc’s heinous libnss behavior
Here’s a fun little thing we ran into the other day.
It doesn’t take much to make a busybox-based chroot environment. Basically make a chroot directory, create the basic file structure inside (/usr, /etc, /lib, and so on), download the busybox binary, and use it to populate symlinks to all the normal /bin utilities.
Having made this simple chroot, we also wanted to run sshd, which also needs ssh-keygen to generate keys. These programs, unlike busybox, are dynamically linked, so we also needed to copy over some static libraries into the chroot’s /lib. Running “ldd” on the sshd and ssh-keygen binaries will show you which libraries it needs; we used a small program which essentially ran ldd and automatically copied over the necessary files.
However, when we ran ‘ssh-keygen’ for the first time, it rudely said:
You don't exist, go away!
We tracked this down in the source code to a call to getpwuid(), which looks up a UID in the password database. We verified that /etc/passwd did in fact have an entry for root, so why was it complaining?
Next we tried strace. Strace showed, oddly, that just before printing the message and exiting, ssh-keygen was trying to open /lib/x86_64_linux_gnu/libnss_files.so
libnss? Why? Why didn’t ldd show that, if it’s trying to load it?
It turns out that the GNU Libc is written by assholes. Libnss is the reason you basically can’t statically compile a program anymore. It’s what they use to magically determine the right place to look up a user. Why doesn’t it show up with ‘ldd’? It’s because they use dlopen to open the libnss shared objects, hiding the dependency until runtime!