长亭百川云 - 文章详情

DirtyPIPE漏洞分析从0到1

SilverNeedleLab

52

2024-07-13

水平有限,如有错误欢迎联系指正vx:1084099570 或 bigric3_

1. 环境搭建

1)编译内核

# 补丁信息

设置编译选项

•Compile the kernel with debug info•Provide GDB scripts for kernel debugging

编译

•make -j8

2)编译bzbox

wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2

编译完成后,生成的文件系统在./install目录下,创建目录及初始化脚本

# mkdir -p  proc sys dev etc/init.d

打包文件系统

chmod +x ./init

启动脚本

#!/bin/sh

2. 复现问题

进入虚拟机后,启动poc,测试作者的poc失败,不分析

./writer >/tmp/foo &

测试作者的exploit[1],只是测试一下越权任意文件写的能力,在busybox中创建如下target

/ $ ls -l ./etc/passwd1

启动虚拟机,执行exp,成功修改了644权限的passwd1文件

/ $ ./exp

3. 分析

1)结论

先创建带PIPE_BUF_FLAG_CAN_MERGE标签的pipe_buf然后利用splice底层的零拷贝机制,splice调用copy_page_to_iter_pipe完成pipe_buf的页和目标文件page_cache的绑定,且完成绑定后未置空pipe_buf的flags,最后利用pipe_write对带PIPE_BUF_FLAG_CAN_MERGE标签的pipe_buf写时,直接获取pipe_buf的页引用,且写时不存在权限检查,最后导致了越权写任意文件任意数据,准确的说是写任意具有读权限的文件任意数据(因为splice底层实现,校验了file的读属性)。

2)补丁信息

漏洞的引入在commit[2],修改了匿名管道缓冲区的merge属性的设置,引入了属性PIPE_BUF_FLAG_CAN_MERGE,同样在漏洞的补丁[3]里,对管道的缓冲区的flags进行了初始化设置为0,如下

diff --git a/lib/iov_iter.c b/lib/iov_iter.c

根据作者的说法,在commit 241699cd72a8 “new iov_iter flavour: pipe-backed” (Linux 4.9, 2016)[4]中新增的两个函数即可实现任意设置pipe_buffer的属性,但是并不能造成什么实际的影响,直到linux5.8的commit引入了可以注入PIPE_BUF_FLAG_CAN_MERGE

下面对着Linux源码和作者公开的exploit调试分析一下。

3)创建带PIPE_BUF_FLAG_CAN_MERGE的空pipe_buf

exp中的代码如下

    // 将pipe的缓冲区全部打上标签,因为pipe的缓冲区是环形数组,每个成员指向一个内存页

write在内核中调用pipe_write,pipe的缓冲区在内核中的实现是一个环形数组,数组的每个元素映射一个内存页。只要缓冲区未满则向管道写入数据,非direct io模式会打上flagPIPE_BUF_FLAG_CAN_MERGE

// pipe.c#414

后续通过read读空pipe管道缓冲区

    /* drain the pipe, freeing all pipe_buffer instances (but

4)利用splice的零拷贝绑定pipe_buf->page到page_cache

继续,exp中通过splice底层的零拷贝机制,将pipe的buf_page引用到文件的page_cache

    /* open the input file and validate the specified offset */

如上代码,splice的参数1为644权限的文件passwd1的句柄,参数3为pipe的写入端,即读取passwd1的数据到pipe管道中。splice在内核中调用函数do_splice

// splice.c#1025

do_splice调用splice_file_to_pipe

// splice.c#1008

do_splice调用如下
==>splice_file_to_pipe()
====>do_splice_to()
======> in->f_op->splice_read(in, ppos, pipe, len, flags); // generic_file_splice_read()
=========> call_read_iter()
=============> file->f_op->read_iter() // generic_file_read_iter()
================> filemap_read() // generic_file_read_iter对非direct io模式调用filemap_read

看函数filemap_read

// filemap.c#2629

copy_folio_to_iter(folio, offset, bytes, iter);继续调用:
====> copy_page_to_iter(&folio->page, offset, bytes, i);
=======> __copy_page_to_iter(page, offset,min(bytes, (size_t)PAGE_SIZE - offset), i);

//iov_iter.c#846

这里时文件向pipe copy,所以调用copy_page_to_iter_pipe,细心的同学或许发现了此处正是补丁修补位置之一,看copy_page_to_iter_pipe代码:

static size_t copy_page_to_iter_pipe(struct page *page, size_t offset, size_t bytes,

如上代码可以看到,仅仅时完成了pipe_buf->page到page的引用,并没有实际的copy,完成零拷贝的同时完成的页绑定,调试获取此时buf->page引用的页地址

gef➤  p *(struct folio_batch*)fbatch

5)越权写

继续看exp中的代码

    const char *const data = ":$1$aaron$pIwpJwMMcozsUxAtRa85w.:0:0:test:/root:/bin/sh\n";       // openssl passwd1 -1 -salt aaron aaron 

write写管道内核中调用pipe_write

//pipe.c#414

下个断点,获取copy_page_from_iter()参数buf->page的值,和前面splice中绑定的页是一致的

这里向管道写时没有权限校验的,且buf->flags存在PIPE_BUF_FLAG_CAN_MERGE时,直接调用copy_page_from_iter完成从pipe缓冲区到文件页的拷贝。
如果没有这个标签的话,实际上会往pipe->tmp_page去写,此时就不会写到目标文件中。

4. 参考

https://dirtypipe.cm4all.com/

References

[1] exploit: https://raw.githubusercontent.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit/main/exploit.c
[2] commit: https://github.com/torvalds/linux/commit/f6dd975583bd8ce088400648fd9819e4691c8958
[3] 补丁: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/?id=9d2231c5d74e13b2a0546fee6737ee4446017903&id2=e783362eb54cd99b2cac8b3a9aeac942e6f6ac07
[4] commit 241699cd72a8 “new iov_iter flavour: pipe-backed” (Linux 4.9, 2016): https://github.com/torvalds/linux/commit/241699cd72a8489c9446ae3910ddd243e9b9061b

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2