CVE-2026-31431(Copy Fail)漏洞FAQ(下)——漏洞技术机理、历史坐标与战略启示篇
时间 : 2026年05月01日
2026年4月29日,韩国安全公司Theori及其AI安全研究平台Xint公开披露了Linux内核本地权限提升漏洞CVE-2026-31431,代号为"Copy Fail"。安天CERT正在密切跟踪和响应该漏洞。
该漏洞存在于Linux内核加密子系统中,影响范围覆盖2017年至今几乎所有主流Linux发行版。攻击者仅需本地普通用户权限,利用一个不足800字节的Python脚本,即可在无竞争条件下稳定获取root权限,该漏洞通过污染page cache中的setuid程序实现执行劫持,并在特定环境配置下具备容器逃逸潜力。
安天CERT编制本FAQ旨在为不同受众提供精准、可操作的威胁认知与处置指引。
■上篇:主要面向公众、网络管理者、网络用户、行业主管部门及企业IT决策者。侧重漏洞的基本认知、影响范围判断、资产排查、修复缓解措施与威胁态势评估,行文力求通俗准确,可直接用于内部通报与管理汇报作为参考资料。
■下篇(本文):面向安全研究者、分析工程师、内核开发者及高级蓝队人员。侧重漏洞的底层技术机理、利用链拆解、与历史漏洞的对比分析,以及对Linux内核安全审计机制的战略反思,技术深度较高。
两篇内容互补,引用来源一致,均基于公开披露信息、oss-security邮件列表、上游内核补丁及社区实测反馈进行交叉验证。
一、漏洞技术机理与根因
Q1:该漏洞涉及Linux内核的哪些具体子系统或机制(AF_ALG、splice、页缓存等)?
A:CVE-2026-31431是一个典型的跨子系统emergent vulnerability,涉及五个独立内核子系统的异常耦合:

