行业资讯
📅 2026/8/1 14:43:59
一篇文章搞懂Linux 文件系统隔离:Mount Namespace 与三个挂载视图 容器安全3/7
容器安全文章3核心概念Mount Namespace挂载命名空间与文件系统视图隔离Linux 文件系统隔离Mount Namespace 与三个挂载视图为什么需要隔离一台服务器跑着网站、数据库、缓存三个服务。如果共享同一个文件系统一个服务被入侵就能读到其他服务的配置文件、数据库密码。文件系统隔离让每个服务只能看到自己的文件即使运行在同一台机器上。下面是实验证明Linux 系统debian实验能更好了解和证明一、核心概念1.1 目录是门文件系统是房间目录本身不存储数据它只是一扇门。门后面是什么房间取决于你在这扇门上挂载了什么。文件系统是独立的房间——有自己的文件、目录、权限。但不挂到门上你就进不去、看不到、用不了。# 造一扇门创建一个空目录mkdir/mnt/door# 门后面是空的ls/mnt/door# 没有任何输出# 在门后挂一个房间# mount 挂载命令# -t tmpfs 指定房间类型为 tmpfs内存文件系统存在内存里重启消失# 第一个 tmpfs 房间类型# 第二个 tmpfs 房间名字可以随便取# /mnt/door 门的位置mount-ttmpfs tmpfs /mnt/door# 现在门后就是一个独立房间了echo我在房间里/mnt/door/msg.txtcat/mnt/door/msg.txt# 我在房间里# 拆下房间门又变空了# umount 卸载把房间从门上拆下来umount/mnt/doorcat/mnt/door/msg.txt# 报错没有那个文件或目录文件系统必须挂载到目录上才能使用。目录是唯一的入口。1.2 什么是 Mount Namespace挂载视图Linux 内核有一个机制叫Mount Namespace挂载命名空间本文简称挂载视图。它的作用让每个进程拥有自己的房间清单挂载表进程之间互相独立。同一个门/mnt/data进程 A 的清单上写着通向房间 X进程 B 的清单上可能写着通向房间 Y或者什么都没写。不是房间变了是进程手里的清单不同。1.3 什么是复制一份创建新挂载视图时内核会复制一份当前的房间清单给新视图。之后两边的清单各自更新互不影响。就像你复印一份文件清单你在这份复印件上新增一条记录原件不会自动更新⚠️重要挂载视图隔离的是房间清单挂载表不是房间里的文件内容。如果两个视图都挂载了同一个房间比如都指向同一个硬盘分区修改文件会互相看到。只有新挂载的房间才是隔离的。二、第一视图宿主机的清单这是你开机后的正常 Linux。所有进程共享同一份房间清单。你在第一视图 终端ls/bin boot dev etc home lib...特点所有挂载操作全系统可见其他视图的新挂载你看不到这是根视图其他视图都从这里复制出去三、第二视图独立的清单3.1 怎么创建sudo以 root 权限执行 unshare脱离共享资源创建自己的副本--mount只脱离房间清单这一项--propagationprivate确保新视图的挂载操作不影响宿主机 systemd 默认把根文件系统的挂载传播类型设为 shared不加这句可能传播回宿主机 /bin/bash启动一个新的命令行 终端sudounshare--mount--propagationprivate /bin/bash执行后你的 shell 进入了一个新的挂载视图。这一刻内核复制了一份房间清单给你。从此你的清单和宿主机的清单各自独立。3.2 验证视图切换查看当前进程的 Mount Namespace ID 终端 readlink /proc/$$/ns/mountmnt:[4026532234]数字与之前不同说明进了新视图3.3 即时验证# 你在第二视图mkdir/mnt/room2mount-ttmpfs tmpfs /mnt/room2echo第二视图的文件/mnt/room2/secret.txtcat/mnt/room2/secret.txt# 第二视图的文件# 退出第二视图回到第一视图exit# 第一视图看不到ls/mnt/room2# 没有任何输出cat/mnt/room2/secret.txt# 报错没有那个文件或目录第二视图的新挂载只写在自己的清单上第一视图的清单没有这条记录。⚠️ 常见错误错误以为 exit 后文件还在exit只是退出视图但挂载的房间还在内存里。如果同一目录在宿主机会被用到可能冲突。解决退出前卸载在第二视图里执行 终端umount/mnt/room2exit⚠️ 安全警告第二视图里/仍然是宿主机的根文件系统只是挂载表不同。不要执行rm -rf /等危险命令会删除宿主机文件。注意清单隔离 ≠ 文件隔离如果第二视图修改了已经存在的文件如/etc/hosts第一视图能看到变化因为底层文件数据是同一个。只有新挂载的房间才是隔离的。验证# 在第二视图里echotest/etc/hosts# 退出到第一视图exitcat/etc/hosts# 能看到 test 被追加进去了四、第三视图在独立清单里再复制一份4.1 怎么创建在第二视图内部执行在第二视图的 shell 里继续输入# 确保你在第二视图里提示符可能变了或者你记得执行过 unsharesudounshare--mount--propagationprivate /bin/bash# 在第二视图里再复制一份清单创建第三视图mkdir/mnt/room2/room3mount-ttmpfs tmpfs /mnt/room2/room3echo第三视图的文件/mnt/room2/room3/deep.txt“在内部执行” 基于第二视图的清单再复制一份作为第三视图的起点。4.2 即时验证# 第三视图能看到所有内容cat/mnt/room2/secret.txt# 第二视图的文件复制时带过来的cat/mnt/room2/room3/deep.txt# 第三视图的文件自己的新挂载# 退出到第二视图exitcat/mnt/room2/secret.txt# ✅ 能看到cat/mnt/room2/room3/deep.txt# ❌ 报错第二视图看不到第三视图的新挂载# 退出到第一视图exitcat/mnt/room2/secret.txt# ❌ 报错第一视图看不到第二视图的新挂载4.3 三个视图对比查看内容第一视图第二视图第三视图/mnt/room2/secret.txt❌ 看不到✅ 能看到✅ 能看到复制时带过来的/mnt/room2/room3/deep.txt❌ 看不到❌ 看不到✅ 能看到修改/etc/hosts已存在文件✅ 能看到变化✅ 能看到变化✅ 能看到变化底层共享规律每多一个视图就多一层清单隔离。外层看不到内层的新挂载除非显式进入内层。⚠️ 常见错误错误在第二视图外创建第三视图如果你在第一视图里执行sudo unshare --mount创建的是另一个第二视图不是第三视图。第三视图必须在第二视图内部创建。五、从视图到容器换房间5.1 问题前面的视图只隔离了清单没换房间第二视图、第三视图的清单虽然独立了但根目录/里的内容还是和第一视图一样。/bin/bash、/etc/passwd都是宿主机的文件。容器需要不仅清单独立根目录/也要换成新房间。5.2 容器怎么做以文件系统隔离为例dockerrun-itubuntu /bin/bash内部三步创建 Mount Namespace第二视图清单隔离挂载容器镜像文件系统新房间不是第三视图用pivot_root把根目录/指向新房间换房间操作pivot_root把进程的根目录切换到指定目录。这是容器运行时的内部机制实验 1-4 不手动操作只理解原理。容器里ls/# bin boot dev etc home lib ...这是 Ubuntu 镜像的内容不是宿主机的cat/etc/os-release# PRETTY_NAMEUbuntu 22.04 LTS宿主机上终端cat/etc/os-release显示宿主机自己的系统如 CentOS5.3 容器的文件系统隔离容器的文件系统隔离 第二视图清单隔离 新房间 换根容器比纯 Namespace 多了一层不仅换清单视图隔离还换房间根目录替换。注意容器没有创建第三视图它复用了第二视图的 Mount Namespace在里面直接挂载新房间并换根。完整的容器还需要 PID Namespace进程隔离、Network Namespace网络隔离、User Namespace用户权限隔离等。这里只讨论文件系统隔离。5.4 安全 implications文件系统隔离机制攻击场景文件系统隔离机制标准容器容器内读取宿主机/etc/shadow✅ 换根后看不到容器内修改宿主机文件✅ 清单隔离保护注意通过 volume 显式挂载宿主机目录容器可以访问。这是配置行为不是隔离机制的绕过。实验 1-4 的理解适用于标准容器。特权容器--privileged或内核漏洞逃逸需要额外防护不在本文范围。六、核心结论第一视图宿主机 │ ├── 房间清单/bin, /etc, /home, /mnt...所有系统挂载 │ ▼ unshare --mount复制清单 第二视图容器边界 │ ├── 复制了第一视图的清单作为起点 ├── 之后独立添加新房间 ├── 第一视图看不到第二视图的新房间 │ ▼ 内部再 unshare mount再复制清单 第三视图嵌套隔离 │ ├── 在第二视图内部再创建的 Mount Namespace ├── 第二视图看不到第三视图的新房间 ├── 底层还是同一个内核 │ └── 宿主机不进入第二视图永远看不到这里 容器第二视图的应用 │ ├── 用第二视图的 Mount Namespace 做边界 ├── 在内部挂载新房间容器镜像 ├── 用 pivot_root 把根目录指向新房间 │ └── 不是第三视图是第二视图 换房间同一内核不同房间清单。跨视图不可见必须显式进入。容器多一步不仅换清单还换房间。七、完整实验带清理# 步骤1创建第二视图记录 PID sudounshare--mount--propagationprivate /bin/bashecho$$# 记下这个 PID如 1234readlink /proc/$$/ns/mount# mnt:[...]记下这个数字# 步骤2第二视图挂载 mkdir/mnt/room2mount-ttmpfs tmpfs /mnt/room2echo第二视图/mnt/room2/a.txt# 步骤3在第二视图里创建第三视图 sudounshare--mount--propagationprivate /bin/bash readlink /proc/$$/ns/mount# mnt:[...]数字与步骤1不同# 步骤4第三视图挂载嵌套在room2内部 mkdir/mnt/room2/room3mount-ttmpfs tmpfs /mnt/room2/room3echo第三视图/mnt/room2/room3/b.txt# 步骤5第三视图验证能看到所有 cat/mnt/room2/a.txt# 第二视图的文件cat/mnt/room2/room3/b.txt# 第三视图的文件# 步骤5.5在第三视图内先卸载内层必须在exit之前umount/mnt/room2/room3# 步骤6回退到第二视图 exitcat/mnt/room2/a.txt# ✅ 能看到cat/mnt/room2/room3/b.txt# ❌ 报错目录存在但为空因为room3已卸载# 步骤6.5用 nsenter 进入第二视图验证 # 需要另开终端窗口执行保持原终端不退出# 1. 保持原终端窗口不关闭第二视图的bash还在运行# 2. 新开一个终端窗口或新标签页# 3. 在新终端里执行以下命令# 4. 验证完成后在新终端里输入 exit 退出# 5. 回到原终端继续步骤7sudonsenter-t1234-m/bin/bashcat/mnt/room2/a.txt# ✅ 能看到cat/mnt/room2/room3/b.txt# ❌ 报错确认第二视图看不到第三视图exit# 回到原终端# 步骤7在第二视图内卸载外层 umount/mnt/room2# 在第二视图内卸载exit# 回退到第一视图# 步骤8第一视图验证看不到第二视图 cat/mnt/room2/a.txt# ❌ 报错# 步骤9确认回到第一视图 readlink /proc/$$/ns/mount# 与步骤1的数字对比确认一致八、清理检查实验后必做# 检查是否还有残留挂载mount|grep/mnt/room# 应该没有任何输出# 检查 room2 目录是否为空umount 后ls/mnt/room2# 没有任何输出目录存在但为空# 检查 room3 目录是否消失随 room2 的 tmpfs 一起释放ls/mnt/room2/room3# 报错没有那个文件或目录# 如果有残留手动卸载sudoumount/mnt/room2/room3sudoumount/mnt/room2# 删除空目录sudormdir/mnt/room2/room3sudormdir/mnt/room2附录 A故障排查现象原因解决umount: not mounted在当前视图里没有这条挂载记录确认你在正确的视图里或检查 mountnsenter: cannot open /proc/1234/ns/mountPID 对应的进程已退出重新执行步骤 1-2确保第二视图的 bash 没退出ls /mnt/room2显示文件可能没执行 umount或 mount 传播到宿主机检查 mount修改/etc/hosts后宿主机也变了这是正常的清单隔离 ≠ 文件隔离理解已存在文件是共享的附录 B后续学习实验 1-4 理解了 Mount Namespace文件系统视图隔离。后续内容User Namespace用户权限隔离容器内的 root 映射到宿主机普通用户附录 C延伸阅读man 2 unshare创建新 Namespace 的系统调用man 1 nsenter进入指定 Namespace 的命令man 2 pivot_root切换根目录的系统调用Linux 内核源码fs/namespace.cMount Namespace 实现