The RedHunt Labs Blog

dirty pipe

Making Sense of the Dirty Pipe Vulnerability (CVE-2022-0847)

Introduction

On Monday 7th March, a vulnerability in the Linux Kernel was disclosed publicly which could allow an attacker to escalate privileges. The vulnerability discovered by Max Kellermann dubbed “Dirty Pipe” affects the Linux Kernel 5.8 and later versions (including Android).

The Basics

As Max mentioned in his write-up, the flaw could allow anyone with read access on a system to write arbitrary data into arbitrary files. In this blog post, we analyze the vulnerability details in-depth and demonstrate how the exploit works to successfully escalate privileges.

Before jumping right into the technical details, let’s get familiar with a few basic terms:

  • Memory Pages: The CPU manages memory in the form of memory pages which usually have a size of 4 KB. 
  • Page Cache: Memory pages are handled by a subsystem in the kernel called page cache. Whenever a file is read, the data is put into the page cache to avoid expensive disk access on the subsequent reads. Similarly, when writing to a file, the data is placed in the page cache and eventually gets into the backing storage device. A page cache becomes “dirty” when the data inside the cache has altered from what is on the disk.
  • Pipes: A pipe is a data channel that can be used to communicate between two processes in Linux. Whenever something is written to a pipe, a page (4 KB) is allocated to it. However, if the last write did not fill up a page completely, a following write may append to the existing page instead of allocating a new one. This is how anonymous pipe buffers work.
  • Pipe Flags: Flags specify status and permissions for the data in the pipe. One of the key flags which play a role in this vulnerability is the PIPE_BUF_FLAG_CAN_MERGE, which signifies that the data buffer inside the pipe can be merged.
  • System Calls: System calls/syscalls are basically ways you can send requests to the kernel from the userspace. Linux 2.6.16 introduced a new syscall named splice() which can move data between file descriptors and pipes directly without userspace interaction.

Digging Deeper

Let’s probe deeper into the technicalities of how the vulnerability actually works. We already know that writing to a file is done through page cache which is handled by the kernel, implying that the page cache is always writable by the kernel. If we try to splice() data from a file into a pipe, the kernel will first load the data into the page cache.

Now, here’s the catch, unlike anonymous buffers, the additional data written to a pipe must NOT be appended to the page because the page is owned by the page cache, not the pipe itself. To understand this better, let’s consider the two pieces of code below:

The first program will keep writing “pew” to STDOUT every 2 seconds.

#include <unistd.h>
#include <stdio.h>

void main() {
 while (1) {
   write(1, "pew\n", 4);
   sleep(2);
 }
}

We can compile and run it like: 

./pew > outfeed.txt

The second program will take input from the STDIN and feed it to a pipe, and then write the string “wep” to it every one second.

#define _GNU_SOURCE

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

void main() {
 while (1) {
   splice(0, 0, 1, 0, 2, 0);
   write(1, "wep\n", 4);
   sleep(1);
 }
}

We need to execute this one feeding the data of the previous pipe like: 

./wep < outfeed.txt | cat > /dev/null

When executed simultaneously under specific time constraints, we can see the string “wep” appear inside the file outfeed.txt, even though pew is the only one writing to the file.

upw wQxy5LUo6R97TBNK3IkwCZ9SzxpS6yAvacA1B icsGRqN Q3K D dfrnXiMIgzjrbOIPuucCXZnruJnNldPZjitiIejkJvMp8nHM2urDkKBuPUnKNSmCiCj DH4jHeSUBLSc

Digging into the source code, we can see that the root cause of the vulnerability dates back to 2016 when the commit 241699cd72a8 introduced two new functions that can allocate new pipe buffers. However, the commit missed the initialization of the flags variable. This made it plausible to create page cache references with arbitrary flags. It wasn’t much of an issue back then since the existing flags weren’t interesting.

However, with the introduction of anonymous pipe buffer merging in f6dd975583bd (PIPE_BUF_FLAG_CAN_MERGE), it became possible to overwrite data within page caches. This is because the kernel is in full control of the page cache, and no permission checking is done when writing to a pipe.

Exploitation

Before talking about exploitability, let’s talk about the constraints to be considered regarding this vulnerability. In order to exploit it:

  • the attacker needs to have read permissions
  • the writable offset must not be on a page boundary
  • the write cannot cross a page boundary and,
  • the file cannot be resized. 

Here are the required steps:

  • First, initialize a pipe.
  • Then we populate the pipe with arbitrary content and then clear it. This step is necessary to set the PIPE_BUF_FLAG_CAN_MERGE flag.
  • Once done, we splice() data from the target file into the pipe just before the offset.
  • Finally, we overwrite the cached file page with write().

Publicly Known Exploits

Kellerman released a working exploit along with his write-up that allows any user to inject their own data into sensitive read-only files, thereby gaining privilege escalation. BLASTY released an updated exploit that spawns a shell by hijacking and restoring the contents of a SUID binary. Other variants of exploits include overwriting the /etc/passwd dropping the “x” flag from the root user, thereby setting an empty password to it.

For a quick demonstration of the exploit, we have added a video demonstration below:

Impact

As a result of this vulnerability, an attacker with read-access on a system can write to any file — even if the file is marked O_RDONLY (read-only), immutable or is on a MS_RDONLY (mounted read-only) filesystem such as btrfs snapshots or CD-ROM mounts.

The CVSS score of the flaw stands at 7.8. The applicability of exploitation being local lessens the severity score of the vulnerability.

Mitigation

The vulnerability is fixed in kernel versions 5.16.11, 5.15.25, and 5.10.102. Google merged a fix for the bug into the Android code base on Feb 24. 

However, the bad news is that, as of today, several major distros do not have patches for the vulnerability. Red Hat Enterprise Linux 4 and 8 remain unpatched as they mention that there “Currently there is no mitigation available for this flaw”. Many of Ubuntu’s bionic and focal flavors too state a similar story.

Conclusion

To compare, the exploitation of “Dirty Pipe” is fairly trivial as per the previously disclosed “Dirty Cow” in 2016. The likelihood of abuse, in this case, is greater owing to its low-level nature, impacting containers, sandboxes, and even the Android ecosystem. It’s only a matter of time before this vulnerability gets weaponized.

Let’s Reduce Your Org’s Attack Surface.

Subscribe & Stay Up to Date

Behind the scenes, we are always generating some awesome CyberSecurity content.

Get notified at the earliest.

Thanks for Subscribing

We will post our awesome blogs directly to your email.

Are you interested in getting a Free Attack Surface Report for your organization?