学 AI 产品 · 专业 AI 产品经理播客第 3 章 · T3 解剖 OpenAI Codex:把安全观写进类型系统 · EP 10
第 3 章 · EP 10

Linux:先建视图,再上 seccomp,最后 exec

时长 13:12音色 云健 · 男声

同步字幕

章节导航(点击跳转)

0:00开场 · 同一条读取两种报错1:35
1:35为什么分两拍 · setuid 的顺序问题1:54
3:29三拍合同 · 函数头把顺序写死1:19
4:49单独起 helper · 留一个收孤儿的人1:43
6:33内层先自检 · 能力没清零就不跑0:53
7:26默认不是 Landlock · 失败也不回退2:03
9:30横向对比 · 三拍拆开后各自漏什么2:12
11:42可带走的原则1:29
解读全文

ep21 · Linux:先建视图,再上 seccomp,最后 exec

本集对应课程章节:OpenAI Codex · 代码模式(Linux 沙箱分层)
内容来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》),本页为二次演绎的解读与音频稿件版本。

一、本集解决什么工程问题

同一条读取:Mac 上回 Operation not permitted,Linux 上回 No such file or directory。

差别不在强度,而在顺序:Linux 先换掉进程能看见的文件树(视图),再收紧它能调用的系统接口(seccomp)。路径常常根本不在挂载里,所以还没轮到系统调用表,内核就已经回了「文件不存在」。

本集回答三件事:

  1. 默认路径为什么分两拍:先用 bubblewrap 建视图,再上 seccomp
  2. 为什么要单独起一个 helper
  3. 为什么类型名还叫 LandlockCommand,默认却走 bubblewrap

一句话骨架:能看见什么,和能调用哪些 syscall,是两道墙。先换世界,再收紧调用。

能力地图

维度读完本集你能做到对应证据
两层分工区分「视图挡看见」与「seccomp 挡调用」视图 → No such file;过滤器 → EPERM
顺序理由解释 no_new_privs 前置会如何打挂 setuid bwraplinux_run_main.rs L117–121、landlock.rs L57–65
三拍合同复述入口函数头的三步注释linux_run_main.rs L152–158
helper 分身说明 argv[0] basename 如何把同一份二进制拐进 helperlauncher.rs L38–39
内层自检说明 capget 未清零即 panic 的意义linux_run_main.rs L216–221、L242–243
失败策略说明 never falls back 与 WSL1 预检linux_run_main.rs L290–294、manager.rs L413–420 / L696–710
横向差异对比 DSH 与 Grok 的三拍放法与失败策略DSH sandbox-local、landlock-run;Grok config/mod.rs L1293–1320

二、为什么分两拍 · setuid 的顺序问题

反直觉的失败:若先打开 PR_SET_NO_NEW_PRIVS 再调系统自带的 bubblewrap,不少发行版上的 setuid 二进制会直接起不来 —— 沙箱在装得最全的机器上反而失败。

原因:no_new_privs 会挡住 setuid 提权,而系统自带的 bwrap 常常正是 setuid 包装器。必须先让它靠提权把命名空间建好,再在自己身上收紧。

  • seccomp:内核过滤器,规定进程还能调用哪些系统接口
  • no_new_privs:seccomp 的前置条件,同时挡住 setuid 提权

默认路径 = 两次进入同一份 helper

拍动作
外层只拼 bubblewrap,文件系统默认只读,再叠可写根;.git / .agents / .codex 即使落在可写根里也保持只读
内层打开 no_new_privs、装网络 seccomp,然后 fork,把进程镜像让给用户命令

字段注释把原因直接写在旗标上:bubblewrap 可能依赖 setuid,必须先建好视图,再收紧 —— linux_run_main.rs L117–121、landlock.rs L57–65。

提示1 · 排查顺序类故障

沙箱在「装得最全的机器」上失败时,先查收紧动作是否早于包装器执行。顺序反了的表现是:工具自身起不来,而不是目标命令被拦。


三、三拍合同 · 函数头把顺序写死

