强曰为道

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

第 4 章 - 命名空间详解

第 4 章:命名空间详解

本章深入讲解 Linux 内核提供的 7 种命名空间在 Bubblewrap 中的用法,包括每种命名空间的工作原理、隔离效果、实际应用场景和组合使用模式。


4.1 命名空间概览

Linux 内核提供 7 种命名空间(Namespace),每种隔离一类系统资源:

命名空间标志位隔离内容最低内核版本bwrap 参数
MountCLONE_NEWNS挂载点2.4.19隐含使用
UserCLONE_NEWUSER用户/组 ID3.8--unshare-user
PIDCLONE_NEWPID进程 ID2.6.19--unshare-pid
NetworkCLONE_NEWNET网络栈2.6.29--unshare-net
UTSCLONE_NEWUTS主机名/域名2.6.19--unshare-uts
CgroupCLONE_NEWCGROUPcgroup 根4.6--unshare-cgroup
IPCCLONE_NEWIPC进程间通信2.6.19--unshare-ipc

命名空间层次关系

宿主系统
├── Mount NS: 全局挂载视图
│   └── User NS (root)
│       └── 其他所有命名空间
│
├── Bubblewrap 沙箱
│   ├── User NS:     UID 1000 → 沙箱内 UID 0 (mapped)
│   ├── Mount NS:    独立的文件系统树
│   ├── PID NS:      进程从 PID 1 开始
│   ├── Network NS:  独立的网络栈(或无网络)
│   ├── UTS NS:      独立的主机名
│   ├── Cgroup NS:   独立的 cgroup 视图
│   └── IPC NS:      独立的 IPC 资源

4.2 User Namespace(用户命名空间)

User Namespace 是 Bubblewrap 能以非特权模式运行的关键。它允许普通用户在沙箱内拥有 “root” 权限,同时在宿主上仍然以普通用户身份运行。

工作原理

宿主系统                           沙箱内
┌──────────────────────┐          ┌──────────────────────┐
│ UID 1000 (user)      │    →     │ UID 0 (root)         │
│ GID 1000 (group)     │    →     │ GID 0 (root)         │
│                      │          │                      │
│ 进程权限: 普通用户     │          │ 进程权限: 沙箱 root   │
│ (只能操作自己的文件)   │          │ (只能操作沙箱内文件)   │
└──────────────────────┘          └──────────────────────┘

基本用法

# 创建带 user namespace 的沙箱
bwrap \
  --ro-bind / / \
  --unshare-user \
  --uid 0 \
  --gid 0 \
  bash

# 在沙箱内验证
id
# uid=0(root) gid=0(root) groups=0(root)

whoami
# root

# 尝试修改只读文件系统——会被阻止
touch /etc/test
# touch: cannot touch '/etc/test': Read-only file system

# 尝试加载内核模块——user namespace 内禁止
modprobe dummy 2>&1 || true
# modprobe: can't change directory to '/lib/modules': No such file or directory

UID/GID 映射

# 自定义 UID/GID 映射
bwrap \
  --ro-bind / / \
  --unshare-user \
  --uid 1000 \
  --gid 1000 \
  bash -c 'id; cat /proc/self/uid_map'
# uid=1000 gid=1000
#          0       1000          1

# 使用 --map-root-user 快速映射到 root
bwrap \
  --ro-bind / / \
  --unshare-user \
  --map-root-user \
  bash -c 'id; cat /proc/self/uid_map'
# uid=0(root) gid=0(root)
#          0       1000          1

安全特性

User Namespace 内的 “root” 受到以下限制:

操作状态原因
修改沙箱内的文件✅ 允许只影响 tmpfs/绑定挂载
修改宿主的文件❌ 禁止不在沙箱的挂载视图中
加载内核模块❌ 禁止CAP_SYS_MODULE 在 user NS 中无效
修改系统时间❌ 禁止需要宿主的 CAP_SYS_TIME
挂载文件系统受限仅限标记了 FS_USERNS_MOUNT 的类型
发送信号到宿主进程❌ 禁止PID 命名空间隔离
配置宿主网络❌ 禁止需要宿主的 CAP_NET_ADMIN

