Ubuntu - 非 root 用户可以在 chroot jail 中运行进程吗?

Ubuntu - 非 root 用户可以在 chroot jail 中运行进程吗?

非 root 用户可以在 Ubuntu 上运行 chroot 进程吗?

答案1

在 Linux 上chroot(2)系统调用只能由具有特权的进程进行。进程所需的能力是 CAP_SYS_CHROOT。

您不能以用户身份 chroot 的原因很简单。假设您有一个 setuid 程序(例如 sudo),它会检查 /etc/sudoers 是否允许您执行某项操作。现在将它放入 chroot chroot 中,并使用您自己的 /etc/sudoers。突然间,您就获得了即时特权升级。

可以设计一个程序来 chroot 自身并将其作为 setuid 进程运行,但这通常被认为是糟糕的设计。chroot 的额外安全性不会引发 setuid 的安全问题。

答案2

@imz--IvanZakharyaschev 对 pehrs 的回答发表评论说,通过引入命名空间,这或许是可行的,但这尚未经过测试,也未作为答案发布。是的,这确实使非 root 用户能够使用 chroot。

给定一个静态链接的dash,一个静态链接的busybox,以及一个bash以非 root 身份运行的 shell:

$ mkdir root
$ cp /path/to/dash root
$ cp /path/to/busybox root
$ unshare -r bash -c 'chroot root /dash -c "/busybox ls -al /"'
total 2700
drwxr-xr-x    2 0        0             4096 Dec  2 19:16 .
drwxr-xr-x    2 0        0             4096 Dec  2 19:16 ..
drwxr-xr-x    1 0        0          1905240 Dec  2 19:15 busybox
drwxr-xr-x    1 0        0           847704 Dec  2 19:15 dash

该命名空间中的 root 用户 ID 被映射到该命名空间之外的非 root 用户 ID,反之亦然,这就是系统将当前用户拥有的文件显示为用户 ID 0 所拥有的原因。常规的ls -al root(不带unshare)会将它们显示为当前用户所拥有。


注意:众所周知,能够使用 的进程chroot能够突破chroot。由于会向普通用户unshare -r授予权限,因此如果在环境中允许这样做,则会带来安全风险。事实上,这是不允许的,并且会失败:chrootchroot

取消共享:取消共享失败:操作不允许

匹配取消分享(2)文档:

增强型PERM(自 Linux 3.9 起)

克隆新用户在中指定旗帜并且调用者处于 chroot 环境中(即,调用者的根目录与其所在的挂载命名空间的根目录不匹配)。

答案3

如今,您希望使用 LXC(Linux 容器)而不是 chroot/BSD jail。它介于 chroot 和虚拟机之间,为您提供大量安全控制和常规可配置性。我相信,要以用户身份运行它,您只需要成为拥有必要文件/设备的组的成员,但也可能涉及功能/系统权限。无论哪种方式,它都应该非常可行,因为 LXC 是相当新的,在 SELinux 等被添加到 Linux 内核之后很久。

另外,请记住,您可以以 root 身份编写脚本,但使用 sudo 授予用户安全权限来运行这些脚本(如果愿意,可以不输入密码,但请确保脚本是安全的)。

答案4

看来,使用用户命名空间实际上可以在没有 root 权限的情况下进行 chroot。下面是一个示例程序,它演示了这是可能的。我才刚刚开始探索 Linux 命名空间的工作原理,所以我不完全确定此代码是否是最佳实践。

另存为user_chroot.cc。用 编译g++ -o user_chroot user_chroot.cc。用法是./user_chroot /path/to/new_rootfs

// references:
// [1]: http://man7.org/linux/man-pages/man7/user_namespaces.7.html
// [2]: http://man7.org/linux/man-pages/man2/unshare.2.html

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

#include <cerrno>
#include <cstdio>
#include <cstring>

int main(int argc, char** argv) {
    if(argc < 2) {
        printf("Usage: %s <rootfs>\n", argv[0]);
    }

    int uid = getuid();
    int gid = getgid();
    printf("Before unshare, uid=%d, gid=%d\n", uid, gid);

    // First, unshare the user namespace and assume admin capability in the
    // new namespace
    int err = unshare(CLONE_NEWUSER);
    if(err) {
        printf("Failed to unshare user namespace\n");
        return 1;
    }

    // write a uid/gid map
    char file_path_buf[100];
    int pid = getpid();
    printf("My pid: %d\n", pid);

    sprintf(file_path_buf, "/proc/%d/uid_map", pid);
    int fd = open(file_path_buf, O_WRONLY);
    if(fd == -1) {
        printf("Failed to open %s for write [%d] %s\n", file_path_buf, errno, 
               strerror(errno));
    } else {
        printf("Writing : %s (fd=%d)\n", file_path_buf, fd);
        err = dprintf(fd, "%d %d 1\n", uid, uid);
        if(err == -1) {
            printf("Failed to write contents [%d]: %s\n", errno, 
                   strerror(errno));
        }
        close(fd);
    }

    sprintf(file_path_buf, "/proc/%d/setgroups", pid);
    fd = open(file_path_buf, O_WRONLY);
    if(fd == -1) {
        printf("Failed to open %s for write [%d] %s\n", file_path_buf, errno, 
               strerror(errno));
    } else {
        dprintf(fd, "deny\n");
        close(fd);
    }

    sprintf(file_path_buf, "/proc/%d/gid_map", pid);
    fd = open(file_path_buf, O_WRONLY);
    if(fd == -1) {
        printf("Failed to open %s for write [%d] %s\n", file_path_buf, errno, 
               strerror(errno));
    } else {
        printf("Writing : %s (fd=%d)\n", file_path_buf, fd);
        err = dprintf(fd, "%d %d 1\n", gid, gid);
        if(err == -1) {
            printf("Failed to write contents [%d]: %s\n", errno, 
                   strerror(errno));
        }
        close(fd);
    }

    // Now chroot into the desired directory
    err = chroot(argv[1]);
    if(err) {
        printf("Failed to chroot\n");
        return 1;
    }

    // Now drop admin in our namespace
    err = setresuid(uid, uid, uid);
    if(err) {
        printf("Failed to set uid\n");
    }

    err = setresgid(gid, gid, gid);
    if(err) {
        printf("Failed to set gid\n");
    }

    // and start a shell
    char argv0[] = "bash";
    char* new_argv[] = {
        argv0,
        NULL
    };

    err = execvp("/bin/bash", new_argv);
    if(err) {
        perror("Failed to start shell");
        return -1;
    }
}

我已经在使用 multistrap 生成的最小 rootfs 上测试了这一点(以非 root 身份执行)。一些系统文件(如/etc/passwd和)/etc/groups已从主机 rootfs 复制到客户机 rootfs。

相关内容