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报告查看器, 云计算安全, 信息网络安全, 内存损坏, 内核安全, 内核提权, 协议分析, 子域名突变, 安全渗透, 容器逃逸, 本地提权, 权限提升, 跨平台攻击, 逆向工具, 零日漏洞, 页面缓存污染