摘要

報告針對 Dirty Frag 漏洞提供深入的技術分析,這是一類影響 Linux 發行版的權限提升漏洞。Dirty Frag 利用兩個不同的 page-cache 寫入漏洞串連而成:xfrm-ESP Page-Cache 寫入 與 RxRPC Page-Cache 寫入。這些漏洞讓無特權的使用者可以透過確定性地修改敏感檔案(如 /etc/passwd /usr/bin/su )的 page-cache 表項,來取得 root 權限。報告詳細說明了根本原因、攻擊機制以及緩解策略,並與 Dirty Pipe、Copy Fail 等類似漏洞進行對照。特別關注的核心程式碼路徑解釋了這些攻擊如何被實現,以及後續的修補程式如何解決它們。

Dirty Frag 利用 ESP 處理路徑繞過 COW機制,污染 page cache 提權 | 資訊安全新聞

1. 簡介

Linux kernel 雖然具備強健的安全機制,但有時仍會潛藏導致嚴重安全事件的漏洞。Dirty Frag 漏洞正是此類關鍵缺陷的代表,它在多種 Linux 發行版上造成本機權限提升 (LPE)。該漏洞由 Hyunwoo Kim (@v4bel) [1] 發現並回報,其特別棘手之處在於能繞過針對類似漏洞的既有緩解措施,因而讓廣泛的系統暴露於風險中。這份報告目的在剖析 Dirty Frag 的技術細節,聚焦於其底層機制、攻擊鏈,以及 kernel 層級用以反制此漏洞的修補程式。

2. 背景:理解 Page-Cache 寫入漏洞

Dirty Frag 與其他著名 Linux kernel 漏洞(即 Dirty Pipe (CVE-2022-0847) 和 Copy Fail (CVE-2026-31431))在概念上有相似之處。Dirty Pipe 利用 pipe buffer 機制的缺陷來覆寫任意檔案,而 Copy Fail 則針對 struct pipe_buffer ,但 Dirty Frag 特別操縱了 struct sk_buff 中的 frag [1] 。這些漏洞都源於 kernel 的 zero-copy 發送路徑(通常涉及 splice() )在無意中讓攻擊者取得對應為唯讀的 page-cache pages 的寫入存取權。

在 Copy Fail 的情境中,攻擊者使用 splice(file -> pipe -> AF_ALG_fd) 將攻擊者控制的 page-cache page 注入傳輸的 scatter-gather 清單 (TX SGL)。在 recv() 操作期間,作為位元組重排的一部分,會發生一個 scratch 寫入。通常,這個寫入會針對 skb 的 tag 區域,不會構成威脅。然而,當攻擊者固定的 page 剛好位於此位置時,完整性假設就會被違反,導致對 page-cache 進行任意的 4 位元組寫入 [1] [2]

Dirty Frag 擴展了此模式,在來自 splice() 的非線性 skb frag 上重現了相同的漏洞。這類漏洞特別危險,因為修改發生在 RAM 中,影響檔案在記憶體中的表示方式,而不會改變其在磁碟上的對應內容。這意味著後續讀取受影響的檔案將會反映攻擊者的修改,即使磁碟本身保持不變 [1] [2]

3. Dirty Frag 的技術分析

Dirty Frag 是一個複合式漏洞,串連了兩種不同的 page-cache 寫入缺陷:xfrm-ESP Page-Cache 寫入 與 RxRPC Page-Cache 寫入。兩者都利用了 kernel 在處理網路封包緩衝區 ( sk_buff ) 及其相關片段 ( frag ) 於加密操作時的行為。

3.1. CVE-2026-43284:xfrm-ESP Page-Cache 寫入

xfrm-ESP Page-Cache 寫入漏洞發生在 esp_input() 函式中,該函式負責處理 IPsec Encapsulating Security Payload (ESP) 封包。核心問題在於對 skb_cow_data() 的呼叫被繞過。理想情況下,在對非線性 skb 執行 in-place AEAD 解密之前, esp_input() 應該配置一個新的 kernel 私有緩衝區並複製 fragment 資料,以防止對共享的 page-cache pages 進行非預期的修改。然而, esp_input() 中的特定分支允許這個關鍵的 copy-on-write (COW) 機制被略過 [1]

請參考以下來自 esp_input() [1] 的簡化程式路徑:

  1. static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
  2. {
  3. [...] // Other code
  4. if (!skb_cloned(skb)) {
  5. if (!skb_is_nonlinear(skb)) { // <=[1]
  6. nfrags = 1;
  7. goto skip_cow;
  8. } else if (!skb_has_frag_list(skb)) {
  9. nfrags = skb_shinfo(skb)->nr_frags;
  10. nfrags++;
  11. goto skip_cow; // &lt;=[2]
  12. }
  13. }
  14. err = skb_cow_data(skb, 0, &trailer);
  15. [...] // Other code
  16. }

