2025 年 11 月 5 日,OCI 一次公布三個 runc 的 container escape 漏洞——CVE-2025-31133、CVE-2025-52565、CVE-2025-52881,全部 CVSS 7.3。三個都跟 masked path 跟掛載順序的邏輯有關,任何能建立 container 的使用者都可能藉此突破隔離。同一份協調披露把 crun 跟 youki 也拉了進去,因為這兩家的對應邏輯結構上跟 runc 一樣,都得同時修。這件事把「換 OCI runtime 就換掉 runc 那些坑」這種想法直接打破,但也把 Youki 這支用 Rust 寫的 runtime 推到聚光燈下——2026 年它通過 containerd 的 e2e 測試、被幾家生產環境採用,正式脫離「實驗性」這個標籤。
這篇拆解 runc、crun、youki 三個 OCI runtime 在 2026 年的差異,以及在自架 VPS 上把 Docker 或 Kubernetes 底下的 runtime 換掉之前需要想清楚的事。
runc 那三個 CVE 撬開的其實不是實作而是架構
runc 是 Docker 從 2015 年拆出來的 reference implementation,OCI runtime spec 也是它一起帶出來的,所以 Docker、containerd、CRI-O 底下預設都是它在跑。三個 CVE 之所以能一次爆出來,關鍵不在 Go 有沒有 buffer overflow,而在 masked path 的實作邏輯:runtime spec 允許容器把 /proc/kcore 這類敏感檔案「遮起來」,做法通常是把一個 tmpfs 或 /dev/null 綁定掛載上去。問題是這套流程有時序,攻擊者只要在對的時間點做出符號連結替換或掛載插隊,就能讓被遮蓋的路徑指向宿主的檔案系統。
crun 的作者 Giuseppe Scrivano 在漏洞公布後直接說明——crun 有結構上一樣的邏輯,換 runtime 不會自動修好。Youki 也在幾天內釋出補丁,同樣是修 mount 的順序、把 procfd 的操作補上額外的檢查。這三家不同語言、不同作者、獨立開發,卻在同一個地方栽跟頭,說明這是 OCI 規範跟 Linux 掛載語意本身留下來的坑,不是誰的實作品質問題。
從這個角度看,換 runtime 換到 Youki 的第一個理由不會是「Rust 可以免疫這批 CVE」——這批不吃這套。真正的問題是下一批。runc 累積十年的 Go 程式碼裡,過去有幾個記憶體管理跟並發相關的 issue 是 Rust 的類型系統會在編譯期就擋掉的。Youki 用 Rust 重寫最直接的好處是把「未初始化記憶體、race condition、use-after-free」這幾類漏洞的攻擊面切掉。這個好處對於一支需要處理不可信輸入、跑在特權模式下的 runtime 特別大。
Youki 的路徑:從實驗品到 containerd e2e 通過
Youki 從 2021 年由 utam0k 起頭,2024 年進入 CNCF sandbox,2026 年釋出 0.6.0。中間最重要的一步是通過 containerd 的 end-to-end 測試——這代表把 containerd 底下的 runtime 從 runc 換成 youki,Kubernetes 完整的 pod 生命週期、CRI 語意、volume mount、network namespace、cgroups v2 資源限制通通能跑,不用改上層任何東西。
架構上 Youki 走的是「小而準」的路線,整個 runtime 目前約兩萬多行 Rust 程式碼,比 runc 的 Go 程式碼小上一個量級。它把跟 Linux 系統呼叫的介面切成幾個獨立的 crate——libcontainer 處理容器生命週期、libcgroups 處理 cgroups v1 跟 v2、libseccomp 處理 seccomp filter、liboci-cli 處理 OCI 規範的 CLI 介面。這種切法讓其他 Rust 專案可以直接引用某個 crate 而不用把整支 runtime 包進來,實務上 Kata Containers 跟一些 sandboxing 專案已經開始在自己的 Rust 元件裡用 libcontainer 當底層。
功能覆蓋上,Youki 支援 rootless container、cgroups v1/v2、seccomp、Linux capabilities、AppArmor、SELinux、systemd cgroup driver、user namespace 跟 idmapped mount。跟 runc 相比缺的主要是幾個較少用的 hooks 跟一些 Docker 專用的 legacy quirk,日常跑 Nginx、PostgreSQL、Redis 這類 workload 完全不會撞到。containerd 的整合是透過標準的 runtime.v2 介面——在 /etc/containerd/config.toml 加一個 runtime class 指向 youki 的 binary,就可以在 pod spec 裡宣告 runtimeClassName: youki 選擇性使用。
效能差異需要放在對的情境裡看
Youki 對外釋出的 benchmark 顯示,在 create/start/delete 一整個 cycle 上比 runc 快大約兩倍。這個數字沒有錯,但實務上要拆開來看。Kubernetes pod 啟動時間裡,OCI runtime 只佔一小段——image pull、CNI 設定、admission webhook、scheduling、kubelet reconcile 加起來通常吃到幾秒鐘,把 runtime 從 100ms 壓到 50ms 對整體感受不明顯。
真的會有感的是三種情境:一是 CI/CD 環境跑大量短命 container,像測試容器每個只活幾秒鐘,這時候 runtime overhead 佔比高,Youki 或 crun 都比 runc 明顯快;二是 serverless-style 的 function runtime,每個請求開一個 container 再殺掉;三是資源緊繃的 VPS 或 edge 節點,runc 一個 helper process 常駐幾十 MB,Youki 完成同樣工作大約只吃 runc 的一半到三分之二。
crun 是這場比較裡另一個要提的角色。crun 用 C 寫,比 runc 快的原因跟 Youki 一樣——省掉 Go runtime 的初始化跟 GC 停頓。Podman 底下 crun 是預設,CRI-O 從 1.35.2 起把 crun 1.27 定為 default OCI runtime。三家真的跑起來,crun 跟 Youki 的差距通常在 10% 以內,兩者跟 runc 才是明顯的一個級距。
Rust 的記憶體安全在 runtime 這個場景到底解什麼
用 Rust 寫一支特權程式最常被拿出來講的就是記憶體安全,但要具體說清楚 Youki 因為 Rust 得到什麼、又沒得到什麼,才不會變成信仰之爭。
Rust 直接消除的漏洞類別包括:buffer overflow、use-after-free、double free、資料競爭、null pointer dereference。這幾類過去在 C 寫的系統程式裡是 CVE 大宗,例如 Linux kernel 每年都有一批 UAF 相關的 LPE。runc 用 Go 寫,這幾類 Go runtime 幫忙擋掉一部分,但 goroutine 的並發模型還是可能引入 data race。Youki 靠 Rust 的 ownership 跟 borrow checker,在編譯期就擋掉這批。
Rust 不會幫忙處理的包括:邏輯漏洞、TOCTOU race、系統呼叫的語意誤解、掛載順序錯誤、權限檢查漏做。這批 CVE-2025-31133 三兄弟就是掛載順序的邏輯錯誤,Rust 的類型系統沒辦法在編譯期發現「這個 mount 應該在那個 mount 之前做完」。Youki 補丁跟 runc 補丁本質是一樣的邏輯修正。
也就是說,換 Youki 是把攻擊面切掉「記憶體安全類」這一大塊,但邏輯類的漏洞誰都逃不掉。這值不值得換,看每個團隊對這兩類漏洞的權重評估——對於跑多租戶容器、把容器隔離當成第一線 security boundary 的環境,多切一塊攻擊面通常划算;對於單一團隊自己跑自己容器的環境,這個好處相對薄。
生產環境要換之前需要驗證的三件事
要在自架 VPS 或 Kubernetes 節點上把 runtime 換成 Youki,實務上需要驗證的東西比想像中少。
第一是 cgroups driver 一致性。containerd 或 kubelet 通常設定成 systemd cgroup driver,Youki 也支援,但預設是 cgroupfs。這個要在 /etc/containerd/config.toml 或 kubelet flag 裡明確指定,否則 systemd 跟 runtime 會搶著管 cgroup 導致 OOM 統計異常或 memory limit 沒吃到。
第二是 seccomp profile 相容性。Docker 跟 Kubernetes 常用 default.json 這類白名單 seccomp profile,Youki 對 OCI seccomp v1 完整支援,但如果專案自己客製過 seccomp 用到比較新的 syscall(像 io_uring 相關的 io_uring_setup),要確認 Youki 對應的版本有加進去。0.6.0 之後對新 syscall 的追蹤比早期好上不少。
第三是 rootless 場景的 subuid/subgid 對齊。Youki rootless 支援跟 runc 差不多成熟,但 user namespace 的 uid/gid mapping 要在 /etc/subuid 跟 /etc/subgid 設定正確,並且對應的 newuidmap/newgidmap binary 要在 PATH。這一段在 Podman 環境通常已經設好,但如果是自己拿 Youki 當獨立 CLI 用,第一次跑會撞到。
實務建議是先在 non-prod 節點裝雙 runtime——保留 runc 當預設,把 Youki 註冊成一個 runtime class,特定 workload 用 runtimeClassName 選過去跑。這樣既能用 Youki 得到效能跟安全的好處,又不會一次把整個叢集押上去。過幾週確認沒有 workload 撞到相容性問題,再考慮把預設換掉。
crun 跟 Youki 之間怎麼選
如果目標只是「不要 runc」,那 crun 跟 Youki 都是可行選項,選擇取決於幾個實際因素。
crun 的優勢是成熟度跟整合度:Podman、CRI-O 都是預設用它,紅帽的商業支援涵蓋這條路徑,RHEL、Fedora、SUSE 的容器堆疊本來就是 crun 為主。如果環境是 RHEL 系或走 Podman/CRI-O 路線,換到 crun 幾乎零風險,也沒有額外的 tooling 學習成本。
Youki 的優勢是語言帶來的長期安全性論述、模組化架構、跟 Rust 生態的整合(例如未來要在 Kata、gVisor 這類 sandboxing 上疊自己的邏輯,直接引用 libcontainer crate 比包 crun 方便)。缺點是 CNCF sandbox 階段的專案通常商業支援選項少,紅帽或 SUSE 這類 vendor 目前不會幫你 troubleshoot Youki 的 issue,出事得自己看程式碼或發 issue。
Ubuntu / Debian 系 + Docker/containerd 的環境,兩個都不是預設,換哪個都要自己配。這種情況 Youki 值得認真考慮,因為架構上更符合下一個十年的方向——記憶體安全的系統語言、模組化的元件、比 runc 小一個量級的 codebase。
結語
runc 這一批 CVE 把「OCI runtime 是穩定基礎設施、不用換」這個假設打掉。換到 Youki 或 crun 都是合理選擇,但要理解換的到底是什麼:換到 Youki 拿到的是記憶體安全類漏洞的攻擊面縮小、更小的 codebase、更好的效能,換不到的是對邏輯類漏洞的免疫。挑選跟部署上需要留意 cgroup driver、seccomp、rootless mapping 這幾件事,其他部分 containerd 跟 Kubernetes 的整合已經足夠成熟。
NCSE Network 的臺灣 VPS 使用是方電訊機房的 Intel Gold CPU 跟 NVMe SSD,跑 Docker、containerd、Kubernetes 這類容器 workload 有充足的效能餘裕做 runtime 切換測試。如果正在評估自架容器平台的架構選擇,或需要一個穩定的臺灣節點做 CI runner、build farm、edge 部署,可以到 NCSE Network 了解 VPS 方案。