2017 年的一個 in-place 加密優化拿掉了一個邊界檢查;九年後,CVE-2026-31431 落地,主線回退那個優化,補上 4 bytes 的邊界守門——這篇從工程的角度看:核心維護者真正交付了什麼,這次硬化堵住了哪一類「使用者控制長度」的輸入驗證缺口,以及下一次同類缺口應該由哪一層工程實踐攔下來。
CVE-2026-31431:algif_aead in-place 邊界硬化與 fast-path 安全契約
CVE-2026-31431 在 2026 年 4 月 29 日公開披露。漏洞位於 Linux 核心 crypto/algif_aead.c——AF_ALG socket 家族暴露給使用者空間的 AEAD 加密路徑。具體出問題的是 authencesn wrapper 的 in-place 路徑:scatterwalk_map_and_copy 在計算寫入位移時,把 assoclen + cryptlen 當成可信長度直接傳下去,結尾的 4 bytes 落在 scatterlist 配置之外。主線的修補(commit a664bf3d603d)回退 2017 年那段 in-place 優化,把這段路徑重新放回會做邊界檢查的通用 path——這是這篇文章關心的:硬化本身、它預防的那一類輸入驗證 bug、以及為什麼這類缺口要等九年才能被找出來。
這篇關心的是:當核心把一個強能力(任意 cipher 操作)暴露給無特權程序時,每一層 in-place 優化都是潛在的邊界缺口;2026 年這次修補只是把其中一個缺口收回去;下一個結構相同的缺口要由哪一層工程實踐攔下來。三個可重複使用的教訓:使用者控制的長度欄位永遠不能在 hot path 上略過邊界檢查;in-place 優化幾乎總是用安全邊界換效能,最低工程義務是把契約寫進測試矩陣;eBPF-LSM 是「未修補時間窗」的標準減災層,應該事先寫好、事先部署,而不是等 CVE 落地後才開始寫。
what landed in mainline
修補本身體積不大——主線那個 commit 的 diff 在三位數行內。
但這幾行的意義在於:
它回退了一段 2017 年合進主線的 in-place 優化。
那段優化原本的設計動機是減少 scatterlist 配置與拷貝;
它的副作用是,
authencesn 在 in-place 路徑上跳過了通用 AEAD 路徑會做的「目標長度 ≥ 來源長度 + auth tag」邊界檢查。
修補後,
這段路徑回到通用 path,
每次寫入前都重新驗證 destination buffer 的長度——
所有 cipher 實作共享同一條邊界守門邏輯,
而不是各自分散。
兩條路徑的差異不只是「跳過 vs 執行邊界檢查」——還有一個設計風格的混用。scatterwalk_map_and_copy 是低階工具函式,契約寫在 header comment 裡,要求「呼叫者保證 offset + length 不超過 scatterlist 範圍」;aead_request_set_crypt 是高階 API,自己驗證契約並回傳 -EINVAL。2017 年那段優化在高階 API 上用低階工具函式、卻沒把低階契約的責任明確寫出來——這是 bug 的精確位置。修補的本質不是「加一個 check」,而是把這條 path 重新接回高階 API,讓既有的契約覆蓋自動生效。
修補的具體位置:crypto/algif_aead.c 的 aead_recvmsg_async。原路徑在 SG_MITER_TO_SG 標記下走 in-place 拷貝;新路徑直接呼叫 aead_request_set_crypt,由通用 AEAD 框架負責 dst/src scatterlist 的長度檢查——若 assoclen + cryptlen + tag_size 超過 dst scatterlist 總長度,回傳 -EINVAL,recvmsg() 把錯誤碼乾淨地交回使用者空間。效能成本:在大檔加密上約增加 1–2% 吞吐量損耗;在 AF_ALG 這條被無特權程序呼叫的路徑上,這是正確的取捨。
commit message 本身節制:「Revert in-place optimization for AEAD recvmsg path. The optimization assumed dst buffer length was validated upstream; this is not guaranteed for user-controlled assoclen. Restore validation by routing through the generic AEAD path.」關鍵字是「assumed」——這個字在 kernel commit 史上是 anti-pattern 指標:當年那個假設沒被寫成檢查、沒被寫成測試。grep mainline log 中的「assumed」+「revert」組合,可以找到一連串結構雷同的歷史 commit。
從這個角度看,整個 kernel crypto subsystem 還有許多 caller 待 audit:git grep "scatterwalk_map_and_copy" 在 crypto/ 目錄下大約有 30—40 個呼叫點,多少個的 offset 來自使用者控制的長度?多少個走過了會做邊界檢查的高階 API?這份清單應該由 maintainer 在 CVE 落地後一個 sprint 內產出。類似的審計也適用於 net/ 的 skb_copy_* 家族、drivers/ 的 ioctl handler、fs/ 的 read/write path。
主線把 backport 標記寫進 Fixes: tag 與 Cc: stable@vger.kernel.org——backport bot 自動拉進 5.10、5.15、6.1、6.6 等 LTS 線,主線 commit 與 distro package 之間的窗口縮短到幾天。修補同時補了一條 selftest(tools/testing/selftests/crypto/algif_aead_oob_test.c),在開啟 KASAN 的 debug kernel 上構造越界寫入場景;不過 selftests 在 distro 預設配置裡通常不會打開,抓 regression 的能力主要依賴 upstream CI 與 distro CI,不是用戶端 runtime self-check。
把這次硬化落實到具體程式碼,下表並列硬化前後同一條 syscall 路徑的邊界檢查模式——責任由「caller 記得」轉成「框架代執行」:
| 檢查點 | 2017 in-place 路徑 before | 2026 通用 path after |
|---|---|---|
| 入口長度 | assoclen = msg->msg_iter.count; /* 直接使用,未夾上界 */ |
assoclen = msg->msg_iter.count;
if (assoclen > ctx->aead_assoclen)
return -EINVAL; |
| scatterlist 配置 | sg_init_table(sg, 1); sg_set_buf(sg, dst, len); /* len 來自使用者 */ |
err = aead_request_set_crypt(
req, src_sg, dst_sg,
cryptlen, iv);
/* 框架驗證 dst_sg 容量 */ |
| 寫入位移 | scatterwalk_map_and_copy(
buf, sg,
assoclen + cryptlen,
tag_size, 1);
/* 4 bytes 落出邊界 */ |
/* 通用 AEAD path 內部 先驗證 dst_len >= src_len + tag 不滿足直接 -EINVAL, 不會抵達寫入步驟 */ |
| 契約位置 | header comment: 「caller 保證 offset+length 在範圍內」 |
framework invariant: 每次 set_crypt 自動驗證 |
| 失效模式 | 沉默越界寫入 (KASAN-off 偵測不到) |
明確 -EINVAL (recvmsg 乾淨返回錯誤) |
互動圖表
in-place 優化讓邊界契約散落在 caller,回退到通用 AEAD path 後框架自動驗證、EINVAL 取代沉默越界寫入。
結構抽象出來是一條 review 規則:低階拷貝工具函式(scatterwalk_*、skb_copy_*、copy_*_iter)若被高階 API 直接呼叫卻繞過該 API 的驗證入口,這就是 2017 年那個 bug 的形狀;grep 這類直接呼叫並回溯資料來源是這次硬化最直接的 takeaway。
the class of bug this prevents going forward
把 CVE-2026-31431 抽象到 bug class 層,它屬於一個老到 Multics 時代就有的家族:「核心讀取使用者提供的長度欄位,沒有把這個欄位夾在已知上界之內,後續路徑用它計算寫入位移」。這個家族在 Linux 核心歷史上已經有過幾次大規模硬化——每一次硬化都堵住一段「使用者控制長度」的攻擊面,但每一次硬化只覆蓋當時已知的 hot path,新加的 in-place 優化、新加的零拷貝路徑、新加的 ioctl 介面,會繞過先前的硬化。
2014 · pre-hardening era
核心使用 copy_from_user / copy_to_user 配對,每個 caller 自己負責邊界檢查。沒有統一守門:caller 忘記檢查就直通;常見失效模式是 ioctl handler 把使用者長度欄位直接傳給 memcpy。
2016 · CONFIG_HARDENED_USERCOPY 提案
Kees Cook 把「hardened usercopy」帶進主線:在 copy_from_user / copy_to_user 路徑上加上 slab / page / stack 邊界檢查,使用者長度欄位超過原始 allocation 的邊界時整個 syscall 失敗。這層硬化覆蓋的是「對 kernel slab 的越界拷貝」。
2017 · in-place 優化打洞
algif_aead 為了減少 scatterlist 拷貝,引入 in-place 優化路徑——這條路徑不經過 copy_*_user,而是直接由核心 cipher 操作在使用者提供的 scatterlist 上原地寫入。CONFIG_HARDENED_USERCOPY 看不到這個路徑——它只在 copy_*_user wrapper 上巡邏。
2020 · BTI + 控制流硬化
arm64 mainline 接入 Branch Target Identification;x86 接入 IBT。這些是針對 控制流 的硬化,不是針對 資料流。一個越界寫入仍然可以成功——只是寫入後構造的 ROP gadget 鏈條變得更困難。Class of bug 沒有被消除,只是 weaponize 成本提高了。
2026 · COPY_FROM_USER 補強
主線 commit a664bf3d603d 把 algif_aead 的 in-place 路徑重新接回通用 AEAD path——通用 path 上的長度檢查重新生效。這個修補只覆蓋一個 caller;其它走 in-place 路徑的 cipher wrapper(algif_skcipher、algif_hash 部分路徑)仍應被 audit。
互動圖表
CONFIG_HARDENED_USERCOPY 只巡邏 copy_*_user 路徑;2017 年的 in-place 優化在它的覆蓋範圍之外打洞。
硬化的軌跡呈現重複的形狀:每一階段都是「發現一類新的越界 path → 加上覆蓋這類 path 的硬化 → 兩三年後新的優化重新打洞」。這不是因為硬化做得不好——是因為硬化的單位是「路徑」,kernel 在持續增加新的路徑。三個結構性觀察:
第一,硬化是巡邏,不是圍牆。HARDENED_USERCOPY 在 copy_*_user 入口做檢查——這是巡邏;Rust 的 &[u8] + slice indexing 在型別層保證越界存取會 panic——這是圍牆。把巡邏轉成圍牆需要設計哲學的轉變:把安全 invariant 從 runtime 檢查上升到型別系統保證。中間階段的折衷是「混合架構」——新子系統用 Rust 或附帶 __counted_by 註解的 C,既有的慢慢補。
第二,覆蓋率是能被測量的數字。perf trace + KCOV 可以列出生產 workload 實際打到的 syscall 路徑;對照 HARDENED_USERCOPY 命中率,可以算出實際被硬化覆蓋的呼叫比例。如果是 70%,剩下 30% 走在 in-place 優化、零拷貝、自管 scatterlist 的路徑——這個 SLI 應該出現在 distro security 的 dashboard 上,當它在某次升級後下降,alert 應該觸發。
第三,類比 bug該被主動搜尋。CVE-2026-31431 的結構是「使用者控制 assoclen → in-place 路徑跳過長度檢查 → 越界寫入」。把這個結構抽象成 grep 或 CodeQL 規則:「找出所有 caller 傳給 scatterwalk_map_and_copy 的 length 參數,回溯 data flow 來源,標註來自使用者輸入的呼叫點」。這份 query 應該開源、由 distro security 共同維護,避免每個組織各自重新寫一遍。
把「主動搜尋同類缺口」拆成三條互補的偵測技術——靜態分析、模糊測試、執行期硬化。三條都針對「使用者控制長度未夾上界」家族,但覆蓋面、誤判率、部署成本各不相同:
CodeQL · 靜態分析的長度污染追蹤
在原始碼層用 data-flow 規則找出「使用者輸入的 length 欄位流入低階拷貝函式」的所有 caller。algif_aead 路徑可以寫成這樣的污染追蹤:
from FunctionCall fc, Parameter p, Expr len
where fc.getTarget().getName() = "scatterwalk_map_and_copy"
and len = fc.getArgument(2) // offset
and len.flow().getSource()
.matches("msg_iter.count|assoclen|cryptlen")
and not exists(BoundCheck bc |
bc.dominates(fc) and bc.checks(len))
select fc, "user-controlled length without prior bound check"
syzkaller · 模糊測試的動態探索
在 syzlang 描述 syscall 序列,fuzzer 隨機組合輸入直到 KASAN 攔到越界。針對 AF_ALG 子協議要描述完整的狀態機:
resource sock_alg[sock]
socket$alg(domain const[AF_ALG], type const[SOCK_SEQPACKET], proto const[0]) sock_alg
bind$alg(fd sock_alg, addr ptr[in, sockaddr_alg], len bytesize[addr])
setsockopt$ALG_SET_KEY(fd sock_alg, level const[SOL_ALG],
opt const[ALG_SET_KEY], key buffer[in], keylen bytesize[key])
sendmsg$alg(fd sock_alg, msg ptr[in, msghdr_alg], f flags[send_flags])
recvmsg$alg(fd sock_alg, msg ptr[inout, recv_msghdr], f flags[recv_flags])
KASAN + HARDENED_USERCOPY · 執行期硬化
在 kernel runtime 上用 shadow memory 與包裝函式攔截越界存取。配置層面是兩個 Kconfig 加上 bpf-lsm policy:
CONFIG_KASAN=y CONFIG_KASAN_INLINE=y CONFIG_HARDENED_USERCOPY=y CONFIG_HARDENED_USERCOPY_FALLBACK=n CONFIG_BPF_LSM=y # 加上 bpf-lsm 對 AF_ALG 的 default-deny # socket_bind hook 回傳 -EPERM # 除非 caller 在 allowlist 上
三種技術是並用而非擇一:靜態分析在 commit-time 抓形狀、fuzzing 在 CI 攔 reviewer 想不到的編排、執行期硬化在 production 兜底。CVE-2026-31431 之所以九年才被發現,是因為這三層在 2017—2025 之間對 algif_aead in-place 路徑都有局部盲區。
下面這張表是同類「使用者控制長度未夾上界」家族的歷史樣本。重要的是結構:使用者輸入一個長度欄位、核心一條 hot path 跳過邊界檢查、後續路徑用這個長度計算寫入位移。每一條對應一次硬化,每一次硬化只覆蓋當時的 hot path:
| CVE | 年 | 子系統 | 硬化點 | 類別 |
|---|---|---|---|---|
| CVE-2016-6516 | 2016 | fs/ioctl | FIDEDUPERANGE 長度檢查 | ioctl 長度 |
| CVE-2017-7184 | 2017 | net/xfrm | XFRM_MSG_NEWAE 邊界 | netlink 長度 |
| CVE-2018-13405 | 2018 | fs/inode | SGID 屬性檢查 | 權限長度 |
| CVE-2020-14386 | 2020 | net/packet | tpacket frame size | 環形緩衝 |
| CVE-2021-22555 | 2021 | net/netfilter | xt_compat 長度 | compat layer |
| CVE-2022-0185 | 2022 | fs/fs_context | legacy_parse_param | 掛載參數 |
| CVE-2023-32233 | 2023 | net/netfilter | nf_tables UAF | 命名空間長度 |
| CVE-2024-1086 | 2024 | net/netfilter | nft_verdict 雙重釋放 | verdict 長度 |
| CVE-2026-31431 | 2026 | crypto/algif_aead | commit a664bf3d603d | cipher in-place |
互動圖表
從 CVE-2016-6516 到 CVE-2026-31431,使用者控制長度未夾上界這個結構在不同子系統每隔 2–3 年重複一次。
表格的價值在於並列起來之後浮現的模式:每一條都是「使用者提供長度/偏移/索引」+「核心某條 path 沒有把這個值夾在已知上界之內」+「修補是補上那條 path 上的檢查」。要終結這個循環,必須把「使用者輸入夾上界」這條規則上升到型別系統或註解層——而不是依靠每個 caller 各自記得。2026 年有兩條進展中的路線:Rust-in-kernel 的 UserSlice 強制要求每次讀取使用者長度都通過一個帶上界的型別構造器;C 端的 __counted_by 屬性把 struct flexible array 的長度欄位綁定到 sibling field,編譯器與 KASAN 都能用這個註解做檢查。
這個原則是可遷移的:你的服務有沒有「使用者控制的長度/偏移/索引欄位」?這些欄位在進入內部 hot path 之前有沒有被夾在已知上界之內?這個動作是寫成型別系統的一部分(fail-fast),還是分散在每個 caller 的 if 語句裡(fail-slow)?前者是圍牆,後者是巡邏。如果你的答案是「應用層會檢查」,那麼下一個問題是:是哪一條 path 上的哪一行檢查?如果不能 30 秒內回答,那個檢查可能不存在,或者只覆蓋部分 path——這正是 2017 年 algif_aead 維護者面對的狀況。
which engineering practice catches the next one
修補已經合進主線,distro 已經分發,Cloudflare 已經用 bpf-lsm 把線上窗口收掉。剩下的工程問題是:下一個結構雷同的 bug 該由哪一層工程實踐攔下來?這個問題對 kernel maintainer、distro security team、生產營運工程師、寫 userspace 服務的工程師各自有不同答案,但有一個共同骨架——主動偵測、被動緩解、結構消除這三層應該並行。
下面這個互動圖把這三層攤開:每個方塊代表一層工程實踐,點擊看每一層的覆蓋範圍、它不知道的事、以及它在這次 CVE-2026-31431 的時間軸上扮演的角色。
syzkaller / KASAN · CI 階段主動偵測
syzkaller 在 Google 的 syzbot infrastructure 上長期 fuzz Linux 核心;KASAN 在 kernel allocator 上提供 shadow memory 監控,越界存取會立刻 panic。理論上 CVE-2026-31431 這種 4 bytes 越界寫入應該被 KASAN 攔到——前提是 fuzzer 能造出觸發條件。
實際上:algif_aead 的 in-place 路徑需要特定的 assoclen + cryptlen 組合 + 合法的 cipher 設定 + splice()-注入的 scatterlist。syzkaller 的 syscall 描述(syzlang)對 AF_ALG 路徑覆蓋不足——它能造出 socket 與 bind,但對複雜的 setsockopt + sendmsg + recvmsg 三步狀態機探索得不深。
工程動作:補強 syzlang 對 AF_ALG 子協議的描述;把所有 cipher in-place 路徑加進 syzkaller 的優先 corpus;在 distro CI 上強制 KASAN 開機跑核心測試。
不知道:當 bug 需要使用者空間複雜編排才能觸發時,純 syscall fuzzer 仍會遺漏。
__counted_by / Rust · 型別層結構消除
C 端的 __counted_by(field) 屬性在 clang 16+ / gcc 14+ 開始支援,把 flexible array 的長度欄位與 sibling field 綁定——編譯器在每次存取時自動驗證索引在範圍內。Rust-in-kernel 的 UserSlice 在型別層強制要求每次從使用者讀取都帶上界。
對 CVE-2026-31431:如果 aead_request 的 dst scatterlist 用 __counted_by 註解,編譯器在 in-place 寫入時會自動檢查 assoclen + cryptlen 不超過總長度——無論是 in-place 還是通用 path。這把「巡邏」升級到「圍牆」。
工程動作:為新的 kernel struct 強制要求 __counted_by;既有 struct 逐步補註解;新子系統優先考慮 Rust 實作。
不知道:既有 C 程式碼的覆蓋是漸進的;in-place 優化路徑往往是最後被補註解的——因為效能敏感的程式碼最不願意接受任何 wrapper。
bpf-lsm allowlist · 未修補窗口被動緩解
Cloudflare 部署的兩階段做法:先用 prometheus-ebpf-exporter 在 fleet 上 instrument 所有 AF_ALG socket bind 呼叫,量測得到「fleet 上只有一個內部服務合法使用 algif_aead」;再寫一個 bpf-lsm 程式掛在 socket_bind hook,對非 allowlist 的 caller 回傳 EPERM。
邏輯三步:non-AF_ALG sockets 放行;AF_ALG + allowlist binary 放行;其它一律拒絕。整個緩解不需要修改核心版本,不需要排程重開機窗口,可以在線上熱部署。
工程動作:把 LSM hook 緩解寫進 IR playbook;對每一個歷史上被濫用的核心子系統(AF_ALG、userfaultfd、io_uring、bpf())預先寫好 allowlist 程式;事先量測合法 caller。
不知道:bpf-lsm 只擋住「未授權程序使用該子系統」——它不能擋住「合法 caller 自己被破解後從合法路徑進入」的情況;它是降低暴露面,不是修補 bug。
code review checklist · 補丁進入主線前
2017 年那次 in-place 優化合進主線時,review 過程沒有捕捉到「跳過邊界檢查」這個副作用——commit 描述聚焦於效能提升,沒有明確說「這條路徑與通用 path 在邊界檢查覆蓋上的差異」。事後看,這是個 review checklist 的缺口。
可重複使用的 review 問題:(1)這個 patch 引入新的 hot path 或 fast path 嗎?(2)這個 path 是否繞過任何既有的安全檢查(HARDENED_USERCOPY、SLAB_VIRTUAL、KASAN 友善的 wrapper)?(3)如果繞過了,新的 path 是否在自己內部補上等價檢查?(4)如果沒補,commit message 是否明確聲明這個 trade-off?
工程動作:把這四個問題寫進 maintainer 的 checklist;要求所有 fast path / in-place / zero-copy 命名的 patch 在 commit message 中明確聲明它的安全 invariant;自動 CI grep 這類關鍵字並要求 reviewer ack。
不知道:人工 review 是 best-effort;當 patch 在 LKML 上來回十幾輪時,review 注意力會分散。Checklist 是 floor,不是 ceiling。
互動圖表
四層防禦各有盲區:syzkaller 探索深度不足、__counted_by 覆蓋漸進、bpf-lsm 是未修補窗口的最速緩解。
四層並不互斥——CVE-2026-31431 暴露的是四層的同時局部失效。補強順序按 ROI 排:先補 bpf-lsm(成本最低、不依賴 kernel 升級);再補 review checklist(maintainer 工作流加 grep + ack);再補 syzkaller corpus;最後做型別系統的長線覆蓋(每季補幾個 struct 的 __counted_by 註解)。把所有資源押在型別系統重寫不可行;只押 syzkaller 也忽略「事先量測 + allowlist 能擋住更廣攻擊面」。
Cloudflare 的關鍵動作不是「48 小時內封鎖了 CVE」——很多人可以——而是「exporter 已經部署在 fleet 上,48 小時內我們知道合法 caller 是誰」。量測在威脅之前完成、緩解在 CVE 之後快速組合,成本在平日:事先決定哪些子系統值得 instrument、事先寫好通用 LSM hook 框架、事先把 allowlist 寫成可審計程式碼。CVE 落地當天再開始,已經來不及。
對小團隊(沒有 fleet 也沒有 exporter)的對應做法很具體:systemd service unit 加一行 RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6,其它(含 AF_ALG)一律拒絕。對絕大多數 web service 這三個 family 就夠用;這是無需 audit 就可以普遍套上的硬化動作。
把這個思路放大到其他「強能力對無特權程序開放」的子系統——userfaultfd、io_uring、bpf()、perf_event_open——每一個都有歷史 CVE 紀錄、每一個都有 fast path 繞過硬化的風險。最有效的動作:盤點 fleet 上的真實 caller、寫 allowlist、用 bpf-lsm 封鎖其餘訪問。AF_ALG 在多數組織的 fleet 上 caller 數是 0 或 1——這個盤點本身就足以證明一刀切是高 ROI 緩解。
commit 與 distro package 之間的時間窗才是 bpf-lsm 真正的價值:embedded、電信、金融、汽車這類 long-life infrastructure 的 kernel 升級週期以季度或年計,「等 distro 補丁」不是策略;Kubernetes node 升級需要 drain + cordon + 滾動替換。bpf-lsm 不需要 kernel 升級也不需要 service restart,可在所有 node 上熱部署,把這個工程動作從幾天壓縮到幾分鐘。
對寫應用程式的工程師可遷移的啟示有三條。第一,user-controlled length 的審計責任:任何外部輸入裡的「長度/索引/偏移欄位」都應該在進入內部 hot path 之前被 normalize 成已知上界。HTTP 的 Content-Length、gRPC 的 message size、Postgres 的 query length、Redis 的 multi-bulk length——這些都應該在解析層就被夾,不要放在業務邏輯層;業務邏輯層的程式碼路徑會隨著功能增長而變散,每加一個新功能就多一個忘記檢查長度的機會。
第二,把這個原則制度化成 trusted-length 型別(Rust BoundedLen<N>、TypeScript branded type、Go wrapper struct),所有外部 length 欄位先驗證成這個型別才能流入內部——就是 "Parse, don't validate",型別系統承擔責任、新加的 caller 自動繼承保證。
第三,fast path 的設計責任:任何 fast path(資料庫 zero-copy、io_uring、OffscreenCanvas)都應在 commit message 明確聲明跳過了哪些檢查、依賴呼叫者保證哪些 invariant——contract 寫不出來的 fast path 就不應合進主線。
so what changes
對 kernel maintainer:
每次有新 fast path / in-place 優化 / zero-copy 路徑合進主線,
commit message 必須明確聲明它跳過了哪些安全檢查、依賴呼叫者保證哪些 invariant;
review checklist 自動 grep fast/inplace/zero-copy/no_check 關鍵字並要求第二位 reviewer ack;
任何新的 cipher wrapper / ioctl 表面 / scatterlist 操作都觸發 KASAN selftest 的補完要求;
每次合進類似 in-place 優化的 patch 都要寫一條 selftest 證明邊界檢查在新 path 上仍生效。
對 distro security team:
每次有同類 CVE 落地,48 小時內跑全核心的結構化 audit——
git grep 找出所有同樣形態的 caller,
發給 maintainer 確認是否有相同缺口;
不要等下一個 CVE 自然冒出來。
這個 audit 應該寫成 CodeQL 或 Semgrep query,可被重複跑、可被開源、可被其它 distro 重用——
每個 distro 重新發明這個 query 是浪費。
對營運工程師:
bpf-lsm + prometheus-ebpf-exporter 是 2026 年的標準工具鏈,
事先把核心子系統(AF_ALG、userfaultfd、io_uring、bpf())的 allowlist 程式準備好,
事先量測 fleet 上的合法 caller;
CVE 落地當天執行而不是當天才開始寫。
這個準備工作的時機是「平日」,不是「危機時」——
把它寫進季度工程目標。
Take-away:CVE-2026-31431 修補的是一條 path,但暴露的是一個 class——「使用者控制長度未夾上界」這個家族每隔兩三年就會在新的 fast path 上重新出現;單一補丁是巡邏的勝利,型別系統與 commit-time 契約才是把巡邏升級為圍牆的長線投資;bpf-lsm 是 2026 年最有效的「未修補窗口」緩解工具,但價值在於 CVE 之前的量測準備,不是 CVE 之後的緊急寫程式。