强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Bubblewrap 容器入门教程 / 第 5 章 - 文件系统隔离

第 5 章:文件系统隔离

本章深入讲解 Bubblewrap 中文件系统隔离的各种技术,包括绑定挂载、只读挂载、tmpfs、proc 文件系统和设备文件系统,以及如何构建精细的文件系统布局。


5.1 文件系统隔离基础

Bubblewrap 的文件系统隔离基于 Linux 的 Mount Namespace。通过精心设计挂载点布局,可以精确控制沙箱进程能访问哪些文件,以及以什么权限访问。

隔离策略概览

策略说明bwrap 参数
完全只读整个文件系统只读--ro-bind / /
选择性可写大部分只读,特定目录可写--ro-bind / / + --bind / --tmpfs
最小可见只暴露必要的文件逐个 --ro-bind 需要的目录
内存文件系统完全在 tmpfs 中运行--tmpfs / + --ro-bind 必要的

文件系统布局示例

沙箱文件系统树:
/
├── bin/        → ro-bind /bin        (只读)
├── dev/        → 最小 dev            (null, zero, random, urandom)
├── etc/        → ro-bind /etc        (只读)
├── home/       → tmpfs               (可写, 内存)
│   └── user/   → tmpfs               (可写, 内存)
├── lib/        → ro-bind /lib        (只读)
├── lib64/      → ro-bind /lib64      (只读)
├── proc/       → proc                (沙箱 PID 视图)
├── run/        → tmpfs               (可写, 内存)
├── sbin/       → ro-bind /sbin       (只读)
├── tmp/        → tmpfs               (可写, 内存)
├── usr/        → ro-bind /usr        (只读)
└── var/        → tmpfs               (可写, 内存)

5.2 绑定挂载(Bind Mount)

绑定挂载是 Linux mount 系统调用的一个特性,允许将一个目录或文件映射到另一个路径。Bubblewrap 提供两种绑定挂载:

