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