4.3 Mount Namespace(挂载命名空间)

Mount Namespace 是最常用的命名空间之一。它使沙箱拥有独立的挂载点视图,沙箱内对挂载点的修改不会影响宿主。

工作原理

宿主 Mount NS:                  沙箱 Mount NS:
/                               /
├── bin                         ├── bin     (ro-bind from /bin)
├── etc     (读写)              ├── etc     (ro-bind from /etc)
├── home                        ├── home    (tmpfs, 空)
│   └── user                    ├── tmp     (tmpfs)
│       └── data                ├── dev     (最小设备)
├── tmp                         └── proc    (沙箱 PID)
├── usr
└── var

基本用法

# Mount namespace 在 bwrap 中是隐含使用的
# 每次使用 --ro-bind、--bind、--tmpfs 等参数时,都会创建新的 mount namespace

bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  --bind /home/user/workspace /workspace \
  --dev /dev \
  --proc /proc \
  bash

# 在沙箱内查看挂载信息
mount | head -20
# /dev/sda1 on / type ext4 (ro,relatime)
# tmpfs on /tmp type tmpfs (rw,nodev,size=...)
# /home/user/workspace on /workspace type ext4 (rw,relatime)
# ...

只读重挂载

# 将特定目录重新以只读方式挂载
bwrap \
  --bind / / \
  --remount-ro /etc \
  --remount-ro /usr \
  --tmpfs /tmp \
  bash

实际应用:构建沙箱

# 编译软件时,源码目录可写,系统目录只读
bwrap \
  --ro-bind / / \
  --bind ~/projects/myapp /src \
  --tmpfs /build \
  --dev /dev \
  --proc /proc \
  --unshare-pid \
  bash -c '
    cd /src
    mkdir -p /build/output
    make -j$(nproc) DESTDIR=/build/output install
  '

4.4 PID Namespace(进程 ID 命名空间)

PID Namespace 使沙箱内的进程拥有独立的进程 ID 空间。沙箱内的第一个进程获得 PID 1,且无法看到或信号宿主的进程。

基本用法

# 创建 PID 命名空间
bwrap \
  --ro-bind / / \
  --unshare-pid \
  --proc /proc \
  --dev /dev \
  bash

# 在沙箱内
echo "My PID: $$"
# My PID: 1  (或 bwrap fork 后的 PID)

ps aux
# USER  PID %CPU %MEM    VSZ   RSS TTY STAT START TIME COMMAND
# root    1  0.0  0.0  ...     ...   ?   S    ...   0:00 bash
# root    2  0.0  0.0  ...     ...   ?   R+   ...   0:00 ps aux

# 尝试查看宿主进程——看不到
ls /proc/ | grep -E '^[0-9]+$' | sort -n | head -5
# 1
# 2  (即 ps 命令自身)

PID 1 的特殊性

在 PID Namespace 中,PID 1 有一些特殊行为:

bwrap \
  --ro-bind / / \
  --unshare-pid \
  --proc /proc \
  --dev /dev \
  bash -c '
    echo "PID 1 is: $$"
    # PID 1 不会收到默认信号处理(除了 SIGKILL)
    # 需要显式处理 SIGTERM 等信号

    # 查看进程树
    pstree -p
  '

--fork 参数

PID 命名空间要求进程通过 fork() 创建(clone() 使用 CLONE_NEWPID 标志)。bwrap--fork 参数确保正确创建新进程:

# --fork 通常与 --unshare-pid 一起使用
# bwrap 在 fork 后创建子进程,子进程获得新的 PID namespace

bwrap \
  --ro-bind / / \
  --unshare-pid \
  --fork \
  --proc /proc \
  --dev /dev \
  bash -c 'echo "PID: $$"'
# PID: 1

实际应用:进程隔离

