buddurid@CTF:~$ 

DirtyPipe - CVE-2016-5195

Categories: kernel DirtyPipe

POC and Analysis of the infamous DirtyPipe exploit , tested on kernel 5.8.0


demo

looking up the CVE advisory , it says it’s exploitable in 5.8.0 (and a bunch of other versions) .

setup

due to heavy dependancy matches , and old versions of the kernel not being forward comaptible with current libc / gcc versions … we’ll be compiling the kernel in a fresh docker container that ~dates to the kernel release date (ubuntu 20.04 / gcc-9 or 10 )

docker run -v $(pwd):/src/ -it ubuntu:20.04 /bin/bash
# inside docker
apt update
apt install -y build-essential gcc-9 bc bison flex libssl-dev libelf-dev
cd /src #where the kernel src is mounted
make mrproper
make defconfig
make -j$(nproc)

We next read the patch

  • the patch just initializes the flags attribute , which confirms the simplicity of the bug
diff --git a/lib/iov_iter.c b/lib/iov_iter.c
index b0e0acdf96c15..6dd5330f7a995 100644
--- a/lib/iov_iter.c
+++ b/lib/iov_iter.c
@@ -414,6 +414,7 @@ static size_t copy_page_to_iter_pipe(struct page *page, size_t offset, size_t by
 		return 0;

 	buf->ops = &page_cache_pipe_buf_ops;
+	buf->flags = 0;
 	get_page(page);
 	buf->page = page;
 	buf->offset = offset;
@@ -577,6 +578,7 @@ static size_t push_pipe(struct iov_iter *i, size_t size,
 			break;

 		buf->ops = &default_pipe_buf_ops;
+		buf->flags = 0;
 		buf->page = page;
 		buf->offset = 0;
 		buf->len = min_t(ssize_t, left, PAGE_SIZE);

analysis

  • since the exploit is all about the pipe_buffer and its flags , we gotta get a look at both in the source
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_PIPE_FS_I_H
#define _LINUX_PIPE_FS_I_H
#define PIPE_DEF_BUFFERS	16
#define PIPE_BUF_FLAG_LRU	0x01	/* page is on the LRU */
#define PIPE_BUF_FLAG_ATOMIC	0x02	/* was atomically mapped */
#define PIPE_BUF_FLAG_GIFT	0x04	/* page is a gift */
#define PIPE_BUF_FLAG_PACKET	0x08	/* read() as a packet */
#define PIPE_BUF_FLAG_CAN_MERGE	0x10	/* can merge buffers */
#define PIPE_BUF_FLAG_WHOLE	0x20	/* read() must return entire buffer or error */
#ifdef CONFIG_WATCH_QUEUE
#define PIPE_BUF_FLAG_LOSS	0x40	/* Message loss happened after this buffer */
#endif
/**
 *	struct pipe_buffer - a linux kernel pipe buffer
*	@page: the page containing the data for the pipe buffer
*	@offset: offset of data inside the @page
*	@len: length of data inside the @page
*	@ops: operations associated with this buffer. See @pipe_buf_operations.
*	@flags: pipe buffer flags. See above.
*	@private: private data owned by the ops.
**/
struct pipe_buffer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags;
    unsigned long private;
};
  • usually , when we use pipes , the flags we get associated to our pipe_buffer structs is 0x0000000000000010 , and this flag allows us the read/write to the associated page
  • the 0x0000000000000010 flag corresponds to the PIPE_BUF_FLAG_CAN_MERGE bit , which means it’s useful and we can use it as uninitialized value for later
  • the CVE description talks about splice usage so we go read about it
  • in short , splice() copies a page from an input_fd and puts it in a pipe_buffer inside our pipe . (we used the example of a fd -> pipe example because thats only what we need)
  • the new pipe_buffer will contain the page * which maps to the content of the input_fd , which means if flags == 0x0000000000000010 just like normal usage , we can write to this file .
  • writing to page through pipe ignores ‘W’ permission , so even if our file is readonly , we can still write to it .

exploit steps :

  • create a pipe_inode_info struct using pipe() , fill it’s pipe_buffer array with write(pipe[1],buf,0x1000) , 0x1000 (page size in x86 ) is needed to set the PIPE_BUF_FLAG_CAN_MERGE flag in pipe_buffer for later .
  • we can get the size of this array by debugging and finding out that pipe_inode_info->nr_accounted == 0x10 , so we need to fill the 16 pipe_buffer 's
  • next write or splice or whatever will be at index 0 due to this modulo buf = &pipe->bufs[i_head & p_mask] , which means it overlaps with our previously allocated pipe_buffer .
  • When we splice(some_fd,offset,pipe[1],...) the created pipe_buffer for it will have PIPE_BUF_FLAG_CAN_MERGE flag set for it , which means we can write to this pipe_buffer , which also means we can write to it’s associated page , even if the some_fd is backed by a read_only file
  • we do this with “/etc/passwd”
  • Write to our pipe which will evidently write to the /etc/passwd file
  • close the fd to commit the changes because of the caching mechanism (optional)

  • generate password
openssl passwd buddurid
# $1$95/Wl9Be$U17sD6MGVPYedWQ7cf/ac/
  • write “root:$1$95/Wl9Be$U17sD6MGVPYedWQ7cf/ac/:0:0:rootttttt:/root:/bin/sh\n” to /etc/passwd , we need to fill the old /etc/passwd so we don’t leave it in an unparsable format for later when we try to escalate to root
  • su root
  • profit