Codex 的 bwrap loopback 报错:Ubuntu 24.04 上的 AppArmor 排查记录
这次排查从一句报错开始:
bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted
当时在 Ubuntu 24.04.5 LTS 上运行 Codex CLI 0.162.0,连 pwd 都无法通过沙箱执行。后面的对照测试把问题指向了 AppArmor:临时放宽非特权用户命名空间限制后,命令执行成功;恢复限制后,沙箱又失败。最后为系统 bwrap 加载专用配置,再次得到了成功执行的输出。
本文整理自这次 ChatGPT 排查对话,并补充发布时核对的官方文档。原始记录验证了功能恢复,没有验证完整安全策略;下面把案例结果和供读者尝试的修复路径分开说明。用户名、工作目录和会话 ID 已省略,示例输出中的目录使用 /work/project。
报错停在什么地方
bwrap 是 bubblewrap 的命令名。这里失败的是沙箱初始化中的 loopback 地址配置。bubblewrap 的源码显示,loopback_setup() 通过 Netlink 的 RTM_NEWADDR 请求向 lo 添加 127.0.0.1/8,失败时输出这条错误。
因此,先检查进程有没有完成这一步的权限。错误本身还不能确定是 AppArmor、容器外层策略,还是其他限制;它也不说明需要修改宿主机的网卡或 loopback 地址。
先收集环境,再看拒绝日志
在发生报错的 Linux 环境中运行:
cat /etc/os-release
codex --version
systemd-detect-virt
id
sysctl kernel.unprivileged_userns_clone user.max_user_namespaces
sysctl kernel.apparmor_restrict_unprivileged_userns
grep -E 'CapEff|Seccomp|NoNewPrivs' /proc/self/status
command -v bwrap
这次对话里的关键结果如下:
| 检查项 | 记录中的结果 | 能说明什么 |
|---|---|---|
| 系统 | Ubuntu 24.04.5 LTS | 需要考虑该发行版的 AppArmor 规则 |
| Codex | 0.162.0,standalone 安装 | 这是案例版本,不代表所有版本行为相同 |
| 虚拟化检测 | none |
该工具没有识别出虚拟化环境 |
kernel.unprivileged_userns_clone |
1 |
此开关允许非特权用户命名空间 |
user.max_user_namespaces |
4126513 |
配额没有被设为零 |
| AppArmor userns 限制 | 1 |
限制已启用,还要查日志确认是否相关 |
| 当前检查进程 | CapEff=0、NoNewPrivs=0、Seccomp=0 |
只是检查进程的状态,不能代表后续子进程 |
普通用户的 CapEff=0 不足以解释这次失败。用户命名空间内的能力还要受到对应命名空间和安全策略的约束。Ubuntu 24.04 的默认策略可以允许命名空间创建,同时拒绝其内部后续使用 capabilities;这解释了为什么 userns 开关已打开,网络配置仍可能失败。Ubuntu 24.04 发布说明
紧接着复现一次报错,再查同一时间段的内核日志:
sudo journalctl -k --since "5 minutes ago" \
| grep -Ei 'apparmor|DENIED|userns|bwrap'
对话中讨论的关键日志字段是:
apparmor="DENIED" operation="capable" profile="unprivileged_userns" capability=12 capname="net_admin"
还提到了 setpcap 被拒绝。net_admin 与 loopback 配置最直接相关,但要核对时间、PID 和进程名,避免把其他程序的拒绝记录归给 Codex。仅看到系统开关等于 1,证据还不够。
让测试真正执行到沙箱命令
排查途中出现了另一条错误:
Not inside a trusted directory and --skip-git-repo-check was not specified.
这次运行被仓库检查提前拦住了,不能用来判断 bwrap 是否恢复。对于自己控制的测试目录,可以使用:
codex exec --skip-git-repo-check 'pwd'
--skip-git-repo-check 只处理这项仓库检查,不会授予 AppArmor 权限。这个案例中,临时放宽限制后,这条命令出现了实际工具执行记录;恢复限制后,再运行又出现 sandbox error。这组对照结果与拒绝日志一起,把故障指向了 AppArmor。
还要注意成功的标准:Codex 在执行失败时,也曾直接根据环境上下文回答工作目录。只有最终回复里出现路径不算通过;应检查工具记录里是否有 shell 命令和 succeeded,并核对其输出。
优先检查发行版提供的 bwrap 策略
这次最初在 PATH 中找不到 bwrap,但 Codex 输出了使用 bundled bubblewrap 的提示。安装系统 bubblewrap 后,相关沙箱错误仍然存在,说明安装可执行文件和加载正确策略是两件事。
发布时核对的官方 OpenAI 沙箱文档说明:Linux 上优先使用 PATH 中的 bwrap,并提供了 Ubuntu 24.04 加载额外 AppArmor profile 的步骤。下面是文档路径,不是原始对话已经验证过的配置。
先安装并确认系统版本:
sudo apt update
sudo apt install bubblewrap apparmor-profiles apparmor-utils
command -v bwrap
readlink -f "$(command -v bwrap)"
bwrap --version
安装包之后,先检查现有策略和额外策略文件:
sudo aa-status
sudo grep -R -n -E 'bwrap|bubblewrap' /etc/apparmor.d
ls -l /usr/share/apparmor/extra-profiles/bwrap-userns-restrict
包版本和本地规则会影响结果。如果目标文件已经存在,先阅读并备份;不要直接覆盖已有定制策略,也不要让多个 profile 竞争同一可执行路径。如果额外策略文件不存在,应先核对包来源和版本。
确认没有现有冲突,且源文件存在时,再按官方文档复制并加载:
sudo install -m 0644 \
/usr/share/apparmor/extra-profiles/bwrap-userns-restrict \
/etc/apparmor.d/bwrap-userns-restrict
sudo apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrict
随后确认限制值和 profile 状态,再执行同一个测试:
sysctl kernel.apparmor_restrict_unprivileged_userns
sudo aa-status | grep -i bwrap
codex exec --skip-git-repo-check 'pwd'
如果仍失败,重新读取内核日志。尤其检查是否还落在 profile="unprivileged_userns",以及实际调用的是哪个 bwrap。PATH 中找到一个文件,只能说明它是候选程序,不能独立证明每次执行都匹配到了预期策略。
原始案例怎样恢复,以及它的边界
对话最后确认系统 bwrap 路径为 /usr/bin/bwrap。在没有看到专用 bwrap profile 的情况下,创建并加载了如下宽松测试配置:
abi <abi/4.0>,
include <tunables/global>
profile bwrap /usr/bin/bwrap flags=(unconfined) {
userns,
capability net_admin,
capability setpcap,
}
配置文件是 /etc/apparmor.d/usr.bin.bwrap。加载后,记录中再次运行 codex exec --skip-git-repo-check 'pwd',得到了下面的工具执行结果,目录已替换为示例值:
sandbox: read-only
/usr/bin/zsh -lc pwd
succeeded in 0ms:
/work/project
这证明该案例中的命令恢复了执行。flags=(unconfined) 是宽松模式;配置中写了两项 capability,不能把它理解为只允许这两项能力的最小权限白名单。规则匹配 /usr/bin/bwrap,也不限于 Codex 的某一次调用。Ubuntu 对这种模式的说明见发布说明。
原始步骤包含把全局限制恢复为 1,但最后一次成功输出之后,没有再贴出 sysctl 和 profile 状态。因此,可以报告功能恢复,不能据此宣称所有安全设置已经复核、配置足够严格,或后续所有任务都会通过。
如果机器已经使用这个自定义配置,改用发行版策略前应先备份、卸载旧的测试 profile,并把旧文件移出 /etc/apparmor.d,再检查新策略的附着路径。不要并行叠加两份配置来碰运气。
临时诊断要保存并恢复原值
原始对话用过 kernel.apparmor_restrict_unprivileged_userns=0 做对照实验。这个设置影响整台主机,应只在自己管理、允许短时变更的测试环境中使用。优先尝试现有发行版策略;如果仍需要这个实验,先保存原值,再保证退出时恢复。
下面的脚本用于 Bash。它恢复读取到的原值,不把原始配置一律假设为 1:
#!/usr/bin/env bash
set -euo pipefail
sudo -v
original_userns=$(sysctl -n kernel.apparmor_restrict_unprivileged_userns)
restore_userns() {
sudo sysctl -w "kernel.apparmor_restrict_unprivileged_userns=${original_userns}"
}
trap restore_userns EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
codex exec --skip-git-repo-check 'pwd'
退出 trap 无法处理断电或 SIGKILL,恢复命令也可能失败。结束后仍需手动检查 sysctl kernel.apparmor_restrict_unprivileged_userns,必要时恢复此前记录的值。不要把诊断用的 0 写成持久 sysctl 配置。
修复后验证同一条命令,再扩大测试
先保持同一个用户、目录和 Codex 沙箱模式,重复原来的 pwd 测试。确认工具执行成功,确认预期 AppArmor profile 已加载,确认全局开关处于预期值,并检查相同时间段是否还有相关拒绝日志。再测试实际项目需要的读取、写入或联网操作;pwd 成功只覆盖最小执行路径。
如果在 Docker 或 LXC 内遇到类似错误,需要另外检查外层容器限制。本次 systemd-detect-virt 返回 none 的记录不能推广到容器环境。当前检查进程的 Seccomp=0 同样不能证明后续所有子进程都不受限制。
这个案例里最容易误判的两步,是把仓库检查停止当成沙箱通过,以及把模型回答的目录当成命令执行结果。沿着原始报错、对应拒绝日志和真实工具输出核对,才能确认修复究竟改变了哪一步。
- 原文作者:春江暮客
- 原文链接:https://www.bobobk.com/codex-bwrap-loopback-apparmor-ubuntu.html
- 版权声明:本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。