Ubuntu 下解决 Codex 的 bubblewrap / user namespace 报错

本文更新于 2026 年 6 月 18 日


在 Ubuntu 24.04 上使用 Codex CLI 时,可能会看到如下提示:

1
⚠ Codex's Linux sandbox uses bubblewrap and needs access to create user namespaces.

系统中已经安装了 bubblewrap,但进一步测试:

1
2
3
4
which bwrap
# /usr/bin/bwrap

codex sandbox -- /bin/echo ok

仍会出现错误:

1
bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted

原因

Codex 在 Linux 上优先使用系统的 /usr/bin/bwrap
Ubuntu 24.04 默认启用了 AppArmor 针对 unprivileged user namespaces 的限制策略,而 bwrap 在创建 sandbox 时需要完成 namespace 与 loopback 相关初始化,因此会在该阶段失败。

不建议采用的处理方式

一个直接的办法是关闭全局限制:

1
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

这样虽然可能立即恢复正常,但代价是将整台机器这一层防护一并关闭。对单个 CLI 工具而言,这种处理方式并不合适。

较为稳妥的处理方式

更合理的做法,是为 bwrap 配置专用的 AppArmor profile:bwrap-userns-restrict

可直接执行以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env bash
set -euo pipefail

dst="/etc/apparmor.d/bwrap-userns-restrict"

detect_local_abi() {
find /etc/apparmor.d/abi -maxdepth 1 \( -type f -o -type l \) -printf '%f\n' 2>/dev/null \
| grep -E '^[0-9]+[.][0-9]+$' \
| sort -V \
| tail -n1 || true
}

sudo apt update
sudo apt install --yes apparmor apparmor-profiles apparmor-profiles-extra wget ca-certificates

tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT

# 优先查找发行版包自带的 bwrap-userns-restrict profile
profile="$(
dpkg-query -L apparmor apparmor-profiles apparmor-profiles-extra 2>/dev/null \
| grep -E '/bwrap-userns-restrict$' \
| head -n1 || true
)"

if [ -n "$profile" ]; then
echo "Using packaged profile: $profile"
cp "$profile" "$tmp"
else
echo "Packaged profile not found; fetching upstream AppArmor profile matching local ABI"

# 如果本机包里没有这个 profile,再根据本机 AppArmor ABI 版本选择对应的 upstream branch。
# 不要直接拉 master,否则可能遇到 abi/5.0 与本机 AppArmor 版本不匹配的问题。
local_abi="$(detect_local_abi)"

if [ -z "$local_abi" ]; then
echo "Cannot find local AppArmor ABI under /etc/apparmor.d/abi; refusing to fetch upstream profile." >&2
exit 1
fi

if dpkg --compare-versions "$local_abi" lt 4.0; then
echo "Local AppArmor ABI is $local_abi, which is too old for bwrap-userns-restrict/userns policy; aborting." >&2
exit 1
fi

# GitLab branch 名通常是 apparmor-4.0、apparmor-4.1、apparmor-5.0 这种格式
series="$(printf '%s\n' "$local_abi" | sed -E 's/^([0-9]+[.][0-9]+).*/\1/')"
branch="apparmor-$series"
url="https://gitlab.com/apparmor/apparmor/-/raw/${branch}/profiles/apparmor/profiles/extras/bwrap-userns-restrict"

echo "Local AppArmor ABI: $local_abi"
echo "Fetching: $url"

if ! wget -qO "$tmp" "$url"; then
echo "Failed to fetch bwrap-userns-restrict from branch ${branch}." >&2
echo "Do not fall back to master automatically; check the AppArmor version/branch manually." >&2
exit 1
fi
fi

# 简单检查一下下载/复制到的文件确实像 bwrap-userns-restrict profile
grep -qE '^[[:space:]]*profile[[:space:]]+bwrap[[:space:]]+/usr/bin/bwrap' "$tmp" || {
echo "File does not look like bwrap-userns-restrict: missing bwrap profile." >&2
exit 1
}

grep -qE '^[[:space:]]*profile[[:space:]]+unpriv_bwrap[[:space:]]' "$tmp" || {
echo "File does not look like bwrap-userns-restrict: missing unpriv_bwrap profile." >&2
exit 1
}

# 兜底处理 “Could not open 'abi/5.0'” 这类问题。
# 正常情况下,按本机 ABI 选择 branch 后不应该触发;
# 但如果 profile 里声明的 abi 版本本机不存在,就把它改成本机实际存在的最高 ABI。
local_abi="$(detect_local_abi)"
policy_abi="$(
sed -nE 's/^[[:space:]]*abi[[:space:]]+<abi\/([^>]+)>[[:space:]]*,[[:space:]]*$/\1/p' "$tmp" \
| head -n1 || true
)"

if [ -n "$policy_abi" ] && [ ! -e "/etc/apparmor.d/abi/$policy_abi" ]; then
if [ -z "$local_abi" ]; then
echo "Profile requires abi/$policy_abi, but no local ABI file was found." >&2
exit 1
fi

