370 lines · c
1// SPDX-License-Identifier: GPL-2.02 3/*4 * Tests for mremap w/ MREMAP_DONTUNMAP.5 *6 * Copyright 2020, Brian Geffon <bgeffon@google.com>7 */8#define _GNU_SOURCE9#include <sys/mman.h>10#include <linux/mman.h>11#include <errno.h>12#include <stdio.h>13#include <stdlib.h>14#include <string.h>15#include <unistd.h>16 17#include "../kselftest.h"18 19unsigned long page_size;20char *page_buffer;21 22static void dump_maps(void)23{24 char cmd[32];25 26 snprintf(cmd, sizeof(cmd), "cat /proc/%d/maps", getpid());27 system(cmd);28}29 30#define BUG_ON(condition, description) \31 do { \32 if (condition) { \33 dump_maps(); \34 ksft_exit_fail_msg("[FAIL]\t%s:%d\t%s:%s\n", \35 __func__, __LINE__, (description), \36 strerror(errno)); \37 } \38 } while (0)39 40// Try a simple operation for to "test" for kernel support this prevents41// reporting tests as failed when it's run on an older kernel.42static int kernel_support_for_mremap_dontunmap()43{44 int ret = 0;45 unsigned long num_pages = 1;46 void *source_mapping = mmap(NULL, num_pages * page_size, PROT_NONE,47 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);48 BUG_ON(source_mapping == MAP_FAILED, "mmap");49 50 // This simple remap should only fail if MREMAP_DONTUNMAP isn't51 // supported.52 void *dest_mapping =53 mremap(source_mapping, num_pages * page_size, num_pages * page_size,54 MREMAP_DONTUNMAP | MREMAP_MAYMOVE, 0);55 if (dest_mapping == MAP_FAILED) {56 ret = errno;57 } else {58 BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,59 "unable to unmap destination mapping");60 }61 62 BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,63 "unable to unmap source mapping");64 return ret;65}66 67// This helper will just validate that an entire mapping contains the expected68// byte.69static int check_region_contains_byte(void *addr, unsigned long size, char byte)70{71 BUG_ON(size & (page_size - 1),72 "check_region_contains_byte expects page multiples");73 BUG_ON((unsigned long)addr & (page_size - 1),74 "check_region_contains_byte expects page alignment");75 76 memset(page_buffer, byte, page_size);77 78 unsigned long num_pages = size / page_size;79 unsigned long i;80 81 // Compare each page checking that it contains our expected byte.82 for (i = 0; i < num_pages; ++i) {83 int ret =84 memcmp(addr + (i * page_size), page_buffer, page_size);85 if (ret) {86 return ret;87 }88 }89 90 return 0;91}92 93// this test validates that MREMAP_DONTUNMAP moves the pagetables while leaving94// the source mapping mapped.95static void mremap_dontunmap_simple()96{97 unsigned long num_pages = 5;98 99 void *source_mapping =100 mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,101 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);102 BUG_ON(source_mapping == MAP_FAILED, "mmap");103 104 memset(source_mapping, 'a', num_pages * page_size);105 106 // Try to just move the whole mapping anywhere (not fixed).107 void *dest_mapping =108 mremap(source_mapping, num_pages * page_size, num_pages * page_size,109 MREMAP_DONTUNMAP | MREMAP_MAYMOVE, NULL);110 BUG_ON(dest_mapping == MAP_FAILED, "mremap");111 112 // Validate that the pages have been moved, we know they were moved if113 // the dest_mapping contains a's.114 BUG_ON(check_region_contains_byte115 (dest_mapping, num_pages * page_size, 'a') != 0,116 "pages did not migrate");117 BUG_ON(check_region_contains_byte118 (source_mapping, num_pages * page_size, 0) != 0,119 "source should have no ptes");120 121 BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,122 "unable to unmap destination mapping");123 BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,124 "unable to unmap source mapping");125 ksft_test_result_pass("%s\n", __func__);126}127 128// This test validates that MREMAP_DONTUNMAP on a shared mapping works as expected.129static void mremap_dontunmap_simple_shmem()130{131 unsigned long num_pages = 5;132 133 int mem_fd = memfd_create("memfd", MFD_CLOEXEC);134 BUG_ON(mem_fd < 0, "memfd_create");135 136 BUG_ON(ftruncate(mem_fd, num_pages * page_size) < 0,137 "ftruncate");138 139 void *source_mapping =140 mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,141 MAP_FILE | MAP_SHARED, mem_fd, 0);142 BUG_ON(source_mapping == MAP_FAILED, "mmap");143 144 BUG_ON(close(mem_fd) < 0, "close");145 146 memset(source_mapping, 'a', num_pages * page_size);147 148 // Try to just move the whole mapping anywhere (not fixed).149 void *dest_mapping =150 mremap(source_mapping, num_pages * page_size, num_pages * page_size,151 MREMAP_DONTUNMAP | MREMAP_MAYMOVE, NULL);152 if (dest_mapping == MAP_FAILED && errno == EINVAL) {153 // Old kernel which doesn't support MREMAP_DONTUNMAP on shmem.154 BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,155 "unable to unmap source mapping");156 return;157 }158 159 BUG_ON(dest_mapping == MAP_FAILED, "mremap");160 161 // Validate that the pages have been moved, we know they were moved if162 // the dest_mapping contains a's.163 BUG_ON(check_region_contains_byte164 (dest_mapping, num_pages * page_size, 'a') != 0,165 "pages did not migrate");166 167 // Because the region is backed by shmem, we will actually see the same168 // memory at the source location still.169 BUG_ON(check_region_contains_byte170 (source_mapping, num_pages * page_size, 'a') != 0,171 "source should have no ptes");172 173 BUG_ON(munmap(dest_mapping, num_pages * page_size) == -1,174 "unable to unmap destination mapping");175 BUG_ON(munmap(source_mapping, num_pages * page_size) == -1,176 "unable to unmap source mapping");177 ksft_test_result_pass("%s\n", __func__);178}179 180// This test validates MREMAP_DONTUNMAP will move page tables to a specific181// destination using MREMAP_FIXED, also while validating that the source182// remains intact.183static void mremap_dontunmap_simple_fixed()184{185 unsigned long num_pages = 5;186 187 // Since we want to guarantee that we can remap to a point, we will188 // create a mapping up front.189 void *dest_mapping =190 mmap(NULL, num_pages * page_size, PROT_READ | PROT_WRITE,191 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);192 BUG_ON(dest_mapping == MAP_FAILED, "mmap");193 memset(dest_mapping, 'X', num_pages * page_size);194 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 void *remapped_mapping =202 mremap(source_mapping, num_pages * page_size, num_pages * page_size,203 MREMAP_FIXED | MREMAP_DONTUNMAP | MREMAP_MAYMOVE,204 dest_mapping);205 BUG_ON(remapped_mapping == MAP_FAILED, "mremap");206 BUG_ON(remapped_mapping != dest_mapping,207 "mremap should have placed the remapped mapping at dest_mapping");208 209 // The dest mapping will have been unmap by mremap so we expect the Xs210 // to be gone and replaced with a's.211 BUG_ON(check_region_contains_byte212 (dest_mapping, num_pages * page_size, 'a') != 0,213 "pages did not migrate");214 215 // And the source mapping will have had its ptes dropped.216 BUG_ON(check_region_contains_byte217 (source_mapping, num_pages * page_size, 0) != 0,218 "source should have no ptes");219 220 BUG_ON(munmap(dest_mapping, num_pages * 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 ksft_test_result_pass("%s\n", __func__);225}226 227// This test validates that we can MREMAP_DONTUNMAP for a portion of an228// 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 the260 // 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_byte264 (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 ksft_test_result_pass("%s\n", __func__);276}277 278// This test validates that we can remap over only a portion of a mapping.279static void mremap_dontunmap_partial_mapping_overwrite(void)280{281 /*282 * source mapping:283 * ---------284 * |aaaaa|285 * ---------286 * dest mapping initially:287 * -----------288 * |XXXXXXXXXX|289 * ------------290 * Source to become:291 * ---------292 * |00000|293 * ---------294 * With the destination mapping containing 5 pages of As.295 * ------------296 * |aaaaaXXXXX|297 * ------------298 */299 void *source_mapping =300 mmap(NULL, 5 * page_size, PROT_READ | PROT_WRITE,301 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);302 BUG_ON(source_mapping == MAP_FAILED, "mmap");303 memset(source_mapping, 'a', 5 * page_size);304 305 void *dest_mapping =306 mmap(NULL, 10 * page_size, PROT_READ | PROT_WRITE,307 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);308 BUG_ON(dest_mapping == MAP_FAILED, "mmap");309 memset(dest_mapping, 'X', 10 * page_size);310 311 // We will grab the last 5 pages of the source and move them.312 void *remapped_mapping =313 mremap(source_mapping, 5 * page_size,314 5 * page_size,315 MREMAP_DONTUNMAP | MREMAP_MAYMOVE | MREMAP_FIXED, dest_mapping);316 BUG_ON(dest_mapping == MAP_FAILED, "mremap");317 BUG_ON(dest_mapping != remapped_mapping, "expected to remap to dest_mapping");318 319 BUG_ON(check_region_contains_byte(source_mapping, 5 * page_size, 0) !=320 0, "first 5 pages of source should have no ptes");321 322 // Finally we expect the destination to have 5 pages worth of a's.323 BUG_ON(check_region_contains_byte(dest_mapping, 5 * page_size, 'a') != 0,324 "dest mapping should contain ptes from the source");325 326 // Finally the last 5 pages shouldn't have been touched.327 BUG_ON(check_region_contains_byte(dest_mapping + (5 * page_size),328 5 * page_size, 'X') != 0,329 "dest mapping should have retained the last 5 pages");330 331 BUG_ON(munmap(dest_mapping, 10 * page_size) == -1,332 "unable to unmap destination mapping");333 BUG_ON(munmap(source_mapping, 5 * page_size) == -1,334 "unable to unmap source mapping");335 ksft_test_result_pass("%s\n", __func__);336}337 338int main(void)339{340 ksft_print_header();341 342 page_size = sysconf(_SC_PAGE_SIZE);343 344 // test for kernel support for MREMAP_DONTUNMAP skipping the test if345 // not.346 if (kernel_support_for_mremap_dontunmap() != 0) {347 ksft_print_msg("No kernel support for MREMAP_DONTUNMAP\n");348 ksft_finished();349 }350 351 ksft_set_plan(5);352 353 // Keep a page sized buffer around for when we need it.354 page_buffer =355 mmap(NULL, page_size, PROT_READ | PROT_WRITE,356 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);357 BUG_ON(page_buffer == MAP_FAILED, "unable to mmap a page.");358 359 mremap_dontunmap_simple();360 mremap_dontunmap_simple_shmem();361 mremap_dontunmap_simple_fixed();362 mremap_dontunmap_partial_mapping();363 mremap_dontunmap_partial_mapping_overwrite();364 365 BUG_ON(munmap(page_buffer, page_size) == -1,366 "unable to unmap page buffer");367 368 ksft_finished();369}370