The crash is initially observed on unbound project. Here is the minimal reproducer against glibc-2.33: ```c /* $ gcc a.c -o a $ unshare --map-root-user ./a */ #include <sys/types.h> #include <pwd.h> #include <grp.h> #include <unistd.h> int main() { // load some NSS modules struct passwd * pw = getpwnam("root"); chdir("/tmp"); chroot("/tmp"); chdir("/"); // access nsswitch.conf in a chroot initgroups("root", 0); } ``` Crash sequence (no root needed): ``` $ unshare --map-root-user strace -f ./a ... newfstatat(AT_FDCWD, "/etc/nsswitch.conf", {st_mode=S_IFREG|0644, st_size=2091, ...}, 0) = 0 newfstatat(AT_FDCWD, "/", {st_mode=S_IFDIR|0755, st_size=3072, ...}, 0) = 0 openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=2091, ...}, AT_EMPTY_PATH) = 0 read(3, "#\n# /etc/nsswitch.conf\n#\n# An ex"..., 1024) = 1024 read(3, "d\t\t\tUse Samba winbind support\n#\t"..., 1024) = 1024 read(3, " files\nshadow: files\nservice"..., 1024) = 43 read(3, "", 1024) = 0 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=2091, ...}, AT_EMPTY_PATH) = 0 close(3) = 0 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=188218, ...}, AT_EMPTY_PATH) = 0 mmap(NULL, 188218, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fda6f3cc000 close(3) = 0 openat(AT_FDCWD, "/lib64/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\3202\0\0\0\0\0\0"..., 832) = 832 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=47496, ...}, AT_EMPTY_PATH) = 0 mmap(NULL, 50568, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fda6f201000 mmap(0x7fda6f204000, 24576, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7fda6f204000 mmap(0x7fda6f20a000, 8192, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x9000) = 0x7fda6f20a000 mmap(0x7fda6f20c000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa000) = 0x7fda6f20c000 close(3) = 0 mprotect(0x7fda6f20c000, 4096, PROT_READ) = 0 munmap(0x7fda6f3cc000, 188218) = 0 openat(AT_FDCWD, "/etc/passwd", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=8924, ...}, AT_EMPTY_PATH) = 0 lseek(3, 0, SEEK_SET) = 0 read(3, "root:x:0:0:System user; root:/ro"..., 1024) = 1024 close(3) = 0 chdir("/tmp") = 0 chroot("/tmp") = 0 chdir("/") = 0 openat(AT_FDCWD, "/proc/sys/kernel/ngroups_max", O_RDONLY|O_CLOEXEC) = -1 ENOENT (Нет такого файла или каталога) newfstatat(AT_FDCWD, "/etc/nsswitch.conf", 0x7ffca0501a20, 0) = -1 ENOENT (Нет такого файла или каталога) newfstatat(AT_FDCWD, "/", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=1520, ...}, 0) = 0 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x100} --- +++ killed by SIGSEGV (core dumped) +++ ... ```
/etc/nsswitch.conf contents (if relevant): $ grep -v ^# /etc/nsswitch.conf aliases: files ethers: files group: files gshadow: files hosts: files dns netgroup: files networks: files dns passwd: files protocols: files publickey: files rpc: files shadow: files services: files
FWIW, creating /etc/nsswitch.conf (identical to the one from the main system) within the chroot doesn't do the trick either, the reproducer still crashes with the same strace. I experimented with adding multiple files like /etc/passwd and /etc/group to the chroot, but couldn't get initgroups() to work at all without crashing. Just for cross-referencing purposes, this was reported downstream against Unbound as well (https://github.com/NLnetLabs/unbound/issues/418), and as a tracking bug in Arch Linux (https://bugs.archlinux.org/task/69532).
I suspect the crash appeared in commit f4f3b09111e9fa38fd485979a3e40b6cfa6a757d Author: DJ Delorie <dj@redhat.com> Date: Mon Nov 9 22:09:34 2020 -0500 nsswitch: use new internal API (callers) Stitch new ABI and types throughout all NSS callers.
Note that we consider expecting name services to work after a chroot to be a security issue, see: commit 429029a73ec2dba7f808f69ec8b9e3d84e13e804 Author: DJ Delorie <dj@redhat.com> Date: Fri Jan 15 19:50:00 2021 -0500 nsswitch: do not reload if "/" changes https://sourceware.org/bugzilla/show_bug.cgi?id=27077 Once an application does a chroot(), in theory, all name services should be locked at whatever state they had at that moment, to avoid corrupting the internal state with whatever is inside the chroot. This protects, for example, container management tools from being exploited by the contents of the container. Having said that, "unsupported" doesn't mean we can't handle it without crashing ;-)
Looking at nss_database_check_reload_and_get() definition '*result' does not seem to be populated in the branch: ``` static bool nss_database_check_reload_and_get (struct nss_database_state *local, nss_action_list *result, enum nss_database database_index) { ... /* Before we reload, verify that "/" hasn't changed. We assume that errors here are very unlikely, but the chance that we're entering a container is also very unlikely, so we err on the side of both very unlikely things not happening at the same time. */ if (__stat64 ("/", &str) != 0 || (local->root_ino != 0 && (str.st_ino != local->root_ino || str.st_dev != local->root_dev))) { /* Change detected; disable reloading. */ atomic_store_release (&local->data.reload_disabled, 1); __libc_lock_unlock (local->lock); __nss_module_disable_loading (); return true; } ``` Should it?
Assuming it should be initialized with success as well proposed the patch: https://sourceware.org/pipermail/libc-alpha/2021-February/122364.html It fixes the crash on small test and on unbound for me.
Fixed in 17f0ff097887008b2d3dca270c8ffbb4b43a8749