1. 簡介

Kernel LPE(local privilege escalation) 研究歷史上大致集中在兩類:確定性的邏輯錯誤,以及依賴於 分配器梳理 (allocator grooming) 的機率性記憶體損壞錯誤。Linux 核心 Open vSwitch (OVS) 資料路徑中最近被記錄的一個漏洞,追蹤編號為 CVE-2026-64531,其特別之處在於它位於這兩類之間:它是一個真實的記憶體安全缺陷 — 一個未經檢查的 16 位元整數截斷 — 但其攻擊路徑幾乎完全是確定性的,完全不需要任何堆積梳理(heap grooming) [1] 。此報告分析了溢位的結構性成因,逐步說明了定義漏洞與修正邏輯的五個程式碼片段,並將此錯誤定位於更廣泛的長度欄位與整數溢位 LPE 漏洞家族中。

你也忽略了 Netlink 的 16 位元極限?OVS 整數溢位揭露 LPE 新路徑! | 資訊安全新聞

2. Netlink 屬性編碼與長度欄位限制

OVS 透過 Generic Netlink 將其流程編程介面暴露給使用者空間。流程「動作」(actions) — 例如轉發封包、重寫隧道標頭(Tunnel header)或呼叫連線追蹤 (conntrack) — 會由核心驗證並重新編碼為內部更密集的表示法,之後才被儲存並於後續重播。此內部表示法重用了標準 Netlink 屬性記錄:

  1. struct nlattr {
  2. __u16 nla_len;
  3. __u16 nla_type;
  4. };

此結構的關鍵屬性是 nla_len 是一個 16 位元欄位,將任何單一屬性 — 包含「巢狀」屬性及其遞迴包含的所有內容 — 限制在 65,535 位元組內。然而,一個流程的 生成動作串流在整體上被允許超過 64 KiB,因為 2025 年的一項變更移除了先前對總串流長度的 32 KiB 上限,該上限在內部重新編碼期間因動作被擴展或縮小而導致了虛假的拒絕 [1] 。該變更本身是合理的,但它悄然移除了唯一一個使個別巢狀屬性遠離 65,536 位元組環繞點的機制。

3. 有漏洞的長度指派

當 OVS 完成生成一個巢狀動作 — 例如一個包含許多子動作的 CLONE 動作 — 它會將最終的封閉總長度回填到巢狀屬性的標頭中。在修復之前,此回填動作沒有執行任何範圍檢查:

  1. static inline void add_nested_action_end(struct sw_flow_actions *sfa, int st_offset)
  2. {
  3. struct nlattr *a =
  4. (struct nlattr *)((unsigned char *)sfa->actions + st_offset);
  5. a->nla_len = sfa->actions_len - st_offset;
  6. }

右側運算式的計算是以一般整數減法進行,且可能合理地超過 65,535,但它被指派給一個 16 位元欄位。此指派會對 65,536 進行無聲的模數截斷:一個真實大小為 65,612 位元組的巢狀屬性,會被記錄為長度僅有 76 位元組。這個單一的未檢查窄化轉換即是整個根源。這個不安全的模式本身早在修補之前十多年就已存在;它只是在周圍的 32 KiB 上限被移除後才出現 [1]

4. 修正性的邊界檢查

上游修復保留了對總動作串流大於 64 KiB 的支援,但現在會拒絕任何單一生成的巢狀屬性無法放入其 16 位元長度欄位的情況,並會釋放任何已為該過大屬性分配的資源(例如 tunnel destination 或巢狀 conntrack 狀態):

  1. static inline int add_nested_action_end(struct sw_flow_actions *sfa, int st_offset)
  2. {
  3. struct nlattr *a;
  4. u32 attr_len;
  5. attr_len = sfa->actions_len - (u32)st_offset;
  6. if (attr_len > U16_MAX)
  7. return -EMSGSIZE;
  8. a = (struct nlattr *)((u8 *)sfa->actions + st_offset);
  9. a->nla_len = attr_len;
  10. return 0;
  11. }

這個變更本身是一個單行的邊界檢查;實際修補程式中周圍的複雜性來自於正確地將新的錯誤回傳值傳播到每個遞迴動作建構器中,以避免部分建構的狀態洩漏。

5. 從截斷的長度到攻擊者控制的解析

環繞之所以值得注意,是因為後續消耗動作串流的消費者會無條件地信任儲存的長度來處理它:

  1. nla_for_each_attr(a, actions, actions_len, rem) {
  2. switch (nla_type(a)) {
  3. ...
  4. }
  5. }

