为什么 initramfs 需要使用新 root 来覆盖 rootfs?

为什么 initramfs 需要使用新 root 来覆盖 rootfs?

我读了关于 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 环境中时,unshareing 用户命名空间将失败。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_rootover 。/

创建了一个最小的 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.
  • 如果进程根目录不是其挂载命名空间的根目录,则禁止非特权进程创建用户命名空间。

相关内容