只读绑定(--ro-bind

# 将宿主的 /usr 以只读方式挂载到沙箱的 /usr
bwrap \
  --ro-bind /usr /usr \
  --ro-bind /lib /lib \
  --ro-bind /lib64 /lib64 \
  --ro-bind /bin /bin \
  --tmpfs / \
  --dev /dev \
  --proc /proc \
  bash

# 在沙箱内尝试写入——被拒绝
touch /usr/test.txt
# touch: cannot touch '/usr/test.txt': Read-only file system

echo "test" > /etc/test.conf
# bash: /etc/test.conf: Read-only file system

读写绑定(--bind

# 将宿主的 ~/documents 以读写方式挂载到沙箱
bwrap \
  --ro-bind / / \
  --bind ~/documents /documents \
  --tmpfs /tmp \
  --dev /dev \
  --proc /proc \
  bash

# 在沙箱内写入——写回到宿主
echo "from sandbox" > /documents/notes.txt
exit

# 在宿主验证
cat ~/documents/notes.txt
# from sandbox

文件级绑定

绑定挂载不只限于目录,也可以绑定单个文件:

# 只暴露特定的配置文件
bwrap \
  --ro-bind / / \
  --tmpfs /etc \
  --ro-bind /etc/passwd /etc/passwd \
  --ro-bind /etc/group /etc/group \
  --ro-bind /etc/hosts /etc/hosts \
  --dev /dev \
  --proc /proc \
  bash

# 现在沙箱的 /etc 只有 passwd、group、hosts 三个文件
ls /etc/
# group  hosts  passwd

绑定挂载的传播属性

# 使用 --bind 创建私有绑定(默认)
bwrap \
  --ro-bind / / \
  --bind ~/data /data \
  bash
# /data 的挂载事件不会传播到宿主

# 从沙箱内挂载额外的文件系统不会影响宿主
bwrap \
  --ro-bind / / \
  --bind ~/data /data \
  bash -c '
    # 尝试在沙箱内挂载 tmpfs
    mount -t tmpfs tmpfs /data/tmp 2>&1 || echo "Mount failed"
  '

5.3 tmpfs 文件系统

tmpfs 是一种基于内存的文件系统,数据存储在 RAM 中,不持久化到磁盘。

基本用法

# 创建 tmpfs 挂载点
bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  --tmpfs /var/tmp \
  --tmpfs /run \
  --dev /dev \
  --proc /proc \
  bash

# 在沙箱内写入 tmpfs
echo "temporary data" > /tmp/data.txt
cat /tmp/data.txt
# temporary data

# 查看 tmpfs 使用情况
df -h /tmp
# Filesystem      Size  Used Avail Use% Mounted on
# tmpfs            16G     0   16G   0% /tmp

限制 tmpfs 大小

# 使用 --size 限制 tmpfs 大小(单位 MB)
bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  --size 64 \
  --dev /dev \
  --proc /proc \
  bash -c '
    df -h /tmp
    # Filesystem      Size  Used Avail Use% Mounted on
    # tmpfs            64M     0   64M   0% /tmp

    # 测试写入限制
    dd if=/dev/zero of=/tmp/bigfile bs=1M count=70 2>&1
    # dd: error writing '/tmp/bigfile': No space left on device
  '

tmpfs 选项

# tmpfs 支持多种挂载选项
bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  --size 128 \
  --dev /dev \
  --proc /proc \
  bash

# 注意:bwrap 的 --size 选项直接传递给 tmpfs 的 size= 挂载选项

tmpfs 的典型使用场景

挂载点用途推荐大小
/tmp临时文件64-512 MB
/var/tmp持久临时文件64-256 MB
/run运行时数据(PID 文件等)16-64 MB
/home/user用户主目录128-1024 MB
/home/user/.cache缓存目录256-2048 MB
/home/user/.config配置目录32-128 MB

5.4 Proc 文件系统

/proc 是一个虚拟文件系统,提供内核和进程信息的接口。

基本用法

# 为 PID 命名空间挂载 proc
bwrap \
  --ro-bind / / \
  --unshare-pid \
  --proc /proc \
  --dev /dev \
  bash

# /proc 只显示沙箱内的进程
ls /proc/*/cmdline 2>/dev/null | while read f; do
    pid=$(echo "$f" | cut -d'/' -f3)
    echo "PID $pid: $(cat "$f" 2>/dev/null | tr '\0' ' ')"
done
# PID 1: bash

没有 --proc 的影响

# 如果不挂载 /proc,很多工具会出错
bwrap \
  --ro-bind / / \
  --unshare-pid \
  --dev /dev \
  bash -c 'ps aux'
# error: /proc must be mounted
#   No /proc directory? Check your bwrap command line.

/proc 中的安全信息

# /proc 包含大量系统信息,沙箱中需要谨慎暴露
bwrap \
  --ro-bind / / \
  --proc /proc \
  --dev /dev \
  bash -c '
    # 内核版本
    cat /proc/version
    # Linux version 6.1.0 ...

    # CPU 信息
    cat /proc/cpuinfo | head -10

    # 内存信息
    cat /proc/meminfo | head -5
  '

5.5 设备文件系统(dev)

--dev /dev 创建一个最小的设备文件系统,只包含必要的设备文件。

创建的设备文件

bwrap \
  --ro-bind / / \
  --dev /dev \
  bash -c 'ls -la /dev/'
设备用途权限
/dev/null丢弃所有写入,读取返回 EOFcrw-rw-rw-
/dev/zero读取返回零字节crw-rw-rw-
/dev/full写入返回 ENOSPCcrw-rw-rw-
/dev/random随机数(阻塞)crw-rw-rw-
/dev/urandom随机数(非阻塞)crw-rw-rw-
/dev/fd/proc/self/fd 符号链接lrwxrwxrwx
/dev/ptmx伪终端主设备crw-rw-rw-
/dev/pts/伪终端从设备目录drwxr-xr-x
/dev/shm/共享内存目录drwxrwxrwt
/dev/stdin/proc/self/fd/0lrwxrwxrwx
/dev/stdout/proc/self/fd/1lrwxrwxrwx
/dev/stderr/proc/self/fd/2lrwxrwxrwx

不使用 --dev 的影响

# 不挂载 /dev,很多程序会报错
bwrap \
  --ro-bind / / \
  --proc /proc \
  bash -c 'ls /dev/null'
# ls: cannot access '/dev/null': No such file or directory

# 程序通常需要 /dev/null
bwrap \
  --ro-bind / / \
  --proc /proc \
  bash -c 'echo test > /dev/null'
# bash: /dev/null: No such file or directory

# 创建符号链接
bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  --symlink /usr/lib /lib \
  --symlink /usr/lib64 /lib64 \
  --symlink /usr/bin /bin \
  --symlink /usr/sbin /sbin \
  --dev /dev \
  --proc /proc \
  bash

# 验证符号链接
ls -la /lib
# lrwxrwxrwx 1 root root 8 ... /lib -> /usr/lib

实际应用:简化文件系统布局

# 为某些需要特定路径的程序创建兼容层
bwrap \
  --ro-bind / / \
  --tmpfs /opt \
  --dir /opt/myapp \
  --bind ~/myapp/lib /opt/myapp/lib \
  --symlink /opt/myapp/lib /usr/local/lib/myapp \
  --dev /dev \
  --proc /proc \
  bash

5.7 创建目录(--dir

# 在沙箱内创建目录
bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  --dir /tmp/workspace \
  --dir /tmp/output \
  --dir /tmp/cache \
  --dev /dev \
  --proc /proc \
  bash -c '
    ls -la /tmp/
    # drwxr-xr-x 2 root root 40 ... cache
    # drwxr-xr-x 2 root root 40 ... output
    # drwxr-xr-x 2 root root 40 ... workspace
  '

5.8 从文件描述符创建文件(--file

# 使用 heredoc 创建配置文件
bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  --dev /dev \
  --proc /proc \
  --file 0 /tmp/config.ini << 'EOF'
[settings]
debug = true
output_dir = /tmp/output
max_threads = 4
EOF

bash -c 'cat /tmp/config.ini'
# [settings]
# debug = true
# output_dir = /tmp/output
# max_threads = 4

实际应用:注入配置文件

# 将宿主的配置文件修改后注入沙箱
cat > /tmp/sandbox-bashrc << 'EOF'
# Sandbox bashrc
export PS1="[sandbox] \u@\h:\w\$ "
alias ll='ls -la'
echo "Welcome to the sandbox!"
EOF

bwrap \
  --ro-bind / / \
  --tmpfs /home/sandbox \
  --file 0 /home/sandbox/.bashrc < /tmp/sandbox-bashrc \
  --dir /home/sandbox \
  --clearenv \
  --setenv HOME /home/sandbox \
  --setenv TERM xterm-256color \
  --setenv PATH /usr/local/bin:/usr/bin:/bin \
  --chdir /home/sandbox \
  --dev /dev \
  --proc /proc \
  bash
# Welcome to the sandbox!
# [sandbox] user@hostname:~$

5.9 高级文件系统布局

最小化可见文件系统

只暴露程序运行所需的最少文件:

# 创建一个只包含必要文件的最小沙箱
bwrap \
  --tmpfs / \
  --ro-bind /usr /usr \
  --ro-bind /lib /lib \
  --ro-bind /lib64 /lib64 \
  --ro-bind /bin /bin \
  --ro-bind /sbin /sbin \
  --ro-bind /etc/passwd /etc/passwd \
  --ro-bind /etc/group /etc/group \
  --ro-bind /etc/nsswitch.conf /etc/nsswitch.conf \
  --ro-bind /etc/resolv.conf /etc/resolv.conf \
  --ro-bind /etc/hosts /etc/hosts \
  --ro-bind /etc/localtime /etc/localtime \
  --ro-bind /etc/ssl /etc/ssl \
  --symlink /usr/lib /lib \
  --symlink /usr/lib64 /lib64 \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  --tmpfs /home \
  --tmpfs /var \
  --tmpfs /run \
  bash

# 现在沙箱内只能看到:
ls /
# bin  dev  etc  home  lib  lib64  proc  run  sbin  tmp  usr  var

选择性暴露用户目录

# 只暴露特定的项目目录,隐藏其他所有个人文件
bwrap \
  --ro-bind / / \
  --tmpfs /home/user \
  --bind ~/projects/current /home/user/project \
  --bind ~/documents/shared /home/user/shared \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash -c '
    ls /home/user/
    # project  shared

    # 看不到其他个人文件
    ls /home/user/.ssh 2>/dev/null || echo "No .ssh directory"
    # No .ssh directory
  '

为特定应用定制文件系统

# Python 开发环境沙箱
bwrap \
  --ro-bind / / \
  --bind ~/projects/python-app /app \
  --tmpfs /home/dev \
  --ro-bind ~/projects/python-app/requirements.txt /tmp/requirements.txt \
  --tmpfs /tmp \
  --dev /dev \
  --proc /proc \
  --unshare-pid \
  --chdir /app \
  bash -c '
    python3 --version
    pip3 install -r /tmp/requirements.txt --user --target /home/dev/libs 2>/dev/null
    PYTHONPATH=/home/dev/libs python3 main.py
  '

5.10 防止文件系统泄露

隐藏敏感文件

# 隐藏 SSH 密钥和其他敏感文件
bwrap \
  --ro-bind / / \
  --tmpfs /home/user/.ssh \
  --tmpfs /home/user/.gnupg \
  --tmpfs /home/user/.aws \
  --tmpfs /home/user/.kube \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash

# 在沙箱内
ls ~/.ssh/
# (空)

ls ~/.aws/
# (空)

只读保护系统关键文件

# 确保系统文件不可修改
bwrap \
  --bind / / \
  --remount-ro /etc \
  --remount-ro /usr \
  --remount-ro /boot \
  --remount-ro /lib \
  --remount-ro /lib64 \
  --tmpfs /tmp \
  --dev /dev \
  --proc /proc \
  bash

# 尝试修改系统文件——被拒绝
echo "malicious" > /etc/passwd
# bash: /etc/passwd: Read-only file system

5.11 文件系统权限总结表

挂载方式可读可写数据持久化数据来源
--ro-bind SRC DST宿主文件同步宿主目录
--bind SRC DST宿主文件同步宿主目录
--tmpfs DST❌ (退出即丢失)内存
--dev DST✅*❌ (退出即丢失)最小设备
--proc DST✅*内核虚拟内核
--symlink SRC DST指向 SRC
--dir DST取决于父挂载点
--file FD DST取决于父挂载点文件描述符

*/dev/proc 的可写性取决于具体文件和内核版本。


5.12 注意事项

⚠️ 重要提醒

  1. 参数顺序决定最终布局:后面的挂载会覆盖前面的。始终将 --ro-bind / / 放在前面,特定目录的覆盖放在后面。

  2. 不要暴露 /proc/kallsyms/proc/kcore:这些文件泄露内核符号和内存信息。bwrap 的 --proc 会正确处理这些。

  3. --dev /dev 不包含磁盘设备:沙箱无法直接访问磁盘(如 /dev/sda)。

  4. tmpfs 占用内存:大文件写入 tmpfs 会消耗物理内存,注意使用 --size 限制。

  5. 绑定挂载的修改是真实的:使用 --bind 的修改会写回宿主文件系统,谨慎使用。

  6. SELinux/AppArmor 可能额外限制:即使命名空间允许,MAC 策略也可能拒绝操作。

  7. /proc 在 PID 命名空间中尤为重要:没有正确的 --proc,许多工具(pstoplsof)将无法正常工作。


5.13 扩展阅读


上一章:第 4 章 - 命名空间详解 | 下一章:第 6 章 - 网络隔离