unshare --map-root-user 设置后切换到原始 uid/用户名

unshare --map-root-user 设置后切换到原始 uid/用户名

我正在使用 unshare 来创建每个进程的安装,它工作得很好

unshare -m --map-root-user

但是,在创建了我的绑定安装之后

mount --bind src dst

我想将 UID 更改为我的原始用户,以便whoami(和其他)像我的用户名一样回显echo $USER

我已经尝试过的答案 使用 unshare 模拟 chroot

然而,su – user1之后做chroot /,我得到

su: Authentication failure
(Ignored)
setgid: Invalid argument

我已经在 Ubuntu 18.04 Beta、Debian Stretch、openSUSE-Leap-42.3 上对此进行了测试。全部都是一样。我想自从这个答案起作用以来,内核中发生了一些变化。

什么是有效且正确的方法来做到这一点(当然没有被真实的根)?

答案1

unshare(1)命令无法做到这一点:

-r, --map-root-user
[...] 作为一个纯粹的便利功能,它不支持更复杂的用例,例如映射多个 UID 和 GID 范围。

补充组(如果有的话)(video, ...)无论如何都会丢失(或映射到nogroup)。

通过再次更改为第二个新用户命名空间,可以恢复映射。这需要一个自定义程序,因为unshare(1)不会这样做。这是一个非常简约的 C 程序作为概念证明(仅限一个用户:uid/gid 1000/1000,零故障检查)。我们称它为revertuid.c

#define _GNU_SOURCE
#include <sched.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h>

int main(int argc, char *argv[]) {
    int fd;

    unshare(CLONE_NEWUSER);
    fd=open("/proc/self/setgroups",O_WRONLY);
    write(fd,"deny",4);
    close(fd);
    fd=open("/proc/self/uid_map",O_WRONLY);
    write(fd,"1000 0 1",8);
    close(fd);
    fd=open("/proc/self/gid_map",O_WRONLY);
    write(fd,"1000 0 1",8);
    close(fd);
    execvp(argv[1],argv+1);
}

它只是对 所做的映射进行反向映射unshare -r -m,这是不可避免的,以便能够成为 root 并使用mount,如下所示:

$ strace unshare -r -m /bin/sleep 1 2>&1 |sed -n '/^unshare/,/^execve/p'
unshare(CLONE_NEWNS|CLONE_NEWUSER)      = 0
open("/proc/self/setgroups", O_WRONLY)  = 3
write(3, "deny", 4)                     = 4
close(3)                                = 0
open("/proc/self/uid_map", O_WRONLY)    = 3
write(3, "0 1000 1", 8)                 = 8
close(3)                                = 0
open("/proc/self/gid_map", O_WRONLY)    = 3
write(3, "0 1000 1", 8)                 = 8
close(3)                                = 0
execve("/bin/sleep", ["/bin/sleep", "1"], [/* 18 vars */]) = 0

所以这给出了:

user@stretch-amd64:~$ gcc -o revertuid revertuid.c
user@stretch-amd64:~$ mkdir -p /tmp/src /tmp/dst
user@stretch-amd64:~$ touch /tmp/src/file
user@stretch-amd64:~$ ls /tmp/dst
user@stretch-amd64:~$ id
uid=1000(user) gid=1000(user) groups=1000(user)
user@stretch-amd64:~$ unshare -r -m
root@stretch-amd64:~# mount --bind /tmp/src /tmp/dst
root@stretch-amd64:~# ls /tmp/dst
file
root@stretch-amd64:~# exec ./revertuid bash
user@stretch-amd64:~$ ls /tmp/dst
file
user@stretch-amd64:~$ id
uid=1000(user) gid=1000(user) groups=1000(user)

或者更短:

user@stretch-amd64:~$ unshare -r -m sh -c 'mount --bind /tmp/src /tmp/dst; exec ./revertuid bash'
user@stretch-amd64:~$ ls /tmp/dst
file

在内核 3.19 之后,行为可能会发生变化,如所示user_namespaces(7):

/proc/[pid]/setgroups该文件是在 Linux 3.19 中添加的,但被向后移植到许多早期的稳定内核系列中,因为它解决了安全问题。该问题涉及具有“rwx---rwx”等权限的文件。

答案2

注意:由于 bwrap 目前在比 util-linux > 2.39.1 更多的地方可用,这个答案使用 bwrap对于 Ubuntu 23.10 之前的系统可能值得考虑。 (另一方面,此解决方案可以正确传播 ^C 而 bwrap 则不能)

然而,与 bwrap 不同的是,此解决方案基于 unshare,可以正确传播 ^C 以进行交互式 CLI 调用。

你不能改变UID,但使用现代 util-linux,您可以以非 root 身份开始,然后用于nsenter获取 root 权限并进行挂载。

有必要使用 unshare 启动命名空间,等待它准备好,以 fake-root 身份执行一些挂载,当挂载准备就绪时,让命名空间运行其任务。

这需要两次同步,在执行挂载之前等待命名空间准备好,并在运行进程之前等待挂载准备好。

用于:

in-unshare [bind-mount] ... [--] command [arg] ...

例如

in-unshare /build-dir=$PWD/build -- make build

# **Note:** this avoids an implicit exec with: `"$@" ; exit $?` which is necessary for proper signal propagation on ^C
# Who would have thought an extra waiting process layer would be so useful?
in-unshare() (
  local session session_pid mounts
  while test $# != 0
  do case "$1" in
     *=*) mounts+=("$1") ; shift ;;
     --) shift ; break ;;
     *) break;
     esac
  done

  coproc mounter {
    # session_pid will be the same as PPID but we need to wait until namespaces are setup
    read -r session_pid || return $?
    exec 0<&-

    # mount as "root" via nsenter
    for mount in "${mounts[@]}"
    do # quit on error without writing to stdout
       nsenter -U -m --target $session_pid mount "${mount#*=}" "${mount%=*}" -o rbind || return $?
    done
    # signal that mounts are setup
    echo $?
  }

  exec {out}>>/dev/fd/${mounter[1]} {in}</dev/fd/${mounter[0]}
  # note avoiding implict exec with "$@" ; exit $? is neccessary for proper signal shutdown on ^C
  # who would have thought an extra waiting process layer would be so useful
  exec unshare --mount --user --map-user=$(id -u) --map-group=$(id -g) --map-users=auto --map-groups=auto --keep-caps --setgroups allow /bin/bash --noprofile --norc -c "echo \$\$ >&${out} && exec ${out}>&- || exit \$? ; read -u ${in} && exec ${in}<&- && \"\$@\" ; exit $?" unshare "$@"
  exit $?
)

相关内容