图注:CVE-2026-31431(Copy Fail)技术架构图
跨子系统耦合型emergent vulnerability。红色箭头表示攻击路径,从用户态 Python PoC 经系统调用层(socket/splice/sendmsg)穿透至内核子系统,最终通过 algif_aead 的 in-place 优化缺陷向页缓存写入 payload,实现 root 提权。
各子系统角色:
| 子系统 | 核心机制 | 在漏洞中的角色 |
|---|---|---|
| AF_ALG | 用户态加密接口(socket地址族) | 向非特权用户暴露内核scatterlist操作能力,提供从用户态到内核加密子系统的合法路径 |
| algif_aead | AEAD算法接口模块 | 2017年引入的in-place优化导致req->src == req->dst,破坏了源/目标缓冲区隔离 |
| authencesn | AEAD算法实现 | 执行AEAD解密时,向destination scatterlist写入AAD的seqno_lo字段(4字节) |
| splice() | 零拷贝数据传输 | 将页缓存页面的引用(而非副本)直接传入AF_ALG socket,保持页面的"页缓存身份" |
| Page Cache | 文件页缓存 | 被错误地放入可写scatterlist,成为攻击者的写入目标 |
Q2:AF_ALG加密接口在该漏洞利用链条中扮演何种角色?
A:AF_ALG是漏洞利用的入口点(Entry Point)和能力放大器(Capability Amplifier)。
AF_ALG基础:
- AF_ALG(Address Family ALGorithm)是Linux内核于2011年引入的套接字地址族,允许用户态程序通过标准BSD socket API调用内核crypto API。
- 支持的算法类型:skcipher(对称加密)、aead(认证加密)、hash(哈希)、rng(随机数)。
- 无需root权限即可创建AF_ALG套接字(只要内核启用了CONFIG_CRYPTO_USER_API_AEAD)。
在漏洞中的三重角色:
- 能力暴露(Capability Exposure):向普通用户态程序开放了访问内核scatterlist机制的能力。正常情况下,用户态无法直接操作页缓存的物理页面。
- 边界突破(Boundary Crossing):提供了从用户态到内核scatterlist的合法路径。攻击者无需内核模块、无需/dev/mem、无需/proc/kcore,仅通过标准socket API即可触达内核内部数据结构。
- 算法触发(Algorithm Trigger):通过绑定authencesn(hmac(sha256),cbc(aes))算法,algif_aead执行解密时会向目标scatterlist写入数据,从而将"只读页缓存页"转化为"可写攻击面"。
关键代码路径:
// 用户态创建AF_ALG socket
int sock = socket(AF_ALG, SOCK_SEQPACKET, 0);
// 绑定AEAD算法
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "aead",
.salg_name = "authencesn(hmac(sha256),cbc(aes))"
};
bind(sock, (struct sockaddr *)&sa, sizeof(sa));
// 通过splice()将文件页传入socket
splice(fd_in, NULL, sock, NULL, size, SPLICE_F_MOVE);
Q3:splice()系统调用为何成为触发该漏洞的关键路径?
A:splice()是连接页缓存与AF_ALG socket的关键桥梁,其"零拷贝"特性是漏洞触发的必要条件。
splice()机制:
- splice()是Linux 2.6.17引入的零拷贝数据传输系统调用,用于在两个文件描述符之间移动数据,无需经过用户态缓冲区。
- 当fd_in是普通文件时,splice()直接传递其页缓存页面(page cache pages)的引用,而非复制数据内容。
- 页缓存页面通常是只读的(引用计数高,映射为只读)。
触发漏洞的关键差异:
| 数据传输方式 | 是否经过页缓存 | 页缓存页是否被传入AF_ALG | 是否触发漏洞 |
|---|---|---|---|
| read() + write() | 是 | ❌ 否(数据被复制到用户态缓冲区) | ❌ 否 |
| sendfile() | 是 | ❌ 否(通常用于网络socket) | ❌ 否 |
| splice() | 是 | ✅ 是(直接传递页缓存页引用) | ✅ 是 |
| mmap() + 直接写入 | 是 | ❌ 否(用户态无法直接写入页缓存) | ❌ 否 |
为什么是splice()而非其他方式?
- read()会将页缓存数据复制到用户态缓冲区,用户态再write()到socket时,数据已进入新的匿名内存页,不再是页缓存页。
- splice()不复制数据,直接传递页缓存页面的引用,保持了页面的"页缓存身份"(struct page的mapping字段指向address_space)。
- 只有splice()能够将"属于页缓存的只读页面"直接送入AF_ALG的处理路径,使得algif_aead错误地将页缓存页当作可写目标。
Q4:攻击者如何利用该漏洞向"只读"或"受信任"文件的页缓存(page cache)写入数据?
A:攻击过程分为四个阶段,形成一个完整的利用链:
阶段一:目标选择
攻击者选择系统上任意可读的setuid二进制文件(如/usr/bin/su)。该文件的页缓存已存在于内存中(系统启动后首次执行时加载)。
阶段二:AF_ALG请求构造
# 创建AF_ALG socket
sock = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)
# 绑定authencesn算法
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# 设置AAD认证标签长度(16字节)
sock.setsockopt(socket.SOL_ALG, socket.ALG_SET_AEAD_AUTHSIZE, 16)
阶段三:splice页缓存页
# 打开目标文件(只读)
target_fd = os.open("/usr/bin/su", os.O_RDONLY)
# 通过splice将页缓存页传入AF_ALG socket
# 关键:splice传递的是页缓存页的引用,而非数据副本
os.splice(target_fd, None, sock.fileno(), None, 4096, os.SPLICE_F_MOVE)
阶段四:触发写入
# 发送消息触发AEAD解密
# 内核执行authencesn解密时,向dst scatterlist(即页缓存页)写入AAD的seqno_lo
sock.sendmsg_afalg(op=socket.ALG_OP_DECRYPT, iv=b"x"*16, assoclen=16)
写入的数据特征:
- 写入位置:目标文件页缓存的特定偏移处(AAD结构中的seqno_lo字段位置)
- 写入内容:4字节受控数据(AAD结构中的seqno_lo字段,即bytes 4–7)
- 写入次数:可重复执行,逐步覆盖更多字节,实现任意代码注入
Q5:为何篡改页缓存中的setuid二进制文件能够实现从普通用户到root的权限跨越?
A:利用的是Linux setuid机制与页缓存执行的特性组合。
setuid机制:
- setuid(Set User ID)是Unix/Linux的文件权限机制,允许普通用户以文件所有者的权限执行程序。
- /usr/bin/su、/usr/bin/sudo、/usr/bin/passwd等文件通常属于root,且设置了setuid位(权限4755)。
- 普通用户执行这些程序时,进程的有效UID(euid)临时提升为0(root)。
攻击逻辑链:
- 攻击者篡改/usr/bin/su的页缓存副本 → 在su的代码路径中注入恶意指令
- 攻击者执行/usr/bin/su → 内核加载/usr/bin/su时,优先使用内存中的页缓存副本 → 磁盘文件未被修改,因此文件权限、校验和均正常
- su在setuid机制下以root权限(euid=0)运行 → 触发攻击者植入的payload → 执行攻击者控制路径(如execve("/bin/sh"))
为何页缓存篡改有效:
- 内核在执行文件时,首先检查页缓存中是否已有该文件的页面(通过inode->i_mapping的address_space查找)。
- 如果页缓存中存在(且未被回收),内核直接使用页缓存中的副本,不会重新从磁盘读取。
- 攻击者修改的是页缓存中的副本,磁盘上的原始文件保持不变。
- 因此ls -l、sha256sum、AIDE、Tripwire等工具检查磁盘文件时显示完全正常。
- 由于修改的是页缓存而非磁盘,不触发inotify/fanotify,不改变磁盘文件校验和,传统完整性监控完全失效。
Q6:该漏洞利用是否依赖竞态条件(race condition),其稳定性与Dirty COW等历史漏洞相比如何?
A:不依赖竞态条件,稳定性远超Dirty COW。
历史漏洞稳定性对比
| 漏洞 | 竞争条件 | 成功概率 | 利用复杂度 | PoC体积 | 跨版本适配 |
|---|---|---|---|---|---|
| CVE-2026-31431 (Copy Fail) | ❌ 无 | 单发成功率极高 | 极简 | 732字节Python | ✅ 无需偏移 |
| CVE-2022-0847 (Dirty Pipe) | ❌ 无 | 单发成功率极高 | 极简 | ~400字节C | ⚠️ 需特定版本 |
| CVE-2016-5195 (Dirty COW) | ✅ 需要 | 需多次尝试 | 中等 | 较复杂 | ⚠️ 需多次尝试 |
| CVE-2021-4034 (PwnKit) | ❌ 无 | 单发成功率极高 | 极简 | ~200字节C | ✅ 通用 |
Copy Fail的稳定性来源
- 确定性执行路径:socket() → bind() → splice() → sendmsg()是顺序执行的系统调用,无并发操作、无时序依赖。
- 无内核状态竞争:不依赖内核内存布局、不依赖特定时序、不依赖CPU核心数、不依赖系统负载。
- 跨版本通用:不需要针对不同内核版本调整偏移地址或ROP gadget,同一PoC可在所有受影响版本上运行。
- 纯Python实现:732字节的Python脚本即可完成利用,无需编译C代码,无需内核模块,无需特定编译器。
- VFS绕过:写入路径完全绕过VFS层,不触发任何文件系统访问控制钩子(DAC/MAC均无效)。
二、历史坐标与战略启示
Q7:Copy Fail与Dirty COW(CVE-2016-5195)、Dirty Pipe(CVE-2022-0847)在触发路径、利用门槛和稳定性上有何异同?
A:
综合对比表
| 对比维度 | CVE-2026-31431 (Copy Fail) | CVE-2022-0847 (Dirty Pipe) | CVE-2016-5195 (Dirty COW) |
|---|---|---|---|
| 发现时间 | 2026年4月 | 2022年2月 | 2016年10月 |
| 潜伏周期 | ~9年(2017–2026) | ~2年(Linux 5.8+) | ~9年(2007–2016) |
| 漏洞类型 | 内核逻辑缺陷 | 内核逻辑缺陷 | 内核竞态条件 |
| 触发路径 | AF_ALG + splice + 页缓存 | Pipe buffer flags 滥用 | COW页竞态条件 |
| 涉及子系统 | 加密子系统、splice、页缓存 | Pipe、页缓存 | MMU、COW、页缓存 |
| 竞争条件 | ❌ 无 | ❌ 无 | ✅ 需要 |
| 利用门槛 | 极低(Python脚本) | 极低(C程序) | 中等 |
| PoC体积 | 732字节Python | ~400字节C | 较复杂 |
| 稳定性 | 单发成功率极高 | 单发成功率极高 | 需多次尝试 |
| 跨版本适配 | ✅ 无需偏移 | ⚠️ 需特定内核版本 | ⚠️ 需多次尝试 |
| 写入目标 | 页缓存(不脏页) | 页缓存(不脏页) | COW私有映射 |
| 磁盘痕迹 | ❌ 无 | ❌ 无 | ⚠️ 可能有 |
| 隐蔽性 | ⭐⭐⭐⭐⭐极高 | ⭐⭐⭐⭐高 | ⭐⭐⭐中等 |
| VFS绕过 | ✅ 是 | ✅ 是 | ❌ 否 |
| 容器逃逸 | ✅ 是(披露方声称) | ❌ 否 | ❌ 否 |
| 利用后持久化 | ❌ 无(重启即消失) | ❌ 无(重启即消失) | ⚠️ 可能持久化 |
| 修复复杂度 | 回退in-place优化 | 限制pipe flags | 修复COW竞态 |
关键差异分析
Copy Fail vs Dirty Pipe:
- 相似性:两者都利用页缓存的"不脏页"特性,修改内存中的文件副本而不触及磁盘,因此均绕过传统文件完整性监控。
- 差异性:Copy Fail的触发路径更"冷门"(AF_ALG加密接口),Dirty Pipe利用的是更通用的pipe机制。Copy Fail额外具备容器逃逸能力(页缓存跨容器共享),而Dirty Pipe仅限于单主机内的页缓存篡改。
Copy Fail vs Dirty COW:
- 相似性:两者都潜伏约9年,都涉及页缓存/内存管理机制,都影响了大量Linux发行版。
- 差异性:Dirty COW需要竞争条件,利用不稳定,成功率依赖运气和系统状态;Copy Fail是确定性漏洞,单发成功率极高。Dirty COW修改的是COW私有映射(可能持久化到磁盘),Copy Fail修改的是全局页缓存(重启即消失,更隐蔽)。
Q8:漏洞源自密码加密安全机制,为什么对应安全机制反过来会成为安全软肋?
A:这是一个关于"安全机制自身成为攻击面"的深刻案例。
密码学安全机制的原始目的:
- AF_ALG的设计初衷是将内核经过FIPS认证的加密实现暴露给用户态,避免用户态库(如OpenSSL)自行实现加密算法可能引入的漏洞。
- algif_aead的in-place优化旨在减少内存复制开销,提升AEAD解密性能约10-20%。
- splice()的零拷贝设计旨在减少数据在内核态与用户态之间的复制,提升I/O性能。
为何成为安全软肋:
- 过度信任内部路径:内核开发者假设"通过AF_ALG传入的数据缓冲区"与"普通文件页缓存"是互斥的。即,AF_ALG处理的是"匿名内存页",而非"文件-backed页缓存页"。但splice()打破了这一假设。
- 性能优化破坏安全不变量:in-place优化的核心假设是"源缓冲区与目标缓冲区是同一内存区域,且该区域是可写的"。但当splice()传入页缓存页时,这一假设失效——页缓存页是"只读的",却被当作"可写的目标缓冲区"。
- 抽象层泄漏:AF_ALG抽象了加密操作,但泄漏了底层scatterlist的实现细节。攻击者不需要理解AEAD算法,只需要理解"向目标scatterlist写入数据"这一副作用。
- 最小权限原则的违背:AF_ALG向所有非特权用户开放了操作内核scatterlist的能力。在微内核或能力系统设计中,此类操作应仅限于特权进程或经过显式授权的进程。
启示:安全机制的设计必须考虑"被误用"的场景。性能优化不应以破坏安全不变量为代价,且任何向用户态暴露内核内部机制(如scatterlist、页缓存引用)的接口,都应经过严格的安全边界分析。
Q9:该漏洞长达九年的潜伏周期对Linux内核代码审计和回归测试机制有何警示意义?
A:
警示一:跨子系统耦合的盲区
Copy Fail涉及五个独立子系统(AF_ALG、algif_aead、splice、authencesn、Page Cache)的交互。传统的代码审计通常由子系统维护者各自负责,难以发现跨子系统的emergent vulnerability。
改进建议:
- 建立跨子系统交互测试矩阵,重点测试"A子系统输出作为B子系统输入"的边缘场景。
- 对splice()这类"万能连接器"系统调用,建立与所有可能目标子系统(字符设备、socket、加密接口、BPF map等)的组合测试。
- 引入"数据流追踪"审计:追踪页缓存页从splice()到AF_ALG到authencesn的完整路径,验证每一环节的访问权限。
警示二:性能优化的安全代价
2017年的in-place优化(req->src == req->dst)从性能角度看是合理的改进,但破坏了"源缓冲区与目标缓冲区隔离"的安全不变量。
改进建议:
- 对涉及"共享缓冲区"、"零拷贝"、"in-place"的性能优化,强制进行安全性评审。
- 引入"不变量检查":在优化后的代码路径中,通过BUG_ON()或WARN_ON()断言src != dst或page_is_writable(dst)。
- 建立"性能优化安全审查清单",要求优化提交者明确说明"该优化破坏了哪些安全假设,以及这些假设在其他代码路径中是否仍然成立"。
警示三:回归测试覆盖不足
内核测试套件(kselftest、LKFT)未能覆盖"splice()传入页缓存页到AF_ALG AEAD解密"这一边缘场景。
改进建议:
- 扩展kselftest,增加splice()与各类字符设备、socket、加密接口的组合测试。
- 引入组合模糊测试(Combinatorial Fuzzing):如Syzkaller应扩展覆盖AF_ALG + splice() + sendmsg()的组合。
- 建立"危险系统调用组合"黑名单,自动生成并执行fuzz测试用例。
警示四:"冷门"子系统的安全债务
AF_ALG自2011年引入后,安全研究关注度远低于网络、文件系统子系统。近15年的代码积累形成了巨大的"安全债务"。
改进建议:
- 对向用户态暴露内核内部机制的接口(如AF_ALG、io_uring、BPF、perf)进行专项安全审计。
- 建立"用户态可触发的内核路径"清单,定期审计。
- 引入"攻击面预算"概念:每个新的用户态-内核态接口必须评估其增加的攻击面,并配套相应的测试和监控。
警示五:AI辅助审计的价值与局限
Copy Fail由Xint的AI辅助代码审计工具在约1小时内从crypto/子系统中发现,表明AI在规模化扫描和模式识别方面具有优势。
改进建议:
- 将AI辅助审计纳入内核安全流程,作为人工审计的前置筛选器。
- 对AI发现的高危候选漏洞进行优先人工复核。
- 但不应过度依赖AI:AI发现的是"候选漏洞",最终的验证、利用开发和修复仍需人类专家完成。
Q10:针对此类由"多机制耦合"引发的emergent vulnerability,未来的漏洞挖掘和防御体系应如何演进?
A:
漏洞挖掘演进方向
| 方向 | 具体措施 | 优先级 |
|---|---|---|
| 组合模糊测试 | 扩展Syzkaller等fuzzer,重点测试系统调用组合(如splice() + socket() + sendmsg()) | 高 |
| 静态分析增强 | 开发跨子系统的数据流分析工具,检测"只读数据被当作可写目标"的模式 | 高 |
| 形式化验证 | 对关键内核路径(如scatterlist处理)使用Coq/Isabelle进行形式化证明 | 中 |
| AI辅助审计 | 训练大语言模型识别"in-place优化"、"零拷贝"、"src==dst"等高风险代码模式 | 中 |
| 红队持续测试 | 建立内核红队,定期针对"用户态可触发的内核路径"进行对抗性测试 | 高 |
| 供应链审计 | 对内核crypto子系统进行专项审计,类似OpenSSL的Heartbleed后审计 | 高 |
防御体系演进方向
| 方向 | 具体措施 | 优先级 |
|---|---|---|
| 运行时微隔离 | 对AF_ALG、io_uring等高风险接口实施默认禁用+ 按需启用策略 | 高 |
| eBPF实时防护 | 部署eBPF探针,监控异常的系统调用组合(如splice()到AF_ALG) | 高 |
| 页缓存完整性 | 研究内核级页缓存完整性保护机制,防止用户态触发的页缓存写入 | 中 |
| 供应链安全 | 在CI/CD中集成内核漏洞扫描,确保容器镜像和固件使用安全内核版本 | 高 |
| 零信任内核 | 即使在内核内部,也坚持"最小权限"原则,限制子系统间的数据流 | 中 |
| 快速修复能力 | 内核Live Patching、容器镜像热更新等技术应成为基础设施标配 | 高 |
战略启示
Copy Fail揭示了现代操作系统的一个根本性挑战:性能优化与安全性之间的张力。零拷贝、in-place计算、共享内存等优化技术极大地提升了系统性能,但也破坏了传统的安全边界。
未来的内核设计应遵循以下原则:
- "默认安全"优于"默认性能":高风险优化应默认关闭,仅在显式配置后启用。例如,AF_ALG的in-place优化可以是一个启动参数选项,而非默认行为。
- "可审计性"作为设计约束:任何新的用户态-内核态接口必须附带可审计的日志路径。内核应提供"系统调用组合审计"能力,记录跨子系统的数据流。
- "快速修复"能力:内核Live Patching、容器镜像热更新、固件OTA等技术应成为基础设施标配。九年潜伏期的根本原因之一是"修复成本过高"(需要重启、需要发行版协调),如果内核具备真正的在线修复能力,此类漏洞的窗口期将大幅缩短。
- "攻击面最小化":向用户态暴露的内核接口应遵循"最小必要"原则。AF_ALG向所有非特权用户开放全部加密算法,这种"过度开放"的设计哲学需要重新审视。