在標籤 [1] 處,如果 skb 不是非線性,或者在標籤 [2] 處,如果它是非線性但缺少 frag_list ,程式碼會直接跳到 skip_cow 。這會繞過 skb_cow_data() 的呼叫,這代表如果攻擊者先前已透過 splice() 將 page-cache page 固定到 frag 中,那麼該 page 就會成為後續 in-place 加密操作的來源與目的地 [1]

實際的寫入發生在 crypto_authenc_esn_decrypt() 函式中,具體來說是一個 4 位元組的儲存操作。這個函式用於帶有 Extended Sequence Numbers (ESN) 的 ESP,它執行一個前處理步驟,將序號的高位 4 個位元組移動到來源 SGL 的末端。有漏洞的部分如下所示 [1]

static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
        [...] // Other code

        /* Move high-order bits of sequence number to the end. */
        scatterwalk_map_and_copy(tmp, src, 0, 8, 0);
        if (src == dst) {
                scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);
                scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);   // <=[3]
                dst = scatterwalk_ffwd(areq_ctx->dst, dst, 4);
        [...] // Other code
}

在標籤 [3] 處的 4 位元組儲存發生在目的地 SGL 的 assoclen + cryptlen 位置。透過精心建構 payload 長度,攻擊者可以確保先前經由 splice() 植入的 page (P) 正好佔據這個位置。寫入的值是來自 ESP 標頭序號的高 32 個位元,使用者可以在 SA 註冊期間透過 XFRMA_REPLAY_ESN_VAL netlink 屬性控制該值 [1]

關鍵的是,這個寫入發生在 AEAD 身份驗證驗證之前。即使身份驗證失敗,page-cache 的修改仍然會持續存在,這讓攻擊者可以在不知道 SA 驗證金鑰的情況下修改敏感檔案。雖然註冊 XFRM SA 需要 CAP_NET_ADMIN 權限,但可以透過使用 unshare(CLONE_NEWUSER | CLONE_NEWNET) 將子程序隔離在一個新的 user/net namespace 中來達成此權限 [1]

3.2. RxRPC Page-Cache 寫入

Dirty Frag 的第二個部分涉及 RxRPC Page-Cache 寫入漏洞。在此情境中, rxkad_verify_packet_1() 函式使用 pcbc(fcrypt) 直接在 fragment 之上執行 in-place 單區塊解密。與 xfrm-ESP 漏洞類似,這個操作會修改無特權使用者具有讀取存取權的 page-cache page,導致持續性的記憶體內變更 [1]

4. 攻擊流程與影響

Dirty Frag 的攻擊程式鎖定 /usr/bin/su 等敏感檔案以取得 root 權限。攻擊者會將 /usr/bin/su 的 page-cache 前 192 個位元組替換為一個靜態的 root-shell ELF。這個 ELF 被設計成在執行時會執行 setgid(0); setuid(0); setgroups(0,NULL); execve("/bin/sh", NULL, ["TERM=xterm",NULL]) ,從而繞過 PAM 並獲得 root shell [1]

這 192 位元組的 ELF 被分成 48 個 4 位元組的區塊寫入,利用了 ESP 變體中任意的 4 位元組 STORE 能力。攻擊步驟如下 [1]

  1. Namespace 隔離: 使用 unshare(CLONE_NEWUSER | CLONE_NEWNET) 在新的 user/net namespace 中取得 CAP_NET_ADMIN 權限。
  2. XFRM SA 註冊: 註冊 48 個 XFRM SA,每個都具有唯一的 SPI,並將 4 位元組的 shellcode 區塊嵌入到 XFRMA_REPLAY_ESN_VAL.seq_hi 中。
  3. 觸發寫入: 對於每個區塊,會建立一對 sk_recv sk_send socket。一個偽造的 ESP 線路標頭被 vmsplice 到一個 pipe 中,接著是從目標檔案(例如 /usr/bin/su )偏移量 i*4 處讀取的 16 個位元組。然後將這個 pipe 內容 splice 到 sk_send ,確保 /usr/bin/su 的 page-cache page 被植入到發送端 skb frag[0] 中。

接收端透過 udp_rcv -> xfrm4_udp_encap_rcv -> xfrm_input -> esp_input 處理 skb 。由於 skb_cow_data() 被繞過,page-cache page 被保留下來,而 crypto_authenc_esn_decrypt() 會執行 4 位元組的儲存,從而在精確的偏移量修改 page-cache。這個過程是確定性的,不需要鎖步 (lock-stepping)。一旦修改完成,page-cache 會一直持續到系統重新啟動或明確清除快取為止。當父程序執行 /usr/bin/su 時,修改後的 ELF 會被對應到記憶體中,導致 root 權限提升 [1]

