cve-2022-0847 dirty pipe 摘录

https://www.anquanke.com/post/id/269886 代码详解
https://blog.csdn.net/Breeze_CAT/article/details/123393188 图解
https://xz.aliyun.com/t/11016 提权
https://bsauce.github.io/2022/04/03/CVE-2022-0847/ 汇总


msfvenom 生成 shellcode
http://brieflyx.me/2015/linux-tools/msfvenom-intro/
https://www.cnblogs.com/elvirangel/p/6963845.html
https://blog.csdn.net/qq_43390703/article/details/104369055


利用总结:

  • (1)创建一个管道(不指定 O_DIRECT );
  • (2)将管道填充满(通过 pipe_write()每次写入整页),这样所有的 pipe 缓存页都初始化过了,pipe->flag 被初始化为PIPE_BUF_FLAG_CAN_MERGE ;
  • (3)将管道清空(通过pipe_read()),这样通过splice 系统调用传送文件的时候就会使用原有的初始化过的buf结构;
  • (4)打开待覆写文件,调用 splice() 将往pipe写入1字节(这样才能将page cache索引到pipe_buffer);
  • (5)调用 write() 继续向pipe写入小于1页的数据(pipe_write()),这时就会覆盖到文件缓存页了,暂时篡改了目标文件。(只要没有其他可写权限的程序进行write操作,该页面并不会被内核标记为“dirty”,也就不会进行页面缓存写会磁盘的操作,此时其他进程读文件会命中页面缓存,从而读取到篡改后到文件数据, 但重启后文件会变回原来的状态)
static ssize_t pipe_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);  // 计算要写入的数据总大小是否是页帧大小的倍数,并将余数保存在 chars 变量中
        /// 3333333333333333
        if (chars && !was_empty) {          // [1] 如果 chars 不为零,而且 pipe 不为空, 则尝试接着当前页往后写
                unsigned int mask = pipe->ring_size - 1;
                struct pipe_buffer *buf = &pipe->bufs[(head - 1) & mask]; // 获取 pipe 头部的缓冲区
                int offset = buf->offset + buf->len;

                if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&   // [2] 如果缓冲区设置了标志位 PIPE_BUF_FLAG_CAN_MERGE, 且写入长度不会跨页, 则将 chars 长度的数据写入到当前的缓冲区中
                    offset + chars <= PAGE_SIZE) {
                        ret = pipe_buf_confirm(pipe, buf);
                        ...
                        ret = copy_page_from_iter(buf->page, offset, chars, from);
                        ...
                        buf->len += ret;
                        if (!iov_iter_count(from))  // 如果剩余要写入的数据大小为零,则直接返回
                                goto out;
                }
        }

        for (;;) {                                        // [3] 如果没办法接着写,则另起一页
                if (!pipe->readers) {   // 判断 pipe 的读者数量是否为零
                        send_sig(SIGPIPE, current, 0);
                        if (!ret)
                                ret = -EPIPE;
                        break;
                }

                head = pipe->head;
                / 11111111111111111
                if (!pipe_full(head, pipe->tail, pipe->max_usage)) { // 如果 pipe 没有被填满
                        unsigned int mask = pipe->ring_size - 1;
                        struct pipe_buffer *buf = &pipe->bufs[head & mask]; // 获取 pipe 头部的缓冲区
                        struct page *page = pipe->tmp_page;
                        int copied;

                        if (!page) {       // [4] 如果还没有为缓冲区分配页帧, 则申请新页
                                page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
                                ...
                                pipe->tmp_page = page;
                        }

                        spin_lock_irq(&pipe->rd_wait.lock); // 使用自旋锁锁住 pipe 的读者等待队列。

                        head = pipe->head;
                        ...
                        pipe->head = head + 1; // 将 pipe_inode_info 结构的 head 字段值加 1
                        spin_unlock_irq(&pipe->rd_wait.lock); // 释放自旋锁

                        /* Insert it into the buffer array */
                        buf = &pipe->bufs[head & mask]; // 设置当前缓冲区的字段
                        buf->page = page;                                // 新页放到页数组中
                        buf->ops = &anon_pipe_buf_ops;
                        buf->offset = 0;
                        buf->len = 0;
                        / 11111111111111111
                        if (is_packetized(filp)) 
                                buf->flags = PIPE_BUF_FLAG_PACKET;
                        else
                                buf->flags = PIPE_BUF_FLAG_CAN_MERGE; // [5] 如果创建 pipe 时未指定 O_DIRECT 选项, 则设置flag
                        pipe->tmp_page = NULL;

                        copied = copy_page_from_iter(page, 0, PAGE_SIZE, from); // [6] 将要写入的数据拷贝到当前的缓冲区
                        ...
                        ret += copied;                // 设置相应的偏移量字段
                        buf->offset = 0;
                        buf->len = copied;

                        if (!iov_iter_count(from))
                                break;
                }

                if (!pipe_full(head, pipe->tail, pipe->max_usage))
                        continue;
                ...
        }
}
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);
	/ 22222222222222222222
	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;
}

你可能感兴趣的:(pwn_cve_kernel,linux,pwn)