# 隔离一个运行多子进程的服务
bwrap \
  --ro-bind / / \
  --unshare-pid \
  --proc /proc \
  --dev /dev \
  --tmpfs /tmp \
  bash -c '
    # 启动后台任务
    sleep 100 &
    sleep 200 &

    # 只能看到沙箱内的进程
    ps aux
    # USER PID %CPU %MEM ... COMMAND
    # root   1 ... bash
    # root   3 ... sleep 100
    # root   4 ... sleep 200
    # root   5 ... ps aux

    # 杀死所有后台任务
    kill %1 %2 2>/dev/null
  '

4.5 Network Namespace(网络命名空间)

Network Namespace 为沙箱提供独立的网络栈,包括网络接口、路由表、iptables 规则等。

无网络模式

# 创建完全无网络的沙箱
bwrap \
  --ro-bind / / \
  --unshare-net \
  --dev /dev \
  --proc /proc \
  bash

# 在沙箱内查看网络
ip addr
# 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
#     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

# 测试网络连接——失败
curl -s --max-time 3 https://example.com || echo "Network unreachable"
# curl: (6) Could not resolve host: example.com

保留回环接口

# 创建网络命名空间并启用回环接口
bwrap \
  --ro-bind / / \
  --unshare-net \
  --dev /dev \
  --proc /proc \
  bash -c '
    # 启用 lo 接口
    ip link set lo up
    ip addr add 127.0.0.1/8 dev lo

    # 现在可以连接 localhost
    ping -c 1 127.0.0.1
    # PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
    # 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.020 ms
  '

进阶:与宿主共享网络

# 不使用 --unshare-net,沙箱共享宿主的网络
bwrap \
  --ro-bind / / \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash -c '
    curl -s --max-time 5 https://example.com | head -5
    # <!doctype html><html>...
  '

网络隔离配置对比:

配置网络状态用途
--unshare-net共享宿主网络需要网络的普通应用
--unshare-net完全无网络不需要网络的离线任务
--unshare-net + ip link set lo up仅 localhost测试本地服务

4.6 UTS Namespace(主机名命名空间)

UTS Namespace 允许沙箱拥有独立的主机名和域名,不影响宿主。

基本用法

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

    cat /proc/sys/kernel/hostname
    # sandbox-env
  '

# 宿主主机名不受影响
hostname
# your-real-hostname

实际应用

# 运行依赖主机名的服务
bwrap \
  --ro-bind / / \
  --unshare-uts \
  --hostname "test-server" \
  --dev /dev \
  --proc /proc \
  bash -c '
    # 服务可以通过主机名识别自己
    echo "Server starting on $(hostname)..."
  '

4.7 Cgroup Namespace(控制组命名空间)

Cgroup Namespace(内核 >= 4.6)隔离 cgroup 文件系统的视图,使沙箱进程只能看到自己的 cgroup 层级。

基本用法

# 创建 cgroup 命名空间
bwrap \
  --ro-bind / / \
  --unshare-cgroup \
  --dev /dev \
  --proc /proc \
  bash

# 在沙箱内查看 cgroup
cat /proc/self/cgroup
# 在宿主上可能显示:  0::/user.slice/user-1000.slice/...
# 在沙箱内显示为:    0::/

安全意义

宿主 cgroup 视图:              沙箱 cgroup 视图:
/sys/fs/cgroup/                /sys/fs/cgroup/
├── system.slice/              ├── (只看到沙箱自身的 cgroup)
│   ├── docker.service/        └── (隐藏了宿主的其他 cgroup)
│   └── sshd.service/
├── user.slice/
└── ...

这防止了沙箱进程通过 cgroup 文件系统获取宿主系统上其他进程的信息。


4.8 IPC Namespace(进程间通信命名空间)

IPC Namespace 隔离 System V IPC 对象(信号量、消息队列、共享内存)和 POSIX 消息队列。

基本用法

# 创建 IPC 命名空间
bwrap \
  --ro-bind / / \
  --unshare-ipc \
  --dev /dev \
  --proc /proc \
  bash

# 在沙箱内查看 IPC 资源
ipcs
# ------ Message Queues --------
# key        msqid      owner      perms      used-bytes   messages
#
# ------ Shared Memory Segments --------
# key        shmid      owner      perms      bytes      nattch     status
#
# ------ Semaphore Arrays --------
# key        semid      owner      perms      nsems

