linux/tools/testing/selftests/vm/mlock2-tests.c
<<
>>
Prefs
   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
  12struct vm_boundaries {
  13        unsigned long start;
  14        unsigned long end;
  15};
  16
  17static int get_vm_area(unsigned long addr, struct vm_boundaries *area)
  18{
  19        FILE *file;
  20        int ret = 1;
  21        char line[1024] = {0};
  22        char *end_addr;
  23        char *stop;
  24        unsigned long start;
  25        unsigned long end;
  26
  27        if (!area)
  28                return ret;
  29
  30        file = fopen("/proc/self/maps", "r");
  31        if (!file) {
  32                perror("fopen");
  33                return ret;
  34        }
  35
  36        memset(area, 0, sizeof(struct vm_boundaries));
  37
  38        while(fgets(line, 1024, file)) {
  39                end_addr = strchr(line, '-');
  40                if (!end_addr) {
  41                        printf("cannot parse /proc/self/maps\n");
  42                        goto out;
  43                }
  44                *end_addr = '\0';
  45                end_addr++;
  46                stop = strchr(end_addr, ' ');
  47                if (!stop) {
  48                        printf("cannot parse /proc/self/maps\n");
  49                        goto out;
  50                }
  51                stop = '\0';
  52
  53                sscanf(line, "%lx", &start);
  54                sscanf(end_addr, "%lx", &end);
  55
  56                if (start <= addr && end > addr) {
  57                        area->start = start;
  58                        area->end = end;
  59                        ret = 0;
  60                        goto out;
  61                }
  62        }
  63out:
  64        fclose(file);
  65        return ret;
  66}
  67
  68static uint64_t get_pageflags(unsigned long addr)
  69{
  70        FILE *file;
  71        uint64_t pfn;
  72        unsigned long offset;
  73
  74        file = fopen("/proc/self/pagemap", "r");
  75        if (!file) {
  76                perror("fopen pagemap");
  77                _exit(1);
  78        }
  79
  80        offset = addr / getpagesize() * sizeof(pfn);
  81
  82        if (fseek(file, offset, SEEK_SET)) {
  83                perror("fseek pagemap");
  84                _exit(1);
  85        }
  86
  87        if (fread(&pfn, sizeof(pfn), 1, file) != 1) {
  88                perror("fread pagemap");
  89                _exit(1);
  90        }
  91
  92        fclose(file);
  93        return pfn;
  94}
  95
  96static uint64_t get_kpageflags(unsigned long pfn)
  97{
  98        uint64_t flags;
  99        FILE *file;
 100
 101        file = fopen("/proc/kpageflags", "r");
 102        if (!file) {
 103                perror("fopen kpageflags");
 104                _exit(1);
 105        }
 106
 107        if (fseek(file, pfn * sizeof(flags), SEEK_SET)) {
 108                perror("fseek kpageflags");
 109                _exit(1);
 110        }
 111
 112        if (fread(&flags, sizeof(flags), 1, file) != 1) {
 113                perror("fread kpageflags");
 114                _exit(1);
 115        }
 116
 117        fclose(file);
 118        return flags;
 119}
 120
 121#define VMFLAGS "VmFlags:"
 122
 123static bool is_vmflag_set(unsigned long addr, const char *vmflag)
 124{
 125        char *line = NULL;
 126        char *flags;
 127        size_t size = 0;
 128        bool ret = false;
 129        FILE *smaps;
 130
 131        smaps = seek_to_smaps_entry(addr);
 132        if (!smaps) {
 133                printf("Unable to parse /proc/self/smaps\n");
 134                goto out;
 135        }
 136
 137        while (getline(&line, &size, smaps) > 0) {
 138                if (!strstr(line, VMFLAGS)) {
 139                        free(line);
 140                        line = NULL;
 141                        size = 0;
 142                        continue;
 143                }
 144
 145                flags = line + strlen(VMFLAGS);
 146                ret = (strstr(flags, vmflag) != NULL);
 147                goto out;
 148        }
 149
 150out:
 151        free(line);
 152        fclose(smaps);
 153        return ret;
 154}
 155
 156#define SIZE "Size:"
 157#define RSS  "Rss:"
 158#define LOCKED "lo"
 159
 160static bool is_vma_lock_on_fault(unsigned long addr)
 161{
 162        bool ret = false;
 163        bool locked;
 164        FILE *smaps = NULL;
 165        unsigned long vma_size, vma_rss;
 166        char *line = NULL;
 167        char *value;
 168        size_t size = 0;
 169
 170        locked = is_vmflag_set(addr, LOCKED);
 171        if (!locked)
 172                goto out;
 173
 174        smaps = seek_to_smaps_entry(addr);
 175        if (!smaps) {
 176                printf("Unable to parse /proc/self/smaps\n");
 177                goto out;
 178        }
 179
 180        while (getline(&line, &size, smaps) > 0) {
 181                if (!strstr(line, SIZE)) {
 182                        free(line);
 183                        line = NULL;
 184                        size = 0;
 185                        continue;
 186                }
 187
 188                value = line + strlen(SIZE);
 189                if (sscanf(value, "%lu kB", &vma_size) < 1) {
 190                        printf("Unable to parse smaps entry for Size\n");
 191                        goto out;
 192                }
 193                break;
 194        }
 195
 196        while (getline(&line, &size, smaps) > 0) {
 197                if (!strstr(line, RSS)) {
 198                        free(line);
 199                        line = NULL;
 200                        size = 0;
 201                        continue;
 202                }
 203
 204                value = line + strlen(RSS);
 205                if (sscanf(value, "%lu kB", &vma_rss) < 1) {
 206                        printf("Unable to parse smaps entry for Rss\n");
 207                        goto out;
 208                }
 209                break;
 210        }
 211
 212        ret = locked && (vma_rss < vma_size);
 213out:
 214        free(line);
 215        if (smaps)
 216                fclose(smaps);
 217        return ret;
 218}
 219
 220#define PRESENT_BIT     0x8000000000000000ULL
 221#define PFN_MASK        0x007FFFFFFFFFFFFFULL
 222#define UNEVICTABLE_BIT (1UL << 18)
 223
 224static int lock_check(char *map)
 225{
 226        unsigned long page_size = getpagesize();
 227        uint64_t page1_flags, page2_flags;
 228
 229        page1_flags = get_pageflags((unsigned long)map);
 230        page2_flags = get_pageflags((unsigned long)map + page_size);
 231
 232        /* Both pages should be present */
 233        if (((page1_flags & PRESENT_BIT) == 0) ||
 234            ((page2_flags & PRESENT_BIT) == 0)) {
 235                printf("Failed to make both pages present\n");
 236                return 1;
 237        }
 238
 239        page1_flags = get_kpageflags(page1_flags & PFN_MASK);
 240        page2_flags = get_kpageflags(page2_flags & PFN_MASK);
 241
 242        /* Both pages should be unevictable */
 243        if (((page1_flags & UNEVICTABLE_BIT) == 0) ||
 244            ((page2_flags & UNEVICTABLE_BIT) == 0)) {
 245                printf("Failed to make both pages unevictable\n");
 246                return 1;
 247        }
 248
 249        if (!is_vmflag_set((unsigned long)map, LOCKED)) {
 250                printf("VMA flag %s is missing on page 1\n", LOCKED);
 251                return 1;
 252        }
 253
 254        if (!is_vmflag_set((unsigned long)map + page_size, LOCKED)) {
 255                printf("VMA flag %s is missing on page 2\n", LOCKED);
 256                return 1;
 257        }
 258
 259        return 0;
 260}
 261
 262static int unlock_lock_check(char *map)
 263{
 264        unsigned long page_size = getpagesize();
 265        uint64_t page1_flags, page2_flags;
 266
 267        page1_flags = get_pageflags((unsigned long)map);
 268        page2_flags = get_pageflags((unsigned long)map + page_size);
 269        page1_flags = get_kpageflags(page1_flags & PFN_MASK);
 270        page2_flags = get_kpageflags(page2_flags & PFN_MASK);
 271
 272        if ((page1_flags & UNEVICTABLE_BIT) || (page2_flags & UNEVICTABLE_BIT)) {
 273                printf("A page is still marked unevictable after unlock\n");
 274                return 1;
 275        }
 276
 277        if (is_vmflag_set((unsigned long)map, LOCKED)) {
 278                printf("VMA flag %s is present on page 1 after unlock\n", LOCKED);
 279                return 1;
 280        }
 281
 282        if (is_vmflag_set((unsigned long)map + page_size, LOCKED)) {
 283                printf("VMA flag %s is present on page 2 after unlock\n", LOCKED);
 284                return 1;
 285        }
 286
 287        return 0;
 288}
 289
 290static int test_mlock_lock()
 291{
 292        char *map;
 293        int ret = 1;
 294        unsigned long page_size = getpagesize();
 295
 296        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 297                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 298        if (map == MAP_FAILED) {
 299                perror("test_mlock_locked mmap");
 300                goto out;
 301        }
 302
 303        if (mlock2_(map, 2 * page_size, 0)) {
 304                if (errno == ENOSYS) {
 305                        printf("Cannot call new mlock family, skipping test\n");
 306                        _exit(0);
 307                }
 308                perror("mlock2(0)");
 309                goto unmap;
 310        }
 311
 312        if (lock_check(map))
 313                goto unmap;
 314
 315        /* Now unlock and recheck attributes */
 316        if (munlock(map, 2 * page_size)) {
 317                perror("munlock()");
 318                goto unmap;
 319        }
 320
 321        ret = unlock_lock_check(map);
 322
 323unmap:
 324        munmap(map, 2 * page_size);
 325out:
 326        return ret;
 327}
 328
 329static int onfault_check(char *map)
 330{
 331        unsigned long page_size = getpagesize();
 332        uint64_t page1_flags, page2_flags;
 333
 334        page1_flags = get_pageflags((unsigned long)map);
 335        page2_flags = get_pageflags((unsigned long)map + page_size);
 336
 337        /* Neither page should be present */
 338        if ((page1_flags & PRESENT_BIT) || (page2_flags & PRESENT_BIT)) {
 339                printf("Pages were made present by MLOCK_ONFAULT\n");
 340                return 1;
 341        }
 342
 343        *map = 'a';
 344        page1_flags = get_pageflags((unsigned long)map);
 345        page2_flags = get_pageflags((unsigned long)map + page_size);
 346
 347        /* Only page 1 should be present */
 348        if ((page1_flags & PRESENT_BIT) == 0) {
 349                printf("Page 1 is not present after fault\n");
 350                return 1;
 351        } else if (page2_flags & PRESENT_BIT) {
 352                printf("Page 2 was made present\n");
 353                return 1;
 354        }
 355
 356        page1_flags = get_kpageflags(page1_flags & PFN_MASK);
 357
 358        /* Page 1 should be unevictable */
 359        if ((page1_flags & UNEVICTABLE_BIT) == 0) {
 360                printf("Failed to make faulted page unevictable\n");
 361                return 1;
 362        }
 363
 364        if (!is_vma_lock_on_fault((unsigned long)map)) {
 365                printf("VMA is not marked for lock on fault\n");
 366                return 1;
 367        }
 368
 369        if (!is_vma_lock_on_fault((unsigned long)map + page_size)) {
 370                printf("VMA is not marked for lock on fault\n");
 371                return 1;
 372        }
 373
 374        return 0;
 375}
 376
 377static int unlock_onfault_check(char *map)
 378{
 379        unsigned long page_size = getpagesize();
 380        uint64_t page1_flags;
 381
 382        page1_flags = get_pageflags((unsigned long)map);
 383        page1_flags = get_kpageflags(page1_flags & PFN_MASK);
 384
 385        if (page1_flags & UNEVICTABLE_BIT) {
 386                printf("Page 1 is still marked unevictable after unlock\n");
 387                return 1;
 388        }
 389
 390        if (is_vma_lock_on_fault((unsigned long)map) ||
 391            is_vma_lock_on_fault((unsigned long)map + page_size)) {
 392                printf("VMA is still lock on fault after unlock\n");
 393                return 1;
 394        }
 395
 396        return 0;
 397}
 398
 399static int test_mlock_onfault()
 400{
 401        char *map;
 402        int ret = 1;
 403        unsigned long page_size = getpagesize();
 404
 405        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 406                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 407        if (map == MAP_FAILED) {
 408                perror("test_mlock_locked mmap");
 409                goto out;
 410        }
 411
 412        if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
 413                if (errno == ENOSYS) {
 414                        printf("Cannot call new mlock family, skipping test\n");
 415                        _exit(0);
 416                }
 417                perror("mlock2(MLOCK_ONFAULT)");
 418                goto unmap;
 419        }
 420
 421        if (onfault_check(map))
 422                goto unmap;
 423
 424        /* Now unlock and recheck attributes */
 425        if (munlock(map, 2 * page_size)) {
 426                if (errno == ENOSYS) {
 427                        printf("Cannot call new mlock family, skipping test\n");
 428                        _exit(0);
 429                }
 430                perror("munlock()");
 431                goto unmap;
 432        }
 433
 434        ret = unlock_onfault_check(map);
 435unmap:
 436        munmap(map, 2 * page_size);
 437out:
 438        return ret;
 439}
 440
 441static int test_lock_onfault_of_present()
 442{
 443        char *map;
 444        int ret = 1;
 445        unsigned long page_size = getpagesize();
 446        uint64_t page1_flags, page2_flags;
 447
 448        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 449                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 450        if (map == MAP_FAILED) {
 451                perror("test_mlock_locked mmap");
 452                goto out;
 453        }
 454
 455        *map = 'a';
 456
 457        if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
 458                if (errno == ENOSYS) {
 459                        printf("Cannot call new mlock family, skipping test\n");
 460                        _exit(0);
 461                }
 462                perror("mlock2(MLOCK_ONFAULT)");
 463                goto unmap;
 464        }
 465
 466        page1_flags = get_pageflags((unsigned long)map);
 467        page2_flags = get_pageflags((unsigned long)map + page_size);
 468        page1_flags = get_kpageflags(page1_flags & PFN_MASK);
 469        page2_flags = get_kpageflags(page2_flags & PFN_MASK);
 470
 471        /* Page 1 should be unevictable */
 472        if ((page1_flags & UNEVICTABLE_BIT) == 0) {
 473                printf("Failed to make present page unevictable\n");
 474                goto unmap;
 475        }
 476
 477        if (!is_vma_lock_on_fault((unsigned long)map) ||
 478            !is_vma_lock_on_fault((unsigned long)map + page_size)) {
 479                printf("VMA with present pages is not marked lock on fault\n");
 480                goto unmap;
 481        }
 482        ret = 0;
 483unmap:
 484        munmap(map, 2 * page_size);
 485out:
 486        return ret;
 487}
 488
 489static int test_munlockall()
 490{
 491        char *map;
 492        int ret = 1;
 493        unsigned long page_size = getpagesize();
 494
 495        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 496                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 497
 498        if (map == MAP_FAILED) {
 499                perror("test_munlockall mmap");
 500                goto out;
 501        }
 502
 503        if (mlockall(MCL_CURRENT)) {
 504                perror("mlockall(MCL_CURRENT)");
 505                goto out;
 506        }
 507
 508        if (lock_check(map))
 509                goto unmap;
 510
 511        if (munlockall()) {
 512                perror("munlockall()");
 513                goto unmap;
 514        }
 515
 516        if (unlock_lock_check(map))
 517                goto unmap;
 518
 519        munmap(map, 2 * page_size);
 520
 521        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 522                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 523
 524        if (map == MAP_FAILED) {
 525                perror("test_munlockall second mmap");
 526                goto out;
 527        }
 528
 529        if (mlockall(MCL_CURRENT | MCL_ONFAULT)) {
 530                perror("mlockall(MCL_CURRENT | MCL_ONFAULT)");
 531                goto unmap;
 532        }
 533
 534        if (onfault_check(map))
 535                goto unmap;
 536
 537        if (munlockall()) {
 538                perror("munlockall()");
 539                goto unmap;
 540        }
 541
 542        if (unlock_onfault_check(map))
 543                goto unmap;
 544
 545        if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
 546                perror("mlockall(MCL_CURRENT | MCL_FUTURE)");
 547                goto out;
 548        }
 549
 550        if (lock_check(map))
 551                goto unmap;
 552
 553        if (munlockall()) {
 554                perror("munlockall()");
 555                goto unmap;
 556        }
 557
 558        ret = unlock_lock_check(map);
 559
 560unmap:
 561        munmap(map, 2 * page_size);
 562out:
 563        munlockall();
 564        return ret;
 565}
 566
 567static int test_vma_management(bool call_mlock)
 568{
 569        int ret = 1;
 570        void *map;
 571        unsigned long page_size = getpagesize();
 572        struct vm_boundaries page1;
 573        struct vm_boundaries page2;
 574        struct vm_boundaries page3;
 575
 576        map = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
 577                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 578        if (map == MAP_FAILED) {
 579                perror("mmap()");
 580                return ret;
 581        }
 582
 583        if (call_mlock && mlock2_(map, 3 * page_size, MLOCK_ONFAULT)) {
 584                if (errno == ENOSYS) {
 585                        printf("Cannot call new mlock family, skipping test\n");
 586                        _exit(0);
 587                }
 588                perror("mlock(ONFAULT)\n");
 589                goto out;
 590        }
 591
 592        if (get_vm_area((unsigned long)map, &page1) ||
 593            get_vm_area((unsigned long)map + page_size, &page2) ||
 594            get_vm_area((unsigned long)map + page_size * 2, &page3)) {
 595                printf("couldn't find mapping in /proc/self/maps\n");
 596                goto out;
 597        }
 598
 599        /*
 600         * Before we unlock a portion, we need to that all three pages are in
 601         * the same VMA.  If they are not we abort this test (Note that this is
 602         * not a failure)
 603         */
 604        if (page1.start != page2.start || page2.start != page3.start) {
 605                printf("VMAs are not merged to start, aborting test\n");
 606                ret = 0;
 607                goto out;
 608        }
 609
 610        if (munlock(map + page_size, page_size)) {
 611                perror("munlock()");
 612                goto out;
 613        }
 614
 615        if (get_vm_area((unsigned long)map, &page1) ||
 616            get_vm_area((unsigned long)map + page_size, &page2) ||
 617            get_vm_area((unsigned long)map + page_size * 2, &page3)) {
 618                printf("couldn't find mapping in /proc/self/maps\n");
 619                goto out;
 620        }
 621
 622        /* All three VMAs should be different */
 623        if (page1.start == page2.start || page2.start == page3.start) {
 624                printf("failed to split VMA for munlock\n");
 625                goto out;
 626        }
 627
 628        /* Now unlock the first and third page and check the VMAs again */
 629        if (munlock(map, page_size * 3)) {
 630                perror("munlock()");
 631                goto out;
 632        }
 633
 634        if (get_vm_area((unsigned long)map, &page1) ||
 635            get_vm_area((unsigned long)map + page_size, &page2) ||
 636            get_vm_area((unsigned long)map + page_size * 2, &page3)) {
 637                printf("couldn't find mapping in /proc/self/maps\n");
 638                goto out;
 639        }
 640
 641        /* Now all three VMAs should be the same */
 642        if (page1.start != page2.start || page2.start != page3.start) {
 643                printf("failed to merge VMAs after munlock\n");
 644                goto out;
 645        }
 646
 647        ret = 0;
 648out:
 649        munmap(map, 3 * page_size);
 650        return ret;
 651}
 652
 653static int test_mlockall(int (test_function)(bool call_mlock))
 654{
 655        int ret = 1;
 656
 657        if (mlockall(MCL_CURRENT | MCL_ONFAULT | MCL_FUTURE)) {
 658                perror("mlockall");
 659                return ret;
 660        }
 661
 662        ret = test_function(false);
 663        munlockall();
 664        return ret;
 665}
 666
 667int main(int argc, char **argv)
 668{
 669        int ret = 0;
 670        ret += test_mlock_lock();
 671        ret += test_mlock_onfault();
 672        ret += test_munlockall();
 673        ret += test_lock_onfault_of_present();
 674        ret += test_vma_management(true);
 675        ret += test_mlockall(test_vma_management);
 676        return ret;
 677}
 678