非 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
授予权限,因此如果在环境中允许这样做,则会带来安全风险。事实上,这是不允许的,并且会失败:chroot
chroot
取消共享:取消共享失败:操作不允许
匹配取消分享(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。