使用crosvm创建开发沙箱
摘要
关于使用crosvm创建隔离开发沙箱的指南,通过在虚拟机中运行开发工具和测试来应对软件供应链风险。
<p><a href="https://lobste.rs/s/mml3sh/creating_development_sandbox_with">评论</a></p>
查看缓存全文
缓存时间: 2026/07/02 04:07
# 使用 crosvm 创建开发沙箱
来源:https://www.erat.org/crosvm.html
## 目录
- [引言](#intro)
- [背景](#background)
- [编译 `crosvm`](#compiling)
- [运行 `crosvm`](#running)
- [宿主机网络配置](#host-networking)
- [启动虚拟机](#starting-vm)
- [虚拟机网络配置](#vm-networking)
- [调整虚拟机磁盘大小](#resizing-disk)
- [管理虚拟机](#managing-vm)
- [开发流程](#development)
- [连接虚拟机中的服务](#vm-servers)
- [签名与推送更改](#signing-pushing)
- [结论](#conclusion)
## 引言¶
在同时存放电子邮件、密码和浏览器 cookie 等敏感信息的电脑上编写软件,会让我感到不安。就在过去几天里,我读到有人因克隆了错误的 GitHub 仓库而生活被颠覆,还有针对开发者的虚假面试骗局,试图诱骗开发者运行 `npm install` 来安装后门。某些工具链(例如 Go)至少能确保编译程序不会执行不可信代码,但测试仍然需要运行。我尽量少用第三方依赖(Go 的标准库在这方面帮了大忙),并在新版本发布后推迟升级库,但语言服务器、格式化工具和 linter 等开发工具仍然增加了供应链攻击的风险。解决这个问题的一种方法是使用完全独立的电脑进行开发,但随身携带第二台笔记本电脑的想法并不吸引人——我更愿意只操心一块硬件。
## 背景¶
长期以来,我主要通过在 Chromebook 上进行开发来应对这些担忧。ChromeOS 为其“主”操作系统(即 Chrome 浏览器和相关用户空间服务)使用了验证启动与只读操作系统分区相结合的方式,同时还提供了 Crostini——一个在主操作系统旁运行独立 Linux 安装的虚拟机系统。至少在理论上,虚拟机中运行的代码只能通过少数受限方式(如共享目录、通过 sommelier 转发的 Wayland 窗口以及音频输出)与主操作系统交互。我的开发流程通常涉及文本模式程序和 Web 服务器,因此通过终端窗口访问虚拟机非常合适。在 Crostini 虚拟机中,我进一步尝试通过使用 `bubblewrap` 将 Node.js 程序和其他第三方开发工具运行在沙箱环境中来限制其行为。
2025 年,谷歌宣布了一项模糊的计划,要将“ChromeOS 体验”迁移到 Android 上:
> 因此,我们认为机会在于如何加速我们在 Android 上所做的所有 AI 进步,并将其尽快带到笔记本电脑形态,同时让笔记本电脑与 Android 生态系统无缝协作。所以,我们正在做的是,基本上把 ChromeOS 体验拿过来,并将其底层技术重新基于 Android。
2026 年初,我使用 ChromeOS 虚拟机时受到了几个 bug 的影响,这些 bug 花了几个月才修复:重启后虚拟机无法启动,以及终端中的字形偶尔被渲染为实心块。Reddit 上的用户为这两个问题都找到了变通方法,但这些 bug 最初发布并长期未修复的事实,对谷歌保持该项目的可靠性来说并不是好兆头。2026 年 5 月,谷歌宣布一种名为 Googlebook、运行另一种名为 Aluminium OS 的系统将取代 Chromebook。对生成式 AI 的承诺/威胁最终促使我开始寻找其他选择。(我的最新 Chromebook 搭载的是 2019 年的 CPU,这也是一个因素。)
我知道新的开发环境很可能是一台运行 Debian 的笔记本电脑,但我不确定如何解决我的安全问题。我曾考虑使用 `bubblewrap` 对所有开发进行沙箱化,但考虑到今年 Linux 内核 CVE 的数量,我对依赖命名空间来保证安全感到不安。crosvm——Crostini 的 hypervisor——以独立形式提供,并有相当多的文档,所以我决定尝试用它来复制之前在 ChromeOS 上做的事情。我知道使用名称中包含“cros”且由谷歌维护的软件,当对 ChromeOS 未来的担忧促使我走上这条路时,可能不是最安全的逃脱路线,但 `crosvm` 截至 2026 年中仍在积极开发中,而且从文档来看,它似乎在 ChromeOS 之外也获得了一定的关注:
> 它现在已用于多个产品和平台,例如 Android 上的 TerminalApp、Cuttlefish 和 Windows。
如果 `crosvm` 在某个时候停止接收更新,我想我会尝试用其他 hypervisor 复制我的设置。(可能是 Firecracker?我很希望这里有一个非大型科技公司的选项,同样有强大的安全声明,并且在需要时支持图形和声音。)
## 编译 `crosvm`¶
[在 Linux 上构建 crosvm](https://crosvm.dev/book/building_crosvm/linux.html) 页面描述了如何检出并编译 `crosvm` 源码。我首先尝试按照“使用开发容器”的说明通过 Docker 构建,但过程失败并出现了无帮助的错误。我没有进一步调试,而是切换到 Podman:
```
$ sudo apt install --no-install-recommends podman passt uidmap libguestfs-tools
$ ./tools/dev_container ./tools/build_release
```
构建过程下载了数 GB 的数据,全部存放在 `~/.local/share/containers` 下。最终,编译好的 `crosvm` 可执行文件位于 `~/.local/share/containers/storage/overlay/5dec291d9e203d8cfa5fb69a301fadd5be2020fb9173e09420444622701788df/diff/scratch/cargo_target/crosvm/x86_64-unknown-linux-gnu/x86_64-unknown-linux-gnu/release/crosvm`。(可能那个 32 字节的十六进制 ID 不是固定的。)该可执行文件动态链接了 `libvirglrenderer.so.1`,看起来它由 Debian 软件包 `libvirglrenderer1` 提供。它还链接了 `libgfxstream_backend.so.0`,这导致了更多问题。我在任何地方都找不到这个库的预编译版本(我甚至尝试在谷歌提供的各种 Android 开发相关归档中翻找,因为那似乎是代码的来源)。源代码看起来在 https://github.com/google/gfxstream,但与其在不确定是否能成功运行 `crosvm` 的情况下花时间构建依赖,我决定暂时在没有图形支持的情况下重新构建。(后来我注意到 `crosvm` 构建过程似乎会自动将库编译到 `~/.local/share/containers/storage/overlay/26a34f735cfaeb84ad3969e2886048652ef2b21f818fe73a3d7b8660f18eaa9c/diff/usr/lib/x86_64-linux-gnu/libgfxstream_backend.so.0.1.2`,我本来可以直接使用那个副本。)
`crosvm` 在其顶层的 `Cargo.toml` 文件中定义了一组默认特性:
```
default = ["audio", "balloon", "document-features", "gpu", "qcow", "usb", "libvda-stub", "net", "slirp"]
```
目前似乎无法在 Cargo 中禁用单个默认特性,因此我进行了另一种构建,仅明确请求我认为可能需要的特性:
```
$ ./tools/dev_container cargo build --release --no-default-features --features audio,balloon,net,qcow,usb
```
生成的 `crosvm` 二进制文件动态链接的库比之前少得多。这次它位于 `~/.local/share/containers/storage/overlay/5dec291d9e203d8cfa5fb69a301fadd5be2020fb9173e09420444622701788df/diff/scratch/cargo_target/release/crosvm`,我将其复制到了 `$PATH` 中的某个目录。
## 运行 `crosvm`¶
### 操作系统镜像
`crosvm` 文档包括[示例用法](https://crosvm.dev/book/running_crosvm/example_usage.html)和[高级用法](https://crosvm.dev/book/running_crosvm/advanced_usage.html)页面。根据这些说明,我从 Debian 官方云镜像网站下载了最新的 `debian-13-nocloud-amd64-XXXXXXXX-XXXX.tar.xz` 文件,并将其解压到 `~/temp/crosvm` 目录中,我计划在那里存储虚拟机的数据。我使用了 `guestfs-tools` 软件包中的 `virt-builder` 来提取内核和 initrd:
```
$ virt-builder --get-kernel ./disk.raw -o .
```
### 宿主机网络配置¶
为了让虚拟机能够访问网络,`crosvm` 可以与其共享一个 TAP 接口。我从 `crosvm` 的[网络](https://crosvm.dev/book/devices/net.html)文档中运行了这些命令,以创建用于 192.168.10.1/24 的 TAP 接口并路由进出流量:
```
$ sudo ip tuntap add mode tap user $USER vnet_hdr crosvm_tap
$ sudo ip addr add 192.168.10.1/24 dev crosvm_tap
$ sudo ip link set crosvm_tap up
$ sudo sysctl net.ipv4.ip_forward=1
$ HOST_DEV=$(ip route get 8.8.8.8 | awk -- '{printf $5}')
$ sudo iptables -t nat -A POSTROUTING -o "$HOST_DEV" -j MASQUERADE
$ sudo iptables -A FORWARD -i "$HOST_DEV" -o crosvm_tap -m state --state RELATED,ESTABLISHED -j ACCEPT
$ sudo iptables -A FORWARD -i crosvm_tap -o "$HOST_DEV" -j ACCEPT
```
由于我(不情愿地)在电脑上使用了 NetworkManager,我需要某种方法使接口永久化。运行以下命令似乎与之前的 `ip` 命令产生相同的结果:
```
$ sudo nmcli con add type tun ifname crosvm_tap con-name crosvm_tap \
mode tap owner $(id -u $USER) \
ipv4.method manual ipv4.addresses 192.168.10.1/24
```
`net.ipv4.ip_forward=1` 的 `sysctl` 设置可以通过创建 `/etc/sysctl.d/ip_forward.conf` 文件来永久生效:
```
# Enable forwarding for crosvm TAP networking.
net.ipv4.ip_forward = 1
```
为了使 `iptables` 规则持久化,我还安装了 `iptables-persistent` 软件包。
### 启动虚拟机¶
此时,我已经准备好尝试启动虚拟机。在我的 `~/temp/crosvm` 目录中,我运行了以下命令:
```
$ crosvm run \
--socket ./crosvm.sock \
--cpus num-cores=4 \
--mem size=2048 \
--net tap-name=crosvm_tap \
--block ./disk.raw \
--initrd ./initrd.img-* \
--params "root=/dev/vda1" \
./vmlinuz-*
```
我看到了熟悉的 Linux 启动序列,随后是设置虚拟机时区和 root 密码的提示。使用上述命令,初始的 `crosvm` 进程会创建一个 `crosvm.sock` 套接字,后续的 `crosvm` 调用可以使用该套接字与正在运行的 hypervisor 交互。
### 虚拟机网络配置¶
以 root 身份登录(使用刚刚设置的密码)后,我运行了以下命令,将虚拟机中的网络接口配置为 192.168.10.2,网关使用 192.168.10.1:
```
# ip addr add 192.168.10.2/24 dev enp0s4
# ip route add default via 192.168.10.1
```
我还更新了虚拟机中的 `/etc/systemd/resolved.conf` 文件,添加以下内容:
```
[Resolve]
DNS=8.8.8.8#dns.google 8.8.4.4#dns.google 2001:4860:4860::8888#dns.google 2001:4860:4860::8844#dns.goo gle
```
并运行了 `systemctl restart systemd-resolved`。此时,我已能从虚拟机内访问外部世界,也能从宿主机 ping 通虚拟机的 192.168.10.2。为了使网络更改永久生效,我在虚拟机中创建了一个 `/etc/systemd/network/10-lan.network` 文件,内容如下:
```
[Match]
Name=e*
Type=ether
[Network]
Address=192.168.10.2/24
Gateway=192.168.10.1
DNS=8.8.8.8
```
然后运行了 `systemctl enable systemd-networkd --now`。
### 调整虚拟机磁盘大小¶
虚拟机启动时使用的是 Debian 镜像提供的 3 GiB 磁盘。为了扩大根分区,我按照 `crosvm` 的[块设备](https://crosvm.dev/book/devices/block.html)文档中的说明操作。在虚拟机已经运行的情况下,我在宿主机上的 `~/temp/crosvm` 中运行了以下命令,将块设备大小调整为 20 GiB:
```
$ crosvm disk resize 0 21474836480 ./crosvm.sock
```
然后,在虚拟机内部,我按照[这个随机页面](https://www.golinuxcloud.com/extend-resize-primary-partition-non-lvm-linux/#method-1-change-size-of-partition-using-parted-cli-utility)的方法,使用 `parted` 删除 `vda1` 根分区并重新创建(从同一扇区开始,使用剩余磁盘空间;幸运的是,我没有像该文档中描述的 swap 分区挡在中间),然后使用 `e2fsprogs` 中的 `resize2fs` 来扩展 `vda1` 以使用新空间。
**务必更新虚拟机中的 `/etc/fstab`,使其引用新分区的 `PARTUUID`(参见 `/dev/disk/by-partuuid`),而不是它的 `UUID`(来自 `/dev/disk/by-uuid`)。** 如果不这样做或弄错了,最终会得到一个只读的根分区。我通过运行 `mount -n -o remount,rw -t ext4 /dev/vda1 /` 来恢复,从而可以写入更新后的 `/etc/fstab`。
### 管理虚拟机¶
我想要一种直接的方式来启动和停止虚拟机,所以我编写了一个名为 `manage_crosvm` 的小脚本:
```
#!/bin/bash -e
readonly NUM_CPU=6
readonly MEM=8192
readonly STOP_SEC=60
usage() {
echo "Usage: $0 [start|stop] <dir>"
exit 2
}
[[ $# -ne 2 || $1 = -* ]] && usage
readonly cmd=$1
readonly dir=$2
readonly socket=$dir/crosvm.sock
name=$(basename "$dir")
readonly name
cd "$dir"
case $cmd in
start)
if [[ -e $socket ]]; then
pids=$(lsof -t "$socket")
if [[ -n $pids ]]; then
echo "Socket $socket already used by process(es) $pids"
exit 1
fi
echo "Deleting stal
相似文章
自托管的开发沙箱与预览URL(Docker、Go、无K8s)
sandboxed 是一个开源引擎,能将单个 Linux 机器转变为一系列隔离的开发沙箱,配备编码代理和实时预览 URL,支持自托管且易于安装。
我在无头Linux上为codex构建了一个计算机使用沙箱框架。GPU直通、计算机使用和codex的sudo权限全部工作。这是一个完美的开发沙箱,可以在最小化"rm -rf /"风险的同时实现全自动工作。
作者使用LXC容器构建了一个AI沙箱管理器,允许Codex代理在无头Linux上拥有完整的sudo权限和GPU直通,同时保护主机系统免受灾难性错误的影响。
我们逆向工程了 Docker Sandbox 未文档化的 MicroVM API
一个团队逆向工程了 Docker Sandboxes 使用的未文档化的 MicroVM API,并构建了开源 Sandbox Agent SDK,用于在微虚拟机中编排 AI 编码代理,以实现安全的不可信代码执行。
在Windows上构建安全有效的沙箱以支持Codex
OpenAI工程师为Windows上的Codex构建了自定义沙箱,以实现安全受限的命令执行,在不依赖原生Windows隔离功能的情况下平衡有效性与安全性。
@ghumare64: https://x.com/ghumare64/status/2055329887431393309
深入探讨为何本地编码代理(如Claude Code和Codex)正逐渐转向libkrun而非Firecracker进行沙盒隔离,原因是Firecracker无法在macOS上原生运行。本文还介绍了iii-sandbox,一个基于libkrun构建的开源硬件隔离执行层。