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 MREMAP_DONTUNMAP will move page tables to a specific
 131// destination using MREMAP_FIXED, also while validating that the source
 132// remains intact.
 133static void mremap_dontunmap_simple_fixed()
 134{
 135        unsigned long num_pages = 5;
 136
 137        // Since we want to guarantee that we can remap to a point, we will
 138        // create a mapping up front.
 139        void *dest_mapping =
 140            mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
 141                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 142        BUG_ON(dest_mapping == MAP_FAILED, "mmap");
 143        memset(dest_mapping, 'X', num_pages * page_size);
 144
 145        void *source_mapping =
 146            mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
 147                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 148        BUG_ON(source_mapping == MAP_FAILED, "mmap");
 149        memset(source_mapping, 'a', num_pages * page_size);
 150
 151        void *remapped_mapping =
 152            mremap(source_mapping, num_pages * page_size, num_pages * page_size,
 153                   MREMAP_FIXED | MREMAP_DONTUNMAP | MREMAP_MAYMOVE,
 154                   dest_mapping);
 155        BUG_ON(remapped_mapping == MAP_FAILED, "mremap");
 156        BUG_ON(remapped_mapping != dest_mapping,
 157               "mremap should have placed the remapped mapping at dest_mapping");
 158
 159        // The dest mapping will have been unmap by mremap so we expect the Xs
 160        // to be gone and replaced with a's.
 161        BUG_ON(check_region_contains_byte
 162               (dest_mapping, num_pages * page_size, 'a') != 0,
 163               "pages did not migrate");
 164
 165        // And the source mapping will have had its ptes dropped.
 166        BUG_ON(check_region_contains_byte
 167               (source_mapping, num_pages * page_size, 0) != 0,
 168               "source should have no ptes");
 169
 170        BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,
 171               "unable to unmap destination mapping");
 172        BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
 173               "unable to unmap source mapping");
 174}
 175
 176// This test validates that we can MREMAP_DONTUNMAP for a portion of an
 177// existing mapping.
 178static void mremap_dontunmap_partial_mapping()
 179{
 180        /*
 181         *  source mapping:
 182         *  --------------
 183         *  | aaaaaaaaaa |
 184         *  --------------
 185         *  to become:
 186         *  --------------
 187         *  | aaaaa00000 |
 188         *  --------------
 189         *  With the destination mapping containing 5 pages of As.
 190         *  ---------
 191         *  | aaaaa |
 192         *  ---------
 193         */
 194        unsigned long num_pages = 10;
 195        void *source_mapping =
 196            mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,
 197                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 198        BUG_ON(source_mapping == MAP_FAILED, "mmap");
 199        memset(source_mapping, 'a', num_pages * page_size);
 200
 201        // We will grab the last 5 pages of the source and move them.
 202        void *dest_mapping =
 203            mremap(source_mapping + (5 * page_size), 5 * page_size,
 204                   5 * page_size,
 205                   MREMAP_DONTUNMAP | MREMAP_MAYMOVE, NULL);
 206        BUG_ON(dest_mapping == MAP_FAILED, "mremap");
 207
 208        // We expect the first 5 pages of the source to contain a's and the
 209        // final 5 pages to contain zeros.
 210        BUG_ON(check_region_contains_byte(source_mapping, 5 * page_size, 'a') !=
 211               0, "first 5 pages of source should have original pages");
 212        BUG_ON(check_region_contains_byte
 213               (source_mapping + (5 * page_size), 5 * page_size, 0) != 0,
 214               "final 5 pages of source should have no ptes");
 215
 216        // Finally we expect the destination to have 5 pages worth of a's.
 217        BUG_ON(check_region_contains_byte(dest_mapping, 5 * page_size, 'a') !=
 218               0, "dest mapping should contain ptes from the source");
 219
 220        BUG_ON(munmap(dest_mapping, 5 * page_size) == -1,
 221               "unable to unmap destination mapping");
 222        BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,
 223               "unable to unmap source mapping");
 224}
 225
 226// This test validates that we can remap over only a portion of a mapping.
 227static void mremap_dontunmap_partial_mapping_overwrite(void)
 228{
 229        /*
 230         *  source mapping:
 231         *  ---------
 232         *  |aaaaa|
 233         *  ---------
 234         *  dest mapping initially:
 235         *  -----------
 236         *  |XXXXXXXXXX|
 237         *  ------------
 238         *  Source to become:
 239         *  ---------
 240         *  |00000|
 241         *  ---------
 242         *  With the destination mapping containing 5 pages of As.
 243         *  ------------
 244         *  |aaaaaXXXXX|
 245         *  ------------
 246         */
 247        void *source_mapping =
 248            mmap(NULL, 5 * page_size, PROT_READ | PROT_WRITE,
 249                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 250        BUG_ON(source_mapping == MAP_FAILED, "mmap");
 251        memset(source_mapping, 'a', 5 * page_size);
 252
 253        void *dest_mapping =
 254            mmap(NULL, 10 * page_size, PROT_READ | PROT_WRITE,
 255                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 256        BUG_ON(dest_mapping == MAP_FAILED, "mmap");
 257        memset(dest_mapping, 'X', 10 * page_size);
 258
 259        // We will grab the last 5 pages of the source and move them.
 260        void *remapped_mapping =
 261            mremap(source_mapping, 5 * page_size,
 262                   5 * page_size,
 263                   MREMAP_DONTUNMAP | MREMAP_MAYMOVE | MREMAP_FIXED, dest_mapping);
 264        BUG_ON(dest_mapping == MAP_FAILED, "mremap");
 265        BUG_ON(dest_mapping != remapped_mapping, "expected to remap to dest_mapping");
 266
 267        BUG_ON(check_region_contains_byte(source_mapping, 5 * page_size, 0) !=
 268               0, "first 5 pages of source should have no ptes");
 269
 270        // Finally we expect the destination to have 5 pages worth of a's.
 271        BUG_ON(check_region_contains_byte(dest_mapping, 5 * page_size, 'a') != 0,
 272                        "dest mapping should contain ptes from the source");
 273
 274        // Finally the last 5 pages shouldn't have been touched.
 275        BUG_ON(check_region_contains_byte(dest_mapping + (5 * page_size),
 276                                5 * page_size, 'X') != 0,
 277                        "dest mapping should have retained the last 5 pages");
 278
 279        BUG_ON(munmap(dest_mapping, 10 * page_size) == -1,
 280               "unable to unmap destination mapping");
 281        BUG_ON(munmap(source_mapping, 5 * page_size) == -1,
 282               "unable to unmap source mapping");
 283}
 284
 285int main(void)
 286{
 287        page_size = sysconf(_SC_PAGE_SIZE);
 288
 289        // test for kernel support for MREMAP_DONTUNMAP skipping the test if
 290        // not.
 291        if (kernel_support_for_mremap_dontunmap() != 0) {
 292                printf("No kernel support for MREMAP_DONTUNMAP\n");
 293                return KSFT_SKIP;
 294        }
 295
 296        // Keep a page sized buffer around for when we need it.
 297        page_buffer =
 298            mmap(NULL, page_size, PROT_READ | PROT_WRITE,
 299                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 300        BUG_ON(page_buffer == MAP_FAILED, "unable to mmap a page.");
 301
 302        mremap_dontunmap_simple();
 303        mremap_dontunmap_simple_fixed();
 304        mremap_dontunmap_partial_mapping();
 305        mremap_dontunmap_partial_mapping_overwrite();
 306
 307        BUG_ON(munmap(page_buffer, page_size) == -1,
 308               "unable to unmap page buffer");
 309
 310        printf("OK\n");
 311        return 0;
 312}
 313