出处:codex-rs/linux-sandbox/src/linux_run_main.rs L152–158


/// Entry point for the Linux sandbox helper.
/// The sequence is:
/// 1. When needed, wrap the command with bubblewrap to construct the
///    filesystem view.
/// 2. Apply in-process restrictions (no_new_privs + seccomp).
/// 3. `execvp` into the final command.

源码口径:本地仓库 openai/codex,commit 4f39251a01,核对日期 2026-08-22。这三步就是默认路径的合同。

两层各挡什么

层挡的是表现
视图看见路径不在挂载里 → No such file or directory,此时还没轮到系统调用表
seccomp调用默认放行,命中 connect / bind / ptrace 等规则才回 EPERM;已经看不见的文件它不管

同一探针换一下,停的位置就变

  • 读 /etc/shadow → 停在视图层,文案 No such file
  • 换成向外 connect → 穿过视图,停在过滤器,文案 EPERM

提示2 · 用探针定位层级

判断一次拦截发生在哪一层,看错误文案就够了:No such file 是视图层,EPERM 是 seccomp 层。这比读日志快得多。


四、单独起 helper · 留一个收孤儿的人

为什么不能让主 CLI 自己 unshare + exec:失败会把整次会话带走,且没法在 PID namespace 里留下一个收孤儿的 1 号进程 —— 超时和取消会留下睡眠进程。

做法:靠 argv[0] 的 basename 分身

机制内容
别名拐入文件名已是 codex-linux-sandbox 则保留,否则改为该别名,把同一份二进制拐进 helper
外层 exec 目标不是 ls,而是 helper 自己,带 --apply-seccomp-then-exec
1 号进程launcher 插入 --as-pid-1,让 bubblewrap 自己当命名空间里的 1 号进程

出处:launcher.rs L38–39、linux_run_main.rs L1520

精髓:同一份二进制进了两次,中间隔着 bubblewrap 建好的世界 —— 第一次为建世界,第二次为在已建好的世界里把自己收死。

跨平台一致性:Windows 看隐藏参数,Linux 看 argv[0]。包装器失败时,固定退出码 + 固定 stderr 前缀更好自动归因。


五、内层自检 · 能力没清零就不跑

步骤内容出处
1capget:effective 或 permitted 任一非零 → 立即 panic,命令不跑linux_run_main.rs L216–221
2过检后 fork;子进程 execvplinux_run_main.rs L1565
3父进程 waitpid(-1) 收掉子孙,退出码原样传出linux_run_main.rs L242–243

父进程为何必须留下:命名空间里它是 1 号进程,孤儿会被交给它收养。没有这个人,超时与取消就会留下没人收的睡眠进程。

一句话概括:进程让出去之后,收尸的人还在。

六、默认不是 Landlock · 失败也不回退

常见误解:类型名还叫 LandlockCommand,crate 目录叫 linux-sandbox,于是不少资料写成「Codex 在 Linux 上用 Landlock」。当前默认的文件系统沙箱已经是 bubblewrap。

  • Landlock 是显式打开的 legacy 回退,特性开关标 Deprecated,默认关
  • legacy 吃不下受限读,也吃不下拆分出来的嵌套只读

失败策略:外层写明 never falls back —— bubblewrap 失败就是失败,不静默换 Landlock。

为什么这条关键:若静默换 Landlock,模型会按「我被关在视图里」规划下一步,实际却还看得见主机文件树 → 下一步行为分叉,且最难排查。

具体情况

场景行为出处
WSL1 且需要 bwraptransform 返回 Wsl1UnsupportedForBubblewrap,命令不进 helpermanager.rs L413–420 / L696–710
全盘可写且不走代理根本不会进 bwrap,WSL1 可以过同上
user namespace 建不出给启动警告,不改走更弱后端features/src/lib.rs L1064–1069
bwrap 失败never falls backlinux_run_main.rs L290–294

提示3 · 文案必须按实际启用的那一层写

