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 <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 19 unsigned long page_size; 20 char *page_buffer; 21 22 static 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 prevents 41 // reporting tests as failed when it's run on an older kernel. 42 static 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't 51 // 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 expected 68 // byte. 69 static 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 leaving 94 // the source mapping mapped. 95 static 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 if 113 // the dest_mapping contains a's. 114 BUG_ON(check_region_contains_byte 115 (dest_mapping, num_pages * page_size, 'a') != 0, 116 "pages did not migrate"); 117 BUG_ON(check_region_contains_byte 118 (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. 129 static 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 if 162 // the dest_mapping contains a's. 163 BUG_ON(check_region_contains_byte 164 (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 same 168 // memory at the source location still. 169 BUG_ON(check_region_contains_byte 170 (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 specific 181 // destination using MREMAP_FIXED, also while validating that the source 182 // remains intact. 183 static 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 will 188 // 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 Xs 210 // to be gone and replaced with a's. 211 BUG_ON(check_region_contains_byte 212 (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_byte 217 (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 an 228 // existing mapping. 229 static 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 ksft_test_result_pass("%s\n", __func__); 276 } 277 278 // This test validates that we can remap over only a portion of a mapping. 279 static 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 338 int 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 if 345 // 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
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.