Linux:先建視圖,再上 seccomp,最後 exec
同一條讀取,Mac 上回 Operation not permitted,Linux 上回 No such file or directory。差別在於 Linux 先換進程能看見的檔案樹,再收緊它能調用的系統接口。
- 讀入權限檔案並做 WSL1 預檢manager.rs L413
- 拼出 helper argv,加上權限檔案 JSONlandlock.rs L23
- 外層 run_main 決定走 bwrap 還是捷徑linux_run_main.rs L152
- 拼內層命令,帶上 --apply-seccomp-then-execlinux_run_main.rs L1520
- bwrap 按順序 ro-bind、bind、unsharebwrap.rs L306
- exec bwrap 時插入 --as-pid-1launcher.rs L39
- 內層 capget,確認能力已清零linux_run_main.rs L216
- 打開 PR_SET_NO_NEW_PRIVS,然後裝 seccomplandlock.rs L61
- fork 用戶命令,父進程 waitpid 收孤兒linux_run_main.rs L243
- execvp 接管進程鏡像linux_run_main.rs L1565
同事在 Mac 上跑 Codex,agent 去讀 ~/.ssh/id_rsa,Seatbelt 立刻回 Operation not permitted。同一份倉庫放到 Linux 筆記本上,常見文案變成 No such file or directory。路徑常常根本不在掛載裏。
如果先打開 PR_SET_NO_NEW_PRIVS,再去調系統自帶的 bwrap,不少發行版上的 setuid 二進制會直接起不來。沙箱在裝得最全的機器上反而失敗。seccomp 是一套內核過濾器,用來規定進程還能調用哪些系統接口;no_new_privs 是它的前置條件,也會擋住 setuid 提權。
預設路徑兩次進入同一份 helper。外層只拼 bubblewrap,把檔案系統換成預設只讀,再疊可寫根,.git、.agents、.codex 即使落在可寫根裏也保持只讀。內層才打開 no_new_privs、裝網絡 seccomp,然後 fork、把進程鏡像讓給用戶命令。
函式頭把順序寫成三步:
/// 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,核對檔案 codex-rs/linux-sandbox/src/linux_run_main.rs,commit 4f39251a01,核對日期 2026-08-22。程式碼塊保留源碼原文,這三步就是預設路徑的合同。字段註釋把原因寫在旗標上:bubblewrap 可能依賴 setuid,必須先建好視圖,再收緊。出處:codex-rs/linux-sandbox/src/linux_run_main.rs 第 117 至 121 行;codex-rs/linux-sandbox/src/landlock.rs 第 57 至 65 行
能看見什麼,和能調用哪些 syscall,是兩道牆。先換世界,再收緊調用。換成別的語言重寫,發行版上只要還有 setuid 包裝器,這個順序就還成立。
bubblewrap 和 seccomp 必須發生在即將變成用戶命令的那個進程裏。主 CLI 自己 unshare 再 exec,失敗會把整次會話帶走,也沒法在 PID namespace 裏留下一個收孤兒的 1 號進程。超時和取消會留下睡眠進程。
Linux 看 argv[0] 的 basename。檔案名已經是 codex-linux-sandbox 就保留,否則改成這個別名,把同一份二進制拐進 helper。外層 exec 的不是 ls,是 helper 自己,帶着 --apply-seccomp-then-exec。launcher 還會插進 --as-pid-1,讓 bubblewrap 自己當命名空間裏的 1 號進程。
內層先 capget。effective 或 permitted 任一非零,立刻 panic,命令不會跑。過了這道檢查,才 fork。子進程 execvp,父進程 waitpid(-1) 把子孫都收掉,退出碼原樣傳出。出處:codex-rs/linux-sandbox/src/linux_run_main.rs 第 216 至 221 行、第 242 至 243 行;codex-rs/linux-sandbox/src/launcher.rs 第 38 至 39 行
獨立輔助進程加先包裝、再收緊,是跨平台同一類自我調用。Windows 看隱藏參數,Linux 看 arg0。包裝器失敗時,固定退出碼加固定 stderr 前綴更好自動歸因。
類型名還叫 LandlockCommand,crate 目錄也叫 linux-sandbox,網上不少資料會寫成 Codex 在 Linux 上用 Landlock。當前預設檔案系統沙箱已經是 bubblewrap。Landlock 是顯式打開的 legacy 回退,特性開關標成 Deprecated,預設關。legacy 吃不下受限讀,也吃不下拆分出來的嵌套只讀。
如果 bwrap 失敗後靜默換 Landlock,模型會按「我被關喺視圖裏面」規劃,實際卻還看得見主機檔案樹。下一步就會分叉。
外層寫明 never falls back。bubblewrap 失敗就失敗。WSL1 在需要 bubblewrap 時,transform 返回 Wsl1UnsupportedForBubblewrap,命令不會進 helper。全盤可寫且不走代理時,根本不會進 bwrap,WSL1 可以過。user namespace 建不出來給啓動警告,不會改走更弱的後端。出處:codex-rs/linux-sandbox/src/linux_run_main.rs 第 290 至 294 行;codex-rs/sandboxing/src/manager.rs 第 413 至 420 行、第 696 至 710 行;codex-rs/features/src/lib.rs 第 1064 至 1069 行
跨平台能力不對等時,最容易犯的錯是用「我們有 Linux 沙箱」覆蓋所有 Linux。本機有新 bwrap、CI 容器禁了 user namespace、同事還在 WSL1,三台機器不是同一道牆。產品文案和模型説明必須按實際啓用的那一層來寫。
DeepSeek Harness:探得過就換檔,合成一拍
DSH 的 Linux 鏈寫死為 bwrap 再 landlock。bwrap 檔是視圖加 exec,沒有 seccomp。探不過才走 landlock-run:在自己身上裝規則,再 exec 目標命令。檔案系統限制和 exec 合在同一份 main 裏。這條路徑沒有 setuid bwrap,所以不必把過濾器再拆到下一拍。
兩邊都走獨立輔助進程。DSH 啓動器失敗是 125,還要同時看到 landlock-run: 致命行,更好自動歸因。Codex helper 失敗是 panic 或原樣 wait status。兩條鏈都探失敗,DSH 拒絕裸跑。它可以換探測過的第二檔,換完仍 fail closed。Codex 預設路徑失敗不回退 Landlock。出處:packages/sandbox/sandbox-local/src/index.ts 第 159 至 166 行;native/landlock-run/packages/entry/src/main.c 第 254 至 261 行
Grok:三拍打在 agent 上,失敗可以回退
Grok 的順序和 Codex 同一條工程理由:先讓可能依賴 setuid 的 bwrap 跑完,再在已經進命名空間的進程裏收緊。差別在主體。Grok 把三拍打在 agent 進程上,命令只繼承結果。視圖是補丁式的 --bind / / 再蓋 deny 路徑,主機樹還在。子進程 spawn 時,若需要限網,才在 pre_exec 裏裝 seccomp。
bwrap exec 失敗且不要求 read-deny 時,它警告一句,回退到 Landlock。內置 Profile 的 apply 失敗記警告並繼續,is_active() 才是真狀態。Codex 外層寫明 never falls back。出處:crates/codegen/xai-grok-shell/src/config/mod.rs 第 1293 至 1320 行;crates/codegen/xai-grok-sandbox/src/lib.rs 第 181 至 192 行
同一條讀取,換開關之後卡在哪
把探針留在讀 /etc/shadow,先打開 WSL1。寫下預測:停在哪一層,文案是什麼。再關掉 WSL1,打開 legacy Landlock,看受限讀會不會變成 UnsupportedOperation。
進階一問:探針換成 connect 外網。為什麼預設路徑會穿過視圖、停在 seccomp,文案從 No such file 變成 EPERM。