457 lines · c
1// SPDX-License-Identifier: GPL-2.02#define _GNU_SOURCE3#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 13struct vm_boundaries {14 unsigned long start;15 unsigned long end;16};17 18static 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 }50out:51 fclose(file);52 return ret;53}54 55#define VMFLAGS "VmFlags:"56 57static 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 84out:85 free(line);86 fclose(smaps);87 return ret;88}89 90#define SIZE "Size:"91#define RSS "Rss:"92#define LOCKED "lo"93 94static 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 124out:125 if (smaps)126 fclose(smaps);127 free(line);128 return value;129}130 131static 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 0x8000000000000000ULL148#define PFN_MASK 0x007FFFFFFFFFFFFFULL149#define UNEVICTABLE_BIT (1UL << 18)150 151static 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 166static 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 176static 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 203static 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 214static 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 227static 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 254static 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 277static 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 303static 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 343static 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 in370 * the same VMA. If they are not we abort this test (Note that this is371 * 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 419static 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 428int 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