Bug 27343 - initgroups() SIGSEGVs when called on a system without nsswich.conf (in a chroot)
Summary: initgroups() SIGSEGVs when called on a system without nsswich.conf (in a chroot)
Status: RESOLVED FIXED
Alias: None
Product: glibc
Classification: Unclassified
Component: nss (show other bugs)
Version: 2.33
: P2 normal
Target Milestone: 2.34
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2021-02-04 07:23 UTC by Sergei Trofimovich
Modified: 2021-02-07 18:21 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
Project(s) to access:
ssh public key:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Sergei Trofimovich 2021-02-04 07:23:02 UTC
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) +++
...

```
Comment 1 Sergei Trofimovich 2021-02-04 07:24:06 UTC
/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
Comment 2 Jonas Witschel 2021-02-04 12:15:25 UTC
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).
Comment 3 Sergei Trofimovich 2021-02-04 22:04:06 UTC
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.
Comment 4 dj@redhat.com 2021-02-04 22:17:27 UTC
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 ;-)
Comment 5 Sergei Trofimovich 2021-02-05 07:14:07 UTC
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?
Comment 6 Sergei Trofimovich 2021-02-05 08:22:16 UTC
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.
Comment 7 dj@redhat.com 2021-02-07 17:41:20 UTC
Fixed in 17f0ff097887008b2d3dca270c8ffbb4b43a8749