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

admin 2026-10-18 11:57:52 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

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整体加固原理

球分享

球点赞

球在看

点击阅读原文查看更多


CVE-2022-0847DirtyPipe漏洞原理深度分析" 网络安全文章

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

CVE-2022-0847DirtyPipe漏洞利用Linux内核pipe_buffer结构体中flags字段未正确初始化的缺陷,通过splice()将文件页缓存引入管道后,残留的PIPE_BUF_FLAG_CAN\\_MERGE标志允许后续write()直接写入该页缓存,从而绕过文件权限检查修改任意只读文件内容。该漏洞影响范围广,建议及时更新内核以修复此高危提权漏洞。
从技术热点到产业落地,共建可信智能时代的硬件安全生态——PANDA2026圆满落幕" 网络安全文章

从技术热点到产业落地,共建可信智能时代的硬件安全生态——PANDA2026圆满落幕"

本文报道了第九届PANDA硬件安全研讨会圆满落幕。大会聚焦AI安全、后量子密码迁移、芯片攻防及出海合规等产业热点,汇聚产学研用测多方专家,通过报告与实战活动推动技术落地。文章同时强调了主办方纽创信安以客户需求驱动的理念,倡导共建可信智能时代的硬件安全生态。
n8n令牌交换漏洞导致攻击者以其它颁发者的用户身份登录" 网络安全文章

n8n令牌交换漏洞导致攻击者以其它颁发者的用户身份登录"

工作流自动化平台n8n企业版存在令牌交换漏洞CVE-2026-59208,当信任多个外部颁发者时,系统仅根据JWT的sub声明匹配用户而忽略iss声明,导致攻击者可利用颁发者A的令牌冒充颁发者B的用户登录。该漏洞仅影响开启此预览功能的OEM部署场景,CVSSv4评分7.6。建议用户升级至2.27.4或2.28.1以上版本,或缩减受信任颁发者至一个及关闭该功能,且需注意官方更新日志未明确提及此修复。
评论:0   参与:  0