Dirty Pipe – What You Need to Know
By: Ofri Ouzan, Security Researcher, Rezilion
CVE-2022-0847 – Also Know As Dirty Pipe
The new serious Linux Kernel vulnerability dubbed ‘Dirty Pipe’, due to its similarity to the 2016 high severity and easy to exploit DirtyCow vulnerability, was originally disclosed on March 7th by Max Kellermann. Kellermann found the bug accidently while researching corrupted log files on a log server.
Dirty Pipe has a wide impact (also affecting any Android release that is based on one of the vulnerable Linux kernel versions) and essentially no effective mitigation apart from upgrading the Linux Kernel to a patched version.
Dirty Pipe is a Linux Kernel bug in the PIPE mechanism due to missing initialization of the `flags` member in the `pipe_buffer` struct. The bug allows an attacker to create an unprivileged process that will inject code into a root process, and through doing so, escalate privileges by getting write permissions to read-only files. This can also be used in order to modify files in container images on the host, effectively poisoning any new containers based on the modified image.
Page cache is used while reading from or writing to disk. New pages are added to the page cache to satisfy User Mode processes’ read requests. If the page is not already in the cache, a new entry is added to the cache and filled with the data read from the disk. If there is enough free memory, the page is kept in the cache for an indefinite period of time and can then be reused by other processes without accessing the disk.
The pipe command can send one process’ output/input/error to another process by pushing the data, the other process will pull the data and will do further processing.
The data written to the pipe will be mapped to a memory page (4KB in size), in case the data did not fill the page completely, the following written data will be mapped (in addition to the previous data) to this page, instead of allocating a new one.
This process is called “anonymous” pipe buffers and the `pipe_buf_operations` structure called `anon_pipe_buf_ops` implements it.
Splice is a kernel system call that moves data between two file descriptors without copying between kernel address space and user address space.
If you splice() data from a file into the pipe, the kernel will first load the data into the page cache. Then it will create a struct `pipe_buffer` pointing inside the page cache (zero-copy), but unlike anonymous pipe buffers, additional data written to the pipe must not be appended to such a page because the page is owned by the page cache, not by the pipe.
By injecting `PIPE_BUF_FLAG_CAN_MERGE` into a page cache reference, it became possible to overwrite data in the page cache, simply by writing new data into the pipe prepared in a special way.
The commit that introduced the bug is: 241699cd72a8489c9446ae3910ddd243e9b9061b where two functions allocate a new struct `pipe_buffer`, but do not initialize the `flags` member. As a result, it became possible to create page cache references with arbitrary flags.
At that point, the bug was not yet severe due to the fact that the flags existing at that point in time were not dangerous, until the f6dd975583bd8ce088400648fd9819e4691c8958 commit which created the PIPE_BUF_FLAG_CAN_MERGE flag and made it critical. This flag allows you to overwrite page cache, so, by injecting PIPE_BUF_FLAG_CAN_MERGE into a page cache reference, instead of creating a new pipe_buffer, the page cache will be overwritten.
Dirty Pipe Exploit
You can examine the exploit of the vulnerability here.
In this example we can see that the exploit splices data (which creates a reference to a page cache) and then, when writing another data string, because the `PIPE_BUF_FLAG_CAN_MERGE` is already set and will not initialize, the data will be written to the existing page cache instead of creating a new `pipe_buffer`.