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