I. Details of vulnerabilities
A Linux kernel local permission promotion vulnerability has been discovered that the newly allocated PIpe_buffer structure member “FLAGS” is not properly initialized in copy_page_to_iter_PIPE and PUSH_PIPE functions. May contain old values PIPE_BUF_FLAG_CAN_MERGE. An attacker can exploit this vulnerability to write data to pages in a page cache supported by read-only files, thereby increasing permissions. The vulnerability number is CVE-2022-0847, also known as “DirtyPipe” because the vulnerability type is similar to “DirtyCow”.
* Disclaimer: This article is limited to technical discussion and sharing. It is strictly prohibited to be used in illegal ways.
Two, related system call implementation
Pipe system call implementation
Call pipe() to create a pipe that returns two file descriptors, fd[1] for read and fd[2] for write. Here, the linux-5.16.10 kernel code is used as an example to call the __do_pipe_flags() function, which is implemented as follows:
Call create_pipe_files() and get_unused_fd_flags() to get the unused file descriptors FDR and FDW, respectively, and write them to pointer fd. The create_pipe_files() function calls get_pipe_inode() to get an inode and initialize the related data structures. The get_pipe_inode() function in turn calls alloc_pipe_info() to allocate a pipe_inode_info structure, which is a kernel pipe structure used for pipe management and operation. In particular, consider the alloc_pipe_info() function, which is implemented as follows:
1, Network security learning route 2, electronic books (white hat) 3, security factory internal video 4, 100 SRC documents 5, common security comprehensive questions 6, CTF contest classic topic analysis 7, full kit 8, emergency response notes
Initialize pipe_bufs to PIPE_DEF_BUFFERS, which is 16, allocate PIPE, and determine the size of pipe_bufs*PAGE_SIZE. Pipe_bufs has a maximum value of 128 and a minimum value of 2.
Then allocate pipe->bufs, normally allocate 16 piPE_buffers at once, then initialize the relevant members of pipe, pipe_buffer in piPE_bufs is not initialized. The piper_buffer structure is defined as follows:
Page is used to store data, the size is a page, OPS is the corresponding memory page operation set, and flags is the buffer type. The 16 piPE_buffers form a circular array of pipe buffers, pipe->head pointing to the buffer production point and pipe->tail pointing to the consumption point, which are cyclically used to read and write data under the management of pipe.
When data is written to the pipe, the pipe_write() function is called, which is partially implemented as follows:
Start with pipe->head to determine whether the pipe is full. If the page is not allocated, allocate a new page and initialize the pipe_buffer member as follows:
Line 527, set buf->flags to PIPE_BUF_FLAG_CAN_MERGE to indicate that the buffer can be merged. Finally, the copy_page_from_iter() function is called to copy the data to the newly allocated page. When the data is read from the pipe, it is the reverse process and does not change the page type of the given buffer.
(2) Splice system call implementation
Splice is a new system call in Linux 2.6.17 that moves data between two files without kernel-mode and user-mode memory copying, but with the help of pipes. The idea is to implement the Reference-counting Pointers of pages of kernel Memory via pipe Buffer without actually copying the data. Instead, create a new pointer to the memory page. That is to say, the copying process is essentially a copy of the pointer, called zero copy technology.
When the splice system call is called, the do_splice() function is called in the kernel, which is implemented as follows:
There are three cases, the first case is in/out type pipe, the second case is in type pipe, the third case is out type pipe, here we analyze the third case. The spilce_file_tp_PIPE () function is called to write data to the PIPE. Generic_file_splice_read () is called. The linux-2.6.17 kernel version is used as an example to understand the zero-copy process. This function is implemented as follows:
The __generic_file_splice_read() function is called as follows:
In ->f_mapping (struct inode); f_mapping (struct inode); f_mapping (struct inode); We then define a splice_PIPE_DESC structure, which is used to relay the memory pages corresponding to file. The next step is to arrange the memory page corresponding to file in SPD, which is a complicated process and will be skipped. Finally, the splice_to_PIPE () function is called to manipulate PIPE and SPD. The key code for this function is shown below:
The memory pages are cyclically removed from SPD -> Pages and placed in the corresponding BUF -> Page. As you can see, the memory page is just being moved and no memory copy is being made.
Iii. Vulnerability principle and patch
(1) The principle of loopholes
In the Linux-5.16.10 kernel, when the splice() function is called to write data to the pipe, the call path is as follows:
It is more complex than the linux-2.6.17 kernel version, and eventually the memory page is manipulated by calling the copy_page_to_iter_pipe() function, which is implemented as follows:
As mentioned earlier, taking buF from PIPE only replaces OPS, page, offset, and len, and does not change BUf ->flags, so the pages contained in this buffer can be merged. If buf->flags is PIPE_BUF_FLAG_CAN_MERGE, line 466, then call copy_page_from_iter(). The destination address is buf->page, and the buf->page is actually from the corresponding memory page in file.
(2) Patches
The patch zeroed buf->flags in copy_page_to_iter_PIPE () and push_PIPE (). The push_pipe() function can be triggered in another path and will not be described again.
4. Utilization analysis
First, the pipe is called to create the pipe and the buffer type in the pipe is set to PIPE_BUF_FLAG_CAN_MERGE by a write read operation.
The file to be overwritten is then written to the PIPE via splice. The overwritten file in the public utilization is /usr/bin/pkexec because the program has suID capability.
When the bug is triggered, the pipe contains the same memory page as the /usr/bin/pkexec file, and the memory pages can be merged. /usr/bin/pkexec/pipe /usr/bin/pkexec