许多容器运行时工具,如 systemd-nspawn、docker 等,专注于为系统管理员和编排工具(例如 Kubernetes)提供运行容器的基础设施。
这些工具不适合提供给非特权用户,因为将此类访问权限转化为宿主机上完全特权级的 root shell 是轻而易举的。
Linux 内核中有一个名为用户命名空间的特性,它允许非特权用户使用容器功能。Bubblewrap 利用这些特性来构建沙箱,从而允许任何用户使用该工具。
从历史上看,bubblewrap 还曾为不支持非特权用户命名空间的系统提供 setuid 模式。然而,该模式已被移除。
最初的 bubblewrap 代码早于用户命名空间出现——它继承了 xdg-app helper 的代码,而后者又远源于 linux-user-chroot。
该工具的维护者认为,即使与发行版上安装的典型软件结合使用,它也不会允许权限提升。然而,它可能会提高已登录用户执行拒绝服务攻击的能力。
具体来说,bubblewrap 使用 PR_SET_NO_NEW_PRIVS 来关闭 setuid 二进制文件,这是摆脱 chroot 等限制的传统方式。
bubblewrap 是用于构建沙箱环境的工具。bubblewrap 并不是一个带有特定安全策略的完整、现成的沙箱。
bubblewrap 的部分用例希望在沙箱与真实系统之间建立安全边界;其他用例则希望能够在沙箱内为进程改变文件系统的布局,但并不打算成为安全边界。因此,沙箱内进程与宿主系统之间的保护级别完全由传递给 bubblewrap 的参数决定。
任何为 bubblewrap 构造命令行参数的程序(通常是更大的框架,如 Flatpak、libgnome-desktop、sandwine 或临时脚本)都负责定义自己的安全模型,并选择合适的 bubblewrap 命令行参数来实现该安全模型。
沙箱安全中一些需要特别留意的方面在下面的局限性一节中进行了描述。
此程序可供所有执行非 root 操作的容器工具共用,例如:
我们还希望看到它能够在 Kubernetes/OpenShift 集群中可用。让非特权用户能够使用容器功能,将大大简化交互式调试等场景。
bubblewrap 可在大多数 Linux 发行版的软件包仓库中获得,并可直接从那里安装。
如果你需要从源代码构建 bubblewrap,可以使用 meson 完成:
meson _builddir
meson compile -C _builddir
meson test -C _builddir
meson install -C _builddir
bubblewrap 的工作原理是创建一个新的、完全空的挂载命名空间,其根目录位于一个宿主机不可见的 tmpfs 上,并会在最后一个进程退出时自动清理。然后,你可以使用命令行选项来构建根文件系统、进程环境以及在命名空间中运行的命令。
源代码中有一个更完整的演示脚本,但这里是一个精简版,它复用宿主机的 /usr 运行一个新的 shell。
bwrap \
--ro-bind /usr /usr \
--symlink usr/lib64 /lib64 \
--proc /proc \
--dev /dev \
--unshare-pid \
--new-session \
bash
这是一个不完整的示例,但有助于说明问题。更常见的情况是,你可能希望以 chroot 为目标,而不是使用宿主机的文件系统树来创建容器。在这种情况下,与其在 tmpfs 中创建符号链接 lib64 -> usr/lib64,你可能已经在目标 rootfs 中创建好了它。
bubblewrap 的目标是在沙箱中运行应用程序,使其对操作系统或用户数据(如主目录)的访问受到限制。
bubblewrap 始终会创建一个新的挂载命名空间,用户可以精确指定沙箱中应可见的文件系统部分。你指定的任何此类目录默认以 nodev 方式挂载,并且可以设置为只读。
此外,你还可以使用以下内核特性:
用户命名空间(CLONE_NEWUSER):这会向沙箱隐藏除当前 uid 和 gid 之外的所有内容。你还可以更改沙箱中 uid/gid 的值。
IPC 命名空间(CLONE_NEWIPC):沙箱将获得所有不同形式 IPC(如 SysV 共享内存和信号量)的独立副本。
PID 命名空间(CLONE_NEWPID):沙箱将看不到沙箱外的任何进程。此外,bubblewrap 会在你的容器内运行一个简单的 pid1,以满足在沙箱中回收子进程的要求。这避免了现在所谓的 Docker pid 1 问题。
网络命名空间(CLONE_NEWNET):沙箱将无法看到网络。相反,它将拥有自己仅包含回环设备的网络命名空间。
UTS 命名空间(CLONE_NEWUTS):沙箱将拥有自己的主机名。
Seccomp 过滤器:你可以传入 seccomp 过滤器,以限制沙箱中可以执行的系统调用。更多信息,请参阅 Seccomp。
如上文沙箱安全一节所述,沙箱内进程与宿主系统之间的保护级别完全由传递给 bubblewrap 的参数决定。此处列出一些需要特别留意的方面。
如果你没有使用 seccomp 过滤器过滤掉 TIOCSTI 命令,则需要 --new-session 参数来防止沙箱外的命令执行(参见 CVE-2017-5226)。
挂载到沙箱中的一切内容都可能被用来提升权限。例如,如果你将 D-Bus 套接字绑定到沙箱中,它就可能被用来通过 systemd 执行命令。你可以使用 xdg-dbus-proxy 来过滤 D-Bus 通信。
一些应用程序会部署自己的沙箱机制,而这些机制可能受到 bubblewrap 沙箱所施加限制的约束。例如,某些 Web 浏览器通过 seccomp 配置其子进程,使其无法访问文件系统。如果你限制了系统调用且不允许 seccomp 系统调用,浏览器将无法应用这些限制。同样,如果这些规则被编译到沙箱中不可用的文件中,浏览器就无法从该文件加载这些规则,也就无法应用这些限制。
Firejail 类似于 bubblewrap 被拆分出来之前的 Flatpak,它将 setuid 工具与大量面向桌面的沙箱功能结合在一起。例如,Firejail 了解 Pulseaudio,而 bubblewrap 不了解。
bubblewrap 的作者认为,审计一个较小的 setuid 程序要容易得多,并且像 Pulseaudio 过滤这样的功能应作为非特权进程保留,正如现在 Flatpak 中的做法。
此外,@cgwalters 认为,考虑到用户操纵路径的方式多种多样,以及系统管理员配置系统的方式也多种多样,尝试白名单化文件路径是个糟糕的主意。bubblewrap 的做法是仅保留少数特定的 Linux 能力(如 CAP_SYS_ADMIN),但始终以调用者的 uid 访问文件系统。这完全杜绝了 TOCTTOU 攻击之类的问题。
Sandstorm.io 需要非特权用户命名空间来设置其沙箱,不过它也可以轻松改为以 setuid 模式运行。@cgwalters 认为他们的代码相当不错,但统一到 bubblewrap 上可能仍有意义。然而,@kentonv(来自 Sandstorm)认为,虽然这在原则上说得通,但就目前而言,切换成本超过了实际收益。这一决定将来可以重新评估,但如今并未被积极推进。
runC 目前正在致力于支持 rootless 容器,在 runC 的安装(使用非特权用户命名空间而非 setuid)、容器的创建和管理过程中都不需要 setuid 或任何其他特权。然而,runC 的标准使用模式类似于 systemd nspawn,它是一种旨在由 root 调用的工具。
bubblewrap 的作者认为,runc 和 systemd-nspawn 并非为 setuid 化而设计,距离支持这种模式还很遥远。不过,借助 rootless 容器,runC 将能够实现 bubblewrap 所支持的某些用例(额外的好处是它是一个标准化且完整的 OCI 运行时)。
binctr 只是 runC 的一个包装器,因此继承了其所有的设计权衡。
选择 bubblewrap 这个名字是为了表达该工具作为应用程序的父进程运行(因此在某种意义上将其包裹起来),并在其周围创建一层保护层(沙箱)。

(Bubblewrap 猫咪,作者 dancing_stupidity)