echo "Profile requires abi/$policy_abi, but local system has abi/$local_abi; rewriting ABI line."
sed -i -E "0,/^[[:space:]]*abi[[:space:]]+<abi\/[^>]+>[[:space:]]*,[[:space:]]*$/s//abi <abi\/${local_abi}>,/" "$tmp"
fi

# 确保 bwrap 和 unpriv_bwrap 这两个 profile 都有 mediate_deleted flag。
# 否则某些 Flatpak 应用的原子保存操作可能被 AppArmor 拒绝。
# 这里写成幂等操作:如果已经有 mediate_deleted,就不会重复添加。
sed -i -E \
-e '/^[[:space:]]*profile[[:space:]]+bwrap[[:space:]]+\/usr\/bin\/bwrap[[:space:]]/ { /mediate_deleted/! s/flags=\(([^)]*)\)/flags=(\1,mediate_deleted)/ }' \
-e '/^[[:space:]]*profile[[:space:]]+unpriv_bwrap[[:space:]]/ { /mediate_deleted/! s/flags=\(([^)]*)\)/flags=(\1,mediate_deleted)/ }' \
"$tmp"

# 确认 mediate_deleted 已经加到两个 profile 的 flags 中。
grep -qE '^[[:space:]]*profile[[:space:]]+bwrap[[:space:]]+/usr/bin/bwrap[[:space:]].*flags=\([^)]*mediate_deleted' "$tmp" || {
echo "Failed to add mediate_deleted to bwrap profile." >&2
exit 1
}
grep -qE '^[[:space:]]*profile[[:space:]]+unpriv_bwrap[[:space:]].*flags=\([^)]*mediate_deleted' "$tmp" || {
echo "Failed to add mediate_deleted to unpriv_bwrap profile." >&2
exit 1
}

# 注意:普通 allow capability 规则不一定能覆盖 profile 中已有的 audit deny capability;
# KeePassXC / Flatpak 原子保存问题的关键通常是上面的 mediate_deleted。
sudo mkdir -p /etc/apparmor.d/local
if ! sudo grep -qxF 'allow capability dac_read_search,' /etc/apparmor.d/local/unpriv_bwrap 2>/dev/null; then
echo 'allow capability dac_read_search,' | sudo tee -a /etc/apparmor.d/local/unpriv_bwrap >/dev/null
fi

# 先只测试编译,不加载到内核,也不写缓存。
# 这样可以提前发现 profile 语法或 ABI 问题。
sudo apparmor_parser -Q -K "$tmp"

# 测试通过后再安装并重新加载 profile。
sudo install -m 0644 "$tmp" "$dst"
sudo apparmor_parser -r "$dst"

# 确认 bwrap / unpriv_bwrap profile 已加载
sudo grep -E '(^bwrap |^unpriv_bwrap )' /sys/kernel/security/apparmor/profiles

# 最后测试 Codex sandbox 是否正常
codex sandbox -- /bin/echo ok

如果最后一条命令执行成功,问题通常已经解决。

验证

可以通过以下命令确认当前状态:

1
2
3
4
which bwrap
sysctl kernel.apparmor_restrict_unprivileged_userns kernel.apparmor_restrict_unprivileged_unconfined
sudo grep -E '(^bwrap |^unpriv_bwrap )' /sys/kernel/security/apparmor/profiles
codex sandbox -- /bin/echo ok

which bwrap 输出为 /usr/bin/bwrap,对应 profile 已加载,且 codex sandbox -- /bin/echo ok 可以正常执行,说明配置已经生效。

额外加固

另一个相关参数是:

1
kernel.apparmor_restrict_unprivileged_unconfined

一些系统上的默认值是 0

这个参数与本文中的 Codex 报错没有直接关系,因此不调整也可以完成修复。

若希望进一步增强限制策略,可以设为 1

1
2
echo "kernel.apparmor_restrict_unprivileged_unconfined=1" | sudo tee /etc/sysctl.d/10-apparmor-hardening.conf
sudo sysctl --load /etc/sysctl.d/10-apparmor-hardening.conf

此操作属于额外的加固,并非本次修复所必需。


可能的其他影响

加载 bwrap-userns-restrict 后,系统上所有经由 /usr/bin/bwrap 启动的程序都会受其约束——最常见的就是 Flatpak 应用。apparmor_parser -r 重载 profile 后,已经在运行的 Flatpak 进程不会自动应用新策略,建议重启受影响的应用。

unpriv_bwrap 子 profile 的 audit deny capability 拒绝了所有 Linux capabilities。除了上述提到 Flatpak 保存问题外,其他 Flatpak 应用如果在沙箱内需要特定 capability,也可能出现功能异常且无明显报错。

排查方式:

1
sudo dmesg | grep -i apparmor | tail -20

若有对应的 deny 记录,在 /etc/apparmor.d/local/unpriv_bwrap 中补上所需的 capability 并重载即可。


参考

Mastodon