// 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>
staticvoid copy_last_line(struct page_content *page, char *last_line)
{ /* Get the last line in the first page */ constchar *end = page->data + page->size - 1; /* skip last newline */ constchar *pos = end - 1;
/* Read the last line of the first page and the first line of the second page */ staticbool read_boundary_lines(FIXTURE_DATA(proc_maps_race) *self, struct line_content *last_line, struct line_content *first_line)
{ if (!read_two_pages(self)) returnfalse;
/* 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)) returnfalse;
print_boundaries("After restore", self);
if (!self->mod_info->vma_mod_check(mod_last_line, mod_first_line,
restored_last_line, restored_first_line)) returnfalse;
/* * 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;
}
staticinlinebool 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;
}
staticinlinebool 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;
}
/* * 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;
/* * 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;
} elseif (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);
}
/* 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, &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, &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));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.