Announcement

👇Official Account👇

Welcome to join the group & private message

Article first/tail QR code

Skip to content

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.17.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:

c
// 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() 函数中:

c
// 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 文件描述符,然后并发关闭两者:

c
// 触发竞态的核心代码结构
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 写入控制

c
// 通过精心构造的竞态,将特定值写入释放后的内存
// 关键是控制写入的值和目标位置

// 技术要点:
// 1. 使用 userfaultfd 或 FUSE 暂停页故障来扩展竞态窗口
// 2. 通过 close() 的精确时序来触发 UAF
// 3. 利用 SLAB_TYPESAFE_BY_RCU 的重分配特性控制写入目标

跨缓存攻击(Cross-Cache Attack)

c
// 从 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);
}

任意内核内存读取

c
// 利用被控制的 file 对象的 f_path 成员
// 通过 /proc/self/fdinfo/<fd> 读取内核地址

// 伪代码
struct file *fake_file = controlled_file;
// 设置 fake_file->f_path.dentry 指向目标内核地址
// 然后 read(/proc/self/fdinfo/<fd>) → 泄露内核内存

ROP 链执行

c
// 最终阶段:通过修改函数指针劫持控制流
// 常用 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.6799%
COS-12198%

竞态窗口仅约 6 条指令宽度,但利用代码通过以下技术实现了极高可靠性:

  1. 窗口扩展:使用 userfaultfd/FUSE 暂停来延长竞态窗口
  2. 无崩溃重试:利用失败时不触发内核 panic,可以安全重试
  3. 精确时序控制:通过 CPU 亲和性绑定和忙等待来精确调度竞态

五、影响范围

5.1 受影响内核版本

内核系列受影响范围
5.x5.15.209 - 5.15.x
6.1.x6.1.175 - 6.1.x
6.4 - 6.186.4 - 6.18.32
6.19 - 7.x6.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 即时措施:更新内核

bash
# Ubuntu/Debian
sudo apt update && sudo apt upgrade linux-image-generic

# RHEL/CentOS
sudo yum update kernel

# 检查当前内核版本
uname -r
# 确保版本 >= 修复后的版本(2026年4月底或之后的补丁)

6.2 临时缓解(效果有限)

由于 epoll 不可禁用,传统缓解措施效果不佳,但仍可降低风险:

bash
# 1. 禁用用户态 userfaultfd(限制竞态窗口扩展能力)
sudo sysctl -w vm.unprivileged_userfaultfd=0

# 2. 限制 FUSE 挂载权限(另一个窗口扩展工具)
# 在 /etc/fstab 中不允许用户挂载 FUSE

# 3. 使用 seccomp 限制 epoll_create1 调用
# 仅对特定服务进程有效,不能全局应用

6.3 容器环境防御

yaml
# 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_RAW

6.4 监控与检测

bash
# 检测异常的 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)2026epoll7.8影响Android+服务器,不可禁用,99%成功率
Copy Fail (CVE-2024-1086)2024nf_tables7.8可选模块可卸载缓解
DirtyPipe (CVE-2022-0847)2022pipe7.8影响Android,无需竞态
StackRot (CVE-2023-041)2023mm7.8需特殊条件触发

Bad Epoll 的独特威胁在于:核心组件不可禁用 + 极高成功率 + 跨平台(含 Android)

八、总结与展望

Bad Epoll 是 2026 年上半年最严重的 Linux 内核漏洞之一。它的核心教训:

  1. 竞态条件仍是内核安全的最大敌人:6 条指令的窗口足以实现 99% 利用率
  2. 核心组件漏洞无简单缓解:不像可选模块可以卸载,epoll 是基础设施
  3. Android 不再是"安全岛":内核 6.4+ 的 Android 设备同样受影响
  4. 供应链安全延伸到内核: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 是目前最有效的临时缓解。

参考资料

上次更新于: