copy failed 原理详解
字数 4119
更新时间 2026-05-15 02:30:22

Copy Fail (CVE-2026-31431) 漏洞原理与 AF_ALG AEAD API 详解教学文档

本文档基于一篇关于 Copy Fail 内核漏洞的技术文章,旨在详细解析该漏洞的原理、相关的 AF_ALG 套接字接口及其AEAD(带关联数据的认证加密)API的实现机制。通过学习,您将理解如何利用内核加密模块中的一个逻辑错误,向只读文件的页缓存中写入数据。

第一部分:漏洞概述 (Copy Fail - CVE-2026-31431)

漏洞本质
Copy Fail 是 Linux 内核身份验证加密模块 (authencesn) 中存在的一个逻辑错误。它允许非特权用户(普通用户)向任意可读文件的页缓存中写入 4 字节的任意数据,并且可以多次写入。

关键特性与影响

  1. 内存写入,非持久化:内核不会将被写入的文件页标记为“脏页”(dirty page),因此不会触发写回操作,硬盘上的原始文件数据不会被修改
  2. 运行时影响:当进程访问该文件时,读取的是内存中的页缓存。由于页缓存中的数据已被篡改,这意味着内存中文件内容的损坏会影响依赖此文件运行的程序,可能导致拒绝服务、权限提升或其他意外行为。
  3. 利用代码:公开的漏洞利用(Exploit)C语言版本位于 GitHub: https://github.com/tgies/copy-fail-c

第二部分:AF_ALG 内核加密套接字接口

AF_ALG 是 Linux 内核提供的一个 Netlink 套接字族,用于在用户空间和内核空间的加密 API 之间建立通信桥梁。它允许用户空间程序通过标准的套接字系统调用(如 socket, bind, setsockopt, sendmsg, recvmsg)来请求内核执行加密操作。

主要功能

  • 支持多种加密算法,包括:
    • 消息摘要(如 HMAC, CMAC)
    • 对称密码
    • AEAD 密码
    • 随机数生成器
  • 原地操作(In-place Operation)send/write 的输入缓冲区和 recv/read 的输出缓冲区可以是同一块内存区域,这有助于提升性能并减少内存拷贝。

内核文档:详细使用方式可参考内核官方文档:https://www.kernel.org/doc/html/v6.1/crypto/userspace-if.html

第三部分:与 AF_ALG API 的交互方式

用户必须通过 socket 系统调用来与内核加密 API 交互。

关键步骤

  1. 创建控制套接字:使用 socket(AF_ALG, SOCK_SEQPACKET, 0) 创建一个控制套接字 (ctrl_sock)。
  2. 绑定算法:填充 struct sockaddr_alg 结构体,指定算法族 (salg_family=AF_ALG)、算法类型(如 salg_type="aead")和具体算法名称(如 salg_name="authencesn(hmac(sha256),cbc(aes))")。然后通过 bind() 将此套接字与算法绑定。
  3. 配置参数:对于 AEAD 等算法,需要使用 setsockopt()SOL_ALG 层级设置密钥 (ALG_SET_KEY) 和认证标签大小 (ALG_SET_AEAD_AUTHSIZE)。
  4. 创建操作套接字:通过对控制套接字调用 accept(),生成一个用于实际数据加密/解密操作的套接字 (op_sock)。
  5. 数据操作:通过 send()/write() 向操作套接字发送待处理的数据,通过 read()/recv() 获取处理结果。

第四部分:AEAD (Authenticated Encryption with Associated Data) 原理

AEAD 是一种同时提供机密性完整性/认证的加密模式。

核心输入

  1. Key (密钥)IV (初始化向量):用于加解密的秘密信息。
  2. Plaintext (明文):需要被加密保护的核心数据。
  3. AAD (关联数据, Associated Data):不需要加密(保持明文),但必须参与完整性校验的数据(例如数据包头部、协议版本号)。

输出

  • Ciphertext (密文):加密后的乱码数据。
  • Tag / MAC (认证标签):基于密钥、明文、AAD等计算出的消息认证码,用于验证数据完整性。

工作流程

  • 加密:输入 [AAD | Plaintext],输出 [AAD | Ciphertext | Auth Tag]
  • 解密:输入 [AAD | Ciphertext | Auth Tag]。内核使用密钥、IV 和 AAD 进行解密并验证 Tag。如果验证成功,输出 [AAD | Plaintext];如果 Tag 不匹配,则返回错误(如 -EBADMSG)。

第五部分:内核中 AEAD API 的实现与漏洞根源