三台机器不是同一道墙:本机有新 bwrap、CI 容器禁了 user namespace、同事还在 WSL1。产品文案与给模型的说明必须按实际启用的那一层来写,不能用「我们有 Linux 沙箱」一把覆盖。


七、横向对比 · 三拍拆开后各自漏什么

项目结构失败策略
DeepSeek HarnessLinux 链写死为 bwrap 再 landlock。bwrap 档 = 视图 + exec,没有 seccomp;探不过才走 landlock-run,在自己身上装规则再 exec。文件系统限制与 exec 合在同一份 main 里启动器失败退出码 125 + landlock-run: 致命行,便于自动归因;两条链都探失败则拒绝裸跑,可换到探测过的第二档,换完仍 fail closed
Codex三拍:bwrap 视图 → 进程内 no_new_privs + seccomp → execvp。同一份 helper 进两次helper 失败为 panic 或原样 wait status;默认路径失败不回退 Landlock
Grok顺序理由与 Codex 相同,但三拍打在 agent 进程上,命令只继承结果。视图为补丁式 --bind / / 再盖 deny 路径,主机树还在;子进程 spawn 时若需限网才在 pre_exec 装 seccompbwrap exec 失败且不要求 read-deny 时警告并回退 Landlock;内置 Profile 的 apply 失败记警告并继续,is_active() 才是真状态

共同点:两边都走独立辅助进程;都承认「先让可能依赖 setuid 的 bwrap 跑完,再收紧」这条工程理由。

关键差异:失败之后谁负责降级 —— Codex never falls back,Grok 可以警告后回退,DSH 换档但 fail closed。


八、审查清单

Linux 沙箱改动或排查时逐项自查:

  • 收紧动作(no_new_privs / seccomp)是否晚于包装器执行
  • 外层是否只拼视图、内层才收紧,两拍职责是否混在一起
  • .git / .agents / .codex 是否即使在可写根里也保持只读
  • helper 分身是否依赖 argv[0] basename;--as-pid-1 是否插入
  • 内层 capget 未清零时是否 panic(而非继续跑)
  • 父进程是否用 waitpid(-1) 收掉子孙,退出码是否原样传出
  • 默认后端是否为 bubblewrap;Landlock 是否仅在显式 legacy 开关下启用
  • 失败是否 never falls back;WSL1 / user namespace 缺失时是否在 spawn 之前拒绝
  • 错误文案是否能区分视图层(No such file)与 seccomp 层(EPERM)
  • 文案与模型说明是否按实际启用的那一层撰写

提示4 · 课堂练习:换开关之后卡在哪

把探针留在读 /etc/shadow:

  1. 先打开 WSL1 —— 预测停在哪一层、文案是什么
  2. 关掉 WSL1,打开 legacy Landlock —— 看受限读是否变成 UnsupportedOperation
  3. 进阶:探针换成 connect 外网 —— 为什么默认路径会穿过视图停在 seccomp,文案从 No such file 变成 EPERM

九、约束说明

  1. 源码口径:依据本地仓库 openai/codex,核对文件 codex-rs/linux-sandbox/src/linux_run_main.rs,commit 4f39251a01,核对日期 2026-08-22;代码块保留源码原文。
  2. 教学化内容:探针路径与开关切换为课程化设定,用于展示分层条件;逻辑轨迹右侧行号对应真实源码。
  3. 横向对比口径:DSH 依据 PLATFORM_CHAINS 与 landlock-run(2026-08-22);Grok 依据 bwrap_reexec_for_profile 与 apply(2026-08-22)。
  4. 平台不对等:三台机器的启用层级可能不同,任何「已沙箱化」的表述都必须指明实际生效的是哪一层。

Linux 默认路径是三拍:先用 bubblewrap 换文件系统视图,再在已经进命名空间的 helper 里上 seccomp,最后才 exec。顺序反了,setuid 的系统 bwrap 会起不来。类型名还叫 Landlock,那是兼容,不是现行默认。进不去就在 spawn 之前拒绝,失败不换更弱的后端。


来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)
本页为二次演绎的解读与音频稿件版本,音频与本页配套发布。