1 // SPDX-License-Identifier: GPL-2.0-or-later 2 3 #define _GNU_SOURCE 4 #include "../kselftest_harness.h" 5 #include <fcntl.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <unistd.h> 9 #include <sys/mman.h> 10 #include <sys/syscall.h> 11 #include <sys/wait.h> 12 #include <linux/perf_event.h> 13 #include "vm_util.h" 14 15 FIXTURE(merge) 16 { 17 unsigned int page_size; 18 char *carveout; 19 struct procmap_fd procmap; 20 }; 21 22 FIXTURE_SETUP(merge) 23 { 24 self->page_size = psize(); 25 /* Carve out PROT_NONE region to map over. */ 26 self->carveout = mmap(NULL, 12 * self->page_size, PROT_NONE, 27 MAP_ANON | MAP_PRIVATE, -1, 0); 28 ASSERT_NE(self->carveout, MAP_FAILED); 29 /* Setup PROCMAP_QUERY interface. */ 30 ASSERT_EQ(open_self_procmap(&self->procmap), 0); 31 } 32 33 FIXTURE_TEARDOWN(merge) 34 { 35 ASSERT_EQ(munmap(self->carveout, 12 * self->page_size), 0); 36 ASSERT_EQ(close_procmap(&self->procmap), 0); 37 } 38 39 TEST_F(merge, mprotect_unfaulted_left) 40 { 41 unsigned int page_size = self->page_size; 42 char *carveout = self->carveout; 43 struct procmap_fd *procmap = &self->procmap; 44 char *ptr; 45 46 /* 47 * Map 10 pages of R/W memory within. MAP_NORESERVE so we don't hit 48 * merge failure due to lack of VM_ACCOUNT flag by mistake. 49 * 50 * |-----------------------| 51 * | unfaulted | 52 * |-----------------------| 53 */ 54 ptr = mmap(&carveout[page_size], 10 * page_size, PROT_READ | PROT_WRITE, 55 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); 56 ASSERT_NE(ptr, MAP_FAILED); 57 /* 58 * Now make the first 5 pages read-only, splitting the VMA: 59 * 60 * RO RW 61 * |-----------|-----------| 62 * | unfaulted | unfaulted | 63 * |-----------|-----------| 64 */ 65 ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ), 0); 66 /* 67 * Fault in the first of the last 5 pages so it gets an anon_vma and 68 * thus the whole VMA becomes 'faulted': 69 * 70 * RO RW 71 * |-----------|-----------| 72 * | unfaulted | faulted | 73 * |-----------|-----------| 74 */ 75 ptr[5 * page_size] = 'x'; 76 /* 77 * Now mprotect() the RW region read-only, we should merge (though for 78 * ~15 years we did not! :): 79 * 80 * RO 81 * |-----------------------| 82 * | faulted | 83 * |-----------------------| 84 */ 85 ASSERT_EQ(mprotect(&ptr[5 * page_size], 5 * page_size, PROT_READ), 0); 86 87 /* Assert that the merge succeeded using PROCMAP_QUERY. */ 88 ASSERT_TRUE(find_vma_procmap(procmap, ptr)); 89 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); 90 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size); 91 } 92 93 TEST_F(merge, mprotect_unfaulted_right) 94 { 95 unsigned int page_size = self->page_size; 96 char *carveout = self->carveout; 97 struct procmap_fd *procmap = &self->procmap; 98 char *ptr; 99 100 /* 101 * |-----------------------| 102 * | unfaulted | 103 * |-----------------------| 104 */ 105 ptr = mmap(&carveout[page_size], 10 * page_size, PROT_READ | PROT_WRITE, 106 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); 107 ASSERT_NE(ptr, MAP_FAILED); 108 /* 109 * Now make the last 5 pages read-only, splitting the VMA: 110 * 111 * RW RO 112 * |-----------|-----------| 113 * | unfaulted | unfaulted | 114 * |-----------|-----------| 115 */ 116 ASSERT_EQ(mprotect(&ptr[5 * page_size], 5 * page_size, PROT_READ), 0); 117 /* 118 * Fault in the first of the first 5 pages so it gets an anon_vma and 119 * thus the whole VMA becomes 'faulted': 120 * 121 * RW RO 122 * |-----------|-----------| 123 * | faulted | unfaulted | 124 * |-----------|-----------| 125 */ 126 ptr[0] = 'x'; 127 /* 128 * Now mprotect() the RW region read-only, we should merge: 129 * 130 * RO 131 * |-----------------------| 132 * | faulted | 133 * |-----------------------| 134 */ 135 ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ), 0); 136 137 /* Assert that the merge succeeded using PROCMAP_QUERY. */ 138 ASSERT_TRUE(find_vma_procmap(procmap, ptr)); 139 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); 140 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size); 141 } 142 143 TEST_F(merge, mprotect_unfaulted_both) 144 { 145 unsigned int page_size = self->page_size; 146 char *carveout = self->carveout; 147 struct procmap_fd *procmap = &self->procmap; 148 char *ptr; 149 150 /* 151 * |-----------------------| 152 * | unfaulted | 153 * |-----------------------| 154 */ 155 ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE, 156 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); 157 ASSERT_NE(ptr, MAP_FAILED); 158 /* 159 * Now make the first and last 3 pages read-only, splitting the VMA: 160 * 161 * RO RW RO 162 * |-----------|-----------|-----------| 163 * | unfaulted | unfaulted | unfaulted | 164 * |-----------|-----------|-----------| 165 */ 166 ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0); 167 ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0); 168 /* 169 * Fault in the first of the middle 3 pages so it gets an anon_vma and 170 * thus the whole VMA becomes 'faulted': 171 * 172 * RO RW RO 173 * |-----------|-----------|-----------| 174 * | unfaulted | faulted | unfaulted | 175 * |-----------|-----------|-----------| 176 */ 177 ptr[3 * page_size] = 'x'; 178 /* 179 * Now mprotect() the RW region read-only, we should merge: 180 * 181 * RO 182 * |-----------------------| 183 * | faulted | 184 * |-----------------------| 185 */ 186 ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0); 187 188 /* Assert that the merge succeeded using PROCMAP_QUERY. */ 189 ASSERT_TRUE(find_vma_procmap(procmap, ptr)); 190 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); 191 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size); 192 } 193 194 TEST_F(merge, mprotect_faulted_left_unfaulted_right) 195 { 196 unsigned int page_size = self->page_size; 197 char *carveout = self->carveout; 198 struct procmap_fd *procmap = &self->procmap; 199 char *ptr; 200 201 /* 202 * |-----------------------| 203 * | unfaulted | 204 * |-----------------------| 205 */ 206 ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE, 207 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); 208 ASSERT_NE(ptr, MAP_FAILED); 209 /* 210 * Now make the last 3 pages read-only, splitting the VMA: 211 * 212 * RW RO 213 * |-----------------------|-----------| 214 * | unfaulted | unfaulted | 215 * |-----------------------|-----------| 216 */ 217 ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0); 218 /* 219 * Fault in the first of the first 6 pages so it gets an anon_vma and 220 * thus the whole VMA becomes 'faulted': 221 * 222 * RW RO 223 * |-----------------------|-----------| 224 * | unfaulted | unfaulted | 225 * |-----------------------|-----------| 226 */ 227 ptr[0] = 'x'; 228 /* 229 * Now make the first 3 pages read-only, splitting the VMA: 230 * 231 * RO RW RO 232 * |-----------|-----------|-----------| 233 * | faulted | faulted | unfaulted | 234 * |-----------|-----------|-----------| 235 */ 236 ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0); 237 /* 238 * Now mprotect() the RW region read-only, we should merge: 239 * 240 * RO 241 * |-----------------------| 242 * | faulted | 243 * |-----------------------| 244 */ 245 ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0); 246 247 /* Assert that the merge succeeded using PROCMAP_QUERY. */ 248 ASSERT_TRUE(find_vma_procmap(procmap, ptr)); 249 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); 250 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size); 251 } 252 253 TEST_F(merge, mprotect_unfaulted_left_faulted_right) 254 { 255 unsigned int page_size = self->page_size; 256 char *carveout = self->carveout; 257 struct procmap_fd *procmap = &self->procmap; 258 char *ptr; 259 260 /* 261 * |-----------------------| 262 * | unfaulted | 263 * |-----------------------| 264 */ 265 ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE, 266 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); 267 ASSERT_NE(ptr, MAP_FAILED); 268 /* 269 * Now make the first 3 pages read-only, splitting the VMA: 270 * 271 * RO RW 272 * |-----------|-----------------------| 273 * | unfaulted | unfaulted | 274 * |-----------|-----------------------| 275 */ 276 ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0); 277 /* 278 * Fault in the first of the last 6 pages so it gets an anon_vma and 279 * thus the whole VMA becomes 'faulted': 280 * 281 * RO RW 282 * |-----------|-----------------------| 283 * | unfaulted | faulted | 284 * |-----------|-----------------------| 285 */ 286 ptr[3 * page_size] = 'x'; 287 /* 288 * Now make the last 3 pages read-only, splitting the VMA: 289 * 290 * RO RW RO 291 * |-----------|-----------|-----------| 292 * | unfaulted | faulted | faulted | 293 * |-----------|-----------|-----------| 294 */ 295 ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0); 296 /* 297 * Now mprotect() the RW region read-only, we should merge: 298 * 299 * RO 300 * |-----------------------| 301 * | faulted | 302 * |-----------------------| 303 */ 304 ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0); 305 306 /* Assert that the merge succeeded using PROCMAP_QUERY. */ 307 ASSERT_TRUE(find_vma_procmap(procmap, ptr)); 308 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); 309 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size); 310 } 311 312 TEST_F(merge, forked_target_vma) 313 { 314 unsigned int page_size = self->page_size; 315 char *carveout = self->carveout; 316 struct procmap_fd *procmap = &self->procmap; 317 pid_t pid; 318 char *ptr, *ptr2; 319 int i; 320 321 /* 322 * |-----------| 323 * | unfaulted | 324 * |-----------| 325 */ 326 ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE, 327 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 328 ASSERT_NE(ptr, MAP_FAILED); 329 330 /* 331 * Fault in process. 332 * 333 * |-----------| 334 * | faulted | 335 * |-----------| 336 */ 337 ptr[0] = 'x'; 338 339 pid = fork(); 340 ASSERT_NE(pid, -1); 341 342 if (pid != 0) { 343 wait(NULL); 344 return; 345 } 346 347 /* Child process below: */ 348 349 /* Reopen for child. */ 350 ASSERT_EQ(close_procmap(&self->procmap), 0); 351 ASSERT_EQ(open_self_procmap(&self->procmap), 0); 352 353 /* unCOWing everything does not cause the AVC to go away. */ 354 for (i = 0; i < 5 * page_size; i += page_size) 355 ptr[i] = 'x'; 356 357 /* 358 * Map in adjacent VMA in child. 359 * 360 * forked 361 * |-----------|-----------| 362 * | faulted | unfaulted | 363 * |-----------|-----------| 364 * ptr ptr2 365 */ 366 ptr2 = mmap(&ptr[5 * page_size], 5 * page_size, PROT_READ | PROT_WRITE, 367 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 368 ASSERT_NE(ptr2, MAP_FAILED); 369 370 /* Make sure not merged. */ 371 ASSERT_TRUE(find_vma_procmap(procmap, ptr)); 372 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); 373 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 5 * page_size); 374 } 375 376 TEST_F(merge, forked_source_vma) 377 { 378 unsigned int page_size = self->page_size; 379 char *carveout = self->carveout; 380 struct procmap_fd *procmap = &self->procmap; 381 pid_t pid; 382 char *ptr, *ptr2; 383 int i; 384 385 /* 386 * |-----------|------------| 387 * | unfaulted | <unmapped> | 388 * |-----------|------------| 389 */ 390 ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE, 391 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); 392 ASSERT_NE(ptr, MAP_FAILED); 393 394 /* 395 * Fault in process. 396 * 397 * |-----------|------------| 398 * | faulted | <unmapped> | 399 * |-----------|------------| 400 */ 401 ptr[0] = 'x'; 402 403 pid = fork(); 404 ASSERT_NE(pid, -1); 405 406 if (pid != 0) { 407 wait(NULL); 408 return; 409 } 410 411 /* Child process below: */ 412 413 /* Reopen for child. */ 414 ASSERT_EQ(close_procmap(&self->procmap), 0); 415 ASSERT_EQ(open_self_procmap(&self->procmap), 0); 416 417 /* unCOWing everything does not cause the AVC to go away. */ 418 for (i = 0; i < 5 * page_size; i += page_size) 419 ptr[i] = 'x'; 420 421 /* 422 * Map in adjacent VMA in child, ptr2 after ptr, but incompatible. 423 * 424 * forked RW RWX 425 * |-----------|-----------| 426 * | faulted | unfaulted | 427 * |-----------|-----------| 428 * ptr ptr2 429 */ 430 ptr2 = mmap(&carveout[6 * page_size], 5 * page_size, PROT_READ | PROT_WRITE | PROT_EXEC, 431 MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); 432 ASSERT_NE(ptr2, MAP_FAILED); 433 434 /* Make sure not merged. */ 435 ASSERT_TRUE(find_vma_procmap(procmap, ptr2)); 436 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr2); 437 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr2 + 5 * page_size); 438 439 /* 440 * Now mprotect forked region to RWX so it becomes the source for the 441 * merge to unfaulted region: 442 * 443 * forked RWX RWX 444 * |-----------|-----------| 445 * | faulted | unfaulted | 446 * |-----------|-----------| 447 * ptr ptr2 448 * 449 * This should NOT result in a merge, as ptr was forked. 450 */ 451 ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ | PROT_WRITE | PROT_EXEC), 0); 452 /* Again, make sure not merged. */ 453 ASSERT_TRUE(find_vma_procmap(procmap, ptr2)); 454 ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr2); 455 ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr2 + 5 * page_size); 456 } 457 458 TEST_F(merge, handle_uprobe_upon_merged_vma) 459 { 460 const size_t attr_sz = sizeof(struct perf_event_attr); 461 unsigned int page_size = self->page_size; 462 const char *probe_file = "./foo"; 463 char *carveout = self->carveout; 464 struct perf_event_attr attr; 465 unsigned long type; 466 void *ptr1, *ptr2; 467 int fd; 468 469 fd = open(probe_file, O_RDWR|O_CREAT, 0600); 470 ASSERT_GE(fd, 0); 471 472 ASSERT_EQ(ftruncate(fd, page_size), 0); 473 if (read_sysfs("/sys/bus/event_source/devices/uprobe/type", &type) != 0) { 474 SKIP(goto out, "Failed to read uprobe sysfs file, skipping"); 475 } 476 477 memset(&attr, 0, attr_sz); 478 attr.size = attr_sz; 479 attr.type = type; 480 attr.config1 = (__u64)(long)probe_file; 481 attr.config2 = 0x0; 482 483 ASSERT_GE(syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0), 0); 484 485 ptr1 = mmap(&carveout[page_size], 10 * page_size, PROT_EXEC, 486 MAP_PRIVATE | MAP_FIXED, fd, 0); 487 ASSERT_NE(ptr1, MAP_FAILED); 488 489 ptr2 = mremap(ptr1, page_size, 2 * page_size, 490 MREMAP_MAYMOVE | MREMAP_FIXED, ptr1 + 5 * page_size); 491 ASSERT_NE(ptr2, MAP_FAILED); 492 493 ASSERT_NE(mremap(ptr2, page_size, page_size, 494 MREMAP_MAYMOVE | MREMAP_FIXED, ptr1), MAP_FAILED); 495 496 out: 497 close(fd); 498 remove(probe_file); 499 } 500 501 TEST_HARNESS_MAIN 502