参考: GitHub - chenaotian/CVE-2022-0847: CVE-2022-0847 POC and Docker and Analysis write up
pipe 是内核提供的一个通信管道,通过pipe/pipe2 函数创建,返回两个文件描述符,一个用于发送数据,另一个用于接受数据,类似管道的两段。

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;}
- 如果当前管道(pipe)中不为空(head==tail判定为空管道),则说明现在管道中有未被读取的数据,则获取head 指针,也就是指向最新的用来写的页,查看该页的len、offset(为了找到数据结尾)。接下来尝试在当前页面续写
- 判断 当前页面是否带有** PIPE_BUF_FLAG_CAN_MERGE flag标记,如果不存在则不允许在当前页面续写**。或当前写入的数据拼接在之前的数据后面长度超过一页(即写入操作跨页),如果跨页,则无法续写。
- 如果无法在上一页续写,则另起一页
- alloc_page 申请一个新的页
- 将新的页放在数组最前面(可能会替换掉原有页面),初始化值。
- buf->flag 默认初始化为 PIPE_BUF_FLAG_CAN_MERGE ,因为默认状态是允许页可以续写的。
- 拷贝写入的数据,没拷贝完重复上述操作。
