1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Dirty Pipe linux内核提权漏洞分析(CVE--0847)

Dirty Pipe linux内核提权漏洞分析(CVE--0847)

时间:2023-04-13 09:25:34

相关推荐

Dirty Pipe linux内核提权漏洞分析(CVE--0847)

漏洞原理

Pipe机制

本质上来说,管道(pipe)就是一种进程间通信的手段,让两个进程可以通过pipe 发送和接收数据。

Page Cache机制

磁盘的IO读写速度是很慢的,所以一般当我们访问一个磁盘文件的时候,首先会将其内容装载到物理内存中,后续的访问都是直接取内存中的副本来读取数据。因为一个文件的内存副本,后续可能会被很多进程打开使用。(想想是不是,平常我们的微信软件可能打开本地的一个文本,word软件也可能打开同一个文本)。为了保证大家都能快速的访问,Linux设计了这样一个Page Cache机制管理起物理内存中映射的页框。

如果用户进程使用read/write读写文件,那么内核会先将载入数据的物理内存映射到内核虚拟内存buffer。然后再将内核的buffer数据拷贝到用户态。

如果追求效率,内核也提供一种零拷贝模式(不发生系统调用,跨越用户和内核的边界做上下文切换)。用户进程可以使用mmap直接将用户态的buffer 映射到 物理内存,不需要进行系统调用,直接访问自己的mmap区域即可访问到那段物理内存内容。

pipe_buf结构

pipe有一个大小为16的ring buffer数组,里面存了16个pipe_buf结构,每个pipe_buf结构又有一个指针指向一个表示物理内存页Page的结构体。每个Page大小为4KB,且不连续存放。【这也说明管道一般大小为4kb*16】

splice接口

这个接口可以将数据从一个文件"零拷贝"到一个pipe管道。

核心思路

通过pipe生成一个管道,然后使用write调用pip_write将管道填满flag为PIPE_BUF_FLAG_CAN_MERGE,然后用read将缓冲区全部释放,但是根据splice进行零拷贝时copy_page_to_iter_pipe没有将flag初始化,导致缓冲区仍然留存PIPE_BUF_FLAG_CAN_MERGE。进而在write上检测flag存在PIPE_BUF_FLAG_CAN_MERGE来达成越权写入操作。

源码分析

linux-5.13\lib\iov_iter.c : 417 : copy_page_to_iter_pipe

