从 Crypto 子系统的一个优化 commit,到 9 年后的任意文件页缓存覆写2026 年 4 月底,安全研究员 Taeyang Lee 公开披露了一个编号为 CVE-2026-31431 的 Linux 内核漏洞,并为其取了一个颇具讽刺意味的名字——Copy Fail。这个名字精确地概括了漏洞的本质:2017 年,一位内核开发者为了修复 AF_ALG 加密接口中"AAD 数据没有从 src 复制到 dst"的 bug,引入了一个 in-place 优化。这个优化本身完全合理,但它无意中打破了内核 crypto 子系统中另一个模块 (authencesn) 长期以来的一个隐含假设——"目标 buffer 是连续的内核内存,向其中写几个字节不会造成任何副作用"。当这两个独立子系统在 splice() 的帮助下与 Page Cache 交汇时,一个无特权的本地用户可以向系统中任意可读文件的页面缓存写入 4 字节可控数据。这不是通常意义上的内存越界写或 UAF。它的危害更加隐蔽和深远:漏洞影响 2017 年至 2026 年之间的所有主流 Linux 发行版内核(CVSS 7.8 High),持续潜伏了近 9 年。ℹ️ 时间线本文将从漏洞触发的前置知识开始,逐步深入根因分析、PoC 原理与内核级动态验证,随后系统性地探索宿主机提权和容器环境下的各类攻击路径及其可行性边界,最后给出防御方案和基于 O_DIRECT + fanotify 的页缓存完整性检测方案。理解 Copy Fail 需要几个前置概念。它们之间存在层层依赖关系:下面逐一展开。当进程通过 read() 读取 /usr/bin/cat 时,内核不会每次都去磁盘拿数据。它会先检查一块叫做 Page Cache 的内存区域——如果文件的对应页面已经缓存在内存中,就直接返回缓存数据。Page Cache 的几个关键特性与本漏洞直接相关:全局共享。Page Cache 以 (inode, page_offset) 为 key 索引,不属于任何特定进程。同一台机器上的所有进程,只要访问的是同一个 inode,就会命中同一份 page cache。进程 A 通过 read() 将某个文件加载到 page cache 后,进程 B 读取同一文件时直接命中缓存,无需再次访问磁盘。回写机制。对于通过正常 write() 路径产生的修改,内核会将对应的 page 标记为 dirty,稍后由回写线程(pdflush / writeback)异步刷到磁盘。但如果某种内核路径绕过了 VFS 层直接修改了 page cache 页面,dirty 标记不会被设置——修改只存在于内存中,重启或 drop_caches 后丢失。即时可见。一旦 page cache 中的某个页面被修改(无论通过何种路径),所有后续的 read() 调用都会立即看到修改后的内容。这包括同一台机器上的其他进程,也包括容器环境下通过 overlayfs 共享同一底层 inode 的进程(详见 Section 6.1)。在内核中,一段逻辑连续的数据(比如 10KB 的加密载荷)在物理内存中通常分布在多个不连续的 4KB 页面上。为了描述"这段数据由哪些页面的哪些偏移组成",内核使用 Scatterlist(SGL,分散-聚集列表)。每个 struct scatterlist entry 描述一段连续的物理内存区域:当一个 SGL 数组不够用时,可以通过 SG_CHAIN 机制链接多个数组:最后一个 entry 的 page_link 不再指向数据页面,而是指向下一个 SGL 数组的起始地址。遍历 SGL 时,scatterwalk 迭代器负责透明地处理这种链式结构。这个设计本身没有问题。但当 SGL 中的某些 entry 指向的不是普通的内核分配内存,而是 page cache 中的页面时,对 SGL 的写操作就等于直接修改了文件的缓存内容——这正是 Copy Fail 的核心利用点。splice() 是 Linux 提供的一种高性能数据传输系统调用。它的核心思想是避免数据在内核空间和用户空间之间来回复制——通过直接在内核管道 buffer 之间移动 页面引用。普通的 read() + write() 流程需要将文件数据拷贝到用户空间 buffer,再从用户空间 buffer 拷贝到目标。而 splice() 直接把文件的 page cache 页面引用传递给管道的另一端,全程不发生数据拷贝。在 AF_ALG 加密接口中,splice() 被用来将文件数据"喂"给加密算法。此时文件的 page cache pages 被直接放入 TX SGL——这些 SGL entry 中的 page_link 直接指向全局共享的 page cache 页面。这是一个关键的设计决策:如果后续有任何代码路径向这个 SGL 写入数据,就相当于直接修改了文件的 page cache。Linux 内核提供了一套用户空间可以直接使用的加密 API,叫做 AF_ALG(Address Family: Algorithm)。它的接口设计为 socket 风格:AF_ALG 还支持通过 splice() 把文件内容直接"喂"给加密算法,避免数据在内核空间和用户空间之间来回复制。这一特性是 Copy Fail 利用链的关键:splice 进入的文件数据在内核中以 page cache page 引用的形式存入 TX SGL,而不是数据拷贝。在内核中,algif_aead.c 负责处理 AEAD 类型的加密请求。它管理 TX SGL(用户发送的数据)和 RX SGL(用户接收 buffer),并最终调用底层加密算法(如 authencesn)执行实际的加解密操作。AEAD(Authenticated Encryption with Associated Data)是一类同时提供保密性和完整性保证的加密方案。它处理的数据格式为:其中 AAD 是明文关联数据(不加密但参与认证),Ciphertext 是密文,Auth Tag 是认证标签。authencesn 是 Linux 内核中的一个 AEAD 算法实现,全称 "authenc with Extended Sequence Number",为 IPsec 的 ESN(扩展序列号)协议设计。AAD 的含义在 AEAD 加密中,AAD(Associated Data)是"需要认证但不需要加密"的附加数据。比如在 TLS 中,AAD 是记录头(内容类型、协议版本、数据长度);在 IPsec 中,AAD 包含安全参数索引和序列号。不同场景下 AAD 的具体内容不同,但 AEAD 算法只需要知道"前 assoclen 字节是 AAD"即可。authencesn 为什么要向 dst buffer 写数据ESN 协议使用 64 位序列号(防止回绕攻击),但网络传输中只携带低 32 位,高 32 位由通信双方本地维护。authencesn 需要在 HMAC 计算时纳入完整的 64 位序列号。它的做法是:这个"临时写入"就是所谓的 ESN scratch write:写入大小是硬编码的 4 字
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-291167.htm
[原创]Copy Fail 深度研究:Linux 页缓存漏洞的根因、利用与检测
179 浏览
2 回复
这不是“漏洞复现文”,而是一篇完整的 Linux Page Cache 攻击面研究。
难得的是,作者不仅技术扎实,而且有非常强的“讲故事能力”,能把极硬核的内核漏洞写得层层推进、可读性极强。
难得的是,作者不仅技术扎实,而且有非常强的“讲故事能力”,能把极硬核的内核漏洞写得层层推进、可读性极强。
精彩,辛苦辛苦,愿意预定为年度好文之一