pivot_root:原理、用途及最简单 Demo
什么是 pivot_root
pivot_root 是 Linux 系统中的一个系统调用(和对应的命令行工具),用于更改进程的根文件系统。与 chroot 类似,pivot_root 将一个指定目录设置为进程的新根目录(/),但它比 chroot 更灵活,特别适合容器环境。它的核心功能是将当前根文件系统(旧根)移动到另一个位置,并将新目录设置为根文件系统。
主要特点
-
交换根文件系统:
pivot_root将一个新目录设置为进程的根(/),并将旧根移动到指定位置(通常是一个子目录)。- 例如,
pivot_root(new_root, put_old)将new_root设置为新根,旧根挂载到put_old。
-
挂载点隔离:
- 与
chroot不同,pivot_root要求新根和旧根是独立的挂载点,通常需要 Mount Namespace 支持。 - 它可以完全隔离旧根的挂载点,防止进程访问主机文件系统。
- 与
-
容器初始化:
pivot_root是容器运行时(如 Docker、containerd)的标准工具,用于将容器镜像的根文件系统设置为容器进程的根。
主要用途
- 容器根文件系统切换:在容器启动时,将镜像的根文件系统(例如
/var/lib/docker/...)设置为容器的新根,隔离主机文件系统。 - 强隔离环境:比
chroot提供更彻底的隔离,防止进程通过挂载点逃逸。 - 系统初始化:在 Linux 启动过程中(initramfs),将临时根切换到真实根文件系统。
- 测试和开发:为进程提供独立的根文件系统,测试特定环境。
与 chroot 的区别
- 隔离程度:
chroot仅更改根目录,进程仍可能看到主机挂载点(如/proc、/dev)。pivot_root要求新根是独立挂载点,旧根被移动到新位置,提供更强的隔离。
- 使用场景:
chroot适合简单隔离(如测试或修复)。pivot_root适合容器,需配合 Mount Namespace。
- 要求:
chroot直接作用于目录。pivot_root需要新根和旧根是挂载点,且进程在 Mount Namespace 中。
与 Mount Namespace 的关系
你的前文提到 Mount Namespace 和 nsenter 的问题(在 Mount Namespace 中挂载 tmpfs 到 /tmp/mnt,但看到主机目录的其他文件)。pivot_root 常与 Mount Namespace 结合使用:
- Mount Namespace 提供独立的挂载点视图,确保新根挂载(如 tmpfs 或容器镜像)仅在命名空间内可见。
pivot_root将新根设置为进程的根目录,隔离主机文件系统。- 结合
nsenter,可以进入 Mount Namespace 验证pivot_root的隔离效果。
pivot_root 最简单 Demo
以下是一个最简单的 pivot_root 示例,展示如何在 Mount Namespace 中使用 pivot_root 切换根文件系统,确保隔离主机文件系统。我们将:
- 创建一个新目录作为新根。
- 使用 Mount Namespace 和 tmpfs 模拟容器根文件系统。
- 通过
pivot_root切换根,并验证隔离。 - 使用
nsenter进入 Mount Namespace 检查结果。
环境要求
- Linux 系统(支持
pivot_root和 Mount Namespace,例如 Ubuntu、CentOS) unshare和nsenter命令(util-linux包)- root 权限(
pivot_root和挂载需要管理员权限) bash和必要库(用于新根环境)
步骤
- 清理旧环境:
确保/tmp/mnt干净,清理旧进程(基于你的ps aux输出,PID15375和15376):
sudo umount /tmp/mnt 2>/dev/null
sudo rm -rf /tmp/mnt
sudo mkdir -p /tmp/mnt
验证进程已清理:
ps aux | grep '[u]nshare --mount'
预期输出为空。
- 创建新根目录:
创建一个新根文件系统,包含bash和必要库:
sudo mkdir -p /tmp/mnt/bin /tmp/mnt/lib /tmp/mnt/lib64 /tmp/mnt/old_root
sudo cp /bin/bash /tmp/mnt/bin/
复制 bash 依赖库(检查 ldd /bin/bash 输出):
ldd /bin/bash
输出示例:
linux-vdso.so.1 (0x00007fffc8bff000)
libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007f0c6c5c9000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0c6c3c7000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0c6c628000)
复制库:
sudo cp /lib/x86_64-linux-gnu/libtinfo.so.6 /tmp/mnt/lib/
sudo cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/mnt/lib/
sudo cp /lib64/ld-linux-x86-64.so.2 /tmp/mnt/lib64/
- 启动 Mount Namespace 并挂载新根:
sudo unshare --mount /bin/bash
在新的 bash shell 中,设置挂载隔离并挂载新根:
mount --make-rprivate -o rec /
mount --bind /tmp/mnt /tmp/mnt
echo "Hello from Mount Namespace!" > /tmp/mnt/testfile
ls /tmp/mnt
输出:
bin lib lib64 old_root testfile
说明:
mount --bind /tmp/mnt /tmp/mnt使/tmp/mnt成为独立挂载点,满足pivot_root的要求。old_root是旧根的存放目录。
- 执行 pivot_root:
切换根文件系统,将/tmp/mnt设为新根,旧根移到/tmp/mnt/old_root:
cd /tmp/mnt
pivot_root . old_root
修复环境:
pivot_root 后,/bin/bash 仍在旧根中,需更新路径:
export PATH=/bin
验证新根:
ls /
输出:
bin lib lib64 old_root testfile
尝试访问旧根:
ls /old_root
输出示例:
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
隔离验证:
尝试访问主机 /tmp:
ls /old_root/tmp
输出显示主机 /tmp 的内容,但新根(/)只包含 /tmp/mnt 的内容,表明隔离成功。
- 验证全局命名空间隔离:
- 保持
unshareshell 运行,打开另一个终端:
ls /tmp/mnt
输出:
bin lib lib64 old_root
全局命名空间看不到 testfile,因为它仅在 Mount Namespace 中创建。
- 使用 nsenter 进入 Mount Namespace:
在另一个终端,查找 PID:
ps aux | grep '[u]nshare --mount'
输出示例:
root 16000 0.0 0.1 16732 7040 pts/0 S+ 09:00 0:00 sudo unshare --mount /bin/bash
进入 Mount Namespace:
sudo nsenter --mount=/proc/16000/ns/mnt /bin/bash
验证新根:
ls /tmp/mnt
输出:
bin lib lib64 old_root testfile
注意:如果 pivot_root 已执行,/tmp/mnt 是旧路径,可能不可见。直接检查新根:
ls /
输出:
bin lib lib64 old_root testfile
- 退出并清理:
- 在
nsentershell:
exit
- 在
unshareshell:
umount old_root
umount /tmp/mnt
exit
- 清理目录:
sudo rm -rf /tmp/mnt
解决主机文件可见问题
在 Mount Namespace 中挂载 tmpfs 到 /tmp/mnt 后,通过 nsenter 进入仍看到主机目录的其他文件。pivot_root 可以解决此问题,因为它完全替换根文件系统,隔离主机文件系统。关键点:
- Mount Namespace 隔离:
mount --make-rprivate确保挂载不传播。 - pivot_root 切换:将
/tmp/mnt设为新根,旧根移到old_root,防止主机文件(如/tmp/mnt的其他文件)可见。 - nsenter 验证:进入 Mount Namespace 检查新根只包含预期内容(如
testfile)。
如果仍看到主机文件,检查:
- 是否正确执行
pivot_root(需要cd /tmp/mnt和挂载点)。 /tmp/mnt是否干净(步骤 1)。- PID 是否正确(步骤 6)。
注意事项
- 权限:
pivot_root、unshare和nsenter需要 root 权限。 - 挂载点要求:
pivot_root要求新根和旧根是独立挂载点,使用mount --bind创建。 - 依赖完整性:新根需包含必要文件(如
bash和库),否则报错。 - 清理:实验完成后,卸载挂载点并删除临时目录。
- 与 chroot 对比:
pivot_root提供更强隔离,适合容器场景。
总结
pivot_root 是一种强大的根文件系统切换工具,通过将新目录设为根并移动旧根,实现比 chroot 更彻底的隔离,常用于容器初始化。结合 Mount Namespace 和 nsenter,可以确保隔离主机文件系统,解决你在 Mount Namespace 中看到主机目录其他文件的问题。本文通过一个简单 Demo 展示了 pivot_root 的使用,确保新根只包含预期内容(如 testfile)。如果你在执行 pivot_root 时遇到问题(如报错或仍看到主机文件),请提供以下信息,我可以进一步协助:
pivot_root . old_root的具体错误信息。unshareshell 中mount | grep /tmp/mnt和ls /tmp/mnt的输出。- 全局命名空间中
ls /tmp/mnt的输出(显示哪些“其他文件”)。 /proc/16000/ns/mnt的ls -l输出。
