Docker 在 2026 年 7 月連續推出 v29.4.2、v29.4.3 兩個修補版本,中間還發生了一次 seccomp profile 被回滾的插曲。原因是 Linux kernel 一個代號 Copy Fail 的漏洞(CVE-2026-31431)——algif_aead 這個 AF_ALG 底下的加密介面,一條 2017 年寫進去的 splice 優化路徑,讓任何非特權使用者能對整臺主機的 page cache 做出四個位元組的可控寫入。串接得當就是完整的容器逃逸,連 CAP_SYS_ADMIN、--privileged 都不需要。
漏洞從 Linux 4.14 一路存在到 6.10 系列,等於過去九年間所有主流發行版都中招。臺灣的自架 VPS 若跑的是 Debian 12、Ubuntu 22.04/24.04、AlmaLinux 9 這批常見版本,只要 Docker Engine 是 v29.4.3 以前的版本,預設就會允許容器內建立 AF_ALG socket,也就是漏洞的入口。這一輪修補不是「補個 kernel 就算了」,它牽動的是 Docker 從 seccomp 到 AppArmor 到 SELinux 的三層預設 profile,跑生產環境的機器都需要重新盤點。
Copy Fail 為什麼四個位元組就能拿到宿主 root
漏洞的核心不是傳統的記憶體毀損。過去九年多數容器逃逸都靠 use-after-free、race condition 這類複雜利用鏈,Copy Fail 走的是完全不同的路徑:它把 kernel 的 page cache 當成可寫入的暫存區。
algif_aead 是 AF_ALG 這個 crypto socket 家族底下負責 AEAD 演算法(例如 AES-GCM)的模組。2017 年為了讓 splice 系統呼叫在做 zero-copy 加密時少一次記憶體搬移,開發者加了一段優化:當 splice 傳進來的 iovec 長度是 0 時,直接把 cache page 當成目的地重複使用,而不是配一塊新的 scratch buffer。當年這段程式碼過了 review,因為在正常呼叫路徑下確實安全——沒有人會傳長度 0 的 buffer 進來做加密。
問題是 AF_ALG 沒有阻擋這件事。攻擊者可以刻意用長度 0 的 iovec 呼叫 splice(2),kernel 就會把使用者能控制的四個位元組寫進「原本被視為唯讀」的 page cache。這個 cache 頁面背後是整個系統共享的 inode 對應——也就是每一支正在讀取這個檔案的行程,都會看到被改過的內容。
寫入不會落到磁碟。這是 Copy Fail 最陰險的地方:任何做磁碟層 file integrity monitoring 的工具都偵測不到,因為 on-disk 的位元組沒有改變。改變的只是 kernel 記憶體裡的那份快取,一直到頁面被踢出 cache 為止。這段窗口對攻擊者已經足夠。
page cache 為什麼會跨過容器的隔離邊界
Docker 用 namespace 把 PID、網路、掛載點都分開了,但 page cache 從來就不在隔離範圍內。這是 kernel 為了效能而故意設計的:兩個容器如果從同一個 base image 拉出 libc.so.6,overlayfs 的 lower layer 是同一份,兩個容器讀這個檔案時共用同一組 struct page。分開就沒有 zero-copy、沒有 dedup、記憶體使用會爆掉。
這個共享設計在 2017 年之前沒問題,因為 page cache 對任何非 root 行程都是唯讀的。Copy Fail 把這個前提打穿了。攻擊者只要能在任何一個共用了 base image 的容器內執行程式,就能把宿主上被所有容器共用的 libc.so.6、python3、/bin/sh 這些檔案在記憶體裡改掉。
從這裡到容器逃逸還差一步。runc 在 CVE-2019-5736 之後把自己的執行檔用唯讀 bind mount 保護起來,但保護的是磁碟上的檔案。攻擊者可以透過 /proc/self/exe 拿到 runc 的檔案描述子,然後把 runc 的 ELF 標頭在 page cache 裡改成一個 #!/proc/self/exe shebang 指向被污染的 /bin/sh。下一次 docker exec 觸發 runc,宿主 kernel 讀進來的就是被改過的 runc——一支跑在宿主 namespace、宿主 root 權限下的 shell。
整條利用鏈從進入容器到拿到宿主 root,PoC 大約需要幾百毫秒。
Docker 29.4.2 為什麼被回滾,29.4.3 才是最終方案
Docker 在漏洞公開的當週先出了 29.4.2,補丁做了兩件事:seccomp 阻擋 socket(AF_ALG)、直接封掉 socketcall(2) 這個舊式的多工 syscall。前者精準,後者是保險做法——因為 seccomp 沒辦法從 socketcall 的參數中攔截 AF_ALG,索性把整條路徑封死。
問題出在第二個動作。socketcall 是 i386、s390、以及 x86_64 相容模式下 32 位元二進位檔案的主要 socket 呼叫入口。Docker 29.4.2 一上線,跑 wine、跑舊 game server、跑任何 32 位元 glibc binary 的容器全部斷網。社群回報湧入不到 24 小時 Docker 就出了 29.4.3。
29.4.3 的做法是承認 seccomp 一層不夠,改用 defense-in-depth:
- seccomp:只保留
socket(AF_ALG)的參數過濾,socketcall放回去。這樣 64 位元原生 syscall 直接走 seccomp 擋掉,32 位元相容路徑放行。 - AppArmor:預設 profile 加上
deny network alg,。這條規則在 Ubuntu、Debian 上會同時攔截socket(2)和socketcall(2)兩條路徑,補上 seccomp 的缺口。 - SELinux:
container_domain類型不允許建立alg_socket。RHEL、AlmaLinux、Rocky 這條線靠這個 CIL policy module 補齊。
三層之間有明確分工,缺一層都仍能擋住多數情境,這就是為什麼 29.4.3 不再依賴單一機制。
自架 VPS 該用什麼順序修
修補順序決定風險窗口大小。建議依以下優先級處理:
第一件事:升級 Docker Engine 到 v29.4.3 或更新。 這是最快能把預設 profile 全部換掉的做法。apt install docker-ce=5:29.4.3~* 或 dnf upgrade docker-ce,重啟 daemon 之後所有新開的容器都會套用新 profile。要留意 daemon 重啟時舊容器會斷開,跑資料庫的機器要排維護窗口。
第二件事:如果沒辦法立刻升 Docker,先把 kernel 模組黑名單。 Debian、Ubuntu、AlmaLinux 都已經釋出更新過的 kernel,能升就升。升不了的機器可以先擋住模組載入,把攻擊面直接切掉:
1 | echo "blacklist af_alg" | sudo tee /etc/modprobe.d/copy-fail.conf |
要注意的是,有些應用會用 AF_ALG 做硬體加速加密(例如某些 IPsec 使用者空間工具、部分 Java crypto provider),黑名單前先在測試機驗證。
第三件事:確認舊容器有沒有沿用舊 profile。 Docker daemon 重啟後新容器會套 v29.4.3 profile,但已經在跑的長期容器仍掛著舊 seccomp。這批容器需要重建,或者手動加上 --security-opt seccomp=/path/to/new-profile.json 重新起。生產環境常見的做法是滾動重啟,一次一個節點。
第四件事:檢查有沒有故意關掉預設 profile 的容器。 用 --security-opt seccomp=unconfined、--cap-add=SYS_ADMIN、--privileged 起來的容器,這波 profile 更新對它們沒有意義。跑 CI runner、跑 Docker-in-Docker、跑舊 GitLab CE 的機器最常見。這些容器的攻擊面比預設容器大得多,Copy Fail 是把它們的實際風險再往上抬一階,理由該重新評估。
第五件事:預設 profile 之外還要看邊界。 kernel 層的補丁與 Docker 預設 profile 都到位之後,剩下的是把「攻擊者能不能進到容器內執行程式」這件事縮小。跑 Web 應用不要用 docker exec 做 admin 通道,把管理面搬到獨立的 SSH 跳板;跑多租戶 SaaS 應用要考慮 gVisor 或 Kata Containers 這類 user-space kernel,因為 page cache 本身就在 sandbox 之外。
結論
Copy Fail 揭露的不是新型別的漏洞——page cache 跨容器共享、AF_ALG 對非特權使用者開放、splice 的優化路徑,這三件事單獨看每一件都合理。合起來看是九年沒被人串起來過。這也是為什麼修補動作不是單點:kernel 補一次、Docker 補一次、seccomp/AppArmor/SELinux 各補一次,缺一都無法完整擋住。
對把服務跑在自架 VPS 上的團隊,這一輪最實際的動作是把 Docker Engine 升到 v29.4.3、確認 host kernel 也已經打上 commit a664bf3d603d 或更新版本、把長期容器排入重建計畫。這些動作合起來大概兩小時的維護窗口,換掉的是一條從 pod 到 host 的完整逃逸路徑。
臺灣的自架環境如果需要一批預設就跑得起新版 Docker、kernel 已經完成 Copy Fail 補丁、並且有 24/7 監控協助追蹤 CVE 動態的 VPS,可以參考 NCSE Network 的 VPS 服務——是方電訊機房、Intel Gold CPU、NVMe SSD,安全性更新的驗證與套用不用自己扛。