我读了关于 initramfs 的 linux 文档和源代码的switch_root
。
文档说:
当切换另一个根设备时,initrd 会pivot_root,然后卸载ramdisk。但 initramfs 是 rootfs:您既不能使用ivot_root rootfs,也不能卸载它。相反,删除 rootfs 中的所有内容以释放空间(find -xdev / -exec rm '{}' ';'),使用新根覆盖 rootfs (cd /newmount; mount --move ./; chroot .),将 stdin/stdout/stderr 连接到新的 /dev/console,并执行新的 init。
并且switch_root
确实是这样做的:
if (chdir(newroot)) {
warn(_("failed to change directory to %s"), newroot);
return -1;
}
...
if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) {
close(cfd);
warn(_("failed to mount moving %s to /"), newroot);
return -1;
}
...
if (chroot(".")) {
close(cfd);
warn(_("failed to change root"));
return -1;
}
为什么我们需要将挂载点移过来/
?
为什么 chroot 到 new_root 还不够?
答案1
编辑:感谢@timothy-baldwin 的编辑。
new_root
超挂载/
会改变挂载命名空间的根目录,没有超挂载的chroot/
会导致系统处于一个chroot
环境(根目录与挂载命名空间的根目录不匹配)。
这会导致一些问题,例如:
1. 在 chroot 内不允许创建用户命名空间。
根据man 2 unshare
,在 chroot 环境中时,unshare
ing 用户命名空间将失败。EPERM
EPERM (since Linux 3.9) CLONE_NEWUSER was specified in flags and the caller is in a chroot environment (i.e., the caller's root directory does not match the root directory of the mount namespace in which it resides).
$ unshare -U
unshare: unshare failed: Operation not permitted
2、输入一个挂载命名空间,会将根目录设置为该命名空间的根目录
输入挂载命名空间会将进程的根目录设置为挂载命名空间的根目录,因此setns
对我们的挂载命名空间执行操作会将我们的根目录设置为rootfs目录。
$ nsenter -m/proc/self/ns/mnt /bin/sh
$ ls -ld /new_root
new_root
我可以看到 new_root 目录位于我的 chroot 之外。
挂载 over/
并不能真正阻止逃逸 chroot
root 用户可以umount
在该目录中重新进入其挂载命名空间 ( setns
) 并查看 rootfs:
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
int main() {
int ns = open("/proc/self/ns/mnt", O_RDONLY);
if (ns == -1) {
perror("open");
goto out;
}
if (umount2("/", MNT_DETACH)) {
perror("umount2");
goto out;
}
if (setns(ns, CLONE_NEWNS)) {
perror("setns");
goto out;
}
char *a[] = { "/bin/sh", NULL };
char *e[] = { NULL };
execve(a[0], a, e);
perror("execve");
out:
return 1;
}
$ gcc -o main main.c
$ unshare -m ./main
/ # ls -d new_root
new_root
/ # mount -t proc proc /proc
/ # cat /proc/mounts
none / rootfs rw 0 0
proc /proc proc rw,relatime 0 0
为了防止 chroot 逃逸,必须 安装new_root
over 。/
创建了一个最小的 initramfs 并switch_root
用此 shell 脚本替换二进制文件以获取 shell:
#!/bin/sh
exec /bin/sh
/bin/sh
还在initramfs 内部更改为静态链接的busybox
.
编译以下代码并静态链接:
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open(".", O_RDONLY | O_CLOEXEC);
if (fd < 0) {
perror("open");
goto out0;
}
if (chroot("tmp")) {
perror("chroot");
goto out1;
}
if (fchdir(fd)) {
perror("fchdir");
goto out1;
}
if (chdir("..")) {
perror("chdir");
goto out1;
}
char *const argvp[] = { "sh", NULL };
char *const envp[] = { NULL };
execve("bin/sh", argvp, envp);
perror("execve");
out1:
close(fd);
out0:
return 1;
}
在我的真实根文件系统的根目录中放置为/escape
.
switch_root
重新启动并在发生之前得到一个 shell 。
无需过度安装根
$ mount --move proc new_root/proc
$ mount --move dev new_root/dev
$ mount --move sys new_root/sys
$ mount --move run new_root/run
$ exec chroot new_root
$ ./escape
$ ls -d new_root
new_root
我逃脱了 chroot。
带超载根
$ mount --move proc new_root/proc
$ mount --move dev new_root/dev
$ mount --move sys new_root/sys
$ mount --move run new_root/run
$ cd new_root
$ mount --move . /
$ exec chroot .
$ ./escape
$ ls -d new_root
ls: cannot access 'new_root': No such file or directory
我无法逃脱 chroot。
答案2
不超载 rootfs 会破坏用户和挂载命名空间:
- 系统
setns
调用会将调用者根目录设置为挂载命名空间的根目录,从而撤消chroot
. - 如果进程根目录不是其挂载命名空间的根目录,则禁止非特权进程创建用户命名空间。