5.1 整体生命周期

用户态通过 AF_ALG 套接字接口与内核 AEAD 交互的流程如下:

  1. 绑定算法:创建基础套接字并通过 bind 指定算法。
  2. 设置密钥:通过 setsockopt 将密钥传递给内核,保存在套接字上下文中。
  3. 建立会话:对基础套接字调用 accept(),返回用于实际操作的新套接字。
  4. 发送与接收数据:通过 sendmsg 发送数据,通过 recvmsg 接收处理结果。

5.2 数据在内核中的流转 (aead_sendmsgaf_alg_sendmsg)

  • 用户数据通过 sendmsg 传入内核。
  • 内核函数 af_alg_sendmsg 负责管理这些数据:
    • 使用散列表 (Scatterlist, sg) 来高效管理可能不连续的内存页。
    • 数据被拷贝到内核空间,并添加到散列表 (ctx->tsgl_list) 中。
    • 如果用户设置了 MSG_MORE 标志,则暂不触发加密/解密操作。

5.3 漏洞触发的关键:af_alg_sendpage 与只读文件页

漏洞利用的核心在于 splice() 系统调用与 af_alg_sendpage 函数的结合。

  1. splice 管道操作:利用 splice()只读文件的页缓存挂载到管道中。
  2. 页移动到套接字:再将管道中的数据移动到 AF_ALG 套接字。此过程会调用 af_alg_sendpage
  3. 零拷贝(Zero-Copy)引用af_alg_sendpage 不是将文件数据拷贝到新的内核页,而是通过 get_page() 增加只读文件页的引用计数,并直接将该页的指针 (page) 挂载到散列表 (sgl->sg) 中。此时,一个本应只读的物理页,被添加到了可写的散列表里,为后续写入埋下伏笔。

5.4 解密过程与越界写入 (_aead_recvmsg)

当用户调用 recvmsg 触发解密时,内核调用 _aead_recvmsg

  1. SGL(散列表)管理:涉及三个主要的 SGL:
    • TX SGL:包含用户传入的完整数据 [AAD | Ciphertext | Auth Tag]
    • RX SGL:内核分配,用于存放输出(解密后的明文)。
    • TAGX (areq->tsgl):专门为 Tag 数据创建的 SGL。
  2. 数据重组
    • 将 TX SGL 中的 AAD 和 Ciphertext 拷贝到新分配的 RX SGL 中。
    • 通过 af_alg_pull_tsgl 释放 TX SGL 中 AAD 和 Ciphertext 所占页的引用,仅保留 Tag 所在物理页的引用,并将其指针赋给 areq->tsgl
    • 使用 sg_chain 将 RX SGL 的尾部链接到 areq->tsgl,形成一个逻辑上连续但物理上包含只读页的缓冲区。
  3. 执行解密:调用 crypto_aead_decrypt(),最终跳转到具体算法实现(如 crypto_authenc_esn_decrypt)。

5.5 漏洞触发点:ESN扩展序列号的写入

authencesn 算法的解密函数中,为了处理 64 位扩展序列号(ESN,用于 IPsec),内核需要将高 32 位序列号临时拼接到数据中参与 HMAC 计算,计算后再丢弃。

scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);

这行代码是关键:

  • dst:指向接收缓冲区,即之前链接好的 RX SGL,其尾部指向包含 Tag 数据的只读文件页
  • assoclen + cryptlen:这个偏移量正好指向 Tag 数据的开始位置。
  • 操作:将 tmp+1(即 ESN 的高 32 位)的 4 字节数据,写入dst 缓冲区中 assoclen + cryptlen 的位置。

漏洞产生:由于 Tag 数据所在的物理页是来自只读文件的页缓存,并且通过 af_alg_sendpage 以零拷贝方式引入,内核在解密过程中错误地向这个只读页执行了写入操作,从而完成了对只读文件页缓存的越权修改。由于内核不认为此页是脏页,所以不会写回磁盘,但内存中的内容已被永久改变,直到该页被释放。

总结

Copy Fail 漏洞的根源在于 Linux 内核加密子系统中 AF_ALG 接口对 AEAD 算法的实现存在缺陷。af_alg_sendpage 允许只读文件页以零拷贝方式进入加密上下文,而随后的 authencesn 解密流程在处理 ESN 时,未检查目标内存页的可写性,便直接向该页执行了内存写入操作。这打破了页缓存的只读保护,使得非特权用户能够篡改任意可读文件在内存中的映像,从而可能破坏应用程序逻辑,构成安全威胁。

相似文章
相似文章
 全屏