强曰为道

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

第 3 章 - 基本用法

第 3 章:基本用法

本章介绍 Bubblewrap 的命令行语法、核心参数、命名空间基础用法和文件系统隔离的基本模式。掌握本章内容后,你就能创建实用的沙箱环境。


3.1 命令行语法

基本格式

bwrap [选项...] -- <命令> [参数...]

所有 bwrap 选项必须出现在 -- 之前,-- 之后是要在沙箱内执行的命令及其参数。

参数顺序

Bubblewrap 的参数是有序的,按从左到右的顺序依次执行挂载和设置操作。这一点非常重要:

# 正确:先绑定只读根文件系统,再覆盖 /tmp
bwrap --ro-bind / / --tmpfs /tmp -- bash

# 错误的思路:参数顺序会影响最终的文件系统布局
# bwrap --tmpfs /tmp --ro-bind / / -- bash
# ↑ /tmp 会被 ro-bind / 覆盖,tmpfs 白设了

3.2 核心参数速查

命名空间参数

参数作用等效缩写
--unshare-user创建新的用户命名空间
--unshare-pid创建新的 PID 命名空间
--unshare-net创建新的网络命名空间
--unshare-uts创建新的 UTS 命名空间
--unshare-cgroup创建新的 cgroup 命名空间
--unshare-ipc创建新的 IPC 命名空间
--unshare-all分离所有命名空间

文件系统参数

参数作用示例
--ro-bind SRC DST只读绑定挂载--ro-bind /usr /usr
--bind SRC DST读写绑定挂载--bind ~/data /data
--dev DST创建最小 dev 文件系统--dev /dev
--proc DST挂载 proc 文件系统--proc /proc
--tmpfs DST创建 tmpfs--tmpfs /tmp
--mqueue DST创建 mqueue 文件系统--mqueue /dev/mqueue
--dir DST创建目录--dir /tmp/mydir
--file FD DST从文件描述符创建文件--file 0 /etc/config
--symlink SRC DST创建符号链接--symlink /usr/lib /lib

用户/权限参数

参数作用
--uid UID设置沙箱内的用户 ID
--gid GID设置沙箱内的组 ID
--hostname NAME设置沙箱内的主机名
--chdir DIR设置沙箱内的工作目录
--setenv KEY VALUE设置环境变量
--unsetenv KEY删除环境变量
--clearenv清除所有环境变量

资源限制参数

参数作用
--size SIZE设置 tmpfs 大小(如 --size 256,单位 MB)
--remount-ro DEST重新以只读方式挂载
--die-with-parent父进程退出时沙箱也退出
--new-session创建新的会话(session)
--cap-add CAP添加权能
--cap-drop CAP删除权能

3.3 第一个沙箱

最小化沙箱

# 最简单的沙箱——什么都不隔离,只是用 bwrap 启动一个 bash
bwrap --ro-bind / / -- bash

# 在沙箱内,一切看起来和宿主一样(因为只读绑定了宿主的整个文件系统)
ls /
cat /etc/hostname
whoami

带隔离的沙箱

# 一个有基本隔离的沙箱
bwrap \
  --ro-bind / / \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  --unshare-pid \
  --unshare-uts \
  --hostname sandbox \
  --chdir /home \
  bash

在沙箱内验证隔离:

# PID 隔离——只看到自己的进程
ps aux
# USER  PID %CPU %MEM    VSZ   RSS TTY STAT START TIME COMMAND
# 1000    1  0.0  0.0  ...     ...   ?   S    ...   0:00 bash
# 1000    2  0.0  0.0  ...     ...   ?   R+   ...   0:00 ps aux

# UTS 隔离——独立的主机名
hostname
# sandbox

# tmpfs 隔离——/tmp 是空的
ls /tmp
# (空)

# 写入测试
echo "hello from sandbox" > /tmp/test.txt
cat /tmp/test.txt
# hello from sandbox
exit

退出沙箱后,宿主的 /tmp 中不会有 test.txt——因为沙箱的 /tmp 是内存中的 tmpfs。


3.4 文件系统隔离基础

挂载点参数详解

