DirtyPipe - CVE-2016-5195
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_bufferand itsflags, 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_bufferstructs is 0x0000000000000010 , and this flag allows us the read/write to the associated page - the 0x0000000000000010 flag corresponds to the
PIPE_BUF_FLAG_CAN_MERGEbit , which means it’s useful and we can use it as uninitialized value for later - the CVE description talks about
spliceusage so we go read about it - in short ,
splice()copies a page from aninput_fdand puts it in apipe_bufferinside ourpipe. (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 theinput_fd, which means ifflags == 0x0000000000000010just like normal usage , we can write to this file . - writing to page through
pipeignores ‘W’ permission , so even if our file is readonly , we can still write to it .
exploit steps :
- create a
pipe_inode_infostruct usingpipe(), fill it’s pipe_buffer array withwrite(pipe[1],buf,0x1000), 0x1000 (page size in x86 ) is needed to set thePIPE_BUF_FLAG_CAN_MERGEflag 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 the16 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 allocatedpipe_buffer. - When we
splice(some_fd,offset,pipe[1],...)the created pipe_buffer for it will havePIPE_BUF_FLAG_CAN_MERGEflag set for it , which means we can write to thispipe_buffer, which also means we can write to it’s associatedpage, even if thesome_fdis 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