文章总结: CVE-2022-0847DirtyPipe漏洞利用Linux内核pipebuffer结构体中flags字段未正确初始化的缺陷,通过splice()将文件页缓存引入管道后,残留的PIPEBUFFLAGCAN_MERGE标志允许后续write()直接写入该页缓存,从而绕过文件权限检查修改任意只读文件内容。该漏洞影响范围广,建议及时更新内核以修复此高危提权漏洞。 综合评分: 95 文章分类: 漏洞分析,二进制安全
CVE-2022-0847 Dirty Pipe 漏洞原理深度分析
4seaynl 4seaynl
看雪学苑
2026年7月17日 18:06 上海
在小说阅读器读本章
去阅读
一、漏洞核心原理概述
这个漏洞的本质是:pipe_buffer 结构体中的 flags 字段未被正确初始化,导致通过 splice() 从文件引入的页面缓存(page cache)被错误地标记为”可合并写入”(PIPE_BUF_FLAG_CAN_MERGE),从而允许后续的 write() 直接写入该页面缓存,修改任意只读文件的内容。
#
二、关键数据结构
// 内核中的 pipe_buffer 结构体
struct pipe_buffer {
struct page *page; // 指向内存页
unsignedint offset; // 页内偏移
unsignedint len; // 数据长度
conststruct pipe_buf_operations *ops;
unsignedint flags; // ← 漏洞关键字段!
unsignedlongprivate;
};
flags 中的关键标志位:
PIPE_BUF_FLAG_CAN_MERGE = 0x10
当此标志被设置时,内核认为”这个 pipe_buffer 可以追加写入数据”,而不是分配新的 buffer。
#
三、利用步骤详解
#
第一步:准备管道 — 污染 flags
#
┌─────────────────────────────────────────────────────┐
│ prepare_pipe(p) │
│ │
│ 1. pipe(p) 创建管道 │
│ │
│ 2. 填满管道(写满所有 pipe_buffer) │
│ ┌──────┬──────┬──────┬──────┬───────────────┐ │
│ │ buf0 │ buf1 │ buf2 │ ... │ bufN │ │
│ │flags=│flags=│flags=│ │flags= │ │
│ │MERGE │MERGE │MERGE │ │MERGE │ │
│ └──────┴──────┴──────┴──────┴───────────────┘ │
│ 写入时内核自动设置 PIPE_BUF_FLAG_CAN_MERGE │
│ │
│ 3. 读空管道(drain) │
│ - 所有 pipe_buffer 被"释放"回环形队列 │
│ - 但 flags 字段 **没有被清零**! │
│ - flags 仍然保留 PIPE_BUF_FLAG_CAN_MERGE │
│ │
│ 结果:管道为空,但下一个被使用的 pipe_buffer │
│ 仍带有残留的 CAN_MERGE 标志 │
└─────────────────────────────────────────────────────┘
第二步:splice() — 将文件页引入管道
┌─────────────────────────────────────────────────────┐
│ splice(fd, &offset, p[1], NULL, 1, 0) │
│ │
│ 目标文件(只读打开): │
│ ┌────────────────────────────────────────────┐ │
│ │ Page Cache (内核中的文件缓存页) │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ ... [byte] [data we want to modify] ...│ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────┘ │
│ │
│ splice 执行 copy_page_to_iter_pipe(): │
│ │
│ pipe_buffer[0] = { │
│ .page = 文件的 page cache 页面(引用!) │
│ .offset = offset_in_page │
│ .len = 1 │
│ .flags = ??? (未初始化!!!) │
│ } │
│ │
│ BUG: copy_page_to_iter_pipe() 没有设置 flags = 0 │
│ 因此 flags 保留了之前残留的 CAN_MERGE 值! │
│ │
│ 此时管道状态: │
│ ┌──────────────────────────────┐ │
│ │ pipe_buffer[0] │ │
│ │ .page → 文件 page cache │ │
│ │ .len = 1 │ │
│ │ .flags = PIPE_BUF_FLAG_CAN_MERGE ← 残留! │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
第三步:write() — 写入数据到页面缓存
┌─────────────────────────────────────────────────────┐
│ write(p[1], data, data_size) │
│ │
│ 内核 pipe_write() 逻辑: │
│ │
│ 检查最后一个 pipe_buffer: │
│ if (buf->flags & PIPE_BUF_FLAG_CAN_MERGE) { │
│ // 认为可以追加写入到同一个页面 │
│ // 直接 copy_from_user 到 buf->page │
│ // 而这个 page 是文件的 page cache! │
│ } │
│ │
│ 结果: │
│ ┌────────────────────────────────────────────┐ │
│ │ Page Cache(被修改!) │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ ... [X][our injected data!!!] ... │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────┘ │
│ │
│ 文件内容被修改!即使: │
│ - 文件以 O_RDONLY 打开 │
│ - 文件设置了 immutable 属性 │
│ - 文件在只读文件系统上 │
└─────────────────────────────────────────────────────┘
#
四、整体攻击流程图
#
用户空间 内核空间
───────── ─────────
1. pipe(p) 分配 pipe_inode_info
分配 pipe_buffer 环形数组
2. write(p[1], junk, pipe_size) 设置所有 buf->flags |= CAN_MERGE
3. read(p[0], buf, pipe_size) "释放" buffer,但 flags 未清零
│
▼
flags 残留 CAN_MERGE ← BUG!
4. open(target, O_RDONLY) 正常打开文件
5. splice(fd → pipe) copy_page_to_iter_pipe()
┌─ buf->page = file page cache
├─ buf->offset = ...
├─ buf->len = 1
└─ buf->flags = (未初始化, 残留CAN_MERGE)
6. write(p[1], payload) pipe_write() 检查 CAN_MERGE
→ 直接写入 page cache!
│
▼
文件内容被覆盖(在内存中)
脏页回写到磁盘 → 持久化修改
#
五、为什么能绕过权限检查?
#
正常写文件流程:
write() → vfs_write() → 权限检查(inode permission) → 实际写入
Dirty Pipe 流程:
write() → pipe_write() → 检查的是 pipe 的权限(可写)
→ 但实际写入的目标是 page cache
→ 绕过文件权限检查
关键点:内核只检查了"能不能往 pipe 里写"
没有检查"pipe_buffer 引用的 page 是否允许写入"
#
六、利用限制
#
限制1: 偏移不能在页边界上(offset % 4096 != 0) 0x1000
原因: splice 必须先读取 1 字节来建立页面引用
如果 offset=0,没有"前一字节"可以 splice
限制2: 写入不能跨页
原因: CAN_MERGE 只允许在同一个 page 内追加
跨页需要新的 pipe_buffer(不带 CAN_MERGE)
限制3: 不能扩大文件
原因: page cache 只存在于文件已有大小范围内
#
七、修复补丁
#
// 修复方式极其简单 — 初始化 flags 字段
// lib/iov_iter.c - copy_page_to_iter_pipe()
// 修复前:
buf->ops = &page_cache_pipe_buf_ops;
buf->page = page;
buf->offset = offset;
buf->len = bytes;
// flags 未设置!
// 修复后:
buf->ops = &page_cache_pipe_buf_ops;
buf->flags = 0; // ← 新增这一行!
buf->page = page;
buf->offset = offset;
buf->len = bytes;
补丁commit: 9d2231c5d74e (Linux 5.16.11, 5.15.25, 5.10.102)
#
八、实际利用场景
#
场景1: 覆盖 /etc/passwd
将 root 用户的密码哈希替换为已知密码 → 直接 su root
场景2: 覆盖 /root/.ssh/authorized_keys(如 PoC 所示)
注入攻击者的 SSH 公钥
场景3: 覆盖 SUID 二进制文件
修改 /usr/bin/su 等 SUID 程序的代码段 → 执行任意代码
场景4: 容器逃逸
容器内修改宿主机文件(通过挂载的卷)
#
九、为什么这个漏洞如此危险
#
| 特性 | 说明 | | — | — | | 利用可靠性 | 100%确定性,无需竞态/堆喷 | | 利用复杂度 | 极低,~100行代码 | | 前置条件 | 仅需文件可读 | | 权限要求 | 普通用户即可 | | 检测难度 | 不触发 SELinux/审计 | | 影响范围 | 5.8 – 5.16.11 所有内核 |
这是近年来最”干净”的内核提权漏洞之一 — 无需绕过任何保护机制,一次成功,无副作用。
#
看雪ID:4seaynl
https://bbs.kanxue.com/user-home-909875.htm
*本文为看雪论坛优秀文章,由 4seaynl 原创,转载请注明来自看雪社区
第十届安全开发者峰会【议题征集】-欢迎投稿
往期推荐
Android ARM64 内核态内存访问与隐蔽交互机制逆向分析
电动牙刷小程序”越权控制”逆向
一道简单但不简约的堆题——CISCN2026初赛堆题robo分析
第二届软件系统安全赛决赛 StudentManagement 详解
APP整体加固原理
球分享
球点赞
球在看
点击阅读原文查看更多
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:看雪学苑 4seaynl 4seaynl《CVE-2022-0847 Dirty Pipe 漏洞原理深度分析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论