static size_t copy_page_to_iter_pipe(struct page *page, size_t offset, size_t bytes,struct iov_iter *i){struct pipe_inode_info *pipe = i->pipe;struct pipe_buffer *buf;unsigned int p_tail = pipe->tail;unsigned int p_mask = pipe->ring_size - 1;unsigned int i_head = i->head;size_t off;··· ···off = i->iov_offset;buf = &pipe->bufs[i_head & p_mask];//[1]获取对应的pipe 缓存页··· ···buf->ops = &page_cache_pipe_buf_ops;//[2]修改pipe 缓存页的相关信息指向文件缓存页get_page(page);buf->page = page;//[2]页指针指向了文件缓存页buf->offset = offset;//[2]offset len 等设置为当前信息(通过splice 传入参数决定)buf->len = bytes;pipe->head = i_head + 1;i->iov_offset = offset + bytes;i->head = i_head;out:i->count -= bytes;return bytes;}

该函数把pipe的page设置成文件的page cache,但是遗漏了flag的初始化0操作。

linux-5.13\fs\pipe.c : 400 : pipe_write

static ssize_tpipe_write(struct kiocb *iocb, struct iov_iter *from){struct file *filp = iocb->ki_filp;struct pipe_inode_info *pipe = filp->private_data;unsigned int head;ssize_t ret = 0;size_t total_len = iov_iter_count(from);ssize_t chars;bool was_empty = false;bool wake_next_writer = false;··· ······ ···head = pipe->head;was_empty = pipe_empty(head, pipe->tail);chars = total_len & (PAGE_SIZE-1);if (chars && !was_empty) {//[1]pipe 缓存不为空,则尝试是否能从当前最后一页"接着"写unsigned int mask = pipe->ring_size - 1;struct pipe_buffer *buf = &pipe->bufs[(head - 1) & mask];int offset = buf->offset + buf->len; if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&offset + chars <= PAGE_SIZE) {/*[2]关键,如果PIPE_BUF_FLAG_CAN_MERGE 标志位存在,代表该页允许接着写*如果写入长度不会跨页,则接着写,否则直接另起一页 */ret = pipe_buf_confirm(pipe, buf);···ret = copy_page_from_iter(buf->page, offset, chars, from);···}buf->len += ret;···}}for (;;) {//[3]如果上一页没法接着写,则重新起一页··· ···head = pipe->head;if (!pipe_full(head, pipe->tail, pipe->max_usage)) {unsigned int mask = pipe->ring_size - 1;struct pipe_buffer *buf = &pipe->bufs[head & mask];struct page *page = pipe->tmp_page;int copied;if (!page) {//[4]重新申请一个新页page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);if (unlikely(!page)) {ret = ret ? : -ENOMEM;break;}pipe->tmp_page = page;}spin_lock_irq(&pipe->rd_wait.lock);head = pipe->head;··· ···pipe->head = head + 1;spin_unlock_irq(&pipe->rd_wait.lock);/* Insert it into the buffer array */buf = &pipe->bufs[head & mask];buf->page = page;//[5]将新申请的页放到页数组中buf->ops = &anon_pipe_buf_ops;buf->offset = 0;buf->len = 0;if (is_packetized(filp))buf->flags = PIPE_BUF_FLAG_PACKET;elsebuf->flags = PIPE_BUF_FLAG_CAN_MERGE;//[6]设置flag,默认PIPE_BUF_FLAG_CAN_MERGEpipe->tmp_page = NULL;copied = copy_page_from_iter(page, 0, PAGE_SIZE, from); //[7]拷贝操作··· ···ret += copied;buf->offset = 0;buf->len = copied;··· ···}··· ···}··· ···return ret;}

1.如果当前管道(pipe)中不为空(head==tail判定为空管道),则说明现在管道中有未被读取的数据,则获取head 指针,也就是指向最新的用来写的页,查看该页的len、offset(为了找到数据结尾)。接下来尝试在当前页面续写

2.判断 当前页面是否带有 PIPE_BUF_FLAG_CAN_MERGE flag标记,如果不存在则不允许在当前页面续写。或当前写入的数据拼接在之前的数据后面长度超过一页(即写入操作跨页),如果跨页,则无法续写。

3.如果无法在上一页续写,则另起一页

4.alloc_page 申请一个新的页

5.将新的页放在数组最前面(可能会替换掉原有页面),初始化值。

6.buf->flag 默认初始化为PIPE_BUF_FLAG_CAN_MERGE ,因为默认状态是允许页可以续写的。

7.拷贝写入的数据,没拷贝完重复上述操作。

漏洞利用

1.创建一个管道

2.将管道填充满(通过pipe_write),这样所有的buf(pipe 缓存页)都初始化过了,flag 默认初始化为PIPE_BUF_FLAG_CAN_MERGE

3.将管道清空(通过pipe_read),这样通过splice 系统调用传送文件的时候就会使用原有的初始化过的buf结构。

4.调用splice 函数将想要篡改的文件传送入

5.继续向pipe写入内容(pipe_write),这时就会覆盖到文件缓存页了,完成暂时文件篡改。

局限性

1.不能持久化。

由于修改的是页面缓存,并未修改磁盘上的文件(有极小概率某个对文件有写权限的进程碰巧执行了读写操作,导致缓存被回写磁盘),虽然可以用于提权等操作,但是如果完成提权后不对被修改的文件重新进行持久化操作的话,当操作系统回收内存或者更简单的重启机器后,所做的修改都将失效。如:修改passwd文件去除掉root用户密码后,简单一个重启操作,root密码就恢复如初了。

2.特殊文件限制。

由于文件系统的特性,一些特殊文件不经过页面缓存,导致此漏洞对这类文件无效。

3.只能写不超过一页的内容

参考文档

/Breeze_CAT/article/details/123393188?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164878673016782246497863%2522%252C%2522scm%2522%253A%25220713.130102334.pc%255Fall.%2522%257D&request_id=164878673016782246497863&biz_id=&utm_medium=distribute.pc_search_result.none-task-code-2allfirst_rank_ecpm_v1~rank_v31_ecpm-2-123393188-1.142v5pc_search_result_cache&utm_term=CVE--0847%E5%88%86%E6%9E%90

/weixin_4488/article/details/123364275?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164878673016782246497863%2522%252C%2522scm%2522%253A%25220713.130102334.pc%255Fall.%2522%257D&request_id=164878673016782246497863&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-4-123364275.142v5pc_search_result_cache&utm_term=CVE--0847%E5%88%86%E6%9E%90&spm=1018.2226.3001.4187

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。