1 // SPDX-License-Identifier: GPL-2.0 2 #define _GNU_SOURCE 3 #include <sys/mman.h> 4 #include <stdint.h> 5 #include <unistd.h> 6 #include <string.h> 7 #include <sys/time.h> 8 #include <sys/resource.h> 9 #include <stdbool.h> 10 #include "mlock2.h" 11 12 #include "../kselftest.h" 13 14 struct vm_boundaries { 15 unsigned long start; 16 unsigned long end; 17 }; 18 19 static int get_vm_area(unsigned long addr, struct vm_boundaries *area) 20 { 21 FILE *file; 22 int ret = 1; 23 char line[1024] = {0}; 24 char *end_addr; 25 char *stop; 26 unsigned long start; 27 unsigned long end; 28 29 if (!area) 30 return ret; 31 32 file = fopen("/proc/self/maps", "r"); 33 if (!file) { 34 perror("fopen"); 35 return ret; 36 } 37 38 memset(area, 0, sizeof(struct vm_boundaries)); 39 40 while(fgets(line, 1024, file)) { 41 end_addr = strchr(line, '-'); 42 if (!end_addr) { 43 printf("cannot parse /proc/self/maps\n"); 44 goto out; 45 } 46 *end_addr = '\0'; 47 end_addr++; 48 stop = strchr(end_addr, ' '); 49 if (!stop) { 50 printf("cannot parse /proc/self/maps\n"); 51 goto out; 52 } 53 54 sscanf(line, "%lx", &start); 55 sscanf(end_addr, "%lx", &end); 56 57 if (start <= addr && end > addr) { 58 area->start = start; 59 area->end = end; 60 ret = 0; 61 goto out; 62 } 63 } 64 out: 65 fclose(file); 66 return ret; 67 } 68 69 #define VMFLAGS "VmFlags:" 70 71 static bool is_vmflag_set(unsigned long addr, const char *vmflag) 72 { 73 char *line = NULL; 74 char *flags; 75 size_t size = 0; 76 bool ret = false; 77 FILE *smaps; 78 79 smaps = seek_to_smaps_entry(addr); 80 if (!smaps) { 81 printf("Unable to parse /proc/self/smaps\n"); 82 goto out; 83 } 84 85 while (getline(&line, &size, smaps) > 0) { 86 if (!strstr(line, VMFLAGS)) { 87 free(line); 88 line = NULL; 89 size = 0; 90 continue; 91 } 92 93 flags = line + strlen(VMFLAGS); 94 ret = (strstr(flags, vmflag) != NULL); 95 goto out; 96 } 97 98 out: 99 free(line); 100 fclose(smaps); 101 return ret; 102 } 103 104 #define SIZE "Size:" 105 #define RSS "Rss:" 106 #define LOCKED "lo" 107 108 static unsigned long get_value_for_name(unsigned long addr, const char *name) 109 { 110 char *line = NULL; 111 size_t size = 0; 112 char *value_ptr; 113 FILE *smaps = NULL; 114 unsigned long value = -1UL; 115 116 smaps = seek_to_smaps_entry(addr); 117 if (!smaps) { 118 printf("Unable to parse /proc/self/smaps\n"); 119 goto out; 120 } 121 122 while (getline(&line, &size, smaps) > 0) { 123 if (!strstr(line, name)) { 124 free(line); 125 line = NULL; 126 size = 0; 127 continue; 128 } 129 130 value_ptr = line + strlen(name); 131 if (sscanf(value_ptr, "%lu kB", &value) < 1) { 132 printf("Unable to parse smaps entry for Size\n"); 133 goto out; 134 } 135 break; 136 } 137 138 out: 139 if (smaps) 140 fclose(smaps); 141 free(line); 142 return value; 143 } 144 145 static bool is_vma_lock_on_fault(unsigned long addr) 146 { 147 bool locked; 148 unsigned long vma_size, vma_rss; 149 150 locked = is_vmflag_set(addr, LOCKED); 151 if (!locked) 152 return false; 153 154 vma_size = get_value_for_name(addr, SIZE); 155 vma_rss = get_value_for_name(addr, RSS); 156 157 /* only one page is faulted in */ 158 return (vma_rss < vma_size); 159 } 160 161 #define PRESENT_BIT 0x8000000000000000ULL 162 #define PFN_MASK 0x007FFFFFFFFFFFFFULL 163 #define UNEVICTABLE_BIT (1UL << 18) 164 165 static int lock_check(unsigned long addr) 166 { 167 bool locked; 168 unsigned long vma_size, vma_rss; 169 170 locked = is_vmflag_set(addr, LOCKED); 171 if (!locked) 172 return false; 173 174 vma_size = get_value_for_name(addr, SIZE); 175 vma_rss = get_value_for_name(addr, RSS); 176 177 return (vma_rss == vma_size); 178 } 179 180 static int unlock_lock_check(char *map) 181 { 182 if (is_vmflag_set((unsigned long)map, LOCKED)) { 183 printf("VMA flag %s is present on page 1 after unlock\n", LOCKED); 184 return 1; 185 } 186 187 return 0; 188 } 189 190 static int test_mlock_lock() 191 { 192 char *map; 193 int ret = 1; 194 unsigned long page_size = getpagesize(); 195 196 map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, 197 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 198 if (map == MAP_FAILED) { 199 perror("test_mlock_locked mmap"); 200 goto out; 201 } 202 203 if (mlock2_(map, 2 * page_size, 0)) { 204 if (errno == ENOSYS) { 205 printf("Cannot call new mlock family, skipping test\n"); 206 _exit(KSFT_SKIP); 207 } 208 perror("mlock2(0)"); 209 goto unmap; 210 } 211 212 if (!lock_check((unsigned long)map)) 213 goto unmap; 214 215 /* Now unlock and recheck attributes */ 216 if (munlock(map, 2 * page_size)) { 217 perror("munlock()"); 218 goto unmap; 219 } 220 221 ret = unlock_lock_check(map); 222 223 unmap: 224 munmap(map, 2 * page_size); 225 out: 226 return ret; 227 } 228 229 static int onfault_check(char *map) 230 { 231 *map = 'a'; 232 if (!is_vma_lock_on_fault((unsigned long)map)) { 233 printf("VMA is not marked for lock on fault\n"); 234 return 1; 235 } 236 237 return 0; 238 } 239 240 static int unlock_onfault_check(char *map) 241 { 242 unsigned long page_size = getpagesize(); 243 244 if (is_vma_lock_on_fault((unsigned long)map) || 245 is_vma_lock_on_fault((unsigned long)map + page_size)) { 246 printf("VMA is still lock on fault after unlock\n"); 247 return 1; 248 } 249 250 return 0; 251 } 252 253 static int test_mlock_onfault() 254 { 255 char *map; 256 int ret = 1; 257 unsigned long page_size = getpagesize(); 258 259 map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, 260 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 261 if (map == MAP_FAILED) { 262 perror("test_mlock_locked mmap"); 263 goto out; 264 } 265 266 if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) { 267 if (errno == ENOSYS) { 268 printf("Cannot call new mlock family, skipping test\n"); 269 _exit(KSFT_SKIP); 270 } 271 perror("mlock2(MLOCK_ONFAULT)"); 272 goto unmap; 273 } 274 275 if (onfault_check(map)) 276 goto unmap; 277 278 /* Now unlock and recheck attributes */ 279 if (munlock(map, 2 * page_size)) { 280 if (errno == ENOSYS) { 281 printf("Cannot call new mlock family, skipping test\n"); 282 _exit(KSFT_SKIP); 283 } 284 perror("munlock()"); 285 goto unmap; 286 } 287 288 ret = unlock_onfault_check(map); 289 unmap: 290 munmap(map, 2 * page_size); 291 out: 292 return ret; 293 } 294 295 static int test_lock_onfault_of_present() 296 { 297 char *map; 298 int ret = 1; 299 unsigned long page_size = getpagesize(); 300 301 map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, 302 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 303 if (map == MAP_FAILED) { 304 perror("test_mlock_locked mmap"); 305 goto out; 306 } 307 308 *map = 'a'; 309 310 if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) { 311 if (errno == ENOSYS) { 312 printf("Cannot call new mlock family, skipping test\n"); 313 _exit(KSFT_SKIP); 314 } 315 perror("mlock2(MLOCK_ONFAULT)"); 316 goto unmap; 317 } 318 319 if (!is_vma_lock_on_fault((unsigned long)map) || 320 !is_vma_lock_on_fault((unsigned long)map + page_size)) { 321 printf("VMA with present pages is not marked lock on fault\n"); 322 goto unmap; 323 } 324 ret = 0; 325 unmap: 326 munmap(map, 2 * page_size); 327 out: 328 return ret; 329 } 330 331 static int test_munlockall() 332 { 333 char *map; 334 int ret = 1; 335 unsigned long page_size = getpagesize(); 336 337 map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, 338 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 339 340 if (map == MAP_FAILED) { 341 perror("test_munlockall mmap"); 342 goto out; 343 } 344 345 if (mlockall(MCL_CURRENT)) { 346 perror("mlockall(MCL_CURRENT)"); 347 goto out; 348 } 349 350 if (!lock_check((unsigned long)map)) 351 goto unmap; 352 353 if (munlockall()) { 354 perror("munlockall()"); 355 goto unmap; 356 } 357 358 if (unlock_lock_check(map)) 359 goto unmap; 360 361 munmap(map, 2 * page_size); 362 363 map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, 364 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 365 366 if (map == MAP_FAILED) { 367 perror("test_munlockall second mmap"); 368 goto out; 369 } 370 371 if (mlockall(MCL_CURRENT | MCL_ONFAULT)) { 372 perror("mlockall(MCL_CURRENT | MCL_ONFAULT)"); 373 goto unmap; 374 } 375 376 if (onfault_check(map)) 377 goto unmap; 378 379 if (munlockall()) { 380 perror("munlockall()"); 381 goto unmap; 382 } 383 384 if (unlock_onfault_check(map)) 385 goto unmap; 386 387 if (mlockall(MCL_CURRENT | MCL_FUTURE)) { 388 perror("mlockall(MCL_CURRENT | MCL_FUTURE)"); 389 goto out; 390 } 391 392 if (!lock_check((unsigned long)map)) 393 goto unmap; 394 395 if (munlockall()) { 396 perror("munlockall()"); 397 goto unmap; 398 } 399 400 ret = unlock_lock_check(map); 401 402 unmap: 403 munmap(map, 2 * page_size); 404 out: 405 munlockall(); 406 return ret; 407 } 408 409 static int test_vma_management(bool call_mlock) 410 { 411 int ret = 1; 412 void *map; 413 unsigned long page_size = getpagesize(); 414 struct vm_boundaries page1; 415 struct vm_boundaries page2; 416 struct vm_boundaries page3; 417 418 map = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE, 419 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 420 if (map == MAP_FAILED) { 421 perror("mmap()"); 422 return ret; 423 } 424 425 if (call_mlock && mlock2_(map, 3 * page_size, MLOCK_ONFAULT)) { 426 if (errno == ENOSYS) { 427 printf("Cannot call new mlock family, skipping test\n"); 428 _exit(KSFT_SKIP); 429 } 430 perror("mlock(ONFAULT)\n"); 431 goto out; 432 } 433 434 if (get_vm_area((unsigned long)map, &page1) || 435 get_vm_area((unsigned long)map + page_size, &page2) || 436 get_vm_area((unsigned long)map + page_size * 2, &page3)) { 437 printf("couldn't find mapping in /proc/self/maps\n"); 438 goto out; 439 } 440 441 /* 442 * Before we unlock a portion, we need to that all three pages are in 443 * the same VMA. If they are not we abort this test (Note that this is 444 * not a failure) 445 */ 446 if (page1.start != page2.start || page2.start != page3.start) { 447 printf("VMAs are not merged to start, aborting test\n"); 448 ret = 0; 449 goto out; 450 } 451 452 if (munlock(map + page_size, page_size)) { 453 perror("munlock()"); 454 goto out; 455 } 456 457 if (get_vm_area((unsigned long)map, &page1) || 458 get_vm_area((unsigned long)map + page_size, &page2) || 459 get_vm_area((unsigned long)map + page_size * 2, &page3)) { 460 printf("couldn't find mapping in /proc/self/maps\n"); 461 goto out; 462 } 463 464 /* All three VMAs should be different */ 465 if (page1.start == page2.start || page2.start == page3.start) { 466 printf("failed to split VMA for munlock\n"); 467 goto out; 468 } 469 470 /* Now unlock the first and third page and check the VMAs again */ 471 if (munlock(map, page_size * 3)) { 472 perror("munlock()"); 473 goto out; 474 } 475 476 if (get_vm_area((unsigned long)map, &page1) || 477 get_vm_area((unsigned long)map + page_size, &page2) || 478 get_vm_area((unsigned long)map + page_size * 2, &page3)) { 479 printf("couldn't find mapping in /proc/self/maps\n"); 480 goto out; 481 } 482 483 /* Now all three VMAs should be the same */ 484 if (page1.start != page2.start || page2.start != page3.start) { 485 printf("failed to merge VMAs after munlock\n"); 486 goto out; 487 } 488 489 ret = 0; 490 out: 491 munmap(map, 3 * page_size); 492 return ret; 493 } 494 495 static int test_mlockall(int (test_function)(bool call_mlock)) 496 { 497 int ret = 1; 498 499 if (mlockall(MCL_CURRENT | MCL_ONFAULT | MCL_FUTURE)) { 500 perror("mlockall"); 501 return ret; 502 } 503 504 ret = test_function(false); 505 munlockall(); 506 return ret; 507 } 508 509 int main(int argc, char **argv) 510 { 511 int ret = 0; 512 ret += test_mlock_lock(); 513 ret += test_mlock_onfault(); 514 ret += test_munlockall(); 515 ret += test_lock_onfault_of_present(); 516 ret += test_vma_management(true); 517 ret += test_mlockall(test_vma_management); 518 return ret; 519 } 520