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