1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright 2022 Google LLC.
4 * Author: Suren Baghdasaryan <surenb@google.com>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 /*
19 * Fork a child that concurrently modifies address space while the main
20 * process is reading /proc/$PID/maps and /proc/$PID/smaps, verifying the
21 * results. Address space modifications include:
22 * VMA splitting and merging
23 *
24 */
25 #define _GNU_SOURCE
26 #include "kselftest_harness.h"
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <pthread.h>
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <linux/fs.h>
36 #include <sys/ioctl.h>
37 #include <sys/mman.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <sys/wait.h>
41
42 #define min(a, b) \
43 ({ \
44 typeof(a) _a = (a); \
45 typeof(b) _b = (b); \
46 _a < _b ? _a : _b; \
47 })
48
49 /* /proc/pid/maps parsing routines */
50 struct page_content {
51 char *data;
52 ssize_t size;
53 };
54
55 #define LINE_MAX_SIZE 256
56
57 struct line_content {
58 char text[LINE_MAX_SIZE];
59 unsigned long start_addr;
60 unsigned long end_addr;
61 };
62
63 enum test_state {
64 INIT,
65 CHILD_READY,
66 PARENT_READY,
67 SETUP_READY,
68 SETUP_MODIFY_MAPS,
69 SETUP_MAPS_MODIFIED,
70 SETUP_RESTORE_MAPS,
71 SETUP_MAPS_RESTORED,
72 TEST_READY,
73 TEST_DONE,
74 };
75
76 enum maps_file {
77 MAPS,
78 SMAPS,
79 };
80
81 struct vma_modifier_info;
82
FIXTURE(proc_maps_race)83 FIXTURE(proc_maps_race)
84 {
85 struct vma_modifier_info *mod_info;
86 struct page_content page1;
87 struct page_content page2;
88 struct line_content last_line;
89 struct line_content first_line;
90 unsigned long duration_sec;
91 enum maps_file maps_file;
92 int shared_mem_size;
93 int skip_pages;
94 int page_size;
95 int vma_count;
96 bool verbose;
97 int maps_fd;
98 pid_t pid;
99 };
100
FIXTURE_VARIANT(proc_maps_race)101 FIXTURE_VARIANT(proc_maps_race)
102 {
103 const enum maps_file maps_file;
104 };
105
FIXTURE_VARIANT_ADD(proc_maps_race,maps)106 FIXTURE_VARIANT_ADD(proc_maps_race, maps) {
107 .maps_file = MAPS,
108 };
109
FIXTURE_VARIANT_ADD(proc_maps_race,smaps)110 FIXTURE_VARIANT_ADD(proc_maps_race, smaps) {
111 .maps_file = SMAPS,
112 };
113
114 typedef bool (*vma_modifier_op)(FIXTURE_DATA(proc_maps_race) *self);
115 typedef bool (*vma_mod_result_check_op)(struct line_content *mod_last_line,
116 struct line_content *mod_first_line,
117 struct line_content *restored_last_line,
118 struct line_content *restored_first_line);
119
120 struct vma_modifier_info {
121 int vma_count;
122 void *addr;
123 int prot;
124 void *next_addr;
125 vma_modifier_op vma_modify;
126 vma_modifier_op vma_restore;
127 vma_mod_result_check_op vma_mod_check;
128 pthread_mutex_t sync_lock;
129 pthread_cond_t sync_cond;
130 enum test_state curr_state;
131 bool exit;
132 void *child_mapped_addr[];
133 };
134
read_page(FIXTURE_DATA (proc_maps_race)* self,struct page_content * page)135 static bool read_page(FIXTURE_DATA(proc_maps_race) *self,
136 struct page_content *page)
137 {
138 ssize_t bytes_read;
139
140 bytes_read = read(self->maps_fd, page->data, self->page_size);
141 if (bytes_read <= 0)
142 return false;
143
144 /* Make sure data always ends with a newline character. */
145 if (page->data[bytes_read - 1] != '\n')
146 return false;
147
148 page->size = bytes_read;
149
150 return true;
151 }
152
parse_vma_line(char * line_start,char * line_end,unsigned long * start,unsigned long * end)153 static bool parse_vma_line(char *line_start, char *line_end,
154 unsigned long *start, unsigned long *end)
155 {
156 bool found;
157
158 *line_end = '\0'; /* stop sscanf at the EOL */
159 found = (sscanf(line_start, "%lx-%lx", start, end) == 2);
160 *line_end = '\n';
161
162 return found;
163 }
164
locate_containing_page(FIXTURE_DATA (proc_maps_race)* self,unsigned long addr,unsigned long size)165 static int locate_containing_page(FIXTURE_DATA(proc_maps_race) *self,
166 unsigned long addr, unsigned long size)
167 {
168 unsigned long start, end;
169 int page = 0;
170
171 if (lseek(self->maps_fd, 0, SEEK_SET) < 0)
172 return -1;
173
174 while (true) {
175 char *curr_pos;
176 char *end_pos;
177
178 if (!read_page(self, &self->page1))
179 return -1;
180
181 curr_pos = self->page1.data;
182 end_pos = self->page1.data + self->page1.size;
183 while (curr_pos < end_pos) {
184 char *line_end;
185
186 line_end = strchr(curr_pos, '\n');
187 if (!line_end)
188 break;
189
190 if (parse_vma_line(curr_pos, line_end, &start, &end) &&
191 start == addr && end == addr + size)
192 return page;
193
194 curr_pos = line_end + 1;
195 }
196 page++;
197 }
198
199 return 0;
200 }
201
read_two_pages(FIXTURE_DATA (proc_maps_race)* self)202 static bool read_two_pages(FIXTURE_DATA(proc_maps_race) *self)
203 {
204 if (lseek(self->maps_fd, 0, SEEK_SET) < 0)
205 return false;
206
207 for (int i = 0; i < self->skip_pages; i++)
208 if (!read_page(self, &self->page1))
209 return false;
210
211 return read_page(self, &self->page1) && read_page(self, &self->page2);
212 }
213
copy_line(const char * line_start,const char * line_end,char * buf,size_t buf_size)214 static void copy_line(const char *line_start, const char *line_end,
215 char *buf, size_t buf_size)
216 {
217 size_t len = min(line_end - line_start, buf_size - 1);
218
219 strncpy(buf, line_start, len);
220 buf[len] = '\0';
221 }
222
copy_first_line(struct page_content * page,char * first_line,size_t line_size)223 static void copy_first_line(struct page_content *page, char *first_line,
224 size_t line_size)
225 {
226 copy_line(page->data, strchr(page->data, '\n'), first_line, line_size);
227 }
228
copy_last_line(struct page_content * page,char * last_line,size_t line_size)229 static void copy_last_line(struct page_content *page, char *last_line,
230 size_t line_size)
231 {
232 /* Get the last line in the first page */
233 const char *end = page->data + page->size - 1;
234 /* skip last newline */
235 const char *pos = end - 1;
236
237 /* search previous newline */
238 while (pos[-1] != '\n')
239 pos--;
240
241 copy_line(pos, end, last_line, line_size);
242 }
243
copy_first_entry(struct page_content * page,char * first_line,size_t line_size)244 static bool copy_first_entry(struct page_content *page, char *first_line,
245 size_t line_size)
246 {
247 char *start_pos = page->data;
248
249 while (start_pos < page->data + page->size) {
250 unsigned long start_addr;
251 unsigned long end_addr;
252 char *end_pos;
253
254 end_pos = strchr(start_pos, '\n');
255 if (!end_pos)
256 break;
257
258 if (parse_vma_line(start_pos, end_pos, &start_addr, &end_addr)) {
259 copy_line(start_pos, end_pos, first_line, line_size);
260 return true;
261 }
262
263 start_pos = end_pos + 1;
264 }
265
266 return false;
267 }
268
copy_last_entry(struct page_content * page,char * last_line,size_t line_size)269 static bool copy_last_entry(struct page_content *page, char *last_line,
270 size_t line_size)
271 {
272 char *end_pos = page->data + page->size - 1;
273 char *start_pos;
274
275 while (end_pos > page->data) {
276 unsigned long start_addr;
277 unsigned long end_addr;
278
279 /* skip last newline */
280 start_pos = end_pos - 1;
281 /* search previous newline */
282 while (start_pos > page->data && start_pos[-1] != '\n')
283 start_pos--;
284 if (parse_vma_line(start_pos, end_pos, &start_addr, &end_addr)) {
285 copy_line(start_pos, end_pos, last_line, line_size);
286 return true;
287 }
288
289 end_pos = start_pos - 1;
290 }
291
292 return false;
293 }
294
295 /* Read the last line of the first page and the first line of the second page */
read_boundary_lines(FIXTURE_DATA (proc_maps_race)* self,struct line_content * last_line,struct line_content * first_line)296 static bool read_boundary_lines(FIXTURE_DATA(proc_maps_race) *self,
297 struct line_content *last_line,
298 struct line_content *first_line)
299 {
300 if (!read_two_pages(self))
301 return false;
302
303 if (self->maps_file == MAPS) {
304 copy_last_line(&self->page1, last_line->text, LINE_MAX_SIZE);
305 copy_first_line(&self->page2, first_line->text, LINE_MAX_SIZE);
306 } else if (self->maps_file == SMAPS) {
307 if (!copy_last_entry(&self->page1, last_line->text, LINE_MAX_SIZE) ||
308 !copy_first_entry(&self->page2, first_line->text, LINE_MAX_SIZE))
309 return false;
310 } else {
311 return false;
312 }
313
314 return sscanf(last_line->text, "%lx-%lx", &last_line->start_addr,
315 &last_line->end_addr) == 2 &&
316 sscanf(first_line->text, "%lx-%lx", &first_line->start_addr,
317 &first_line->end_addr) == 2;
318 }
319
320 /* Thread synchronization routines */
wait_for_state(struct vma_modifier_info * mod_info,enum test_state state)321 static void wait_for_state(struct vma_modifier_info *mod_info, enum test_state state)
322 {
323 pthread_mutex_lock(&mod_info->sync_lock);
324 while (mod_info->curr_state != state)
325 pthread_cond_wait(&mod_info->sync_cond, &mod_info->sync_lock);
326 pthread_mutex_unlock(&mod_info->sync_lock);
327 }
328
signal_state(struct vma_modifier_info * mod_info,enum test_state state)329 static void signal_state(struct vma_modifier_info *mod_info, enum test_state state)
330 {
331 pthread_mutex_lock(&mod_info->sync_lock);
332 mod_info->curr_state = state;
333 pthread_cond_signal(&mod_info->sync_cond);
334 pthread_mutex_unlock(&mod_info->sync_lock);
335 }
336
stop_vma_modifier(struct vma_modifier_info * mod_info)337 static void stop_vma_modifier(struct vma_modifier_info *mod_info)
338 {
339 wait_for_state(mod_info, SETUP_READY);
340 mod_info->exit = true;
341 signal_state(mod_info, SETUP_MODIFY_MAPS);
342 }
343
print_first_lines(char * text,int nr)344 static void print_first_lines(char *text, int nr)
345 {
346 const char *end = text;
347
348 while (nr && (end = strchr(end, '\n')) != NULL) {
349 nr--;
350 end++;
351 }
352
353 if (end) {
354 int offs = end - text;
355
356 text[offs] = '\0';
357 printf("%s", text);
358 text[offs] = '\n';
359 printf("\n");
360 } else {
361 printf("%s", text);
362 }
363 }
364
print_last_lines(char * text,int nr)365 static void print_last_lines(char *text, int nr)
366 {
367 const char *start = text + strlen(text);
368
369 nr++; /* to ignore the last newline */
370 while (nr) {
371 while (start > text && *start != '\n')
372 start--;
373 nr--;
374 start--;
375 }
376 printf("%s", start);
377 }
378
print_boundaries(const char * title,FIXTURE_DATA (proc_maps_race)* self)379 static void print_boundaries(const char *title, FIXTURE_DATA(proc_maps_race) *self)
380 {
381 if (!self->verbose)
382 return;
383
384 printf("%s", title);
385 /* Print 3 boundary lines from each page */
386 print_last_lines(self->page1.data, 3);
387 printf("-----------------page boundary-----------------\n");
388 print_first_lines(self->page2.data, 3);
389 }
390
print_boundaries_on(bool condition,const char * title,FIXTURE_DATA (proc_maps_race)* self)391 static bool print_boundaries_on(bool condition, const char *title,
392 FIXTURE_DATA(proc_maps_race) *self)
393 {
394 if (self->verbose && condition)
395 print_boundaries(title, self);
396
397 return condition;
398 }
399
report_test_start(const char * name,bool verbose)400 static void report_test_start(const char *name, bool verbose)
401 {
402 if (verbose)
403 printf("==== %s ====\n", name);
404 }
405
406 static struct timespec print_ts;
407
start_test_loop(struct timespec * ts,bool verbose)408 static void start_test_loop(struct timespec *ts, bool verbose)
409 {
410 if (verbose)
411 print_ts.tv_sec = ts->tv_sec;
412 }
413
end_test_iteration(struct timespec * ts,bool verbose)414 static void end_test_iteration(struct timespec *ts, bool verbose)
415 {
416 if (!verbose)
417 return;
418
419 /* Update every second */
420 if (print_ts.tv_sec == ts->tv_sec)
421 return;
422
423 printf(".");
424 fflush(stdout);
425 print_ts.tv_sec = ts->tv_sec;
426 }
427
end_test_loop(bool verbose)428 static void end_test_loop(bool verbose)
429 {
430 if (verbose)
431 printf("\n");
432 }
433
capture_mod_pattern(FIXTURE_DATA (proc_maps_race)* self,struct line_content * mod_last_line,struct line_content * mod_first_line,struct line_content * restored_last_line,struct line_content * restored_first_line)434 static bool capture_mod_pattern(FIXTURE_DATA(proc_maps_race) *self,
435 struct line_content *mod_last_line,
436 struct line_content *mod_first_line,
437 struct line_content *restored_last_line,
438 struct line_content *restored_first_line)
439 {
440 print_boundaries("Before modification", self);
441
442 signal_state(self->mod_info, SETUP_MODIFY_MAPS);
443 wait_for_state(self->mod_info, SETUP_MAPS_MODIFIED);
444
445 /* Copy last line of the first page and first line of the last page */
446 if (!read_boundary_lines(self, mod_last_line, mod_first_line))
447 return false;
448
449 print_boundaries("After modification", self);
450
451 signal_state(self->mod_info, SETUP_RESTORE_MAPS);
452 wait_for_state(self->mod_info, SETUP_MAPS_RESTORED);
453
454 /* Copy last line of the first page and first line of the last page */
455 if (!read_boundary_lines(self, restored_last_line, restored_first_line))
456 return false;
457
458 print_boundaries("After restore", self);
459
460 if (!self->mod_info->vma_mod_check(mod_last_line, mod_first_line,
461 restored_last_line, restored_first_line))
462 return false;
463
464 /*
465 * The content of these lines after modify+resore should be the same
466 * as the original.
467 */
468 return strcmp(restored_last_line->text, self->last_line.text) == 0 &&
469 strcmp(restored_first_line->text, self->first_line.text) == 0;
470 }
471
query_addr_at(int maps_fd,void * addr,unsigned long * vma_start,unsigned long * vma_end)472 static bool query_addr_at(int maps_fd, void *addr,
473 unsigned long *vma_start, unsigned long *vma_end)
474 {
475 struct procmap_query q;
476
477 memset(&q, 0, sizeof(q));
478 q.size = sizeof(q);
479 /* Find the VMA at the split address */
480 q.query_addr = (unsigned long long)addr;
481 q.query_flags = 0;
482 if (ioctl(maps_fd, PROCMAP_QUERY, &q))
483 return false;
484
485 *vma_start = q.vma_start;
486 *vma_end = q.vma_end;
487
488 return true;
489 }
490
split_vma(FIXTURE_DATA (proc_maps_race)* self)491 static inline bool split_vma(FIXTURE_DATA(proc_maps_race) *self)
492 {
493 /* PROT_NONE differs from both readable neighbors. */
494 return mmap(self->mod_info->addr, self->page_size, PROT_NONE,
495 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED;
496 }
497
merge_vma(FIXTURE_DATA (proc_maps_race)* self)498 static inline bool merge_vma(FIXTURE_DATA(proc_maps_race) *self)
499 {
500 return mmap(self->mod_info->addr, self->page_size, self->mod_info->prot,
501 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED;
502 }
503
check_split_result(struct line_content * mod_last_line,struct line_content * mod_first_line,struct line_content * restored_last_line,struct line_content * restored_first_line)504 static inline bool check_split_result(struct line_content *mod_last_line,
505 struct line_content *mod_first_line,
506 struct line_content *restored_last_line,
507 struct line_content *restored_first_line)
508 {
509 /* Make sure vmas at the boundaries are changing */
510 return strcmp(mod_last_line->text, restored_last_line->text) != 0 &&
511 strcmp(mod_first_line->text, restored_first_line->text) != 0;
512 }
513
shrink_vma(FIXTURE_DATA (proc_maps_race)* self)514 static inline bool shrink_vma(FIXTURE_DATA(proc_maps_race) *self)
515 {
516 return mremap(self->mod_info->addr, self->page_size * 3,
517 self->page_size, 0) != MAP_FAILED;
518 }
519
expand_vma(FIXTURE_DATA (proc_maps_race)* self)520 static inline bool expand_vma(FIXTURE_DATA(proc_maps_race) *self)
521 {
522 return mremap(self->mod_info->addr, self->page_size,
523 self->page_size * 3, 0) != MAP_FAILED;
524 }
525
check_shrink_result(struct line_content * mod_last_line,struct line_content * mod_first_line,struct line_content * restored_last_line,struct line_content * restored_first_line)526 static inline bool check_shrink_result(struct line_content *mod_last_line,
527 struct line_content *mod_first_line,
528 struct line_content *restored_last_line,
529 struct line_content *restored_first_line)
530 {
531 /* Make sure only the last vma of the first page is changing */
532 return strcmp(mod_last_line->text, restored_last_line->text) != 0 &&
533 strcmp(mod_first_line->text, restored_first_line->text) == 0;
534 }
535
remap_vma(FIXTURE_DATA (proc_maps_race)* self)536 static inline bool remap_vma(FIXTURE_DATA(proc_maps_race) *self)
537 {
538 /*
539 * Remap the last page of the next vma into the middle of the vma.
540 * This splits the current vma and the first and middle parts (the
541 * parts at lower addresses) become the last vma objserved in the
542 * first page and the first vma observed in the last page.
543 */
544 return mremap(self->mod_info->next_addr + self->page_size * 2, self->page_size,
545 self->page_size, MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP,
546 self->mod_info->addr + self->page_size) != MAP_FAILED;
547 }
548
patch_vma(FIXTURE_DATA (proc_maps_race)* self)549 static inline bool patch_vma(FIXTURE_DATA(proc_maps_race) *self)
550 {
551 return mprotect(self->mod_info->addr + self->page_size, self->page_size,
552 self->mod_info->prot) == 0;
553 }
554
check_remap_result(struct line_content * mod_last_line,struct line_content * mod_first_line,struct line_content * restored_last_line,struct line_content * restored_first_line)555 static inline bool check_remap_result(struct line_content *mod_last_line,
556 struct line_content *mod_first_line,
557 struct line_content *restored_last_line,
558 struct line_content *restored_first_line)
559 {
560 /* Make sure vmas at the boundaries are changing */
561 return strcmp(mod_last_line->text, restored_last_line->text) != 0 &&
562 strcmp(mod_first_line->text, restored_first_line->text) != 0;
563 }
564
FIXTURE_SETUP(proc_maps_race)565 FIXTURE_SETUP(proc_maps_race)
566 {
567 const char *verbose = getenv("VERBOSE");
568 const char *duration = getenv("DURATION");
569 struct vma_modifier_info *mod_info;
570 pthread_mutexattr_t mutex_attr;
571 pthread_condattr_t cond_attr;
572 unsigned long first_map_addr;
573 unsigned long last_map_addr;
574 unsigned long duration_sec;
575 char fname[32];
576
577 self->page_size = (unsigned long)sysconf(_SC_PAGESIZE);
578 self->verbose = verbose && !strncmp(verbose, "1", 1);
579 self->maps_file = variant->maps_file;
580 duration_sec = duration ? atol(duration) : 0;
581 self->duration_sec = duration_sec ? duration_sec : 5UL;
582
583 /*
584 * Have to map enough vmas for /proc/pid/maps to contain more than one
585 * page worth of vmas. Assume at least 32 bytes per line in maps output
586 */
587 self->vma_count = self->page_size / 32 + 1;
588 self->shared_mem_size = sizeof(struct vma_modifier_info) + self->vma_count * sizeof(void *);
589
590 /* map shared memory for communication with the child process */
591 self->mod_info = (struct vma_modifier_info *)mmap(NULL, self->shared_mem_size,
592 PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
593 ASSERT_NE(self->mod_info, MAP_FAILED);
594 mod_info = self->mod_info;
595
596 /* Initialize shared members */
597 pthread_mutexattr_init(&mutex_attr);
598 pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
599 ASSERT_EQ(pthread_mutex_init(&mod_info->sync_lock, &mutex_attr), 0);
600 pthread_condattr_init(&cond_attr);
601 pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
602 ASSERT_EQ(pthread_cond_init(&mod_info->sync_cond, &cond_attr), 0);
603 mod_info->vma_count = self->vma_count;
604 mod_info->curr_state = INIT;
605 mod_info->exit = false;
606
607 self->pid = fork();
608 if (!self->pid) {
609 /* Child process modifying the address space */
610 int prot = PROT_READ | PROT_WRITE;
611 int i;
612
613 for (i = 0; i < mod_info->vma_count; i++) {
614 mod_info->child_mapped_addr[i] = mmap(NULL, self->page_size * 3, prot,
615 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
616 ASSERT_NE(mod_info->child_mapped_addr[i], MAP_FAILED);
617 /* change protection in adjacent maps to prevent merging */
618 prot ^= PROT_WRITE;
619 }
620 signal_state(mod_info, CHILD_READY);
621 wait_for_state(mod_info, PARENT_READY);
622 while (true) {
623 signal_state(mod_info, SETUP_READY);
624 wait_for_state(mod_info, SETUP_MODIFY_MAPS);
625 if (mod_info->exit)
626 break;
627
628 ASSERT_TRUE(mod_info->vma_modify(self));
629 signal_state(mod_info, SETUP_MAPS_MODIFIED);
630 wait_for_state(mod_info, SETUP_RESTORE_MAPS);
631 ASSERT_TRUE(mod_info->vma_restore(self));
632 signal_state(mod_info, SETUP_MAPS_RESTORED);
633
634 wait_for_state(mod_info, TEST_READY);
635 while (mod_info->curr_state != TEST_DONE) {
636 ASSERT_TRUE(mod_info->vma_modify(self));
637 ASSERT_TRUE(mod_info->vma_restore(self));
638 }
639 }
640 for (i = 0; i < mod_info->vma_count; i++)
641 munmap(mod_info->child_mapped_addr[i], self->page_size * 3);
642
643 exit(0);
644 }
645
646 switch (self->maps_file) {
647 case MAPS:
648 sprintf(fname, "/proc/%d/maps", self->pid);
649 break;
650 case SMAPS:
651 sprintf(fname, "/proc/%d/smaps", self->pid);
652 break;
653 default:
654 ksft_exit_fail();
655 }
656 self->maps_fd = open(fname, O_RDONLY);
657 ASSERT_NE(self->maps_fd, -1);
658
659 /* Wait for the child to map the VMAs */
660 wait_for_state(mod_info, CHILD_READY);
661
662 /* Read first two pages */
663 self->page1.data = malloc(self->page_size);
664 ASSERT_NE(self->page1.data, NULL);
665 self->page2.data = malloc(self->page_size);
666 ASSERT_NE(self->page2.data, NULL);
667
668 first_map_addr = (unsigned long)mod_info->child_mapped_addr[0];
669 last_map_addr = (unsigned long)mod_info->child_mapped_addr[mod_info->vma_count - 1];
670
671 self->skip_pages = locate_containing_page(self,
672 min(first_map_addr, last_map_addr),
673 self->page_size * 3);
674 ASSERT_NE(self->skip_pages, -1);
675 ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line));
676
677 /*
678 * Find the addresses corresponding to the last line in the first page
679 * and the first line in the last page.
680 */
681 mod_info->addr = NULL;
682 mod_info->next_addr = NULL;
683 for (int i = 0; i < mod_info->vma_count; i++) {
684 if (mod_info->child_mapped_addr[i] == (void *)self->last_line.start_addr) {
685 mod_info->addr = mod_info->child_mapped_addr[i];
686 mod_info->prot = PROT_READ;
687 /* Even VMAs have write permission */
688 if ((i % 2) == 0)
689 mod_info->prot |= PROT_WRITE;
690 } else if (mod_info->child_mapped_addr[i] == (void *)self->first_line.start_addr) {
691 mod_info->next_addr = mod_info->child_mapped_addr[i];
692 }
693
694 if (mod_info->addr && mod_info->next_addr)
695 break;
696 }
697 ASSERT_TRUE(mod_info->addr && mod_info->next_addr);
698
699 signal_state(mod_info, PARENT_READY);
700 }
701
FIXTURE_TEARDOWN(proc_maps_race)702 FIXTURE_TEARDOWN(proc_maps_race)
703 {
704 int status;
705
706 stop_vma_modifier(self->mod_info);
707
708 free(self->page2.data);
709 free(self->page1.data);
710
711 for (int i = 0; i < self->vma_count; i++)
712 munmap(self->mod_info->child_mapped_addr[i], self->page_size);
713 close(self->maps_fd);
714 waitpid(self->pid, &status, 0);
715 munmap(self->mod_info, self->shared_mem_size);
716 }
717
TEST_F(proc_maps_race,test_maps_tearing_from_split)718 TEST_F(proc_maps_race, test_maps_tearing_from_split)
719 {
720 struct vma_modifier_info *mod_info = self->mod_info;
721
722 struct line_content split_last_line;
723 struct line_content split_first_line;
724 struct line_content restored_last_line;
725 struct line_content restored_first_line;
726
727 wait_for_state(mod_info, SETUP_READY);
728
729 /* re-read the file to avoid using stale data from previous test */
730 ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line));
731
732 mod_info->vma_modify = split_vma;
733 mod_info->vma_restore = merge_vma;
734 mod_info->vma_mod_check = check_split_result;
735
736 report_test_start("Tearing from split", self->verbose);
737 ASSERT_TRUE(capture_mod_pattern(self, &split_last_line, &split_first_line,
738 &restored_last_line, &restored_first_line));
739
740 /* Now start concurrent modifications for self->duration_sec */
741 signal_state(mod_info, TEST_READY);
742
743 struct line_content new_last_line;
744 struct line_content new_first_line;
745 struct timespec start_ts, end_ts;
746
747 clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts);
748 start_test_loop(&start_ts, self->verbose);
749 do {
750 bool last_line_changed;
751 bool first_line_changed;
752 unsigned long vma_start;
753 unsigned long vma_end;
754
755 ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line));
756
757 /* Check if we read vmas after split */
758 if (!strcmp(new_last_line.text, split_last_line.text)) {
759 /*
760 * The vmas should be consistent with split results,
761 * however if vma was concurrently restored after a
762 * split, it can be reported twice (first the original
763 * split one, then the same vma but extended after the
764 * merge) because we found it as the next vma again.
765 * In that case new first line will be the same as the
766 * last restored line.
767 */
768 ASSERT_FALSE(print_boundaries_on(
769 strcmp(new_first_line.text, split_first_line.text) &&
770 strcmp(new_first_line.text, restored_last_line.text),
771 "Split result invalid", self));
772 } else {
773 /* The vmas should be consistent with merge results */
774 ASSERT_FALSE(print_boundaries_on(
775 strcmp(new_last_line.text, restored_last_line.text),
776 "Merge result invalid", self));
777 ASSERT_FALSE(print_boundaries_on(
778 strcmp(new_first_line.text, restored_first_line.text),
779 "Merge result invalid", self));
780 }
781 /*
782 * First and last lines should change in unison. If the last
783 * line changed then the first line should change as well and
784 * vice versa.
785 */
786 last_line_changed = strcmp(new_last_line.text, self->last_line.text) != 0;
787 first_line_changed = strcmp(new_first_line.text, self->first_line.text) != 0;
788 ASSERT_EQ(last_line_changed, first_line_changed);
789 if (self->maps_file == MAPS) {
790 /* Check if PROCMAP_QUERY ioclt() finds the right VMA */
791 ASSERT_TRUE(query_addr_at(self->maps_fd, mod_info->addr + self->page_size,
792 &vma_start, &vma_end));
793 /*
794 * The vma at the split address can be either the same as
795 * original one (if read before the split) or the same as the
796 * first line in the second page (if read after the split).
797 */
798 ASSERT_TRUE((vma_start == self->last_line.start_addr &&
799 vma_end == self->last_line.end_addr) ||
800 (vma_start == split_first_line.start_addr &&
801 vma_end == split_first_line.end_addr));
802 }
803 clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts);
804 end_test_iteration(&end_ts, self->verbose);
805 } while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec);
806 end_test_loop(self->verbose);
807
808 /* Signal the modifyer thread to stop and wait until it exits */
809 signal_state(mod_info, TEST_DONE);
810 }
811
TEST_F(proc_maps_race,test_maps_tearing_from_resize)812 TEST_F(proc_maps_race, test_maps_tearing_from_resize)
813 {
814 struct vma_modifier_info *mod_info = self->mod_info;
815
816 struct line_content shrunk_last_line;
817 struct line_content shrunk_first_line;
818 struct line_content restored_last_line;
819 struct line_content restored_first_line;
820
821 wait_for_state(mod_info, SETUP_READY);
822
823 /* re-read the file to avoid using stale data from previous test */
824 ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line));
825
826 mod_info->vma_modify = shrink_vma;
827 mod_info->vma_restore = expand_vma;
828 mod_info->vma_mod_check = check_shrink_result;
829
830 report_test_start("Tearing from resize", self->verbose);
831 ASSERT_TRUE(capture_mod_pattern(self, &shrunk_last_line, &shrunk_first_line,
832 &restored_last_line, &restored_first_line));
833
834 /* Now start concurrent modifications for self->duration_sec */
835 signal_state(mod_info, TEST_READY);
836
837 struct line_content new_last_line;
838 struct line_content new_first_line;
839 struct timespec start_ts, end_ts;
840
841 clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts);
842 start_test_loop(&start_ts, self->verbose);
843 do {
844 unsigned long vma_start;
845 unsigned long vma_end;
846
847 ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line));
848
849 /* Check if we read vmas after shrinking it */
850 if (!strcmp(new_last_line.text, shrunk_last_line.text)) {
851 /*
852 * The vmas should be consistent with shrunk results,
853 * however if the vma was concurrently restored, it
854 * can be reported twice (first as shrunk one, then
855 * as restored one) because we found it as the next vma
856 * again. In that case new first line will be the same
857 * as the last restored line.
858 */
859 ASSERT_FALSE(print_boundaries_on(
860 strcmp(new_first_line.text, shrunk_first_line.text) &&
861 strcmp(new_first_line.text, restored_last_line.text),
862 "Shrink result invalid", self));
863 } else {
864 /* The vmas should be consistent with the original/resored state */
865 ASSERT_FALSE(print_boundaries_on(
866 strcmp(new_last_line.text, restored_last_line.text),
867 "Expand result invalid", self));
868 ASSERT_FALSE(print_boundaries_on(
869 strcmp(new_first_line.text, restored_first_line.text),
870 "Expand result invalid", self));
871 }
872 if (self->maps_file == MAPS) {
873 /* Check if PROCMAP_QUERY ioclt() finds the right VMA */
874 ASSERT_TRUE(query_addr_at(self->maps_fd, mod_info->addr,
875 &vma_start, &vma_end));
876 /*
877 * The vma should stay at the same address and have either the
878 * original size of 3 pages or 1 page if read after shrinking.
879 */
880 ASSERT_TRUE(vma_start == self->last_line.start_addr &&
881 (vma_end - vma_start == self->page_size * 3 ||
882 vma_end - vma_start == self->page_size));
883 }
884 clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts);
885 end_test_iteration(&end_ts, self->verbose);
886 } while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec);
887 end_test_loop(self->verbose);
888
889 /* Signal the modifyer thread to stop and wait until it exits */
890 signal_state(mod_info, TEST_DONE);
891 }
892
TEST_F(proc_maps_race,test_maps_tearing_from_remap)893 TEST_F(proc_maps_race, test_maps_tearing_from_remap)
894 {
895 struct vma_modifier_info *mod_info = self->mod_info;
896
897 struct line_content remapped_last_line;
898 struct line_content remapped_first_line;
899 struct line_content restored_last_line;
900 struct line_content restored_first_line;
901
902 wait_for_state(mod_info, SETUP_READY);
903
904 /* re-read the file to avoid using stale data from previous test */
905 ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line));
906
907 mod_info->vma_modify = remap_vma;
908 mod_info->vma_restore = patch_vma;
909 mod_info->vma_mod_check = check_remap_result;
910
911 report_test_start("Tearing from remap", self->verbose);
912 ASSERT_TRUE(capture_mod_pattern(self, &remapped_last_line, &remapped_first_line,
913 &restored_last_line, &restored_first_line));
914
915 /* Now start concurrent modifications for self->duration_sec */
916 signal_state(mod_info, TEST_READY);
917
918 struct line_content new_last_line;
919 struct line_content new_first_line;
920 struct timespec start_ts, end_ts;
921
922 clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts);
923 start_test_loop(&start_ts, self->verbose);
924 do {
925 unsigned long vma_start;
926 unsigned long vma_end;
927
928 ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line));
929
930 /* Check if we read vmas after remapping it */
931 if (!strcmp(new_last_line.text, remapped_last_line.text)) {
932 /*
933 * The vmas should be consistent with remap results,
934 * however if the vma was concurrently restored, it
935 * can be reported twice (first as split one, then
936 * as restored one) because we found it as the next vma
937 * again. In that case new first line will be the same
938 * as the last restored line.
939 */
940 ASSERT_FALSE(print_boundaries_on(
941 strcmp(new_first_line.text, remapped_first_line.text) &&
942 strcmp(new_first_line.text, restored_last_line.text),
943 "Remap result invalid", self));
944 } else {
945 /* The vmas should be consistent with the original/resored state */
946 ASSERT_FALSE(print_boundaries_on(
947 strcmp(new_last_line.text, restored_last_line.text),
948 "Remap restore result invalid", self));
949 ASSERT_FALSE(print_boundaries_on(
950 strcmp(new_first_line.text, restored_first_line.text),
951 "Remap restore result invalid", self));
952 }
953 if (self->maps_file == MAPS) {
954 /* Check if PROCMAP_QUERY ioclt() finds the right VMA */
955 ASSERT_TRUE(query_addr_at(self->maps_fd, mod_info->addr + self->page_size,
956 &vma_start, &vma_end));
957 /*
958 * The vma should either stay at the same address and have the
959 * original size of 3 pages or we should find the remapped vma
960 * at the remap destination address with size of 1 page.
961 */
962 ASSERT_TRUE((vma_start == self->last_line.start_addr &&
963 vma_end - vma_start == self->page_size * 3) ||
964 (vma_start == self->last_line.start_addr + self->page_size &&
965 vma_end - vma_start == self->page_size));
966 }
967 clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts);
968 end_test_iteration(&end_ts, self->verbose);
969 } while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec);
970 end_test_loop(self->verbose);
971
972 /* Signal the modifyer thread to stop and wait until it exits */
973 signal_state(mod_info, TEST_DONE);
974 }
975
976 TEST_HARNESS_MAIN
977