graph TD A[Unprivileged User] --> B{"unshare(CLONE_NEWUSER | CLONE_NEWNET)"} B --> C[Gain CAP_NET_ADMIN in User Namespace] C --> D[Register XFRM SAs with Shellcode Chunks] D --> E[Craft ESP Packet with Splice] E --> F["Kernel Network Stack (udp_rcv -> xfrm_input -> esp_input)"] F --> G{"skb_cow_data() Bypass"} G --> H["crypto_authenc_esn_decrypt() In-place Write"] H --> I["Modify /usr/bin/su Page Cache (4-byte chunks)"] I --> J[Execute /usr/bin/su] J --> K[Root Shell]

圖 1:Dirty Frag (xfrm-ESP 變體) 的簡化攻擊流程

5. 緩解措施與修補程式分析

針對 Dirty Frag 的緩解措施主要集中在防止 page-cache pages(尤其是那些透過 splice() 固定的頁面)被直接用作 in-place 加密操作中的可寫入目的地。修補程式引入了 SKBFL_SHARED_FRAG 標記。這個標記會設定在那些源自 IPv4/IPv6 datagram 附加路徑中 splice() 的 page fragments 上 [1]

esp_input() 函式(及其 IPv6 對應函式 esp6_input() )中的關鍵變更是增加對這個新標記的檢查。修改後的程式碼確保如果一個 skb 具有共享的 fragment(即 skb_has_shared_frag(skb) 回傳 true),則無論其他條件如何,它都會被強制路由到 skb_cow_data() 路徑。這會強制將資料複製到一個私有緩衝區,然後再執行任何 in-place 操作,從而防止對原始 page-cache 的修改 [1]

以下來自 net/ipv4/esp4.c 的修補程式片段說明了這一點 [1]

diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c
index 6dfc0bcde..6a5febbdb 100644
--- a/net/ipv4/esp4.c
+++ b/net/ipv4/esp4.c
@@ -873,7 +873,8 @@ static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
 			nfrags = 1;

 			goto skip_cow;
-		} else if (!skb_has_frag_list(skb)) {
+		} else if (!skb_has_frag_list(skb) &&
+			   !skb_has_shared_frag(skb)) {
 			nfrags = skb_shinfo(skb)->nr_frags;
 			nfrags++;

在條件判斷式中加入 && !skb_has_shared_frag(skb) 確保如果 skb 包含共享的 fragment,則不再走 skip_cow 路徑。這有效地關閉了漏洞,強制對可能共享的 page-cache pages 實施 copy-on-write 行為 [1]

類似地, net/ipv4/ip_output.c (和 net/ipv6/ip6_output.c )中的修改確保當資料透過 splice() 附加時,特別是在未指定 MSG_NO_SHARED_FRAGS 的情況下,能正確設定 SKBFL_SHARED_FRAG 標記 [1]

diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
index e4790cc7b..5bcd73cbd 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -1233,6 +1233,8 @@ static int __ip_append_data(struct sock *sk,
 			if (err < 0)
 				goto error;
 			copy = err;
+			if (!(flags & MSG_NO_SHARED_FRAGS))
+				skb_shinfo(skb)->flags |= SKBFL_SHARED_FRAG;
 			wmem_alloc_delta += copy;
 		} else if (!zc) {
 			int i = skb_shinfo(skb)->nr_frags;

這種全面的方法確保任何可能被攻擊者透過 splice() 操縱的 page-cache page 在加密操作之前被正確隔離,從而防止構成 Dirty Frag 漏洞的 in-place 寫入。

6. 結論

Dirty Frag 漏洞凸顯了保護複雜 kernel 子系統(尤其是涉及 zero-copy 資料路徑和加密操作的子系統)所面臨的持續挑戰。透過串連兩種不同的 page-cache 寫入漏洞(xfrm-ESP 與 RxRPC),Dirty Frag 展示了一種強效的本機權限提升方法。對其根本原因(被繞過的 copy-on-write 機制以及在 in-place 解密期間未預期的 scratch 寫入)的詳細分析,強調了在 kernel 內部進行細緻記憶體管理和資料流完整性檢查的重要性。後續的修補程式引入了 SKBFL_SHARED_FRAG 標記並強制執行適當的 copy-on-write 行為,成為防禦此類攻擊的關鍵防線,從而強化了 Linux 系統的整體安全態勢。