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 verifying the results. Address 21 * 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 <sys/mman.h> 36 #include <sys/stat.h> 37 #include <sys/types.h> 38 #include <sys/wait.h> 39 40 /* /proc/pid/maps parsing routines */ 41 struct page_content { 42 char *data; 43 ssize_t size; 44 }; 45 46 #define LINE_MAX_SIZE 256 47 48 struct line_content { 49 char text[LINE_MAX_SIZE]; 50 unsigned long start_addr; 51 unsigned long end_addr; 52 }; 53 54 enum test_state { 55 INIT, 56 CHILD_READY, 57 PARENT_READY, 58 SETUP_READY, 59 SETUP_MODIFY_MAPS, 60 SETUP_MAPS_MODIFIED, 61 SETUP_RESTORE_MAPS, 62 SETUP_MAPS_RESTORED, 63 TEST_READY, 64 TEST_DONE, 65 }; 66 67 struct vma_modifier_info; 68 69 FIXTURE(proc_maps_race) 70 { 71 struct vma_modifier_info *mod_info; 72 struct page_content page1; 73 struct page_content page2; 74 struct line_content last_line; 75 struct line_content first_line; 76 unsigned long duration_sec; 77 int shared_mem_size; 78 int page_size; 79 int vma_count; 80 int maps_fd; 81 pid_t pid; 82 }; 83 84 typedef bool (*vma_modifier_op)(FIXTURE_DATA(proc_maps_race) *self); 85 typedef bool (*vma_mod_result_check_op)(struct line_content *mod_last_line, 86 struct line_content *mod_first_line, 87 struct line_content *restored_last_line, 88 struct line_content *restored_first_line); 89 90 struct vma_modifier_info { 91 int vma_count; 92 void *addr; 93 int prot; 94 void *next_addr; 95 vma_modifier_op vma_modify; 96 vma_modifier_op vma_restore; 97 vma_mod_result_check_op vma_mod_check; 98 pthread_mutex_t sync_lock; 99 pthread_cond_t sync_cond; 100 enum test_state curr_state; 101 bool exit; 102 void *child_mapped_addr[]; 103 }; 104 105 106 static bool read_two_pages(FIXTURE_DATA(proc_maps_race) *self) 107 { 108 ssize_t bytes_read; 109 110 if (lseek(self->maps_fd, 0, SEEK_SET) < 0) 111 return false; 112 113 bytes_read = read(self->maps_fd, self->page1.data, self->page_size); 114 if (bytes_read <= 0) 115 return false; 116 117 self->page1.size = bytes_read; 118 119 bytes_read = read(self->maps_fd, self->page2.data, self->page_size); 120 if (bytes_read <= 0) 121 return false; 122 123 self->page2.size = bytes_read; 124 125 return true; 126 } 127 128 static void copy_first_line(struct page_content *page, char *first_line) 129 { 130 char *pos = strchr(page->data, '\n'); 131 132 strncpy(first_line, page->data, pos - page->data); 133 first_line[pos - page->data] = '\0'; 134 } 135 136 static void copy_last_line(struct page_content *page, char *last_line) 137 { 138 /* Get the last line in the first page */ 139 const char *end = page->data + page->size - 1; 140 /* skip last newline */ 141 const char *pos = end - 1; 142 143 /* search previous newline */ 144 while (pos[-1] != '\n') 145 pos--; 146 strncpy(last_line, pos, end - pos); 147 last_line[end - pos] = '\0'; 148 } 149 150 /* Read the last line of the first page and the first line of the second page */ 151 static bool read_boundary_lines(FIXTURE_DATA(proc_maps_race) *self, 152 struct line_content *last_line, 153 struct line_content *first_line) 154 { 155 if (!read_two_pages(self)) 156 return false; 157 158 copy_last_line(&self->page1, last_line->text); 159 copy_first_line(&self->page2, first_line->text); 160 161 return sscanf(last_line->text, "%lx-%lx", &last_line->start_addr, 162 &last_line->end_addr) == 2 && 163 sscanf(first_line->text, "%lx-%lx", &first_line->start_addr, 164 &first_line->end_addr) == 2; 165 } 166 167 /* Thread synchronization routines */ 168 static void wait_for_state(struct vma_modifier_info *mod_info, enum test_state state) 169 { 170 pthread_mutex_lock(&mod_info->sync_lock); 171 while (mod_info->curr_state != state) 172 pthread_cond_wait(&mod_info->sync_cond, &mod_info->sync_lock); 173 pthread_mutex_unlock(&mod_info->sync_lock); 174 } 175 176 static void signal_state(struct vma_modifier_info *mod_info, enum test_state state) 177 { 178 pthread_mutex_lock(&mod_info->sync_lock); 179 mod_info->curr_state = state; 180 pthread_cond_signal(&mod_info->sync_cond); 181 pthread_mutex_unlock(&mod_info->sync_lock); 182 } 183 184 static void stop_vma_modifier(struct vma_modifier_info *mod_info) 185 { 186 wait_for_state(mod_info, SETUP_READY); 187 mod_info->exit = true; 188 signal_state(mod_info, SETUP_MODIFY_MAPS); 189 } 190 191 static bool capture_mod_pattern(FIXTURE_DATA(proc_maps_race) *self, 192 struct line_content *mod_last_line, 193 struct line_content *mod_first_line, 194 struct line_content *restored_last_line, 195 struct line_content *restored_first_line) 196 { 197 signal_state(self->mod_info, SETUP_MODIFY_MAPS); 198 wait_for_state(self->mod_info, SETUP_MAPS_MODIFIED); 199 200 /* Copy last line of the first page and first line of the last page */ 201 if (!read_boundary_lines(self, mod_last_line, mod_first_line)) 202 return false; 203 204 signal_state(self->mod_info, SETUP_RESTORE_MAPS); 205 wait_for_state(self->mod_info, SETUP_MAPS_RESTORED); 206 207 /* Copy last line of the first page and first line of the last page */ 208 if (!read_boundary_lines(self, restored_last_line, restored_first_line)) 209 return false; 210 211 if (!self->mod_info->vma_mod_check(mod_last_line, mod_first_line, 212 restored_last_line, restored_first_line)) 213 return false; 214 215 /* 216 * The content of these lines after modify+resore should be the same 217 * as the original. 218 */ 219 return strcmp(restored_last_line->text, self->last_line.text) == 0 && 220 strcmp(restored_first_line->text, self->first_line.text) == 0; 221 } 222 223 static inline bool split_vma(FIXTURE_DATA(proc_maps_race) *self) 224 { 225 return mmap(self->mod_info->addr, self->page_size, self->mod_info->prot | PROT_EXEC, 226 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED; 227 } 228 229 static inline bool merge_vma(FIXTURE_DATA(proc_maps_race) *self) 230 { 231 return mmap(self->mod_info->addr, self->page_size, self->mod_info->prot, 232 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED; 233 } 234 235 static inline bool check_split_result(struct line_content *mod_last_line, 236 struct line_content *mod_first_line, 237 struct line_content *restored_last_line, 238 struct line_content *restored_first_line) 239 { 240 /* Make sure vmas at the boundaries are changing */ 241 return strcmp(mod_last_line->text, restored_last_line->text) != 0 && 242 strcmp(mod_first_line->text, restored_first_line->text) != 0; 243 } 244 245 FIXTURE_SETUP(proc_maps_race) 246 { 247 const char *duration = getenv("DURATION"); 248 struct vma_modifier_info *mod_info; 249 pthread_mutexattr_t mutex_attr; 250 pthread_condattr_t cond_attr; 251 unsigned long duration_sec; 252 char fname[32]; 253 254 self->page_size = (unsigned long)sysconf(_SC_PAGESIZE); 255 duration_sec = duration ? atol(duration) : 0; 256 self->duration_sec = duration_sec ? duration_sec : 5UL; 257 258 /* 259 * Have to map enough vmas for /proc/pid/maps to contain more than one 260 * page worth of vmas. Assume at least 32 bytes per line in maps output 261 */ 262 self->vma_count = self->page_size / 32 + 1; 263 self->shared_mem_size = sizeof(struct vma_modifier_info) + self->vma_count * sizeof(void *); 264 265 /* map shared memory for communication with the child process */ 266 self->mod_info = (struct vma_modifier_info *)mmap(NULL, self->shared_mem_size, 267 PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 268 ASSERT_NE(self->mod_info, MAP_FAILED); 269 mod_info = self->mod_info; 270 271 /* Initialize shared members */ 272 pthread_mutexattr_init(&mutex_attr); 273 pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED); 274 ASSERT_EQ(pthread_mutex_init(&mod_info->sync_lock, &mutex_attr), 0); 275 pthread_condattr_init(&cond_attr); 276 pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED); 277 ASSERT_EQ(pthread_cond_init(&mod_info->sync_cond, &cond_attr), 0); 278 mod_info->vma_count = self->vma_count; 279 mod_info->curr_state = INIT; 280 mod_info->exit = false; 281 282 self->pid = fork(); 283 if (!self->pid) { 284 /* Child process modifying the address space */ 285 int prot = PROT_READ | PROT_WRITE; 286 int i; 287 288 for (i = 0; i < mod_info->vma_count; i++) { 289 mod_info->child_mapped_addr[i] = mmap(NULL, self->page_size * 3, prot, 290 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 291 ASSERT_NE(mod_info->child_mapped_addr[i], MAP_FAILED); 292 /* change protection in adjacent maps to prevent merging */ 293 prot ^= PROT_WRITE; 294 } 295 signal_state(mod_info, CHILD_READY); 296 wait_for_state(mod_info, PARENT_READY); 297 while (true) { 298 signal_state(mod_info, SETUP_READY); 299 wait_for_state(mod_info, SETUP_MODIFY_MAPS); 300 if (mod_info->exit) 301 break; 302 303 ASSERT_TRUE(mod_info->vma_modify(self)); 304 signal_state(mod_info, SETUP_MAPS_MODIFIED); 305 wait_for_state(mod_info, SETUP_RESTORE_MAPS); 306 ASSERT_TRUE(mod_info->vma_restore(self)); 307 signal_state(mod_info, SETUP_MAPS_RESTORED); 308 309 wait_for_state(mod_info, TEST_READY); 310 while (mod_info->curr_state != TEST_DONE) { 311 ASSERT_TRUE(mod_info->vma_modify(self)); 312 ASSERT_TRUE(mod_info->vma_restore(self)); 313 } 314 } 315 for (i = 0; i < mod_info->vma_count; i++) 316 munmap(mod_info->child_mapped_addr[i], self->page_size * 3); 317 318 exit(0); 319 } 320 321 sprintf(fname, "/proc/%d/maps", self->pid); 322 self->maps_fd = open(fname, O_RDONLY); 323 ASSERT_NE(self->maps_fd, -1); 324 325 /* Wait for the child to map the VMAs */ 326 wait_for_state(mod_info, CHILD_READY); 327 328 /* Read first two pages */ 329 self->page1.data = malloc(self->page_size); 330 ASSERT_NE(self->page1.data, NULL); 331 self->page2.data = malloc(self->page_size); 332 ASSERT_NE(self->page2.data, NULL); 333 334 ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line)); 335 336 /* 337 * Find the addresses corresponding to the last line in the first page 338 * and the first line in the last page. 339 */ 340 mod_info->addr = NULL; 341 mod_info->next_addr = NULL; 342 for (int i = 0; i < mod_info->vma_count; i++) { 343 if (mod_info->child_mapped_addr[i] == (void *)self->last_line.start_addr) { 344 mod_info->addr = mod_info->child_mapped_addr[i]; 345 mod_info->prot = PROT_READ; 346 /* Even VMAs have write permission */ 347 if ((i % 2) == 0) 348 mod_info->prot |= PROT_WRITE; 349 } else if (mod_info->child_mapped_addr[i] == (void *)self->first_line.start_addr) { 350 mod_info->next_addr = mod_info->child_mapped_addr[i]; 351 } 352 353 if (mod_info->addr && mod_info->next_addr) 354 break; 355 } 356 ASSERT_TRUE(mod_info->addr && mod_info->next_addr); 357 358 signal_state(mod_info, PARENT_READY); 359 360 } 361 362 FIXTURE_TEARDOWN(proc_maps_race) 363 { 364 int status; 365 366 stop_vma_modifier(self->mod_info); 367 368 free(self->page2.data); 369 free(self->page1.data); 370 371 for (int i = 0; i < self->vma_count; i++) 372 munmap(self->mod_info->child_mapped_addr[i], self->page_size); 373 close(self->maps_fd); 374 waitpid(self->pid, &status, 0); 375 munmap(self->mod_info, self->shared_mem_size); 376 } 377 378 TEST_F(proc_maps_race, test_maps_tearing_from_split) 379 { 380 struct vma_modifier_info *mod_info = self->mod_info; 381 382 struct line_content split_last_line; 383 struct line_content split_first_line; 384 struct line_content restored_last_line; 385 struct line_content restored_first_line; 386 387 wait_for_state(mod_info, SETUP_READY); 388 389 /* re-read the file to avoid using stale data from previous test */ 390 ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line)); 391 392 mod_info->vma_modify = split_vma; 393 mod_info->vma_restore = merge_vma; 394 mod_info->vma_mod_check = check_split_result; 395 396 ASSERT_TRUE(capture_mod_pattern(self, &split_last_line, &split_first_line, 397 &restored_last_line, &restored_first_line)); 398 399 /* Now start concurrent modifications for self->duration_sec */ 400 signal_state(mod_info, TEST_READY); 401 402 struct line_content new_last_line; 403 struct line_content new_first_line; 404 struct timespec start_ts, end_ts; 405 406 clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts); 407 do { 408 bool last_line_changed; 409 bool first_line_changed; 410 411 ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line)); 412 413 /* Check if we read vmas after split */ 414 if (!strcmp(new_last_line.text, split_last_line.text)) { 415 /* 416 * The vmas should be consistent with split results, 417 * however if vma was concurrently restored after a 418 * split, it can be reported twice (first the original 419 * split one, then the same vma but extended after the 420 * merge) because we found it as the next vma again. 421 * In that case new first line will be the same as the 422 * last restored line. 423 */ 424 ASSERT_FALSE(strcmp(new_first_line.text, split_first_line.text) && 425 strcmp(new_first_line.text, restored_last_line.text)); 426 } else { 427 /* The vmas should be consistent with merge results */ 428 ASSERT_FALSE(strcmp(new_last_line.text, restored_last_line.text)); 429 ASSERT_FALSE(strcmp(new_first_line.text, restored_first_line.text)); 430 } 431 /* 432 * First and last lines should change in unison. If the last 433 * line changed then the first line should change as well and 434 * vice versa. 435 */ 436 last_line_changed = strcmp(new_last_line.text, self->last_line.text) != 0; 437 first_line_changed = strcmp(new_first_line.text, self->first_line.text) != 0; 438 ASSERT_EQ(last_line_changed, first_line_changed); 439 440 clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts); 441 } while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec); 442 443 /* Signal the modifyer thread to stop and wait until it exits */ 444 signal_state(mod_info, TEST_DONE); 445 } 446 447 TEST_HARNESS_MAIN 448