CVE-2022-0847DirtyPipe漏洞原理深度分析

admin 2026-07-18 05:09:22 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: CVE-2022-0847DirtyPipe漏洞利用Linux内核pipebuffer结构体中flags字段未正确初始化的缺陷,通过splice()将文件页缓存引入管道后,残留的PIPEBUFFLAGCAN_MERGE标志允许后续write()直接写入该页缓存,从而绕过文件权限检查修改任意只读文件内容。该漏洞影响范围广,建议及时更新内核以修复此高危提权漏洞。 综合评分: 95 文章分类: 漏洞分析,二进制安全


cover_image

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 漏洞原理深度分析》

评论:0   参与:  0