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 static inline bool shrink_vma(FIXTURE_DATA(proc_maps_race) *self) 246 { 247 return mremap(self->mod_info->addr, self->page_size * 3, 248 self->page_size, 0) != MAP_FAILED; 249 } 250 251 static inline bool expand_vma(FIXTURE_DATA(proc_maps_race) *self) 252 { 253 return mremap(self->mod_info->addr, self->page_size, 254 self->page_size * 3, 0) != MAP_FAILED; 255 } 256 257 static inline bool check_shrink_result(struct line_content *mod_last_line, 258 struct line_content *mod_first_line, 259 struct line_content *restored_last_line, 260 struct line_content *restored_first_line) 261 { 262 /* Make sure only the last vma of the first page is changing */ 263 return strcmp(mod_last_line->text, restored_last_line->text) != 0 && 264 strcmp(mod_first_line->text, restored_first_line->text) == 0; 265 } 266 267 FIXTURE_SETUP(proc_maps_race) 268 { 269 const char *duration = getenv("DURATION"); 270 struct vma_modifier_info *mod_info; 271 pthread_mutexattr_t mutex_attr; 272 pthread_condattr_t cond_attr; 273 unsigned long duration_sec; 274 char fname[32]; 275 276 self->page_size = (unsigned long)sysconf(_SC_PAGESIZE); 277 duration_sec = duration ? atol(duration) : 0; 278 self->duration_sec = duration_sec ? duration_sec : 5UL; 279 280 /* 281 * Have to map enough vmas for /proc/pid/maps to contain more than one 282 * page worth of vmas. Assume at least 32 bytes per line in maps output 283 */ 284 self->vma_count = self->page_size / 32 + 1; 285 self->shared_mem_size = sizeof(struct vma_modifier_info) + self->vma_count * sizeof(void *); 286 287 /* map shared memory for communication with the child process */ 288 self->mod_info = (struct vma_modifier_info *)mmap(NULL, self->shared_mem_size, 289 PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 290 ASSERT_NE(self->mod_info, MAP_FAILED); 291 mod_info = self->mod_info; 292 293 /* Initialize shared members */ 294 pthread_mutexattr_init(&mutex_attr); 295 pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED); 296 ASSERT_EQ(pthread_mutex_init(&mod_info->sync_lock, &mutex_attr), 0); 297 pthread_condattr_init(&cond_attr); 298 pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED); 299 ASSERT_EQ(pthread_cond_init(&mod_info->sync_cond, &cond_attr), 0); 300 mod_info->vma_count = self->vma_count; 301 mod_info->curr_state = INIT; 302 mod_info->exit = false; 303 304 self->pid = fork(); 305 if (!self->pid) { 306 /* Child process modifying the address space */ 307 int prot = PROT_READ | PROT_WRITE; 308 int i; 309 310 for (i = 0; i < mod_info->vma_count; i++) { 311 mod_info->child_mapped_addr[i] = mmap(NULL, self->page_size * 3, prot, 312 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 313 ASSERT_NE(mod_info->child_mapped_addr[i], MAP_FAILED); 314 /* change protection in adjacent maps to prevent merging */ 315 prot ^= PROT_WRITE; 316 } 317 signal_state(mod_info, CHILD_READY); 318 wait_for_state(mod_info, PARENT_READY); 319 while (true) { 320 signal_state(mod_info, SETUP_READY); 321 wait_for_state(mod_info, SETUP_MODIFY_MAPS); 322 if (mod_info->exit) 323 break; 324 325 ASSERT_TRUE(mod_info->vma_modify(self)); 326 signal_state(mod_info, SETUP_MAPS_MODIFIED); 327 wait_for_state(mod_info, SETUP_RESTORE_MAPS); 328 ASSERT_TRUE(mod_info->vma_restore(self)); 329 signal_state(mod_info, SETUP_MAPS_RESTORED); 330 331 wait_for_state(mod_info, TEST_READY); 332 while (mod_info->curr_state != TEST_DONE) { 333 ASSERT_TRUE(mod_info->vma_modify(self)); 334 ASSERT_TRUE(mod_info->vma_restore(self)); 335 } 336 } 337 for (i = 0; i < mod_info->vma_count; i++) 338 munmap(mod_info->child_mapped_addr[i], self->page_size * 3); 339 340 exit(0); 341 } 342 343 sprintf(fname, "/proc/%d/maps", self->pid); 344 self->maps_fd = open(fname, O_RDONLY); 345 ASSERT_NE(self->maps_fd, -1); 346 347 /* Wait for the child to map the VMAs */ 348 wait_for_state(mod_info, CHILD_READY); 349 350 /* Read first two pages */ 351 self->page1.data = malloc(self->page_size); 352 ASSERT_NE(self->page1.data, NULL); 353 self->page2.data = malloc(self->page_size); 354 ASSERT_NE(self->page2.data, NULL); 355 356 ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line)); 357 358 /* 359 * Find the addresses corresponding to the last line in the first page 360 * and the first line in the last page. 361 */ 362 mod_info->addr = NULL; 363 mod_info->next_addr = NULL; 364 for (int i = 0; i < mod_info->vma_count; i++) { 365 if (mod_info->child_mapped_addr[i] == (void *)self->last_line.start_addr) { 366 mod_info->addr = mod_info->child_mapped_addr[i]; 367 mod_info->prot = PROT_READ; 368 /* Even VMAs have write permission */ 369 if ((i % 2) == 0) 370 mod_info->prot |= PROT_WRITE; 371 } else if (mod_info->child_mapped_addr[i] == (void *)self->first_line.start_addr) { 372 mod_info->next_addr = mod_info->child_mapped_addr[i]; 373 } 374 375 if (mod_info->addr && mod_info->next_addr) 376 break; 377 } 378 ASSERT_TRUE(mod_info->addr && mod_info->next_addr); 379 380 signal_state(mod_info, PARENT_READY); 381 382 } 383 384 FIXTURE_TEARDOWN(proc_maps_race) 385 { 386 int status; 387 388 stop_vma_modifier(self->mod_info); 389 390 free(self->page2.data); 391 free(self->page1.data); 392 393 for (int i = 0; i < self->vma_count; i++) 394 munmap(self->mod_info->child_mapped_addr[i], self->page_size); 395 close(self->maps_fd); 396 waitpid(self->pid, &status, 0); 397 munmap(self->mod_info, self->shared_mem_size); 398 } 399 400 TEST_F(proc_maps_race, test_maps_tearing_from_split) 401 { 402 struct vma_modifier_info *mod_info = self->mod_info; 403 404 struct line_content split_last_line; 405 struct line_content split_first_line; 406 struct line_content restored_last_line; 407 struct line_content restored_first_line; 408 409 wait_for_state(mod_info, SETUP_READY); 410 411 /* re-read the file to avoid using stale data from previous test */ 412 ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line)); 413 414 mod_info->vma_modify = split_vma; 415 mod_info->vma_restore = merge_vma; 416 mod_info->vma_mod_check = check_split_result; 417 418 ASSERT_TRUE(capture_mod_pattern(self, &split_last_line, &split_first_line, 419 &restored_last_line, &restored_first_line)); 420 421 /* Now start concurrent modifications for self->duration_sec */ 422 signal_state(mod_info, TEST_READY); 423 424 struct line_content new_last_line; 425 struct line_content new_first_line; 426 struct timespec start_ts, end_ts; 427 428 clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts); 429 do { 430 bool last_line_changed; 431 bool first_line_changed; 432 433 ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line)); 434 435 /* Check if we read vmas after split */ 436 if (!strcmp(new_last_line.text, split_last_line.text)) { 437 /* 438 * The vmas should be consistent with split results, 439 * however if vma was concurrently restored after a 440 * split, it can be reported twice (first the original 441 * split one, then the same vma but extended after the 442 * merge) because we found it as the next vma again. 443 * In that case new first line will be the same as the 444 * last restored line. 445 */ 446 ASSERT_FALSE(strcmp(new_first_line.text, split_first_line.text) && 447 strcmp(new_first_line.text, restored_last_line.text)); 448 } else { 449 /* The vmas should be consistent with merge results */ 450 ASSERT_FALSE(strcmp(new_last_line.text, restored_last_line.text)); 451 ASSERT_FALSE(strcmp(new_first_line.text, restored_first_line.text)); 452 } 453 /* 454 * First and last lines should change in unison. If the last 455 * line changed then the first line should change as well and 456 * vice versa. 457 */ 458 last_line_changed = strcmp(new_last_line.text, self->last_line.text) != 0; 459 first_line_changed = strcmp(new_first_line.text, self->first_line.text) != 0; 460 ASSERT_EQ(last_line_changed, first_line_changed); 461 462 clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts); 463 } while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec); 464 465 /* Signal the modifyer thread to stop and wait until it exits */ 466 signal_state(mod_info, TEST_DONE); 467 } 468 469 TEST_F(proc_maps_race, test_maps_tearing_from_resize) 470 { 471 struct vma_modifier_info *mod_info = self->mod_info; 472 473 struct line_content shrunk_last_line; 474 struct line_content shrunk_first_line; 475 struct line_content restored_last_line; 476 struct line_content restored_first_line; 477 478 wait_for_state(mod_info, SETUP_READY); 479 480 /* re-read the file to avoid using stale data from previous test */ 481 ASSERT_TRUE(read_boundary_lines(self, &self->last_line, &self->first_line)); 482 483 mod_info->vma_modify = shrink_vma; 484 mod_info->vma_restore = expand_vma; 485 mod_info->vma_mod_check = check_shrink_result; 486 487 ASSERT_TRUE(capture_mod_pattern(self, &shrunk_last_line, &shrunk_first_line, 488 &restored_last_line, &restored_first_line)); 489 490 /* Now start concurrent modifications for self->duration_sec */ 491 signal_state(mod_info, TEST_READY); 492 493 struct line_content new_last_line; 494 struct line_content new_first_line; 495 struct timespec start_ts, end_ts; 496 497 clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts); 498 do { 499 ASSERT_TRUE(read_boundary_lines(self, &new_last_line, &new_first_line)); 500 501 /* Check if we read vmas after shrinking it */ 502 if (!strcmp(new_last_line.text, shrunk_last_line.text)) { 503 /* 504 * The vmas should be consistent with shrunk results, 505 * however if the vma was concurrently restored, it 506 * can be reported twice (first as shrunk one, then 507 * as restored one) because we found it as the next vma 508 * again. In that case new first line will be the same 509 * as the last restored line. 510 */ 511 ASSERT_FALSE(strcmp(new_first_line.text, shrunk_first_line.text) && 512 strcmp(new_first_line.text, restored_last_line.text)); 513 } else { 514 /* The vmas should be consistent with the original/resored state */ 515 ASSERT_FALSE(strcmp(new_last_line.text, restored_last_line.text)); 516 ASSERT_FALSE(strcmp(new_first_line.text, restored_first_line.text)); 517 } 518 519 clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts); 520 } while (end_ts.tv_sec - start_ts.tv_sec < self->duration_sec); 521 522 /* Signal the modifyer thread to stop and wait until it exits */ 523 signal_state(mod_info, TEST_DONE); 524 } 525 526 TEST_HARNESS_MAIN 527