Container 基础 —— pivot_root

Container 基础 —— pivot_root

Tags
Linux
Container
Published
January 5, 2025
Author
Johnh3
之前有说过 chroot 这个系统调用, pivot_root 这个系统调用看起来和它很像,但实际上有很大的不同,来看看两者的区别。
 
chroot 是更改进程的根目录,所以它的更改只对执行的进程及其子进程有效,这种隔离并不完全,因为它并没有更改实际的挂载。
关键就是这个挂载,因为 chroot 没有对挂载做任何改动,虽然从进程的视角而言,根目录是新指定的,但事实上它并不是一个真正的根文件系统,这就意味着如果有办法绕过 chroot 视角的限制,就能访问到外部。
 
pivot_root 是替换根文件系统,会将目标作为新的根文件系统挂载。这种隔离级别明显高于 chroot ,因为它是从根文件系统这一层面来达成效果的。
 
下面我们看看 pivot_root 的使用。
pivot_root(8) 接收两个参数:
pivot_root new_root put_old
它会将 new_root 作为新的根文件系统,并将当前的根文件系统挂载到 put_old
它还有一系列的限制:
  • new_rootput_old 必须都是目录
  • new_rootput_old 不能是当前的根
  • put_old 必须在 new_root 目录下
  • new_root 必须是一个挂载点
  • new_root 和当前根的父挂载点的传播类型不能是 MS_SHARED,并且如果 put_old 也是一个挂载点,它的传播类型也不能是 MS_SHARED 。这些限制是为了保证 pivot_root 不会传播改动到其他的命名空间。
  • 当前的根目录必须是一个挂载点
 
按照上面的限制,我们来实验一下怎么调用 pivot_root(8)
# 创建 new 目录 mkdir new # 挂载一个临时文件系统到 new 目录 mount -t tmpfs mytmpfs new # 创建 old 目录 mkdir new/old
一般而言,根目录都是一个挂载点。所以目前,我们的环境除了第 5 个限制,已经都满足了,先试一下能不能执行了。
root@j1800:~/new# pivot_root . old/ pivot_root: failed to change root from `.' to `old/': Invalid argument
报错了,看来是第 5 点限制未能满足,那就查看一下:
findmnt -o TARGET,PROPAGATION TARGET PROPAGATION / shared /root/new shared
 
果不其然,那怎么才能让它们不再是 MS_SHARED 传播类型呢?在执行 mount 时是可以指定传播类型的:
mount --make-private / mount --make-private -t tmpfs mytmpfs new
这样之后,我们就能执行 pivot_root(8) 了,但是。
 
如果你已经执行了,那可能要重启系统了,因为我们的新根文件系统中什么都没有,切换后将失去所有的操控能力。
为了更安全的实验,我们下面将使用命名空间的功能来隔离挂载的影响,这里不会去介绍命名空间的作用,因为计划在后面专门去写命名空间的使用。
unshare --mount --fork
执行完上面的命令后,我们会进入一个隔离了挂载点的命名空间中,所有的挂载点都会变更为 private ,这样我们后续所有的挂载操作都不会影响到命名空间外,这样就能安全的实验了。
为了切换根文件系统后能够进行操作,我们还需要准备一个可用的新根文件系统,这里利用已有的 docker 镜像,比如 busybox
docker pull buxybox docker run --rm -d --name busybox busybox top -b docker export -o busybox.tar busybox docker stop busybox mkdir busybox tar -xzf busybox.tar -C busybox mount -t tmpfs mytmpfs new cp -r busybox/* new mkdir new/old cd new pivot_root . old
到现在为止,我们已经能够切换根文件系统了。为了尽可能的安全,我们还需执行两个操作,就是 cdexecumount 。因为 pivot_root 不一定会切换当前程序的目录,比如可能还处在旧的根文件系统中,所以最好手动切换一下。另外我们的旧根还在,这也可能导致问题,所以需要将旧的根文件系统 umount
cd / && exec /bin/sh # 由于 umount 依赖 /proc mount -t proc myproc /proc umount -l /old
 
前面我们都是新建了一个 old 目录来挂载旧的根文件系统,一般用于安全隔离的话,我们又会手动 umount 掉这个旧的根文件系统,那这个 old 目录往往是一个临时目录。于是,有人就在思考有没有办法节省掉这个临时目录的创建。参考自 lxc 的实现,能发现他们采用了一种特殊的方式,这个方式并没有在文档中说明,所以事实上这是一种内部机制的利用,且这种机制有可能在后面的内核版本中被更改。
当我们把 put_old 指定为 . 时,并不是指定当前的目录,而是 /proc/self/cwd 。于是,利用这个特性,我们就可以不用创建临时目录了。
cp -r busybox/* new unshare -mf mount --bind new new cd new pivot_root . . # 此时我们就仍然处在旧的根文件系统中,umount 旧的根文件系统 umount -l . cd / && exec /bin/sh # 此时就切换完毕了 # 下面退出 exit rm -rf new/*
 
 
相关链接