# 沙箱看不到宿主的 IPC 对象

4.9 命名空间组合模式

完全隔离(--unshare-all

# 分离所有支持的命名空间
bwrap \
  --ro-bind / / \
  --unshare-all \
  --proc /proc \
  --dev /dev \
  --tmpfs /tmp \
  bash

# 验证所有命名空间
cat /proc/self/uid_map
#          0       1000          1  (User NS)

mount | wc -l
# 较少  (Mount NS)

ps aux
# PID 1: bash, PID 2: ps  (PID NS)

ip addr
# 仅 lo (Network NS)

hostname
# (默认或空)  (UTS NS)

cat /proc/self/cgroup
# 0::/  (Cgroup NS)

ipcs
# 全空  (IPC NS)

最小隔离

# 只使用必要的隔离
bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  bash
# 仅 Mount NS,共享其他所有命名空间

选择性隔离

# 需要网络但不需进程隔离
bwrap \
  --ro-bind / / \
  --unshare-user \
  --unshare-uts \
  --hostname "web-sandbox" \
  --dev /dev \
  --tmpfs /tmp \
  curl -s https://example.com

隔离级别对照表

场景UserMountPIDNetUTSCgroupIPC
最小沙箱
隔离文件系统
隔离进程
离线沙箱
完全隔离
Flatpak 应用可选

4.10 查看沙箱的命名空间信息

从宿主查看

# 获取 bwrap 进程的 PID
BWRAP_PID=$(pgrep -n bwrap)

# 查看其命名空间
ls -la /proc/$BWRAP_PID/ns/
# lrwxrwxrwx 1 user user 0 ... cgroup -> 'cgroup:[4026531835]'
# lrwxrwxrwx 1 user user 0 ... ipc -> 'ipc:[4026532510]'
# lrwxrwxrwx 1 user user 0 ... mnt -> 'mnt:[4026532508]'
# lrwxrwxrwx 1 user user 0 ... net -> 'net:[4026532511]'
# lrwxrwxrwx 1 user user 0 ... pid -> 'pid:[4026532509]'
# lrwxrwxrwx 1 user user 0 ... uts -> 'uts:[4026532507]'

# 与宿主的命名空间对比
ls -la /proc/1/ns/
# 不同的编号表示不同的命名空间实例

从沙箱内查看

bwrap \
  --ro-bind / / \
  --unshare-all \
  --proc /proc \
  --dev /dev \
  bash -c '
    echo "=== 沙箱命名空间信息 ==="
    echo "User NS:    $(readlink /proc/self/ns/user)"
    echo "Mount NS:   $(readlink /proc/self/ns/mnt)"
    echo "PID NS:     $(readlink /proc/self/ns/pid)"
    echo "Network NS: $(readlink /proc/self/ns/net)"
    echo "UTS NS:     $(readlink /proc/self/ns/uts)"
    echo "Cgroup NS:  $(readlink /proc/self/ns/cgroup)"
    echo "IPC NS:     $(readlink /proc/self/ns/ipc)"
    echo ""
    echo "UID Map:    $(cat /proc/self/uid_map)"
    echo "PID:        $$"
  '

4.11 注意事项

⚠️ 重要提醒

  1. PID 命名空间需要 --fork:PID 命名空间要求第一个进程通过 fork() 创建。bwrap 通常会自动处理,但在某些情况下可能需要显式指定。

  2. --unshare-pid 必须配合 --proc:否则 /proc 会显示宿主的进程信息。

  3. User Namespace 嵌套限制:内核默认限制 user namespace 的嵌套深度(通常为 32 层)。

  4. 网络命名空间需要 root 或 user namespace:创建网络命名空间通常需要 user namespace 支持。

  5. Cgroup Namespace 需要内核 >= 4.6:较旧的内核不支持 cgroup 命名空间。

  6. IPC 隔离不阻止 D-Bus:D-Bus 使用 Unix socket 而非 System V IPC,需要额外处理。


4.12 扩展阅读


上一章:第 3 章 - 基本用法 | 下一章:第 5 章 - 文件系统隔离