linux/tools/testing/selftests/vm/mremap_dontunmap.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2
   3/*
   4 * Tests for mremap w/ MREMAP_DONTUNMAP.
   5 *
   6 * Copyright 2020, Brian Geffon <bgeffon@google.com>
   7 */
   8#define _GNU_SOURCE
   9#include <sys/mman.h>
  10#include <errno.h>
  11#include <stdio.h>
  12#include <stdlib.h>
  13#include <string.h>
  14#include <unistd.h>
  15
  16#include "../kselftest.h"
  17
  18#ifndef MREMAP_DONTUNMAP
  19#define MREMAP_DONTUNMAP 4
  20#endif
  21
  22unsigned long page_size;
  23char *page_buffer;
  24
  25static void dump_maps(void)
  26{
  27        char cmd[32];
  28
  29        snprintf(cmd, sizeof(cmd), "cat /proc/%d/maps", getpid());
  30        system(cmd);
  31}
  32
  33#define BUG_ON(condition, description)                                        \
  34        do {                                                                  \
  35                if (condition) {                                              \
  36                        fprintf(stderr, "[FAIL]\t%s():%d\t%s:%s\n", __func__, \
  37                                __LINE__, (description), strerror(errno));    \
  38                        dump_maps();                                      \
  39                        exit(1);                                              \
  40                }                                                             \
  41        } while (0)
  42
  43// Try a simple operation for to "test" for kernel support this prevents
  44// reporting tests as failed when it's run on an older kernel.
  45static int kernel_support_for_mremap_dontunmap()
  46{
  47        int ret = 0;
  48        unsigned long num_pages = 1;
  49        void *source_mapping = mmap(NULL, num_pages * page_size, PROT_NONE,
  50                                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  51        BUG_ON(source_mapping == MAP_FAILED, "mmap");
  52
  53        // This simple remap should only fail if MREMAP_DONTUNMAP isn't
  54        // supported.
  55        void *dest_mapping =
  56            mremap(source_mapping, num_pages * page_size, num_pages * page_size,
  57                   MREMAP_DONTUNMAP | MREMAP_MAYMOVE, 0);
  58        if (dest_mapping == MAP_FAILED) {
  59                ret = errno;
  60        } else {
  61                BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,
  62                       "unable to unmap destination mapping");
  63        }
  64
  65        BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
  66               "unable to unmap source mapping");
  67        return ret;
  68}
  69
  70// This helper will just validate that an entire mapping contains the expected
  71// byte.
  72static int check_region_contains_byte(void *addr, unsigned long size, char byte)
  73{
  74        BUG_ON(size & (page_size - 1),
  75               "check_region_contains_byte expects page multiples");
  76        BUG_ON((unsigned long)addr & (page_size - 1),
  77               "check_region_contains_byte expects page alignment");
  78
  79        memset(page_buffer, byte, page_size);
  80
  81        unsigned long num_pages = size / page_size;
  82        unsigned long i;
  83
  84        // Compare each page checking that it contains our expected byte.
  85        for (i = 0; i < num_pages; ++i) {
  86                int ret =
  87                    memcmp(addr + (i * page_size), page_buffer, page_size);
  88                if (ret) {
  89                        return ret;
  90                }
  91        }
  92
  93        return 0;
  94}
  95
  96// this test validates that MREMAP_DONTUNMAP moves the pagetables while leaving
  97// the source mapping mapped.
  98static void mremap_dontunmap_simple()
  99{
 100        unsigned long num_pages = 5;
 101
 102        void *source_mapping =
 103            mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
 104                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 105        BUG_ON(source_mapping == MAP_FAILED, "mmap");
 106
 107        memset(source_mapping, 'a', num_pages * page_size);
 108
 109        // Try to just move the whole mapping anywhere (not fixed).
 110        void *dest_mapping =
 111            mremap(source_mapping, num_pages * page_size, num_pages * page_size,
 112                   MREMAP_DONTUNMAP | MREMAP_MAYMOVE, NULL);
 113        BUG_ON(dest_mapping == MAP_FAILED, "mremap");
 114
 115        // Validate that the pages have been moved, we know they were moved if
 116        // the dest_mapping contains a's.
 117        BUG_ON(check_region_contains_byte
 118               (dest_mapping, num_pages * page_size, 'a') != 0,
 119               "pages did not migrate");
 120        BUG_ON(check_region_contains_byte
 121               (source_mapping, num_pages * page_size, 0) != 0,
 122               "source should have no ptes");
 123
 124        BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,
 125               "unable to unmap destination mapping");
 126        BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
 127               "unable to unmap source mapping");
 128}
 129
 130// This test validates that MREMAP_DONTUNMAP on a shared mapping works as expected.
 131static void mremap_dontunmap_simple_shmem()
 132{
 133        unsigned long num_pages = 5;
 134
 135        int mem_fd = memfd_create("memfd", MFD_CLOEXEC);
 136        BUG_ON(mem_fd < 0, "memfd_create");
 137
 138        BUG_ON(ftruncate(mem_fd, num_pages * page_size) < 0,
 139                        "ftruncate");
 140
 141        void *source_mapping =
 142            mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
 143                 MAP_FILE | MAP_SHARED, mem_fd, 0);
 144        BUG_ON(source_mapping == MAP_FAILED, "mmap");
 145
 146        BUG_ON(close(mem_fd) < 0, "close");
 147
 148        memset(source_mapping, 'a', num_pages * page_size);
 149
 150        // Try to just move the whole mapping anywhere (not fixed).
 151        void *dest_mapping =
 152            mremap(source_mapping, num_pages * page_size, num_pages * page_size,
 153                   MREMAP_DONTUNMAP | MREMAP_MAYMOVE, NULL);
 154        if (dest_mapping == MAP_FAILED && errno == EINVAL) {
 155                // Old kernel which doesn't support MREMAP_DONTUNMAP on shmem.
 156                BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
 157                        "unable to unmap source mapping");
 158                return;
 159        }
 160
 161        BUG_ON(dest_mapping == MAP_FAILED, "mremap");
 162
 163        // Validate that the pages have been moved, we know they were moved if
 164        // the dest_mapping contains a's.
 165        BUG_ON(check_region_contains_byte
 166               (dest_mapping, num_pages * page_size, 'a') != 0,
 167               "pages did not migrate");
 168
 169        // Because the region is backed by shmem, we will actually see the same
 170        // memory at the source location still.
 171        BUG_ON(check_region_contains_byte
 172               (source_mapping, num_pages * page_size, 'a') != 0,
 173               "source should have no ptes");
 174
 175        BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,
 176               "unable to unmap destination mapping");
 177        BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
 178               "unable to unmap source mapping");
 179}
 180
 181// This test validates MREMAP_DONTUNMAP will move page tables to a specific
 182// destination using MREMAP_FIXED, also while validating that the source
 183// remains intact.
 184static void mremap_dontunmap_simple_fixed()
 185{
 186        unsigned long num_pages = 5;
 187
 188        // Since we want to guarantee that we can remap to a point, we will
 189        // create a mapping up front.
 190        void *dest_mapping =
 191            mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
 192                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 193        BUG_ON(dest_mapping == MAP_FAILED, "mmap");
 194        memset(dest_mapping, 'X', num_pages * page_size);
 195
 196        void *source_mapping =
 197            mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
 198                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 199        BUG_ON(source_mapping == MAP_FAILED, "mmap");
 200        memset(source_mapping, 'a', num_pages * page_size);
 201
 202        void *remapped_mapping =
 203            mremap(source_mapping, num_pages * page_size, num_pages * page_size,
 204                   MREMAP_FIXED | MREMAP_DONTUNMAP | MREMAP_MAYMOVE,
 205                   dest_mapping);
 206        BUG_ON(remapped_mapping == MAP_FAILED, "mremap");
 207        BUG_ON(remapped_mapping != dest_mapping,
 208               "mremap should have placed the remapped mapping at dest_mapping");
 209
 210        // The dest mapping will have been unmap by mremap so we expect the Xs
 211        // to be gone and replaced with a's.
 212        BUG_ON(check_region_contains_byte
 213               (dest_mapping, num_pages * page_size, 'a') != 0,
 214               "pages did not migrate");
 215
 216        // And the source mapping will have had its ptes dropped.
 217        BUG_ON(check_region_contains_byte
 218               (source_mapping, num_pages * page_size, 0) != 0,
 219               "source should have no ptes");
 220
 221        BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,
 222               "unable to unmap destination mapping");
 223        BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
 224               "unable to unmap source mapping");
 225}
 226
 227// This test validates that we can MREMAP_DONTUNMAP for a portion of an
 228// existing mapping.
 229static void mremap_dontunmap_partial_mapping()
 230{
 231        /*
 232         *  source mapping:
 233         *  --------------
 234         *  | aaaaaaaaaa |
 235         *  --------------
 236         *  to become:
 237         *  --------------
 238         *  | aaaaa00000 |
 239         *  --------------
 240         *  With the destination mapping containing 5 pages of As.
 241         *  ---------
 242         *  | aaaaa |
 243         *  ---------
 244         */
 245        unsigned long num_pages = 10;
 246        void *source_mapping =
 247            mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
 248                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 249        BUG_ON(source_mapping == MAP_FAILED, "mmap");
 250        memset(source_mapping, 'a', num_pages * page_size);
 251
 252        // We will grab the last 5 pages of the source and move them.
 253        void *dest_mapping =
 254            mremap(source_mapping + (5 * page_size), 5 * page_size,
 255                   5 * page_size,
 256                   MREMAP_DONTUNMAP | MREMAP_MAYMOVE, NULL);
 257        BUG_ON(dest_mapping == MAP_FAILED, "mremap");
 258
 259        // We expect the first 5 pages of the source to contain a's and the
 260        // final 5 pages to contain zeros.
 261        BUG_ON(check_region_contains_byte(source_mapping, 5 * page_size, 'a') !=
 262               0, "first 5 pages of source should have original pages");
 263        BUG_ON(check_region_contains_byte
 264               (source_mapping + (5 * page_size), 5 * page_size, 0) != 0,
 265               "final 5 pages of source should have no ptes");
 266
 267        // Finally we expect the destination to have 5 pages worth of a's.
 268        BUG_ON(check_region_contains_byte(dest_mapping, 5 * page_size, 'a') !=
 269               0, "dest mapping should contain ptes from the source");
 270
 271        BUG_ON(munmap(dest_mapping, 5 * page_size) == -1,
 272               "unable to unmap destination mapping");
 273        BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
 274               "unable to unmap source mapping");
 275}
 276
 277// This test validates that we can remap over only a portion of a mapping.
 278static void mremap_dontunmap_partial_mapping_overwrite(void)
 279{
 280        /*
 281         *  source mapping:
 282         *  ---------
 283         *  |aaaaa|
 284         *  ---------
 285         *  dest mapping initially:
 286         *  -----------
 287         *  |XXXXXXXXXX|
 288         *  ------------
 289         *  Source to become:
 290         *  ---------
 291         *  |00000|
 292         *  ---------
 293         *  With the destination mapping containing 5 pages of As.
 294         *  ------------
 295         *  |aaaaaXXXXX|
 296         *  ------------
 297         */
 298        void *source_mapping =
 299            mmap(NULL, 5 * page_size, PROT_READ | PROT_WRITE,
 300                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 301        BUG_ON(source_mapping == MAP_FAILED, "mmap");
 302        memset(source_mapping, 'a', 5 * page_size);
 303
 304        void *dest_mapping =
 305            mmap(NULL, 10 * page_size, PROT_READ | PROT_WRITE,
 306                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 307        BUG_ON(dest_mapping == MAP_FAILED, "mmap");
 308        memset(dest_mapping, 'X', 10 * page_size);
 309
 310        // We will grab the last 5 pages of the source and move them.
 311        void *remapped_mapping =
 312            mremap(source_mapping, 5 * page_size,
 313                   5 * page_size,
 314                   MREMAP_DONTUNMAP | MREMAP_MAYMOVE | MREMAP_FIXED, dest_mapping);
 315        BUG_ON(dest_mapping == MAP_FAILED, "mremap");
 316        BUG_ON(dest_mapping != remapped_mapping, "expected to remap to dest_mapping");
 317
 318        BUG_ON(check_region_contains_byte(source_mapping, 5 * page_size, 0) !=
 319               0, "first 5 pages of source should have no ptes");
 320
 321        // Finally we expect the destination to have 5 pages worth of a's.
 322        BUG_ON(check_region_contains_byte(dest_mapping, 5 * page_size, 'a') != 0,
 323                        "dest mapping should contain ptes from the source");
 324
 325        // Finally the last 5 pages shouldn't have been touched.
 326        BUG_ON(check_region_contains_byte(dest_mapping + (5 * page_size),
 327                                5 * page_size, 'X') != 0,
 328                        "dest mapping should have retained the last 5 pages");
 329
 330        BUG_ON(munmap(dest_mapping, 10 * page_size) == -1,
 331               "unable to unmap destination mapping");
 332        BUG_ON(munmap(source_mapping, 5 * page_size) == -1,
 333               "unable to unmap source mapping");
 334}
 335
 336int main(void)
 337{
 338        page_size = sysconf(_SC_PAGE_SIZE);
 339
 340        // test for kernel support for MREMAP_DONTUNMAP skipping the test if
 341        // not.
 342        if (kernel_support_for_mremap_dontunmap() != 0) {
 343                printf("No kernel support for MREMAP_DONTUNMAP\n");
 344                return KSFT_SKIP;
 345        }
 346
 347        // Keep a page sized buffer around for when we need it.
 348        page_buffer =
 349            mmap(NULL, page_size, PROT_READ | PROT_WRITE,
 350                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 351        BUG_ON(page_buffer == MAP_FAILED, "unable to mmap a page.");
 352
 353        mremap_dontunmap_simple();
 354        mremap_dontunmap_simple_shmem();
 355        mremap_dontunmap_simple_fixed();
 356        mremap_dontunmap_partial_mapping();
 357        mremap_dontunmap_partial_mapping_overwrite();
 358
 359        BUG_ON(munmap(page_buffer, page_size) == -1,
 360               "unable to unmap page buffer");
 361
 362        printf("OK\n");
 363        return 0;
 364}
 365