]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/vm/shared_shadow_inval_test.c
if_ovpn tests: test pf's route-to
[FreeBSD/FreeBSD.git] / tests / sys / vm / shared_shadow_inval_test.c
1 /*
2  * Copyright (c) 2021 Dell Inc. or its subsidiaries. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25
26 /*
27  * Test behavior when a mapping of a shared shadow vm object is
28  * invalidated by COW from another mapping.
29  *
30  * This is a regression test for an issue isolated by rlibby@FreeBSD.org
31  * from an issue detected by stress2's collapse.sh by jeff@FreeBSD.org.
32  * The issue became CVE-2021-29626.
33  *
34  * This file is written as an ATF test suite but may be compiled as a
35  * standalone program with -DSTANDALONE (and optionally -DDEBUG).
36  */
37
38 #include <sys/types.h>
39 #include <sys/mman.h>
40 #include <sys/procctl.h>
41 #include <sys/wait.h>
42 #include <machine/atomic.h>
43
44 #include <err.h>
45 #include <errno.h>
46 #include <stdbool.h>
47 #include <stddef.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <unistd.h>
51
52 #ifdef  STANDALONE
53 #define ATF_REQUIRE(x)  do {            \
54         if (!(x))                       \
55                 errx(1, "%s", #x);      \
56 } while (0)
57 #else
58 #include <atf-c.h>
59 #endif
60
61 #ifdef  DEBUG
62 #define dprintf(...)    printf(__VA_ARGS__)
63 #else
64 #define dprintf(...)
65 #endif
66
67 #define DEPTH   5
68
69 struct shared_state {
70         void *p;
71         size_t len;
72         size_t modlen;
73         bool collapse;
74         bool block_xfer;
75         bool okay;
76         volatile bool exiting[DEPTH];
77         volatile bool exit;
78         volatile bool p3_did_write;
79 };
80
81 static long g_pagesize;
82
83 /*
84  * Program flow.  There are three or four processes that are descendants
85  * of the process running the test (P0), where arrows go from parents to
86  * children, and thicker arrows indicate sharing a certain memory region
87  * without COW semantics:
88  *     P0 -> P1 -> P2 => P3
89  *             \=> P4
90  * The main idea is that P1 maps a memory region, and that region is
91  * shared with P2/P3, but with COW semantics.  When P3 modifies the
92  * memory, P2 ought to see that modification.  P4 optionally exists to
93  * defeat a COW optimization.
94  */
95
96 #define child_err(...)  do {                                            \
97         ss->exit = true;                                                \
98         err(1, __VA_ARGS__);                                            \
99 } while (0)
100
101 #define child_errx(...) do {                                            \
102         ss->exit = true;                                                \
103         errx(1, __VA_ARGS__);                                           \
104 } while (0)
105
106 #define SLEEP_TIME_US   1000
107
108 static void child(struct shared_state *ss, int depth);
109
110 static pid_t
111 child_fork(struct shared_state *ss, int depth)
112 {
113         pid_t pid = fork();
114         if (pid == -1)
115                 child_err("fork");
116         else if (pid == 0)
117                 child(ss, depth);
118         return pid;
119 }
120
121 static void
122 child_fault(struct shared_state *ss)
123 {
124         size_t i;
125
126         for (i = 0; i < ss->len; i += g_pagesize)
127                 (void)((volatile char *)ss->p)[i];
128 }
129
130 static void
131 child_write(struct shared_state *ss, int val, size_t len)
132 {
133         size_t i;
134
135         for (i = 0; i < len; i += g_pagesize)
136                 ((int *)ss->p)[i / sizeof(int)] = val;
137         atomic_thread_fence_rel();
138 }
139
140 static void
141 child_wait_p3_write(struct shared_state *ss)
142 {
143         while (!ss->p3_did_write) {
144                 if (ss->exit)
145                         exit(1);
146                 usleep(SLEEP_TIME_US);
147         }
148         atomic_thread_fence_acq();
149 }
150
151 static void
152 child_verify(struct shared_state *ss, int depth, int newval, int oldval)
153 {
154         size_t i;
155         int expectval, foundval;
156
157         for (i = 0; i < ss->len; i += g_pagesize) {
158                 expectval = i < ss->modlen ? newval : oldval;
159                 foundval = ((int *)ss->p)[i / sizeof(int)];
160                 if (foundval == expectval)
161                         continue;
162                 child_errx("P%d saw %d but expected %d, %d was the old value",
163                     depth, foundval, expectval, oldval);
164         }
165 }
166
167 static void
168 child(struct shared_state *ss, int depth)
169 {
170         pid_t mypid, oldval, pid;
171
172         if (depth < 1 || depth >= DEPTH)
173                 child_errx("Bad depth %d", depth);
174         mypid = getpid();
175         dprintf("P%d (pid %d) started\n", depth, mypid);
176         switch (depth) {
177         case 1:
178                 /* Shared memory undergoing test. */
179                 ss->p = mmap(NULL, ss->len, PROT_READ | PROT_WRITE,
180                     MAP_SHARED | MAP_ANON, -1, 0);
181                 if (ss->p == MAP_FAILED)
182                         child_err("mmap");
183                 /* P1 stamps the shared memory. */
184                 child_write(ss, mypid, ss->len);
185                 if (ss->block_xfer) {
186                         /*
187                          * P4 is forked so that its existence blocks a page COW
188                          * path where the page is simply transferred between
189                          * objects, rather than being copied.
190                          */
191                         child_fork(ss, 4);
192                 }
193                 /*
194                  * P1 specifies that modifications from its child processes not
195                  * be shared with P1.  Child process reads can be serviced from
196                  * pages in P1's object, but writes must be COW'd.
197                  */
198                 if (minherit(ss->p, ss->len, INHERIT_COPY) != 0)
199                         child_err("minherit");
200                 /* Fork P2. */
201                 child_fork(ss, depth + 1);
202                 /* P1 and P4 wait for P3's writes before exiting. */
203                 child_wait_p3_write(ss);
204                 child_verify(ss, depth, mypid, mypid);
205                 if (!ss->collapse) {
206                         /* Hang around to prevent collapse. */
207                         while (!ss->exit)
208                                 usleep(SLEEP_TIME_US);
209                 }
210                 /* Exit so the P2 -> P1/P4 shadow chain can collapse. */
211                 break;
212         case 2:
213                 /*
214                  * P2 now specifies that modifications from its child processes
215                  * be shared.  P2 and P3 will share a shadow object.
216                  */
217                 if (minherit(ss->p, ss->len, INHERIT_SHARE) != 0)
218                         child_err("minherit");
219                 /*
220                  * P2 faults a page in P1's object before P1 exits and the
221                  * shadow chain is collapsed.
222                  */
223                 child_fault(ss);
224                 oldval = atomic_load_acq_int(ss->p);
225                 /* Fork P3. */
226                 pid = child_fork(ss, depth + 1);
227                 if (ss->collapse) {
228                         /* Wait for P1 and P4 to exit, triggering collapse. */
229                         while (!ss->exiting[1] ||
230                             (ss->block_xfer && !ss->exiting[4]))
231                                 usleep(SLEEP_TIME_US);
232                         /*
233                          * This is racy, just guess at how long it may take
234                          * them to finish exiting.
235                          */
236                         usleep(100 * 1000);
237                 }
238                 /* P2 waits for P3's modification. */
239                 child_wait_p3_write(ss);
240                 child_verify(ss, depth, pid, oldval);
241                 ss->okay = true;
242                 ss->exit = true;
243                 break;
244         case 3:
245                 /*
246                  * P3 writes the memory.  A page is faulted into the shared
247                  * P2/P3 shadow object.  P2's mapping of the page in P1's
248                  * object must now be shot down, or else P2 will wrongly
249                  * continue to have that page mapped.
250                  */
251                 child_write(ss, mypid, ss->modlen);
252                 ss->p3_did_write = true;
253                 dprintf("P3 (pid %d) wrote its pid\n", mypid);
254                 break;
255         case 4:
256                 /* Just hang around until P3 is done writing. */
257                 oldval = atomic_load_acq_int(ss->p);
258                 child_wait_p3_write(ss);
259                 child_verify(ss, depth, oldval, oldval);
260                 break;
261         default:
262                 child_errx("Bad depth %d", depth);
263         }
264
265         dprintf("P%d (pid %d) exiting\n", depth, mypid);
266         ss->exiting[depth] = true;
267         exit(0);
268 }
269
270 static void
271 do_shared_shadow_inval(bool collapse, bool block_xfer, bool full_mod)
272 {
273         struct shared_state *ss;
274         pid_t pid;
275
276         pid = getpid();
277
278         dprintf("P0 (pid %d) %s(collapse=%d, block_xfer=%d, full_mod=%d)\n",
279             pid, __func__, (int)collapse, (int)block_xfer, (int)full_mod);
280
281         g_pagesize = sysconf(_SC_PAGESIZE);
282         ATF_REQUIRE(g_pagesize > 0);
283
284         ATF_REQUIRE(procctl(P_PID, pid, PROC_REAP_ACQUIRE, NULL) == 0);
285
286         /* Shared memory for coordination. */
287         ss = mmap(NULL, sizeof(*ss), PROT_READ | PROT_WRITE,
288             MAP_SHARED | MAP_ANON, -1, 0);
289         ATF_REQUIRE(ss != MAP_FAILED);
290
291         ss->len = 2 * 1024 * 1024 + g_pagesize; /* 2 MB + page size */
292         ss->modlen = full_mod ? ss->len : ss->len / 2;
293         ss->collapse = collapse;
294         ss->block_xfer = block_xfer;
295
296         pid = fork();
297         ATF_REQUIRE(pid != -1);
298         if (pid == 0)
299                 child(ss, 1);
300
301         /* Wait for all descendants to exit. */
302         do {
303                 pid = wait(NULL);
304         } while (pid != -1 || errno != ECHILD);
305
306         atomic_thread_fence_acq();
307         ATF_REQUIRE(ss->okay);
308
309         ATF_REQUIRE(munmap(ss, sizeof(*ss)) == 0);
310         ATF_REQUIRE(procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL) == 0);
311 }
312
313 #ifdef STANDALONE
314 int
315 main(void)
316 {
317
318         do_shared_shadow_inval(false, false, false);
319         do_shared_shadow_inval(false, false, true);
320         do_shared_shadow_inval(false, true, false);
321         do_shared_shadow_inval(false, true, true);
322         do_shared_shadow_inval(true, false, false);
323         do_shared_shadow_inval(true, false, true);
324         do_shared_shadow_inval(true, true, false);
325         do_shared_shadow_inval(true, true, true);
326         printf("pass\n");
327 }
328 #else
329
330 #define SHARED_SHADOW_INVAL_TC(suffix, collapse, block_xfer, full_mod)  \
331 ATF_TC_WITHOUT_HEAD(shared_shadow_inval__##suffix);                     \
332 ATF_TC_BODY(shared_shadow_inval__##suffix, tc)                          \
333 {                                                                       \
334         do_shared_shadow_inval(collapse, block_xfer, full_mod);         \
335 }
336
337 SHARED_SHADOW_INVAL_TC(nocollapse_noblockxfer_nofullmod, false, false, false);
338 SHARED_SHADOW_INVAL_TC(nocollapse_noblockxfer_fullmod, false, false, true);
339 SHARED_SHADOW_INVAL_TC(nocollapse_blockxfer_nofullmod, false, true, false);
340 SHARED_SHADOW_INVAL_TC(nocollapse_blockxfer_fullmod, false, true, true);
341 SHARED_SHADOW_INVAL_TC(collapse_noblockxfer_nofullmod, true, false, false);
342 SHARED_SHADOW_INVAL_TC(collapse_noblockxfer_fullmod, true, false, true);
343 SHARED_SHADOW_INVAL_TC(collapse_blockxfer_nofullmod, true, true, false);
344 SHARED_SHADOW_INVAL_TC(collapse_blockxfer_fullmod, true, true, true);
345
346 ATF_TP_ADD_TCS(tp)
347 {
348         ATF_TP_ADD_TC(tp,
349             shared_shadow_inval__nocollapse_noblockxfer_nofullmod);
350         ATF_TP_ADD_TC(tp, shared_shadow_inval__nocollapse_noblockxfer_fullmod);
351         ATF_TP_ADD_TC(tp, shared_shadow_inval__nocollapse_blockxfer_nofullmod);
352         ATF_TP_ADD_TC(tp, shared_shadow_inval__nocollapse_blockxfer_fullmod);
353         ATF_TP_ADD_TC(tp, shared_shadow_inval__collapse_noblockxfer_nofullmod);
354         ATF_TP_ADD_TC(tp, shared_shadow_inval__collapse_noblockxfer_fullmod);
355         ATF_TP_ADD_TC(tp, shared_shadow_inval__collapse_blockxfer_nofullmod);
356         ATF_TP_ADD_TC(tp, shared_shadow_inval__collapse_blockxfer_fullmod);
357
358         return atf_no_error();
359 }
360 #endif