每個迴圈在處理每個屬性後會前進恰好 nla_len 個位元組。如果一個 CLONE 屬性的真實範圍是 65,612 位元組,但其記錄長度為 76,則解析器會在僅 76 位元組後就停止處理真實的 CLONE ,並將後續的位元組 — 這些位元組實際上仍在 CLONE 自己的 payload 內部 — 視為一個獨立的後續動作。因為 conntrack 動作在轉換為其內部形式時會擴展約 32 倍(幾個位元組會變成 x86-64 架構上 164 位元組的 ovs_conntrack_info 結構),數百個小型 conntrack 動作就足以輕易地將封閉屬性推過 65,536 位元組邊界,而不需要大量的不同動作類型。擴展後的 conntrack 資料部分 — 連線標籤和逾時名稱欄位 — 是直接受攻擊者控制的,因此,解析器在 wrap 之後指向的位元組,可以被塑造成一個偽造的、格式正確的假動作標頭。因為落點偏移是精心製作的輸入(而非 race 或 allocator layout)的確定性函數,對於給定的核心建構版本,相同的偽造動作每次執行都會出現在相同的偏移量上 [1]

sequenceDiagram participant U as Unprivileged process (CAP_NET_ADMIN in userns) participant K as OVS kernel datapath U->>K: Install flow: CLONE{EXEC + 400x small CT actions} K->>K: Expand CT actions to ovs_conntrack_info (164B each) K->>K: add_nested_action_end() computes len=65612, truncates nla_len=76 U->>K: Request flow dump K->>U: nla_for_each_attr walks 76 bytes, resumes inside CT#0 payload K->>U: Parses attacker-crafted fake OUTPUT(len=512) spanning real CT pointers U->>U: Recovers leaked FTP-helper pointer -> module base -> kernel base (KASLR defeated) U->>K: Delete flow containing forged tunnel SET (fake tun_dst) K->>K: dst_release(fake tun_dst) decrements attacker-chosen kernel word U->>K: Repeat deletion to decrement task credential (fsuid/fsgid) to 0 K->>U: Victim process now holds root-equivalent credentials

6. 從讀取操作到認證遞減

第二個偽造的動作 — 一個假的 tunnel SET — 從同一個 wrap 同時提供了讀取和寫入操作(Primitive)。當 OVS 將流程的 tunnel metadata 序列化回使用者空間時,它會解參考 (dereference) 攻擊者選擇的 tun_dst 指標,並複製出固定偏移處的欄位;透過選擇 tun_dst = 目標位址 − 欄位偏移量 ,可以一次一個欄位地讀取任意核心位元組。同一個偽造動作在流程被刪除時會兼作受限的寫入,因為拆卸 (teardown) 過程會釋放偽造的 tunnel 目的地:

  1. dst_release((struct dst_entry *)ovs_tun->tun_dst);

dst_release() 會遞減一個位於其所給定指標的固定偏移處的參考計數 (reference-count) 字詞。透過選擇 tun_dst 使其表面上的 refcount 欄位重疊到任意目標字詞上 — 例如受害任務的 fsuid — 每次流程刪除都會將該字詞減一。這不是一個任意寫入,但重複刪除足以將 UID 或能力字詞降低到零,之後被破壞的程序就可以寫入 /etc/sudoers 並在主機上獲得 root 權限 [1]

7. 長度欄位與溢位類 LPE 錯誤的比較視角

無號窄化轉換(unsigned narrowing conversion)導致確定性的截斷後解析的模式並非此資料路徑所獨有。另一個不相關的核心 Netlink 程式碼路徑大約在同一時期被獨立修補,以拒絕過大的 payload 參數,防止它們在計算分配大小時被靜默截斷,這說明了「攻擊者影響的長度增長超過固定寬度欄位」此一類別的問題,會在不相關的 Netlink 周邊子系統中重複出現 [2] 。在核心之外,同樣的溢位轉損壞模式也曾被記錄在使用者空間的直譯器中,其中一個由攻擊者提供的起始/結束索引計算出的 unpack() 長度可能溢位並損壞直譯器堆疊,如同 Lua 腳本引擎中整數溢位漏洞的技術分析 所述。更廣泛地說,此漏洞與其他近期單一子系統的核心錯誤屬於同一個 LPE 系譜,例如一個 檔案系統驅動程式中的越界寫入 ,它同樣將狹隘的子系統本地記憶體安全錯誤轉化為完整的 root 權限掌控,並且它與邏輯層級的 LPE 鏈共享其「不需梳理」的確定性,例如在特權工具中 chroot 範圍內的名稱服務切換 (name-service-switch) 濫用 。OVS 案例的特殊之處在於,確定性的偏移量並非來自競爭視窗,而是來自每個核心建構的靜態符號與結構佈局資料,這些資料可以從公開的符號表中獲取一次並無限期地重複使用。

8. 結論

CVE-2026-64531 證明了從線路協定(wire protocol)繼承而來的固定寬度長度欄位,每當內部的重新編碼步驟允許攻擊者影響的資料增長超過該欄位的容量且缺乏對應的邊界檢查時,就會持續成為核心記憶體安全錯誤的來源。技術上的補救措施 — 在進行窄化指派之前加上一個明確的 U16_MAX 比較,以及為已分配的巢狀狀態加入失敗時的謹慎清理邏輯 — 雖然很少,但它的缺失長達十多年,這凸顯了審計每一個將較寬長度值寫回協定強制窄欄位之程式碼點 (code point) 的價值,特別是在可從無權限網路或使用者命名空間觸及的路徑中。