mremap_dontunmap.c (11755B)
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}