当前位置: 首页 > news >正文

一篇文章搞懂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/mount=>mnt:[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),第一视图能看到变化,因为底层文件数据是同一个。只有新挂载的房间才是隔离的。

验证:

# 在第二视图里echo"test">>/etc/hosts# 退出到第一视图exitcat/etc/hosts# => 能看到 "test" 被追加进去了

四、第三视图:在独立清单里再复制一份

4.1 怎么创建?

在第二视图内部执行(在第二视图的 shell 里继续输入):

# 确保你在第二视图里(提示符可能变了,或者你记得执行过 unshare)sudounshare--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

内部三步:

  1. 创建 Mount Namespace(第二视图,清单隔离)
  2. 挂载容器镜像文件系统(新房间,不是第三视图)
  3. pivot_root把根目录/指向新房间(换房间操作)

pivot_root:把进程的根目录切换到指定目录。这是容器运行时的内部机制,实验 1-4 不手动操作,只理解原理。

容器里:

ls/# => bin boot dev etc home lib ...(这是 Ubuntu 镜像的内容,不是宿主机的)cat/etc/os-release# => PRETTY_NAME="Ubuntu 22.04 LTS"

宿主机上:

终端:cat/etc/os-release=>显示宿主机自己的系统(如 CentOS)

5.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在当前视图里没有这条挂载记录确认你在正确的视图里,或检查 `mount
nsenter: 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.c:Mount Namespace 实现
http://www.cnnetsun.cn/news/3783800.html

相关文章:

  • BiliBiliToolPro漫画任务全攻略:3分钟实现B站漫画自动签到与阅读
  • Awoo Installer:Nintendo Switch游戏安装的终极解决方案,简单快速的免费安装器指南 [特殊字符]
  • ESPHome驱动reTerminal E系列:打造本地交互显示终端的完整指南
  • FreeRTOS线程切换耗时测试:原理、实践与性能优化
  • 终极指南:如何用tiny11builder轻松打造精简Windows 11镜像 [特殊字符]
  • 小马宝莉官方高清大合照资源获取与批量处理技术指南
  • ESP32-S3-Tiny硬件方案解析:低成本物联网核心板设计与实践
  • 3分钟学会安全烧录:Balena Etcher让你告别SD卡/USB镜像烧录烦恼
  • 方达炬 发明一例新字词 一例方程符:七级财务责任及七级千进制方程符¹⁰⁰⁰⁰⁰⁰‰¹⁰⁰⁰⁰⁰‰¹⁰⁰⁰⁰‰¹⁰⁰⁰‰¹⁰⁰‰¹⁰‰¹‰
  • 拒绝盲目跟风!AI Agent 架构选型实战指南:从 ReAct 到 LLMCompiler
  • 终极Windows版Mifare Classic工具:告别命令行,轻松管理NFC卡片的完整指南
  • Pico-8游戏画面驱动8段数码管:嵌入式图形处理与硬件交互实践
  • 2026 实测:Scrapy 项目接入代理 IP,哪些坑最容易导致采集不稳定?
  • 芯片设计行业术语解析:从RTL到Tapeout的核心“黑话”指南
  • 2.66英寸电子纸模块驱动全解析:从SPI通信到多平台实战应用
  • 123、YOLOv8改进实战:多尺度训练策略——动态输入尺寸与尺度抖动的工程实现与效果分析
  • 新手部署 OpenClaw 避坑指南,路径设置与安全软件兼容要点(含安装包)
  • 微信小程序获取手机号全流程实战:从原理到避坑指南
  • ESP32-S3触摸屏开发实战:从硬件选型到LVGL图形界面开发
  • GetQzonehistory:3步完成QQ空间历史说说完美备份的终极指南
  • 基于CH32V307的智能温控系统设计与PID算法实现
  • ESP32-S3驱动1.85寸触摸屏全攻略:从硬件解析到LVGL界面开发
  • Stata工具变量法实战:两阶段最小二乘法解决内生性问题
  • C++动态规划精解:从01背包问题到空间优化与实战技巧
  • 终极指南:如何用XInputTest免费检测游戏手柄延迟与轮询率
  • Swift二维码生成的终极指南:如何快速实现专业级二维码功能
  • 彻底解决浏览器ERR_UNSAFE_PORT错误:从原理到实践的完整指南
  • USB转RS232线缆:硬件拆解、驱动配置与工业通信实战指南
  • IG引入Assum:LPL下路务实补强与战术适配分析
  • 如何快速掌握UE4SS:面向新手的虚幻引擎脚本系统完整教程