CVE-2026-46242 Bad Epoll 深度分析:Linux 内核 epoll UAF 竞态条件提权从原理到复现
导语
2026 年 7 月,安全社区集中曝光了一个代号"Bad Epoll"的 Linux 内核本地权限提升(LPE)漏洞——CVE-2026-46242,CVSS 7.8。韩国首尔大学研究员 Jaeyoung Chung 发现并提交利用,Google kernelCTF 项目为此支付了至少 $71,337 的奖金。
该漏洞的特殊之处在于:它影响 Linux 6.4+ 所有版本(服务器、桌面、Android),利用成功率达 99%,且 epoll 作为内核核心功能无法被禁用——这意味着没有简单的临时缓解措施。
本文将从漏洞原理、竞态条件机制、完整利用链、影响范围到防御策略,做一次系统性深度拆解。
一、漏洞概述
1.1 基础属性
| 属性 | 值 |
|---|---|
| CVE编号 | CVE-2026-46242 |
| 代号 | Bad Epoll |
| CVSS v3.1 | 7.8(高危) |
| 漏洞类型 | Use-After-Free(UAF)+ 竞态条件 |
| 影响组件 | Linux 内核 epoll 子系统 |
| 权限要求 | 本地低权限用户 |
| 攻击复杂度 | 中等(需触发竞态) |
| 发现者 | Jaeyoung Chung(首尔大学) |
| 修复时间 | 2026 年 4 月底合并主线 |
| PoC | 公开,GitHub J-jaeyoung/bad-epoll |
1.2 为什么"Bad"
- 核心功能不可禁用:epoll 是 Linux 事件驱动的基石,不像某些可选模块可以卸载
- 跨平台影响:不仅影响 Linux 服务器/桌面,还影响 Android(Pixel 10 已验证可触发 UAF)
- 极高可靠性:竞态窗口约 6 条指令宽度,但利用代码通过窗口扩展+无崩溃重试达到 ~99% 成功率
- 沙箱逃逸潜力:可从 Chrome 渲染器沙箱内部触发,配合渲染器漏洞可实现完整内核代码执行
二、epoll 子系统原理速览
2.1 epoll 是什么
epoll 是 Linux 内核提供的高效 I/O 多路复用机制,自 Linux 2.5.44 引入,替代了传统的 select/poll:
// epoll 三大 API
int epoll_create1(int flags); // 创建 epoll 实例
int epoll_ctl(int epfd, int op, // 添加/修改/删除监视
int fd, struct epoll_event *event);
int epoll_wait(int epfd, // 阻塞等待事件
struct epoll_event *events,
int maxevents, int timeout);epoll 的核心数据结构在内核中:
epoll 实例内部结构
├── eventpoll 结构体(每个 epoll_create1 调用创建一个)
│ ├── wq(等待队列,epoll_wait 阻塞在此)
│ ├── rbr(红黑树,存储所有注册的监视项)
│ ├── rdllist(就绪链表,有事件发生的 fd 链表)
│ └── mtx(互斥锁,保护内部数据)
│ └── ep_lock(保护 eventpoll 结构体自身)
├── epitem 结构体(每个注册的 fd 对应一个)
│ ├── ffd(关联的文件描述符 + file 指针)
│ ├── rbn(红黑树节点)
│ ├── rdllink(就绪链表节点)
│ └── nwait(监视次数)
└── 每个被监视的 file 对象
├── f_ep(指向监视它的 eventpoll 集合的链表)
└── f_lock(保护 f_ep 的自旋锁)2.2 关键操作流程
注册监视(epoll_ctl ADD):
用户 epoll_ctl(epfd, EPOLL_CTL_ADD, target_fd, &event)
→ sys_epoll_ctl()
→ ep_insert()
├── 创建 epitem,关联 target_fd 的 file 对象
├── 将 epitem 插入 eventpoll 的红黑树
└── 将 eventpoll 信息写入 file->f_ep 链表 ← 关键!关闭监视(file 关闭时自动清理):
用户 close(target_fd)
→ filp_close()
→ __fput()(延迟释放)
→ eventpoll_release_file()
├── 遍历 file->f_ep 链表
├── 对每个关联的 eventpoll 调用 ep_remove()
└── 清理 epitem 并释放资源Bug 就藏在 ep_remove() 和并发 __fput() 的交互中。
三、漏洞原理:UAF 竞态条件
3.1 漏洞根源
2023 年的一个内核提交在 2500 行 epoll 代码路径中引入了两个独立竞态条件。核心问题在 ep_remove() 函数中:
// ep_remove() 的关键路径(简化版)
static int ep_remove(struct eventpoll *ep, struct epitem *epi)
{
struct file *file = epi->ffd.file;
// 步骤1:在 file->f_lock 保护下清除 file->f_ep
spin_lock(&file->f_lock);
hlist_del_rcu(&epi->fllink); // 从 file->f_ep 链表移除
spin_unlock(&file->f_lock); // ← 释放锁
// 步骤2:此时 file 对象仍"在使用"
// 但 __fput() 可能并发执行,认为 file->f_ep 已为空
// → 跳过 eventpoll_release_file() 直接执行 f_op->release
// → eventpoll 结构体被错误释放!
// 步骤3:后续对已释放 eventpoll 的操作 → UAF
}竞态窗口:在 spin_unlock(&file->f_lock) 之后到 ep_remove() 完成之前,大约 6 条指令的宽度。
3.2 触发条件
攻击者创建两个相互监视的 epoll 文件描述符,然后并发关闭两者:
// 触发竞态的核心代码结构
int epfd_a = epoll_create1(0);
int epfd_b = epoll_create1(0);
// 交叉监视:A 监视 B,B 监视 A
struct epoll_event ev = { .events = EPOLLIN };
epoll_ctl(epfd_a, EPOLL_CTL_ADD, epfd_b, &ev);
epoll_ctl(epfd_b, EPOLL_CTL_ADD, epfd_a, &ev);
// 并发关闭 → 竞态触发
close(epfd_a); // 线程1: __fput() → eventpoll_release_file → ep_remove()
close(epfd_b); // 线程2: 并发 __fput() 看到临时 NULL → 跳过清理3.3 UAF 形成过程
时间线:
T1: close(epfd_a)
→ __fput(epfd_a)
→ eventpoll_release_file(file_a)
→ ep_remove(ep_a, epi_a_b)
→ spin_lock(&file_b->f_lock)
→ hlist_del_rcu(&epi_a_b->fllink) // 清除 file_b->f_ep 中对 ep_a 的引用
→ spin_unlock(&file_b->f_lock) // ← 竞态窗口开始!
T2: close(epfd_b) ← 精心安排在此刻
→ __fput(epfd_b)
→ 检查 file_b->f_ep → 发现临时 NULL(因为 hlist_del_rcu 已执行)
→ 跳过 eventpoll_release_file() // ← 关键跳跃!
→ 直接执行 f_op->release(epfd_b) // ← 释放 eventpoll B 结构体
T3: ep_remove(ep_a, epi_a_b) 继续
→ 尝试访问已释放的 eventpoll B 结构体 → UAF!
路径 A 的 hlist_del_rcu() 写入操作落在已被 kfree() 释放的内存上,
造成 UAF 写入。3.4 为什么 SLAB_TYPESAFE_BY_RCU 加剧了问题
Linux 内核的 struct file 使用 SLAB_TYPESAFE_BY_RCU 机制——释放的内存槽可能在 RCU 宽限期后被 alloc_empty_file() 重新分配:
SLAB_TYPESAFE_BY_RCU 机制:
┌───────────────────────────────────────────────────────┐
│ 1. kfree(file_b_obj) → 释放回 kmalloc-256 缓存 │
│ 2. RCU 宽限期过后 → 缓存槽可被重新分配 │
│ 3. alloc_empty_file() → 占用同一槽位 │
│ 4. 新的 file 对象与旧的 eventpoll 引用共存 │
│ 5. ep_remove() 对旧引用的写入 → 覆盖新 file 的字段 │
│ 6. → 跨缓存攻击:完全控制 file 对象内容 │
└───────────────────────────────────────────────────────┘四、完整利用链
4.1 从 UAF 到 Root
发现者公开的利用路径:
利用链全景:
┌─────────────────────────────────────────────────────────────────────┐
│ Step 1: 触发 8 字节 UAF 写入 │
│ 通过竞态条件写入已被释放的 kmalloc-192 内存 │
│ │
│ Step 2: 转化为 file 对象的 UAF │
│ 控制释放后的内存内容 │
│ │
│ Step 3: 跨缓存攻击 │
│ 完全控制 file 结构体内容 │
│ │
│ Step 4: 任意内核内存读取 │
│ 通过 /proc/self/fdinfo 读取内核内存 │
│ │
│ Step 5: 劫持控制流 │
│ 执行 ROP 链 │
│ │
│ Step 6: 获取 root shell │
│ 修改 cred 结构体 → uid=0 │
└─────────────────────────────────────────────────────────────────────┘4.2 关键利用技术
8 字节 UAF 写入控制:
// 通过精心构造的竞态,将特定值写入释放后的内存
// 关键是控制写入的值和目标位置
// 技术要点:
// 1. 使用 userfaultfd 或 FUSE 暂停页故障来扩展竞态窗口
// 2. 通过 close() 的精确时序来触发 UAF
// 3. 利用 SLAB_TYPESAFE_BY_RCU 的重分配特性控制写入目标跨缓存攻击(Cross-Cache Attack):
// 从 kmalloc-192(epitem 缓存)跨越到 kmalloc-256(file 缓存)
// 步骤:
// 1. 喷射大量 file 对象占满 kmalloc-256
// 2. 释放部分 file 对象创造空洞
// 3. 触发 UAF 使释放的 epitem 槽位被新 file 占据
// 4. 通过旧 epitem 引用操作新 file → 完全控制
// spray technique
for (int i = 0; i < SPRAY_COUNT; i++) {
spray_fds[i] = open("/dev/null", O_RDONLY);
}任意内核内存读取:
// 利用被控制的 file 对象的 f_path 成员
// 通过 /proc/self/fdinfo/<fd> 读取内核地址
// 伪代码
struct file *fake_file = controlled_file;
// 设置 fake_file->f_path.dentry 指向目标内核地址
// 然后 read(/proc/self/fdinfo/<fd>) → 泄露内核内存ROP 链执行:
// 最终阶段:通过修改函数指针劫持控制流
// 常用 ROP gadgets:
// push rax; pop rcx; ret (栈迁移)
// mov rdi, rax; call ... (传参)
// swapgs; iretq (返回用户态)
// 最终目标:修改当前进程的 cred 结构体
// cred->uid = 0;
// cred->gid = 0;
// cred->euid = 0;
// → 获得 root 权限4.3 成功率数据
| 目标环境 | 成功率 |
|---|---|
| LTS 6.12.67 | 99% |
| COS-121 | 98% |
竞态窗口仅约 6 条指令宽度,但利用代码通过以下技术实现了极高可靠性:
- 窗口扩展:使用 userfaultfd/FUSE 暂停来延长竞态窗口
- 无崩溃重试:利用失败时不触发内核 panic,可以安全重试
- 精确时序控制:通过 CPU 亲和性绑定和忙等待来精确调度竞态
五、影响范围
5.1 受影响内核版本
| 内核系列 | 受影响范围 |
|---|---|
| 5.x | 5.15.209 - 5.15.x |
| 6.1.x | 6.1.175 - 6.1.x |
| 6.4 - 6.18 | 6.4 - 6.18.32 |
| 6.19 - 7.x | 6.19 - 7.0.9 |
注意:Linux 6.1 老旧版本不受影响,缺陷是在 6.4 版才引入。
5.2 受影响操作系统
- Red Hat Enterprise Linux 9 / 10
- Ubuntu 24.04 LTS、25.10、26.04 LTS
- Debian 12 / 13
- CentOS Stream / AlmaLinux / Rocky Linux
- 国产操作系统:银河麒麟(Kylin)、统信 UOS、openEuler 等基于 Linux 内核的国产发行版
5.3 Android 影响
- Pixel 10(kernel v6.6+):已确认可触发 UAF
- Pixel 8(kernel v6.1):不受影响
- 其他基于 kernel v6.4+ 的 Android 设备理论上受影响
5.4 Chrome 渲染器沙箱逃逸
Bad Epoll 可从 Chrome 渲染器沙箱内部触发,这意味着:
攻击链:
渲染器漏洞(V8 OOB 等) → 渲染器沙箱内执行 → Bad Epoll UAF → 内核提权 → 完整系统控制六、防御策略
6.1 即时措施:更新内核
# Ubuntu/Debian
sudo apt update && sudo apt upgrade linux-image-generic
# RHEL/CentOS
sudo yum update kernel
# 检查当前内核版本
uname -r
# 确保版本 >= 修复后的版本(2026年4月底或之后的补丁)6.2 临时缓解(效果有限)
由于 epoll 不可禁用,传统缓解措施效果不佳,但仍可降低风险:
# 1. 禁用用户态 userfaultfd(限制竞态窗口扩展能力)
sudo sysctl -w vm.unprivileged_userfaultfd=0
# 2. 限制 FUSE 挂载权限(另一个窗口扩展工具)
# 在 /etc/fstab 中不允许用户挂载 FUSE
# 3. 使用 seccomp 限制 epoll_create1 调用
# 仅对特定服务进程有效,不能全局应用6.3 容器环境防御
# Kubernetes Pod 安全策略
apiVersion: security.openshift.io/v1
kind: SecurityContextConstraints
metadata:
name: restricted-epoll
spec:
# 限制容器的内核功能访问
allowedCapabilities:
- NET_BIND_SERVICE
# 不允许特权模式
allowPrivilegedContainer: false
# 强制非 root 运行
requiredDropCapabilities:
- KILL
- SYS_ADMIN
- NET_RAW6.4 监控与检测
# 检测异常的 epoll 使用模式
# 监控交叉监视(epoll A 监视 epoll B)行为
# 内核审计规则
auditctl -a always,exit -F arch=b64 -S epoll_create1
auditctl -a always,exit -F arch=b64 -S epoll_ctl
# eBPF 检测脚本(简化版)
# 检测同一进程创建多个 epoll 实例并交叉注册6.5 长期架构建议
防御架构:
┌─────────────────────────────────────────────────────┐
│ 应用层 │
│ ├── 最小权限原则(容器/进程以非 root 运行) │
│ ├── seccomp 过滤(限制 epoll 交叉注册模式) │
│ └─────────────────────────────────────────────────── │
│ 内核层 │
│ ├── 及时更新内核(终极防御) │
│ ├── 启用 LOCKDOWN(限制内核修改) │
│ ├── CONFIG_HARDENED_USERCOPY(加强拷贝检查) │
│ └─────────────────────────────────────────────────── │
│ 监控层 │
│ ├── 内核审计 + eBPF 实时监控 │
│ ├── 异常 epoll 行为告警 │
│ └─────────────────────────────────────────────────── │
│ 响应层 │
│ ├── 内核漏洞自动修补系统(如 KernelCare) │
│ ├── 紧急内核热补丁能力 │
│ └─────────────────────────────────────────────────── │
└─────────────────────────────────────────────────────┘七、与同类漏洞对比
| 漏洞 | 年份 | 组件 | CVSS | 特点 |
|---|---|---|---|---|
| Bad Epoll (CVE-2026-46242) | 2026 | epoll | 7.8 | 影响Android+服务器,不可禁用,99%成功率 |
| Copy Fail (CVE-2024-1086) | 2024 | nf_tables | 7.8 | 可选模块可卸载缓解 |
| DirtyPipe (CVE-2022-0847) | 2022 | pipe | 7.8 | 影响Android,无需竞态 |
| StackRot (CVE-2023-041) | 2023 | mm | 7.8 | 需特殊条件触发 |
Bad Epoll 的独特威胁在于:核心组件不可禁用 + 极高成功率 + 跨平台(含 Android)。
八、总结与展望
Bad Epoll 是 2026 年上半年最严重的 Linux 内核漏洞之一。它的核心教训:
- 竞态条件仍是内核安全的最大敌人:6 条指令的窗口足以实现 99% 利用率
- 核心组件漏洞无简单缓解:不像可选模块可以卸载,epoll 是基础设施
- Android 不再是"安全岛":内核 6.4+ 的 Android 设备同样受影响
- 供应链安全延伸到内核:2023 年的一个提交引入了缺陷,3 年后才被发现
修复时间线:
2023: 引入缺陷的内核提交
↓
2026 Q1: Jaeyoung Chung 发现并提交利用到 kernelCTF
↓
2026 4月底: 修复代码合并 Linux 内核主线
↓
2026 7月: 各发行版发布安全公告 + 补丁
↓
2026 7月5日: PoC 公开(GitHub),安全社区广泛报道给运维和安全团队的建议:立即检查所有 Linux 服务器和 Android 设备的内核版本,确保已更新到包含 2026 年 4 月底补丁的版本。对于无法立即更新的系统,禁用 vm.unprivileged_userfaultfd 是目前最有效的临时缓解。

