Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2022-0847 — Proof-of-concept exploit and detailed analysis of CVE-2022-0847 (Dirty Pipe) Linux kernel privilege escalation vulnerability, including Docker environment for testing and debugging. | Kitploit
Tools/GitHubGitHub/chenaotian/cve-2022-0847
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary ExploitationLabs & Practice
GitHubchenaotian/cve-2022-0847

CVE-2022-0847

Proof-of-concept exploit and detailed analysis of CVE-2022-0847 (Dirty Pipe) Linux kernel privilege escalation vulnerability, including Docker environment for testing and debugging.

View Repository
257664 years agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2022-0847 Dirty Pipe Linux Kernel Privilege Escalation Analysis

[toc]

This article was first published on Huawei Security's official account, this is the blog version (more complete)

First published link: https://mp.weixin.qq.com/s/6VhWBOzJ7uu80nzFxe5jpg

Vulnerability Introduction

Vulnerability ID: CVE-2022-0847 (Alias: dirty pipe)

Vulnerable product: linux kernel - splice syscall

Affected versions: Linux 5.8 patch f6dd975583bd introduced ~ 5.16.11, 5.15.25, 5.10.102 fixed

Hazard: Write content not exceeding one page to any readable file (sufficient), can be used for local privilege escalation.

Environment Setup

Vulnerability analysis docker: chenaotian/cve-2022-0847 (If still inaccessible, then I haven't uploaded it yet)

Provides:

  • Compiled vulnerable debuggable kernel 5.13
  • qemu, gdb, linux kernel 5.13 source code
  • exp

Start:

cd ~/cve-2022-0847
gcc exp.c -o exp --static && cp exp ./rootfs && cd rootfs
find . | cpio -o --format=newc > ../rootfs.img
cd ../ 
./boot.sh

Debug:

gdb ./vmlinux
target remote :10086
directory /root/linux-5.13
b do_splice
b copy_page_to_iter_pipe 
b pipe_write
ignore 3 15
...
p *(struct pipe_inode_info *) pipe
p (struct pipe_buffer)pipe->bufs[0]

Vulnerability Principle

The brief principle of the vulnerability is that calling the splice function can send a file to the pipe in a "zero-copy" manner. The zero-copy at the code level directly uses the file cache page (page cache) as the buf page of the pipe. However, this introduces an uninitialized variable vulnerability, causing the file cache page to be treated as a normal pipe cache page in subsequent pipe channels and thus "overwritten" and modified. In this case, the kernel does not mark this cache page as "dirty", and it will not be flushed to disk in the short term (until the next reboot or similar). During this period, all scenarios accessing the file will use the modified file cache page, achieving a "temporary arbitrary write to any readable file" operation. This can complete local privilege escalation.

Vulnerability Trigger Point

According to the patch, the vulnerability trigger point is in the copy_page_to_iter_pipe function, which adds an initialization operation for buf->flags, so this is an uninitialized variable vulnerability.

image-20220308170149137

The call point of copy_page_to_iter_pipe appears in the splice system call. The splice function (system call) transports file content into the pipe through a "zero-copy" method. Compared to the traditional method of directly sending file content into the pipe, performance is better. Details are introduced below.

Pipe Principle and pipe_write

First, the vulnerability alias is dirty pipe, so let's first understand the pipe. pipe is a communication channel provided by the kernel, created by the pipe/pipe2 function, returning two file descriptors, one for sending data and the other for receiving data, similar to the two ends of a pipe. The specific usage is not elaborated.

image-20220309124007780

Briefly talk about the implementation in the kernel. Usually, the total length of the pipe cache space is 65536 bytes, managed in the form of pages, a total of 16 pages (one page is 4096 bytes). The pages are not contiguous but managed through an array, forming a circular linked list. Two linked list pointers are maintained, one for writing (pipe->head) and one for reading (pipe->tail). Here we mainly analyze the pipe_write function:

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

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);
	if (chars && !was_empty) { 
        //[1] If the pipe cache is not empty, try to write "continuously" from the current last page
		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] Key: if the PIPE_BUF_FLAG_CAN_MERGE flag exists, it means this page allows continuous writing
             * If the write length does not cross a page, continue writing; otherwise start a new page */
			ret = pipe_buf_confirm(pipe, buf);
			···
			ret = copy_page_from_iter(buf->page, offset, chars, from);
			···
			}
			buf->len += ret;
			···
		}
	}

	for (;;) {//[3] If the previous page cannot be written continuously, start a new page
		··· ···
		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] Allocate a new 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;
			··· ···
			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] Put the newly allocated page into the page array
			buf->ops = &anon_pipe_buf_ops;
			buf->offset = 0;
			buf->len = 0;
			if (is_packetized(filp))
				buf->flags = PIPE_BUF_FLAG_PACKET;
			else
				buf->flags = PIPE_BUF_FLAG_CAN_MERGE;
            	//[6] Set flag, default PIPE_BUF_FLAG_CAN_MERGE
			pipe->tmp_page = NULL;

			copied = copy_page_from_iter(page, 0, PAGE_SIZE, from); 
            //[7] Copy operation
			··· ···
			ret += copied;
			buf->offset = 0;
			buf->len = copied;

			··· ···
		}
        ··· ···
    }
	··· ···
	return ret;
}
  1. If the current pipe is not empty (head==tail indicates an empty pipe), it means there is unread data in the pipe. Then get the head pointer, which points to the latest page used for writing, check the len and offset of that page (to find the end of data), and try to continue writing on the current page.
  2. Determine whether the current page has the PIPE_BUF_FLAG_CAN_MERGE flag; if not, continuous writing on the current page is not allowed. Or if the written data appended to the previous data exceeds one page (i.e., the write operation crosses a page), if it crosses a page, continuous writing is not possible.
  3. If continuous writing on the previous page is not possible, start a new page.
  4. alloc_page allocates a new page.
  5. Place the new page at the front of the array (may replace the existing page), initialize values.
  6. buf->flag is initialized to PIPE_BUF_FLAG_CAN_MERGE by default, because the default state allows the page to be written continuously.
  7. Copy the written data, repeat the above if not finished.

The key to exploitation is the uninitialized PIPE_BUF_FLAG_CAN_MERGE flag in splice, which determines whether we can continue writing on a "not fully written" pipe page.

splice to copy_page_to_iter_pipe

As mentioned above, the pipe manages 16 pages as cache. The zero-copy method of splice is to directly replace the cache pages in pipe with the file cache pages (change the pipe cache page pointer to point to the file cache page).

Download Tool