只读绑定(--ro-bind

将宿主的文件或目录以只读方式映射到沙箱中。沙箱进程无法修改这些文件:

bwrap \
  --ro-bind /usr /usr \
  --ro-bind /lib /lib \
  --ro-bind /lib64 /lib64 \
  --ro-bind /bin /bin \
  --ro-bind /etc /etc \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash

读写绑定(--bind

将宿主的目录以读写方式映射到沙箱中。沙箱进程可以修改这些文件,修改会反映到宿主

# 将宿主的 ~/projects 绑定到沙箱的 /projects(读写)
bwrap \
  --ro-bind / / \
  --bind ~/projects /projects \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash

# 在沙箱内
ls /projects
# 你的项目文件...
echo "sandbox edit" > /projects/test.txt
exit

# 在宿主检查
cat ~/projects/test.txt
# sandbox edit

tmpfs

创建一个内存中的临时文件系统。数据在沙箱退出后消失,大小默认受限于系统内存:

bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  --tmpfs /var/tmp \
  --tmpfs /run \
  --tmpfs /home/user/.cache \
  bash

# 查看 tmpfs 信息
df -h /tmp
# Filesystem      Size  Used Avail Use% Mounted on
# tmpfs            16G     0   16G   0% /tmp

指定 tmpfs 大小:

# 限制 /tmp 大小为 256 MB
bwrap --ro-bind / / --tmpfs /tmp --size 256 -- bash

dev 文件系统

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

bwrap --ro-bind / / --dev /dev -- bash

ls -la /dev/
# total 0
# drwxr-xr-x  2 root root      80 ...
# crw-rw-rw-  1 root root 1, 3 ... null
# crw-rw-rw-  1 root root 1, 5 ... zero
# crw-rw-rw-  1 root root 1, 7 ... full
# crw-rw-rw-  1 root root 1, 8 ... random
# crw-rw-rw-  1 root root 1, 9 ... urandom
# lrwxrwxrwx  1 root root       0 ... fd -> /proc/self/fd
# crw-rw-rw-  1 root root 5, 0 ... ptmx
# drwxr-xr-x  2 root root       0 ... pts
# drwxr-xr-x  2 root root       0 ... shm
# lrwxrwxrwx  1 root root       0 ... stdin -> /proc/self/fd/0
# lrwxrwxrwx  1 root root       0 ... stdout -> /proc/self/fd/1
# lrwxrwxrwx  1 root root       0 ... stderr -> /proc/self/fd/2

proc 文件系统

--proc /proc 为沙箱的 PID 命名空间挂载 proc 文件系统:

bwrap \
  --ro-bind / / \
  --unshare-pid \
  --proc /proc \
  --dev /dev \
  bash

# /proc 只显示沙箱内的进程
ls /proc/ | head -5
# 1
# bus
# cgroups
# cmdline

3.5 实用沙箱示例

示例 1:只读文件浏览器

# 一个只能读不能写的文件浏览器
bwrap \
  --ro-bind / / \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  --unshare-pid \
  --unshare-uts \
  --hostname readonly-sandbox \
  bash

示例 2:代码编译沙箱

# 在隔离环境中编译 C 代码
# 源码目录可写,其余只读
cat > /tmp/hello.c << 'EOF'
#include <stdio.h>
int main() {
    printf("Hello from sandbox!\n");
    return 0;
}
EOF

bwrap \
  --ro-bind / / \
  --bind /tmp /tmp \
  --dev /dev \
  --proc /proc \
  --unshare-pid \
  bash -c 'gcc /tmp/hello.c -o /tmp/hello && /tmp/hello'
# Hello from sandbox!

示例 3:不可信脚本执行

# 安全地执行不可信的 shell 脚本
cat > /tmp/untrusted.sh << 'EOF'
#!/bin/bash
echo "I'm running in a sandbox!"
echo "My PID is $$"
echo "I can see these files in /tmp:"
ls /tmp
# 即使脚本尝试恶意操作,也会被限制
rm -rf / 2>/dev/null || echo "rm / failed (expected)"
EOF

chmod +x /tmp/untrusted.sh

bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  --unshare-all \
  --proc /proc \
  --dev /dev \
  --die-with-parent \
  bash /tmp/untrusted.sh

示例 4:网络工具隔离

# 允许网络访问但隔离文件系统
bwrap \
  --ro-bind / / \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  --unshare-pid \
  curl -s https://example.com | head -5

示例 5:数据处理沙箱

# 在沙箱中处理敏感数据
mkdir -p /tmp/sandbox-data

# 准备数据
echo "sensitive data line 1" > /tmp/sandbox-data/input.txt
echo "sensitive data line 2" >> /tmp/sandbox-data/input.txt

# 在沙箱中处理
bwrap \
  --ro-bind / / \
  --bind /tmp/sandbox-data /data \
  --tmpfs /tmp \
  --dev /dev \
  --proc /proc \
  bash -c '
    wc -l /data/input.txt
    sort /data/input.txt > /tmp/sorted.txt
    cat /tmp/sorted.txt
  '

# 清理
rm -rf /tmp/sandbox-data

3.6 环境变量控制

保持宿主环境变量

# 默认情况下,bwrap 会继承宿主的环境变量
bwrap --ro-bind / / -- bash -c 'echo $HOME $USER $PATH'
# /home/user user /usr/local/bin:/usr/bin:/bin

清除并自定义环境变量

# 清除所有环境变量,只设置需要的
bwrap \
  --ro-bind / / \
  --clearenv \
  --setenv HOME /home/sandbox \
  --setenv USER sandbox \
  --setenv PATH /usr/bin:/bin \
  --setenv LANG en_US.UTF-8 \
  --tmpfs /home/sandbox \
  --chdir /home/sandbox \
  bash

# 在沙箱内
env
# HOME=/home/sandbox
# USER=sandbox
# PATH=/usr/bin:/bin
# LANG=en_US.UTF-8
# PWD=/home/sandbox

隔离敏感环境变量

# 防止敏感环境变量泄露到沙箱
bwrap \
  --ro-bind / / \
  --unsetenv AWS_SECRET_ACCESS_KEY \
  --unsetenv DB_PASSWORD \
  --unsetenv API_TOKEN \
  --tmpfs /tmp \
  bash -c 'echo "AWS key: $AWS_SECRET_ACCESS_KEY"'
# AWS key:

3.7 主机名隔离

# 设置沙箱内的主机名
bwrap \
  --ro-bind / / \
  --unshare-uts \
  --hostname "my-sandbox" \
  bash -c 'hostname'
# my-sandbox

# 验证宿主的主机名未受影响
hostname
# your-real-hostname

💡 注意--hostname 隐含了 --unshare-uts,可以不显式指定 --unshare-uts


3.8 工作目录设置

# 设置沙箱启动时的工作目录
bwrap \
  --ro-bind / / \
  --tmpfs /tmp/workspace \
  --chdir /tmp/workspace \
  bash -c 'pwd'
# /tmp/workspace

# 如果指定的目录不存在,bwrap 会报错
bwrap --ro-bind / / --chdir /nonexistent -- true
# bwrap: Can't chdir to /nonexistent: No such file or directory

3.9 组合使用模式

通用隔离模式

一个实用的通用沙箱模板:

# 通用隔离沙箱
bwrap \
  --ro-bind / / \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  --tmpfs /var/tmp \
  --tmpfs /run \
  --unshare-pid \
  --unshare-uts \
  --hostname sandbox \
  --die-with-parent \
  --new-session \
  "$@"

保存为脚本:

#!/bin/bash
# sandbox.sh - 通用沙箱启动脚本

exec bwrap \
  --ro-bind / / \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  --unshare-pid \
  --unshare-uts \
  --hostname sandbox \
  --die-with-parent \
  --new-session \
  "$@"

使用:

# 在沙箱中运行任意命令
./sandbox.sh bash
./sandbox.sh python3
./sandbox.sh vim /tmp/test.txt
./sandbox.sh gcc -o /tmp/prog source.c

3.10 参数顺序陷阱

这是 Bubblewrap 最容易踩的坑之一。参数是按顺序处理的,后面的操作会覆盖前面的操作:

# ❌ 错误:ro-bind / 会覆盖前面的 tmpfs /tmp
bwrap --tmpfs /tmp --ro-bind / / -- bash
# /tmp 现在是宿主 /tmp 的只读镜像

# ✅ 正确:先设置基础文件系统,再覆盖特定挂载点
bwrap --ro-bind / / --tmpfs /tmp -- bash
# /tmp 是 tmpfs

参数处理流程图

参数从左到右依次处理:

--ro-bind / /        →  根文件系统 = 宿主的只读镜像
       ↓
--dev /dev           →  /dev = 最小设备文件系统
       ↓
--proc /proc         →  /proc = 沙箱 PID 的 proc
       ↓
--tmpfs /tmp         →  /tmp = 空的 tmpfs(覆盖了宿主的 /tmp)
       ↓
--bind ~/data /data  →  /data = 宿主 ~/data 的读写绑定
       ↓
--dir /tmp/mydir     →  在 tmpfs 中创建 /tmp/mydir
       ↓
执行用户命令

3.11 注意事项

⚠️ 重要提醒

  1. 参数顺序很重要:后面的挂载操作会覆盖前面的。始终先设置基础文件系统,再覆盖特定目录。

  2. --ro-bind / / 是常见起点:大多数沙箱以这个参数开始,然后在此基础上添加或覆盖。

  3. 不要忘记 --dev--proc:很多程序需要 /dev/null/dev/urandom/proc。没有这些,程序可能会出错。

  4. --die-with-parent 推荐加上:防止沙箱进程在父进程退出后变成孤儿进程。

  5. --new-session 推荐加上:防止沙箱进程通过终端信号影响宿主。

  6. 读写绑定的文件会写回宿主--bind 的修改是真实的,不是隔离的。如果需要写入隔离,使用 tmpfs。


3.12 扩展阅读


上一章:第 2 章 - 安装与环境配置 | 下一章:第 4 章 - 命名空间详解