The Dirty Pipe vulnerability, which is been tracked as CVE-2022-0847 is somehow similar to the popular nasty bug in Linux called "Dirty Cow" but is easier to exploit. The vulnerability was discovered by a security researcher named, Max Kellermann, from CM4all hosting.
When Kellermann gets the support tickets about corrupt files from one of its hosting clients, he started to fix that up. While looking at the issue he found there was a corrupt log file on one of the log servers, it could be decompressed, but gzip reported a CRC error. "I could not explain why it was corrupt, but I assumed the nightly split process had crashed and left a corrupt file behind. I fixed the file’s CRC manually, closed the ticket, and soon forgot about the problem." Kellermann wrote on blogpost.
Later on, he found multiple similar issues raising on their server. Every time, the file’s contents looked correct, only the CRC at the end of the file was wrong. After looking deeper into the issue, he drew a conclusion: this must be a kernel bug.
Looking into the kernel code and some quick checks he confirmed the bug resides in the Pipes of the kernel that affects Linux 5.10 (Debian Bullseye) but not Linux 4.19 (Debian Buster).
A pipe is a tool for unidirectional inter-process communication. One end is for pushing data into it, the other end can pull that data. The Linux kernel implements this by a ring of struct pipe_buffer, each referring to a page. The first write to a pipe allocates a page (space for 4 kB worth of data). If the most recent write does not fill the page completely, the following write may append to that existing page instead of allocating a new one. This is how “anonymous” pipe buffers work (anon_pipe_buf_ops).
Exploitation of Dirty Pipe Vulnerability (CVE-2022-0847)
In his research, Kellermann came to know that it is possible to overwrite the page cache even in the absence of writers (writers used for the bisect), with no timing constraints, at (almost) arbitrary positions with arbitrary data. But there are some limitations which are:
- the attacker must have read permissions (because it needs to splice() a page into a pipe)
- the offset must not be on a page boundary (because at least one byte of that page must have been spliced into the pipe)
- the write cannot cross a page boundary (because a new anonymous buffer would be created for the rest)
- the file cannot be resized (because the pipe has its own page fill management and does not tell the page cache how much data has been appended)
- Create a pipe.
- Fill the pipe with arbitrary data (to set the PIPE_BUF_FLAG_CAN_MERGE flag in all ring entries).
- Drain the pipe (leaving the flag set in all struct pipe_buffer instances on the struct pipe_inode_info ring).
- Splice data from the target file (opened with O_RDONLY) into the pipe from just before the target offset.
- Write arbitrary data into the pipe; this data will overwrite the cached file page instead of creating a new anonymous struct pipe_buffer because PIPE_BUF_FLAG_CAN_MERGE is set.