| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright 2022 Google LLC. |
| * Author: Suren Baghdasaryan <surenb@google.com> |
| * |
| * Permission to use, copy, modify, and distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| /* |
| * Fork a child that concurrently modifies address space while the main |
| * process is reading /proc/$PID/maps and verifying the results. Address |
| * space modifications include: |
| * VMA splitting and merging |
| * |
| */ |
| #define _GNU_SOURCE |
| #include "../kselftest_harness.h" |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| /* /proc/pid/maps parsing routines */ |
| struct page_content { |
| char *data; |
| ssize_t size; |
| }; |
| |
| #define LINE_MAX_SIZE 256 |
| |
| struct line_content { |
| char text[LINE_MAX_SIZE]; |
| unsigned long start_addr; |
| unsigned long end_addr; |
| }; |
| |
| enum test_state { |
| INIT, |
| CHILD_READY, |
| PARENT_READY, |
| SETUP_READY, |
| SETUP_MODIFY_MAPS, |
| SETUP_MAPS_MODIFIED, |
| SETUP_RESTORE_MAPS, |
| SETUP_MAPS_RESTORED, |
| TEST_READY, |
| TEST_DONE, |
| }; |
| |
| struct vma_modifier_info; |
| |
| FIXTURE(proc_maps_race) |
| { |
| struct vma_modifier_info *mod_info; |
| struct page_content page1; |
| struct page_content page2; |
| struct line_content last_line; |
| struct line_content first_line; |
| unsigned long duration_sec; |
| int shared_mem_size; |
| int page_size; |
| int vma_count; |
| bool verbose; |
| int maps_fd; |
| pid_t pid; |
| }; |
| |
| typedef bool (*vma_modifier_op)(FIXTURE_DATA(proc_maps_race) *self); |
| typedef bool (*vma_mod_result_check_op)(struct line_content *mod_last_line, |
| struct line_content *mod_first_line, |
| struct line_content *restored_last_line, |
| struct line_content *restored_first_line); |
| |
| struct vma_modifier_info { |
| int vma_count; |
| void *addr; |
| int prot; |
| void *next_addr; |
| vma_modifier_op vma_modify; |
| vma_modifier_op vma_restore; |
| vma_mod_result_check_op vma_mod_check; |
| pthread_mutex_t sync_lock; |
| pthread_cond_t sync_cond; |
| enum test_state curr_state; |
| bool exit; |
| void *child_mapped_addr[]; |
| }; |
| |
| |
| static bool read_two_pages(FIXTURE_DATA(proc_maps_race) *self) |
| { |
| ssize_t bytes_read; |
| |
| if (lseek(self->maps_fd, 0, SEEK_SET) < 0) |
| return false; |
| |
| bytes_read = read(self->maps_fd, self->page1.data, self->page_size); |
| if (bytes_read <= 0) |
| return false; |
| |
| self->page1.size = bytes_read; |
| |
| bytes_read = read(self->maps_fd, self->page2.data, self->page_size); |
| if (bytes_read <= 0) |
| return false; |
| |
| self->page2.size = bytes_read; |
| |
| return true; |
| } |
| |
| static void copy_first_line(struct page_content *page, char *first_line) |
| { |
| char *pos = strchr(page->data, '\n'); |
| |
| strncpy(first_line, page->data, pos - page->data); |
| first_line[pos - page->data] = '\0'; |
| } |
| |
| static void copy_last_line(struct page_content *page, char *last_line) |
| { |
| /* Get the last line in the first page */ |
| const char *end = page->data + page->size - 1; |
| /* skip last newline */ |
| const char *pos = end - 1; |
| |
| /* search previous newline */ |
| while (pos[-1] != '\n') |
| pos--; |
| strncpy(last_line, pos, end - pos); |
| last_line[end - pos] = '\0'; |
| } |
| |
| /* Read the last line of the first page and the first line of the second page */ |
| static bool read_boundary_lines(FIXTURE_DATA(proc_maps_race) *self, |
| struct line_content *last_line, |
| struct line_content *first_line) |
| { |
| if (!read_two_pages(self)) |
| return false; |
| |
| copy_last_line(&self->page1, last_line->text); |
| copy_first_line(&self->page2, first_line->text); |
| |
| return sscanf(last_line->text, "%lx-%lx", &last_line->start_addr, |
| &last_line->end_addr) == 2 && |
| sscanf(first_line->text, "%lx-%lx", &first_line->start_addr, |
| &first_line->end_addr) == 2; |
| } |
| |
| /* Thread synchronization routines */ |
| static void wait_for_state(struct vma_modifier_info *mod_info, enum test_state state) |
| { |
| pthread_mutex_lock(&mod_info->sync_lock); |
| while (mod_info->curr_state != state) |
| pthread_cond_wait(&mod_info->sync_cond, &mod_info->sync_lock); |
| pthread_mutex_unlock(&mod_info->sync_lock); |
| } |
| |
| static void signal_state(struct vma_modifier_info *mod_info, enum test_state state) |
| { |
| pthread_mutex_lock(&mod_info->sync_lock); |
| mod_info->curr_state = state; |
| pthread_cond_signal(&mod_info->sync_cond); |
| pthread_mutex_unlock(&mod_info->sync_lock); |
| } |
| |
| static void stop_vma_modifier(struct vma_modifier_info *mod_info) |
| { |
| wait_for_state(mod_info, SETUP_READY); |
| mod_info->exit = true; |
| signal_state(mod_info, SETUP_MODIFY_MAPS); |
| } |
| |
| static void print_first_lines(char *text, int nr) |
| { |
| const char *end = text; |
| |
| while (nr && (end = strchr(end, '\n')) != NULL) { |
| nr--; |
| end++; |
| } |
| |
| if (end) { |
| int offs = end - text; |
| |
| text[offs] = '\0'; |
| printf(text); |
| text[offs] = '\n'; |
| printf("\n"); |
| } else { |
| printf(text); |
| } |
| } |
| |
| static void print_last_lines(char *text, int nr) |
| { |
| const char *start = text + strlen(text); |
| |
| nr++; /* to ignore the last newline */ |
| while (nr) { |
| while (start > text && *start != '\n') |
| start--; |
| nr--; |
| start--; |
| } |
| printf(start); |
| } |
| |
| static void print_boundaries(const char *title, FIXTURE_DATA(proc_maps_race) *self) |
| { |
| if (!self->verbose) |
| return; |
| |
| printf("%s", title); |
| /* Print 3 boundary lines from each page */ |
| print_last_lines(self->page1.data, 3); |
| printf("-----------------page boundary-----------------\n"); |
| print_first_lines(self->page2.data, 3); |
| } |
| |
| static bool print_boundaries_on(bool condition, const char *title, |
| FIXTURE_DATA(proc_maps_race) *self) |
| { |
| if (self->verbose && condition) |
| print_boundaries(title, self); |
| |
| return condition; |
| } |
| |
| static void report_test_start(const char *name, bool verbose) |
| { |
| if (verbose) |
| printf("==== %s ====\n", name); |
| } |
| |
| static struct timespec print_ts; |
| |
| static void start_test_loop(struct timespec *ts, bool verbose) |
| { |
| if (verbose) |
| print_ts.tv_sec = ts->tv_sec; |
| } |
| |
| static void end_test_iteration(struct timespec *ts, bool verbose) |
| { |
| if (!verbose) |
| return; |
| |
| /* Update every second */ |
| if (print_ts.tv_sec == ts->tv_sec) |
| return; |
| |
| printf("."); |
| fflush(stdout); |
| print_ts.tv_sec = ts->tv_sec; |
| } |
| |
| static void end_test_loop(bool verbose) |
| { |
| if (verbose) |
| printf("\n"); |
| } |
| |
| static bool 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) |
| { |
| print_boundaries("Before modification", self); |
| |
| signal_state(self->mod_info, SETUP_MODIFY_MAPS); |
| wait_for_state(self->mod_info, SETUP_MAPS_MODIFIED); |
| |
| /* Copy last line of the first page and first line of the last page */ |
| if (!read_boundary_lines(self, mod_last_line, mod_first_line)) |
| return false; |
| |
| print_boundaries("After modification", self); |
| |
| signal_state(self->mod_info, SETUP_RESTORE_MAPS); |
| wait_for_state(self->mod_info, SETUP_MAPS_RESTORED); |
| |
| /* Copy last line of the first page and first line of the last page */ |
| if (!read_boundary_lines(self, restored_last_line, restored_first_line)) |
| return false; |
| |
| print_boundaries("After restore", self); |
| |
| if (!self->mod_info->vma_mod_check(mod_last_line, mod_first_line, |
| restored_last_line, restored_first_line)) |
| return false; |
| |
| /* |
| * The content of these lines after modify+resore should be the same |
| * as the original. |
| */ |
| return strcmp(restored_last_line->text, self->last_line.text) == 0 && |
| strcmp(restored_first_line->text, self->first_line.text) == 0; |
| } |
| |
| static inline bool split_vma(FIXTURE_DATA(proc_maps_race) *self) |
| { |
| return mmap(self->mod_info->addr, self->page_size, self->mod_info->prot | PROT_EXEC, |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED; |
| } |
| |
| static inline bool merge_vma(FIXTURE_DATA(proc_maps_race) *self) |
| { |
| return mmap(self->mod_info->addr, self->page_size, self->mod_info->prot, |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED; |
| } |
| |
| static inline bool 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) |
| { |
| /* Make sure vmas at the boundaries are changing */ |
| return strcmp(mod_last_line->text, restored_last_line->text) != 0 && |
| strcmp(mod_first_line->text, restored_first_line->text) != 0; |
| } |
| |
| static inline bool shrink_vma(FIXTURE_DATA(proc_maps_race) *self) |
| { |
| return mremap(self->mod_info->addr, self->page_size * 3, |
| self->page_size, 0) != MAP_FAILED; |
| } |
| |
| static inline bool expand_vma(FIXTURE_DATA(proc_maps_race) *self) |
| { |
| return mremap(self->mod_info->addr, self->page_size, |
| self->page_size * 3, 0) != MAP_FAILED; |
| } |
| |
| static inline bool 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) |
| { |
| /* Make sure only the last vma of the first page is changing */ |
| return strcmp(mod_last_line->text, restored_last_line->text) != 0 && |
| strcmp(mod_first_line->text, restored_first_line->text) == 0; |
| } |
| |
| static inline bool remap_vma(FIXTURE_DATA(proc_maps_race) *self) |
| { |
| /* |
| * Remap the last page of the next vma into the middle of the vma. |
| * This splits the current vma and the first and middle parts (the |
| * parts at lower addresses) become the last vma objserved in the |
| * first page and the first vma observed in the last page. |
| */ |
| return mremap(self->mod_info->next_addr + self->page_size * 2, self->page_size, |
| self->page_size, MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP, |
| self->mod_info->addr + self->page_size) != MAP_FAILED; |
| } |
| |
| static inline bool patch_vma(FIXTURE_DATA(proc_maps_race) *self) |
| { |
| return mprotect(self->mod_info->addr + self->page_size, self->page_size, |
| self->mod_info->prot) == 0; |
| } |
| |
| static inline bool 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) |
| { |
| /* Make sure vmas at the boundaries are changing */ |
| return strcmp(mod_last_line->text, restored_last_line->text) != 0 && |
| strcmp(mod_first_line->text, restored_first_line->text) != 0; |
| } |
| |
| FIXTURE_SETUP(proc_maps_race) |
| { |
| const char *verbose = getenv("VERBOSE"); |
| const char *duration = getenv("DURATION"); |
| struct vma_modifier_info *mod_info; |
| pthread_mutexattr_t mutex_attr; |
| pthread_condattr_t cond_attr; |
| unsigned long duration_sec; |
| char fname[32]; |
| |
| self->page_size = (unsigned long)sysconf(_SC_PAGESIZE); |
| self->verbose = verbose && !strncmp(verbose, "1", 1); |
| duration_sec = duration ? atol(duration) : 0; |
| self->duration_sec = duration_sec ? duration_sec : 5UL; |
| |
| /* |
| * Have to map enough vmas for /proc/pid/maps to contain more than one |
| * page worth of vmas. Assume at least 32 bytes per line in maps output |
| */ |
| self->vma_count = self->page_size / 32 + 1; |
| self->shared_mem_size = sizeof(struct vma_modifier_info) + self->vma_count * sizeof(void *); |
| |
| /* map shared memory for communication with the child process */ |
| self->mod_info = (struct vma_modifier_info *)mmap(NULL, self->shared_mem_size, |
| PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); |
| ASSERT_NE(self->mod_info, MAP_FAILED); |
| mod_info = self->mod_info; |
| |
| /* Initialize shared members */ |
| pthread_mutexattr_init(&mutex_attr); |
| pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED); |
| ASSERT_EQ(pthread_mutex_init(&mod_info->sync_lock, &mutex_attr), 0); |
| pthread_condattr_init(&cond_attr); |
| pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED); |
| ASSERT_EQ(pthread_cond_init(&mod_info->sync_cond, &cond_attr), 0); |
| mod_info->vma_count = self->vma_count; |
| mod_info->curr_state = INIT; |
| mod_info->exit = false; |
| |
| self->pid = fork(); |
| if (!self->pid) { |
| /* Child process modifying the address space */ |
| int prot = PROT_READ | PROT_WRITE; |
| int i; |
| |
| for (i = 0; i < mod_info->vma_count; i++) { |
| mod_info->child_mapped_addr[i] = mmap(NULL, self->page_size * 3, prot, |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| ASSERT_NE(mod_info->child_mapped_addr[i], MAP_FAILED); |
| /* change protection in adjacent maps to prevent merging */ |
| prot ^= PROT_WRITE; |
| } |
| signal_state(mod_info, CHILD_READY); |
| wait_for_state(mod_info, PARENT_READY); |
| while (true) { |
| signal_state(mod_info, SETUP_READY); |
| wait_for_state(mod_info, SETUP_MODIFY_MAPS); |
| if (mod_info->exit) |
| break; |
| |
| ASSERT_TRUE(mod_info->vma_modify(self)); |
| signal_state(mod_info, SETUP_MAPS_MODIFIED); |
| wait_for_state(mod_info, SETUP_RESTORE_MAPS); |
| ASSERT_TRUE(mod_info->vma_restore(self)); |
| signal_state(mod_info, SETUP_MAPS_RESTORED); |
| |
| wait_for_state(mod_info, TEST_READY); |
| while (mod_info->curr_state != TEST_DONE) { |
| ASSERT_TRUE(mod_info->vma_modify(self)); |
| ASSERT_TRUE(mod_info->vma_restore(self)); |
| } |
| } |
| for (i = 0; i < mod_info->vma_count; i++) |
| munmap(mod_info->child_mapped_addr[i], self->page_size * 3); |
| |
| exit(0); |
| } |
| |
| sprintf(fname, "/proc/%d/maps", self->pid); |
| self->maps_fd = open(fname, O_RDONLY); |
| ASSERT_NE(self->maps_fd, -1); |
| |
| /* Wait for the child to map the VMAs */ |
| wait_for_state(mod_info, CHILD_READY); |
| |
| /* Read first two pages */ |
| self->page1.data = malloc(self->page_size); |
| ASSERT_NE(self->page1.data, NULL); |
| self->page2.data = malloc(self->page_size); |
| ASSERT_NE(self->page2.data, NULL); |
| |
| ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line)); |
| |
| /* |
| * Find the addresses corresponding to the last line in the first page |
| * and the first line in the last page. |
| */ |
| mod_info->addr = NULL; |
| mod_info->next_addr = NULL; |
| for (int i = 0; i < mod_info->vma_count; i++) { |
| if (mod_info->child_mapped_addr[i] == (void *)self->last_line.start_addr) { |
| mod_info->addr = mod_info->child_mapped_addr[i]; |
| mod_info->prot = PROT_READ; |
| /* Even VMAs have write permission */ |
| if ((i % 2) == 0) |
| mod_info->prot |= PROT_WRITE; |
| } else if (mod_info->child_mapped_addr[i] == (void *)self->first_line.start_addr) { |
| mod_info->next_addr = mod_info->child_mapped_addr[i]; |
| } |
| |
| if (mod_info->addr && mod_info->next_addr) |
| break; |
| } |
| ASSERT_TRUE(mod_info->addr && mod_info->next_addr); |
| |
| signal_state(mod_info, PARENT_READY); |
| |
| } |
| |
| FIXTURE_TEARDOWN(proc_maps_race) |
| { |
| int status; |
| |
| stop_vma_modifier(self->mod_info); |
| |
| free(self->page2.data); |
| free(self->page1.data); |
| |
| for (int i = 0; i < self->vma_count; i++) |
| munmap(self->mod_info->child_mapped_addr[i], self->page_size); |
| close(self->maps_fd); |
| waitpid(self->pid, &status, 0); |
| munmap(self->mod_info, self->shared_mem_size); |
| } |
| |
| TEST_F(proc_maps_race, test_maps_tearing_from_split) |
| { |
| struct vma_modifier_info *mod_info = self->mod_info; |
| |
| struct line_content split_last_line; |
| struct line_content split_first_line; |
| struct line_content restored_last_line; |
| struct line_content restored_first_line; |
| |
| wait_for_state(mod_info, SETUP_READY); |
| |
| /* re-read the file to avoid using stale data from previous test */ |
| ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line)); |
| |
| mod_info->vma_modify = split_vma; |
| mod_info->vma_restore = merge_vma; |
| mod_info->vma_mod_check = check_split_result; |
| |
| report_test_start("Tearing from split", self->verbose); |
| ASSERT_TRUE(capture_mod_pattern(self, &split_last_line, &split_first_line, |
| &restored_last_line, &restored_first_line)); |
| |
| /* Now start concurrent modifications for self->duration_sec */ |
| signal_state(mod_info, TEST_READY); |
| |
| struct line_content new_last_line; |
| struct line_content new_first_line; |
| struct timespec start_ts, end_ts; |
| |
| clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts); |
| start_test_loop(&start_ts, self->verbose); |
| do { |
| bool last_line_changed; |
| bool first_line_changed; |
| |
| ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line)); |
| |
| /* Check if we read vmas after split */ |
| if (!strcmp(new_last_line.text, split_last_line.text)) { |
| /* |
| * The vmas should be consistent with split results, |
| * however if vma was concurrently restored after a |
| * split, it can be reported twice (first the original |
| * split one, then the same vma but extended after the |
| * merge) because we found it as the next vma again. |
| * In that case new first line will be the same as the |
| * last restored line. |
| */ |
| ASSERT_FALSE(print_boundaries_on( |
| strcmp(new_first_line.text, split_first_line.text) && |
| strcmp(new_first_line.text, restored_last_line.text), |
| "Split result invalid", self)); |
| } else { |
| /* The vmas should be consistent with merge results */ |
| ASSERT_FALSE(print_boundaries_on( |
| strcmp(new_last_line.text, restored_last_line.text), |
| "Merge result invalid", self)); |
| ASSERT_FALSE(print_boundaries_on( |
| strcmp(new_first_line.text, restored_first_line.text), |
| "Merge result invalid", self)); |
| } |
| /* |
| * First and last lines should change in unison. If the last |
| * line changed then the first line should change as well and |
| * vice versa. |
| */ |
| last_line_changed = strcmp(new_last_line.text, self->last_line.text) != 0; |
| first_line_changed = strcmp(new_first_line.text, self->first_line.text) != 0; |
| ASSERT_EQ(last_line_changed, first_line_changed); |
| |
| clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts); |
| end_test_iteration(&end_ts, self->verbose); |
| } while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec); |
| end_test_loop(self->verbose); |
| |
| /* Signal the modifyer thread to stop and wait until it exits */ |
| signal_state(mod_info, TEST_DONE); |
| } |
| |
| TEST_F(proc_maps_race, test_maps_tearing_from_resize) |
| { |
| struct vma_modifier_info *mod_info = self->mod_info; |
| |
| struct line_content shrunk_last_line; |
| struct line_content shrunk_first_line; |
| struct line_content restored_last_line; |
| struct line_content restored_first_line; |
| |
| wait_for_state(mod_info, SETUP_READY); |
| |
| /* re-read the file to avoid using stale data from previous test */ |
| ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line)); |
| |
| mod_info->vma_modify = shrink_vma; |
| mod_info->vma_restore = expand_vma; |
| mod_info->vma_mod_check = check_shrink_result; |
| |
| report_test_start("Tearing from resize", self->verbose); |
| ASSERT_TRUE(capture_mod_pattern(self, &shrunk_last_line, &shrunk_first_line, |
| &restored_last_line, &restored_first_line)); |
| |
| /* Now start concurrent modifications for self->duration_sec */ |
| signal_state(mod_info, TEST_READY); |
| |
| struct line_content new_last_line; |
| struct line_content new_first_line; |
| struct timespec start_ts, end_ts; |
| |
| clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts); |
| start_test_loop(&start_ts, self->verbose); |
| do { |
| ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line)); |
| |
| /* Check if we read vmas after shrinking it */ |
| if (!strcmp(new_last_line.text, shrunk_last_line.text)) { |
| /* |
| * The vmas should be consistent with shrunk results, |
| * however if the vma was concurrently restored, it |
| * can be reported twice (first as shrunk one, then |
| * as restored one) because we found it as the next vma |
| * again. In that case new first line will be the same |
| * as the last restored line. |
| */ |
| ASSERT_FALSE(print_boundaries_on( |
| strcmp(new_first_line.text, shrunk_first_line.text) && |
| strcmp(new_first_line.text, restored_last_line.text), |
| "Shrink result invalid", self)); |
| } else { |
| /* The vmas should be consistent with the original/resored state */ |
| ASSERT_FALSE(print_boundaries_on( |
| strcmp(new_last_line.text, restored_last_line.text), |
| "Expand result invalid", self)); |
| ASSERT_FALSE(print_boundaries_on( |
| strcmp(new_first_line.text, restored_first_line.text), |
| "Expand result invalid", self)); |
| } |
| |
| clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts); |
| end_test_iteration(&end_ts, self->verbose); |
| } while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec); |
| end_test_loop(self->verbose); |
| |
| /* Signal the modifyer thread to stop and wait until it exits */ |
| signal_state(mod_info, TEST_DONE); |
| } |
| |
| TEST_F(proc_maps_race, test_maps_tearing_from_remap) |
| { |
| struct vma_modifier_info *mod_info = self->mod_info; |
| |
| struct line_content remapped_last_line; |
| struct line_content remapped_first_line; |
| struct line_content restored_last_line; |
| struct line_content restored_first_line; |
| |
| wait_for_state(mod_info, SETUP_READY); |
| |
| /* re-read the file to avoid using stale data from previous test */ |
| ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line)); |
| |
| mod_info->vma_modify = remap_vma; |
| mod_info->vma_restore = patch_vma; |
| mod_info->vma_mod_check = check_remap_result; |
| |
| report_test_start("Tearing from remap", self->verbose); |
| ASSERT_TRUE(capture_mod_pattern(self, &remapped_last_line, &remapped_first_line, |
| &restored_last_line, &restored_first_line)); |
| |
| /* Now start concurrent modifications for self->duration_sec */ |
| signal_state(mod_info, TEST_READY); |
| |
| struct line_content new_last_line; |
| struct line_content new_first_line; |
| struct timespec start_ts, end_ts; |
| |
| clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts); |
| start_test_loop(&start_ts, self->verbose); |
| do { |
| ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line)); |
| |
| /* Check if we read vmas after remapping it */ |
| if (!strcmp(new_last_line.text, remapped_last_line.text)) { |
| /* |
| * The vmas should be consistent with remap results, |
| * however if the vma was concurrently restored, it |
| * can be reported twice (first as split one, then |
| * as restored one) because we found it as the next vma |
| * again. In that case new first line will be the same |
| * as the last restored line. |
| */ |
| ASSERT_FALSE(print_boundaries_on( |
| strcmp(new_first_line.text, remapped_first_line.text) && |
| strcmp(new_first_line.text, restored_last_line.text), |
| "Remap result invalid", self)); |
| } else { |
| /* The vmas should be consistent with the original/resored state */ |
| ASSERT_FALSE(print_boundaries_on( |
| strcmp(new_last_line.text, restored_last_line.text), |
| "Remap restore result invalid", self)); |
| ASSERT_FALSE(print_boundaries_on( |
| strcmp(new_first_line.text, restored_first_line.text), |
| "Remap restore result invalid", self)); |
| } |
| |
| clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts); |
| end_test_iteration(&end_ts, self->verbose); |
| } while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec); |
| end_test_loop(self->verbose); |
| |
| /* Signal the modifyer thread to stop and wait until it exits */ |
| signal_state(mod_info, TEST_DONE); |
| } |
| |
| TEST_HARNESS_MAIN |