1 // SPDX-License-Identifier: GPL-2.0-or-later
2
3 #define _GNU_SOURCE
4 #include "../kselftest_harness.h"
5 #include <fcntl.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <unistd.h>
9 #include <sys/mman.h>
10 #include <sys/syscall.h>
11 #include <sys/wait.h>
12 #include <linux/perf_event.h>
13 #include "vm_util.h"
14
FIXTURE(merge)15 FIXTURE(merge)
16 {
17 unsigned int page_size;
18 char *carveout;
19 struct procmap_fd procmap;
20 };
21
FIXTURE_SETUP(merge)22 FIXTURE_SETUP(merge)
23 {
24 self->page_size = psize();
25 /* Carve out PROT_NONE region to map over. */
26 self->carveout = mmap(NULL, 12 * self->page_size, PROT_NONE,
27 MAP_ANON | MAP_PRIVATE, -1, 0);
28 ASSERT_NE(self->carveout, MAP_FAILED);
29 /* Setup PROCMAP_QUERY interface. */
30 ASSERT_EQ(open_self_procmap(&self->procmap), 0);
31 }
32
FIXTURE_TEARDOWN(merge)33 FIXTURE_TEARDOWN(merge)
34 {
35 ASSERT_EQ(munmap(self->carveout, 12 * self->page_size), 0);
36 ASSERT_EQ(close_procmap(&self->procmap), 0);
37 }
38
TEST_F(merge,mprotect_unfaulted_left)39 TEST_F(merge, mprotect_unfaulted_left)
40 {
41 unsigned int page_size = self->page_size;
42 char *carveout = self->carveout;
43 struct procmap_fd *procmap = &self->procmap;
44 char *ptr;
45
46 /*
47 * Map 10 pages of R/W memory within. MAP_NORESERVE so we don't hit
48 * merge failure due to lack of VM_ACCOUNT flag by mistake.
49 *
50 * |-----------------------|
51 * | unfaulted |
52 * |-----------------------|
53 */
54 ptr = mmap(&carveout[page_size], 10 * page_size, PROT_READ | PROT_WRITE,
55 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
56 ASSERT_NE(ptr, MAP_FAILED);
57 /*
58 * Now make the first 5 pages read-only, splitting the VMA:
59 *
60 * RO RW
61 * |-----------|-----------|
62 * | unfaulted | unfaulted |
63 * |-----------|-----------|
64 */
65 ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ), 0);
66 /*
67 * Fault in the first of the last 5 pages so it gets an anon_vma and
68 * thus the whole VMA becomes 'faulted':
69 *
70 * RO RW
71 * |-----------|-----------|
72 * | unfaulted | faulted |
73 * |-----------|-----------|
74 */
75 ptr[5 * page_size] = 'x';
76 /*
77 * Now mprotect() the RW region read-only, we should merge (though for
78 * ~15 years we did not! :):
79 *
80 * RO
81 * |-----------------------|
82 * | faulted |
83 * |-----------------------|
84 */
85 ASSERT_EQ(mprotect(&ptr[5 * page_size], 5 * page_size, PROT_READ), 0);
86
87 /* Assert that the merge succeeded using PROCMAP_QUERY. */
88 ASSERT_TRUE(find_vma_procmap(procmap, ptr));
89 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
90 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size);
91 }
92
TEST_F(merge,mprotect_unfaulted_right)93 TEST_F(merge, mprotect_unfaulted_right)
94 {
95 unsigned int page_size = self->page_size;
96 char *carveout = self->carveout;
97 struct procmap_fd *procmap = &self->procmap;
98 char *ptr;
99
100 /*
101 * |-----------------------|
102 * | unfaulted |
103 * |-----------------------|
104 */
105 ptr = mmap(&carveout[page_size], 10 * page_size, PROT_READ | PROT_WRITE,
106 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
107 ASSERT_NE(ptr, MAP_FAILED);
108 /*
109 * Now make the last 5 pages read-only, splitting the VMA:
110 *
111 * RW RO
112 * |-----------|-----------|
113 * | unfaulted | unfaulted |
114 * |-----------|-----------|
115 */
116 ASSERT_EQ(mprotect(&ptr[5 * page_size], 5 * page_size, PROT_READ), 0);
117 /*
118 * Fault in the first of the first 5 pages so it gets an anon_vma and
119 * thus the whole VMA becomes 'faulted':
120 *
121 * RW RO
122 * |-----------|-----------|
123 * | faulted | unfaulted |
124 * |-----------|-----------|
125 */
126 ptr[0] = 'x';
127 /*
128 * Now mprotect() the RW region read-only, we should merge:
129 *
130 * RO
131 * |-----------------------|
132 * | faulted |
133 * |-----------------------|
134 */
135 ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ), 0);
136
137 /* Assert that the merge succeeded using PROCMAP_QUERY. */
138 ASSERT_TRUE(find_vma_procmap(procmap, ptr));
139 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
140 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size);
141 }
142
TEST_F(merge,mprotect_unfaulted_both)143 TEST_F(merge, mprotect_unfaulted_both)
144 {
145 unsigned int page_size = self->page_size;
146 char *carveout = self->carveout;
147 struct procmap_fd *procmap = &self->procmap;
148 char *ptr;
149
150 /*
151 * |-----------------------|
152 * | unfaulted |
153 * |-----------------------|
154 */
155 ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE,
156 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
157 ASSERT_NE(ptr, MAP_FAILED);
158 /*
159 * Now make the first and last 3 pages read-only, splitting the VMA:
160 *
161 * RO RW RO
162 * |-----------|-----------|-----------|
163 * | unfaulted | unfaulted | unfaulted |
164 * |-----------|-----------|-----------|
165 */
166 ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0);
167 ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0);
168 /*
169 * Fault in the first of the middle 3 pages so it gets an anon_vma and
170 * thus the whole VMA becomes 'faulted':
171 *
172 * RO RW RO
173 * |-----------|-----------|-----------|
174 * | unfaulted | faulted | unfaulted |
175 * |-----------|-----------|-----------|
176 */
177 ptr[3 * page_size] = 'x';
178 /*
179 * Now mprotect() the RW region read-only, we should merge:
180 *
181 * RO
182 * |-----------------------|
183 * | faulted |
184 * |-----------------------|
185 */
186 ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0);
187
188 /* Assert that the merge succeeded using PROCMAP_QUERY. */
189 ASSERT_TRUE(find_vma_procmap(procmap, ptr));
190 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
191 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size);
192 }
193
TEST_F(merge,mprotect_faulted_left_unfaulted_right)194 TEST_F(merge, mprotect_faulted_left_unfaulted_right)
195 {
196 unsigned int page_size = self->page_size;
197 char *carveout = self->carveout;
198 struct procmap_fd *procmap = &self->procmap;
199 char *ptr;
200
201 /*
202 * |-----------------------|
203 * | unfaulted |
204 * |-----------------------|
205 */
206 ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE,
207 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
208 ASSERT_NE(ptr, MAP_FAILED);
209 /*
210 * Now make the last 3 pages read-only, splitting the VMA:
211 *
212 * RW RO
213 * |-----------------------|-----------|
214 * | unfaulted | unfaulted |
215 * |-----------------------|-----------|
216 */
217 ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0);
218 /*
219 * Fault in the first of the first 6 pages so it gets an anon_vma and
220 * thus the whole VMA becomes 'faulted':
221 *
222 * RW RO
223 * |-----------------------|-----------|
224 * | unfaulted | unfaulted |
225 * |-----------------------|-----------|
226 */
227 ptr[0] = 'x';
228 /*
229 * Now make the first 3 pages read-only, splitting the VMA:
230 *
231 * RO RW RO
232 * |-----------|-----------|-----------|
233 * | faulted | faulted | unfaulted |
234 * |-----------|-----------|-----------|
235 */
236 ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0);
237 /*
238 * Now mprotect() the RW region read-only, we should merge:
239 *
240 * RO
241 * |-----------------------|
242 * | faulted |
243 * |-----------------------|
244 */
245 ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0);
246
247 /* Assert that the merge succeeded using PROCMAP_QUERY. */
248 ASSERT_TRUE(find_vma_procmap(procmap, ptr));
249 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
250 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size);
251 }
252
TEST_F(merge,mprotect_unfaulted_left_faulted_right)253 TEST_F(merge, mprotect_unfaulted_left_faulted_right)
254 {
255 unsigned int page_size = self->page_size;
256 char *carveout = self->carveout;
257 struct procmap_fd *procmap = &self->procmap;
258 char *ptr;
259
260 /*
261 * |-----------------------|
262 * | unfaulted |
263 * |-----------------------|
264 */
265 ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE,
266 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
267 ASSERT_NE(ptr, MAP_FAILED);
268 /*
269 * Now make the first 3 pages read-only, splitting the VMA:
270 *
271 * RO RW
272 * |-----------|-----------------------|
273 * | unfaulted | unfaulted |
274 * |-----------|-----------------------|
275 */
276 ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0);
277 /*
278 * Fault in the first of the last 6 pages so it gets an anon_vma and
279 * thus the whole VMA becomes 'faulted':
280 *
281 * RO RW
282 * |-----------|-----------------------|
283 * | unfaulted | faulted |
284 * |-----------|-----------------------|
285 */
286 ptr[3 * page_size] = 'x';
287 /*
288 * Now make the last 3 pages read-only, splitting the VMA:
289 *
290 * RO RW RO
291 * |-----------|-----------|-----------|
292 * | unfaulted | faulted | faulted |
293 * |-----------|-----------|-----------|
294 */
295 ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0);
296 /*
297 * Now mprotect() the RW region read-only, we should merge:
298 *
299 * RO
300 * |-----------------------|
301 * | faulted |
302 * |-----------------------|
303 */
304 ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0);
305
306 /* Assert that the merge succeeded using PROCMAP_QUERY. */
307 ASSERT_TRUE(find_vma_procmap(procmap, ptr));
308 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
309 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size);
310 }
311
TEST_F(merge,forked_target_vma)312 TEST_F(merge, forked_target_vma)
313 {
314 unsigned int page_size = self->page_size;
315 char *carveout = self->carveout;
316 struct procmap_fd *procmap = &self->procmap;
317 pid_t pid;
318 char *ptr, *ptr2;
319 int i;
320
321 /*
322 * |-----------|
323 * | unfaulted |
324 * |-----------|
325 */
326 ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE,
327 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
328 ASSERT_NE(ptr, MAP_FAILED);
329
330 /*
331 * Fault in process.
332 *
333 * |-----------|
334 * | faulted |
335 * |-----------|
336 */
337 ptr[0] = 'x';
338
339 pid = fork();
340 ASSERT_NE(pid, -1);
341
342 if (pid != 0) {
343 wait(NULL);
344 return;
345 }
346
347 /* Child process below: */
348
349 /* Reopen for child. */
350 ASSERT_EQ(close_procmap(&self->procmap), 0);
351 ASSERT_EQ(open_self_procmap(&self->procmap), 0);
352
353 /* unCOWing everything does not cause the AVC to go away. */
354 for (i = 0; i < 5 * page_size; i += page_size)
355 ptr[i] = 'x';
356
357 /*
358 * Map in adjacent VMA in child.
359 *
360 * forked
361 * |-----------|-----------|
362 * | faulted | unfaulted |
363 * |-----------|-----------|
364 * ptr ptr2
365 */
366 ptr2 = mmap(&ptr[5 * page_size], 5 * page_size, PROT_READ | PROT_WRITE,
367 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
368 ASSERT_NE(ptr2, MAP_FAILED);
369
370 /* Make sure not merged. */
371 ASSERT_TRUE(find_vma_procmap(procmap, ptr));
372 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr);
373 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 5 * page_size);
374 }
375
TEST_F(merge,forked_source_vma)376 TEST_F(merge, forked_source_vma)
377 {
378 unsigned int page_size = self->page_size;
379 char *carveout = self->carveout;
380 struct procmap_fd *procmap = &self->procmap;
381 pid_t pid;
382 char *ptr, *ptr2;
383 int i;
384
385 /*
386 * |-----------|------------|
387 * | unfaulted | <unmapped> |
388 * |-----------|------------|
389 */
390 ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE,
391 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
392 ASSERT_NE(ptr, MAP_FAILED);
393
394 /*
395 * Fault in process.
396 *
397 * |-----------|------------|
398 * | faulted | <unmapped> |
399 * |-----------|------------|
400 */
401 ptr[0] = 'x';
402
403 pid = fork();
404 ASSERT_NE(pid, -1);
405
406 if (pid != 0) {
407 wait(NULL);
408 return;
409 }
410
411 /* Child process below: */
412
413 /* Reopen for child. */
414 ASSERT_EQ(close_procmap(&self->procmap), 0);
415 ASSERT_EQ(open_self_procmap(&self->procmap), 0);
416
417 /* unCOWing everything does not cause the AVC to go away. */
418 for (i = 0; i < 5 * page_size; i += page_size)
419 ptr[i] = 'x';
420
421 /*
422 * Map in adjacent VMA in child, ptr2 after ptr, but incompatible.
423 *
424 * forked RW RWX
425 * |-----------|-----------|
426 * | faulted | unfaulted |
427 * |-----------|-----------|
428 * ptr ptr2
429 */
430 ptr2 = mmap(&carveout[6 * page_size], 5 * page_size, PROT_READ | PROT_WRITE | PROT_EXEC,
431 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0);
432 ASSERT_NE(ptr2, MAP_FAILED);
433
434 /* Make sure not merged. */
435 ASSERT_TRUE(find_vma_procmap(procmap, ptr2));
436 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr2);
437 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr2 + 5 * page_size);
438
439 /*
440 * Now mprotect forked region to RWX so it becomes the source for the
441 * merge to unfaulted region:
442 *
443 * forked RWX RWX
444 * |-----------|-----------|
445 * | faulted | unfaulted |
446 * |-----------|-----------|
447 * ptr ptr2
448 *
449 * This should NOT result in a merge, as ptr was forked.
450 */
451 ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ | PROT_WRITE | PROT_EXEC), 0);
452 /* Again, make sure not merged. */
453 ASSERT_TRUE(find_vma_procmap(procmap, ptr2));
454 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr2);
455 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr2 + 5 * page_size);
456 }
457
TEST_F(merge,handle_uprobe_upon_merged_vma)458 TEST_F(merge, handle_uprobe_upon_merged_vma)
459 {
460 const size_t attr_sz = sizeof(struct perf_event_attr);
461 unsigned int page_size = self->page_size;
462 const char *probe_file = "./foo";
463 char *carveout = self->carveout;
464 struct perf_event_attr attr;
465 unsigned long type;
466 void *ptr1, *ptr2;
467 int fd;
468
469 fd = open(probe_file, O_RDWR|O_CREAT, 0600);
470 ASSERT_GE(fd, 0);
471
472 ASSERT_EQ(ftruncate(fd, page_size), 0);
473 if (read_sysfs("/sys/bus/event_source/devices/uprobe/type", &type) != 0) {
474 SKIP(goto out, "Failed to read uprobe sysfs file, skipping");
475 }
476
477 memset(&attr, 0, attr_sz);
478 attr.size = attr_sz;
479 attr.type = type;
480 attr.config1 = (__u64)(long)probe_file;
481 attr.config2 = 0x0;
482
483 ASSERT_GE(syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0), 0);
484
485 ptr1 = mmap(&carveout[page_size], 10 * page_size, PROT_EXEC,
486 MAP_PRIVATE | MAP_FIXED, fd, 0);
487 ASSERT_NE(ptr1, MAP_FAILED);
488
489 ptr2 = mremap(ptr1, page_size, 2 * page_size,
490 MREMAP_MAYMOVE | MREMAP_FIXED, ptr1 + 5 * page_size);
491 ASSERT_NE(ptr2, MAP_FAILED);
492
493 ASSERT_NE(mremap(ptr2, page_size, page_size,
494 MREMAP_MAYMOVE | MREMAP_FIXED, ptr1), MAP_FAILED);
495
496 out:
497 close(fd);
498 remove(probe_file);
499 }
500
501 TEST_HARNESS_MAIN
502