1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 #include <sys/mman.h>
4 #include <stdint.h>
5 #include <unistd.h>
6 #include <string.h>
7 #include <sys/time.h>
8 #include <sys/resource.h>
9 #include <stdbool.h>
10 #include "../kselftest.h"
11 #include "mlock2.h"
12
13 struct vm_boundaries {
14 unsigned long start;
15 unsigned long end;
16 };
17
get_vm_area(unsigned long addr,struct vm_boundaries * area)18 static int get_vm_area(unsigned long addr, struct vm_boundaries *area)
19 {
20 FILE *file;
21 int ret = 1;
22 char line[1024] = {0};
23 unsigned long start;
24 unsigned long end;
25
26 if (!area)
27 return ret;
28
29 file = fopen("/proc/self/maps", "r");
30 if (!file) {
31 perror("fopen");
32 return ret;
33 }
34
35 memset(area, 0, sizeof(struct vm_boundaries));
36
37 while(fgets(line, 1024, file)) {
38 if (sscanf(line, "%lx-%lx", &start, &end) != 2) {
39 ksft_print_msg("cannot parse /proc/self/maps\n");
40 goto out;
41 }
42
43 if (start <= addr && end > addr) {
44 area->start = start;
45 area->end = end;
46 ret = 0;
47 goto out;
48 }
49 }
50 out:
51 fclose(file);
52 return ret;
53 }
54
55 #define VMFLAGS "VmFlags:"
56
is_vmflag_set(unsigned long addr,const char * vmflag)57 static bool is_vmflag_set(unsigned long addr, const char *vmflag)
58 {
59 char *line = NULL;
60 char *flags;
61 size_t size = 0;
62 bool ret = false;
63 FILE *smaps;
64
65 smaps = seek_to_smaps_entry(addr);
66 if (!smaps) {
67 ksft_print_msg("Unable to parse /proc/self/smaps\n");
68 goto out;
69 }
70
71 while (getline(&line, &size, smaps) > 0) {
72 if (!strstr(line, VMFLAGS)) {
73 free(line);
74 line = NULL;
75 size = 0;
76 continue;
77 }
78
79 flags = line + strlen(VMFLAGS);
80 ret = (strstr(flags, vmflag) != NULL);
81 goto out;
82 }
83
84 out:
85 free(line);
86 fclose(smaps);
87 return ret;
88 }
89
90 #define SIZE "Size:"
91 #define RSS "Rss:"
92 #define LOCKED "lo"
93
get_value_for_name(unsigned long addr,const char * name)94 static unsigned long get_value_for_name(unsigned long addr, const char *name)
95 {
96 char *line = NULL;
97 size_t size = 0;
98 char *value_ptr;
99 FILE *smaps = NULL;
100 unsigned long value = -1UL;
101
102 smaps = seek_to_smaps_entry(addr);
103 if (!smaps) {
104 ksft_print_msg("Unable to parse /proc/self/smaps\n");
105 goto out;
106 }
107
108 while (getline(&line, &size, smaps) > 0) {
109 if (!strstr(line, name)) {
110 free(line);
111 line = NULL;
112 size = 0;
113 continue;
114 }
115
116 value_ptr = line + strlen(name);
117 if (sscanf(value_ptr, "%lu kB", &value) < 1) {
118 ksft_print_msg("Unable to parse smaps entry for Size\n");
119 goto out;
120 }
121 break;
122 }
123
124 out:
125 if (smaps)
126 fclose(smaps);
127 free(line);
128 return value;
129 }
130
is_vma_lock_on_fault(unsigned long addr)131 static bool is_vma_lock_on_fault(unsigned long addr)
132 {
133 bool locked;
134 unsigned long vma_size, vma_rss;
135
136 locked = is_vmflag_set(addr, LOCKED);
137 if (!locked)
138 return false;
139
140 vma_size = get_value_for_name(addr, SIZE);
141 vma_rss = get_value_for_name(addr, RSS);
142
143 /* only one page is faulted in */
144 return (vma_rss < vma_size);
145 }
146
147 #define PRESENT_BIT 0x8000000000000000ULL
148 #define PFN_MASK 0x007FFFFFFFFFFFFFULL
149 #define UNEVICTABLE_BIT (1UL << 18)
150
lock_check(unsigned long addr)151 static int lock_check(unsigned long addr)
152 {
153 bool locked;
154 unsigned long vma_size, vma_rss;
155
156 locked = is_vmflag_set(addr, LOCKED);
157 if (!locked)
158 return false;
159
160 vma_size = get_value_for_name(addr, SIZE);
161 vma_rss = get_value_for_name(addr, RSS);
162
163 return (vma_rss == vma_size);
164 }
165
unlock_lock_check(char * map)166 static int unlock_lock_check(char *map)
167 {
168 if (is_vmflag_set((unsigned long)map, LOCKED)) {
169 ksft_print_msg("VMA flag %s is present on page 1 after unlock\n", LOCKED);
170 return 1;
171 }
172
173 return 0;
174 }
175
test_mlock_lock(void)176 static void test_mlock_lock(void)
177 {
178 char *map;
179 unsigned long page_size = getpagesize();
180
181 map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
182 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
183 if (map == MAP_FAILED)
184 ksft_exit_fail_msg("mmap error: %s", strerror(errno));
185
186 if (mlock2_(map, 2 * page_size, 0)) {
187 munmap(map, 2 * page_size);
188 ksft_exit_fail_msg("mlock2(0): %s\n", strerror(errno));
189 }
190
191 ksft_test_result(lock_check((unsigned long)map), "%s: Locked\n", __func__);
192
193 /* Now unlock and recheck attributes */
194 if (munlock(map, 2 * page_size)) {
195 munmap(map, 2 * page_size);
196 ksft_exit_fail_msg("munlock(): %s\n", strerror(errno));
197 }
198
199 ksft_test_result(!unlock_lock_check(map), "%s: Locked\n", __func__);
200 munmap(map, 2 * page_size);
201 }
202
onfault_check(char * map)203 static int onfault_check(char *map)
204 {
205 *map = 'a';
206 if (!is_vma_lock_on_fault((unsigned long)map)) {
207 ksft_print_msg("VMA is not marked for lock on fault\n");
208 return 1;
209 }
210
211 return 0;
212 }
213
unlock_onfault_check(char * map)214 static int unlock_onfault_check(char *map)
215 {
216 unsigned long page_size = getpagesize();
217
218 if (is_vma_lock_on_fault((unsigned long)map) ||
219 is_vma_lock_on_fault((unsigned long)map + page_size)) {
220 ksft_print_msg("VMA is still lock on fault after unlock\n");
221 return 1;
222 }
223
224 return 0;
225 }
226
test_mlock_onfault(void)227 static void test_mlock_onfault(void)
228 {
229 char *map;
230 unsigned long page_size = getpagesize();
231
232 map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
233 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
234 if (map == MAP_FAILED)
235 ksft_exit_fail_msg("mmap error: %s", strerror(errno));
236
237 if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
238 munmap(map, 2 * page_size);
239 ksft_exit_fail_msg("mlock2(MLOCK_ONFAULT): %s\n", strerror(errno));
240 }
241
242 ksft_test_result(!onfault_check(map), "%s: VMA marked for lock on fault\n", __func__);
243
244 /* Now unlock and recheck attributes */
245 if (munlock(map, 2 * page_size)) {
246 munmap(map, 2 * page_size);
247 ksft_exit_fail_msg("munlock(): %s\n", strerror(errno));
248 }
249
250 ksft_test_result(!unlock_onfault_check(map), "VMA open lock after fault\n");
251 munmap(map, 2 * page_size);
252 }
253
test_lock_onfault_of_present(void)254 static void test_lock_onfault_of_present(void)
255 {
256 char *map;
257 unsigned long page_size = getpagesize();
258
259 map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
260 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
261 if (map == MAP_FAILED)
262 ksft_exit_fail_msg("mmap error: %s", strerror(errno));
263
264 *map = 'a';
265
266 if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
267 munmap(map, 2 * page_size);
268 ksft_test_result_fail("mlock2(MLOCK_ONFAULT) error: %s", strerror(errno));
269 }
270
271 ksft_test_result(is_vma_lock_on_fault((unsigned long)map) ||
272 is_vma_lock_on_fault((unsigned long)map + page_size),
273 "VMA with present pages is not marked lock on fault\n");
274 munmap(map, 2 * page_size);
275 }
276
test_munlockall0(void)277 static void test_munlockall0(void)
278 {
279 char *map;
280 unsigned long page_size = getpagesize();
281
282 map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
283 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
284 if (map == MAP_FAILED)
285 ksft_exit_fail_msg("mmap error: %s\n", strerror(errno));
286
287 if (mlockall(MCL_CURRENT)) {
288 munmap(map, 2 * page_size);
289 ksft_exit_fail_msg("mlockall(MCL_CURRENT): %s\n", strerror(errno));
290 }
291
292 ksft_test_result(lock_check((unsigned long)map), "%s: Locked memory area\n", __func__);
293
294 if (munlockall()) {
295 munmap(map, 2 * page_size);
296 ksft_exit_fail_msg("munlockall(): %s\n", strerror(errno));
297 }
298
299 ksft_test_result(!unlock_lock_check(map), "%s: No locked memory\n", __func__);
300 munmap(map, 2 * page_size);
301 }
302
test_munlockall1(void)303 static void test_munlockall1(void)
304 {
305 char *map;
306 unsigned long page_size = getpagesize();
307
308 map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
309 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
310 if (map == MAP_FAILED)
311 ksft_exit_fail_msg("mmap error: %s", strerror(errno));
312
313 if (mlockall(MCL_CURRENT | MCL_ONFAULT)) {
314 munmap(map, 2 * page_size);
315 ksft_exit_fail_msg("mlockall(MCL_CURRENT | MCL_ONFAULT): %s\n", strerror(errno));
316 }
317
318 ksft_test_result(!onfault_check(map), "%s: VMA marked for lock on fault\n", __func__);
319
320 if (munlockall()) {
321 munmap(map, 2 * page_size);
322 ksft_exit_fail_msg("munlockall(): %s\n", strerror(errno));
323 }
324
325 ksft_test_result(!unlock_onfault_check(map), "%s: Unlocked\n", __func__);
326
327 if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
328 munmap(map, 2 * page_size);
329 ksft_exit_fail_msg("mlockall(MCL_CURRENT | MCL_FUTURE): %s\n", strerror(errno));
330 }
331
332 ksft_test_result(lock_check((unsigned long)map), "%s: Locked\n", __func__);
333
334 if (munlockall()) {
335 munmap(map, 2 * page_size);
336 ksft_exit_fail_msg("munlockall() %s\n", strerror(errno));
337 }
338
339 ksft_test_result(!unlock_lock_check(map), "%s: No locked memory\n", __func__);
340 munmap(map, 2 * page_size);
341 }
342
test_vma_management(bool call_mlock)343 static void test_vma_management(bool call_mlock)
344 {
345 void *map;
346 unsigned long page_size = getpagesize();
347 struct vm_boundaries page1;
348 struct vm_boundaries page2;
349 struct vm_boundaries page3;
350
351 map = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
352 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
353 if (map == MAP_FAILED)
354 ksft_exit_fail_msg("mmap error: %s", strerror(errno));
355
356 if (call_mlock && mlock2_(map, 3 * page_size, MLOCK_ONFAULT)) {
357 munmap(map, 3 * page_size);
358 ksft_test_result_fail("mlock error: %s", strerror(errno));
359 }
360
361 if (get_vm_area((unsigned long)map, &page1) ||
362 get_vm_area((unsigned long)map + page_size, &page2) ||
363 get_vm_area((unsigned long)map + page_size * 2, &page3)) {
364 munmap(map, 3 * page_size);
365 ksft_test_result_fail("couldn't find mapping in /proc/self/maps");
366 }
367
368 /*
369 * Before we unlock a portion, we need to that all three pages are in
370 * the same VMA. If they are not we abort this test (Note that this is
371 * not a failure)
372 */
373 if (page1.start != page2.start || page2.start != page3.start) {
374 munmap(map, 3 * page_size);
375 ksft_test_result_fail("VMAs are not merged to start, aborting test");
376 }
377
378 if (munlock(map + page_size, page_size)) {
379 munmap(map, 3 * page_size);
380 ksft_test_result_fail("munlock(): %s", strerror(errno));
381 }
382
383 if (get_vm_area((unsigned long)map, &page1) ||
384 get_vm_area((unsigned long)map + page_size, &page2) ||
385 get_vm_area((unsigned long)map + page_size * 2, &page3)) {
386 munmap(map, 3 * page_size);
387 ksft_test_result_fail("couldn't find mapping in /proc/self/maps");
388 }
389
390 /* All three VMAs should be different */
391 if (page1.start == page2.start || page2.start == page3.start) {
392 munmap(map, 3 * page_size);
393 ksft_test_result_fail("failed to split VMA for munlock");
394 }
395
396 /* Now unlock the first and third page and check the VMAs again */
397 if (munlock(map, page_size * 3)) {
398 munmap(map, 3 * page_size);
399 ksft_test_result_fail("munlock(): %s", strerror(errno));
400 }
401
402 if (get_vm_area((unsigned long)map, &page1) ||
403 get_vm_area((unsigned long)map + page_size, &page2) ||
404 get_vm_area((unsigned long)map + page_size * 2, &page3)) {
405 munmap(map, 3 * page_size);
406 ksft_test_result_fail("couldn't find mapping in /proc/self/maps");
407 }
408
409 /* Now all three VMAs should be the same */
410 if (page1.start != page2.start || page2.start != page3.start) {
411 munmap(map, 3 * page_size);
412 ksft_test_result_fail("failed to merge VMAs after munlock");
413 }
414
415 ksft_test_result_pass("%s call_mlock %d\n", __func__, call_mlock);
416 munmap(map, 3 * page_size);
417 }
418
test_mlockall(void)419 static void test_mlockall(void)
420 {
421 if (mlockall(MCL_CURRENT | MCL_ONFAULT | MCL_FUTURE))
422 ksft_exit_fail_msg("mlockall failed: %s\n", strerror(errno));
423
424 test_vma_management(false);
425 munlockall();
426 }
427
main(int argc,char ** argv)428 int main(int argc, char **argv)
429 {
430 int ret, size = 3 * getpagesize();
431 void *map;
432
433 ksft_print_header();
434
435 map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
436 if (map == MAP_FAILED)
437 ksft_exit_fail_msg("mmap error: %s", strerror(errno));
438
439 ret = mlock2_(map, size, MLOCK_ONFAULT);
440 if (ret && errno == ENOSYS)
441 ksft_finished();
442
443 munmap(map, size);
444
445 ksft_set_plan(13);
446
447 test_mlock_lock();
448 test_mlock_onfault();
449 test_munlockall0();
450 test_munlockall1();
451 test_lock_onfault_of_present();
452 test_vma_management(true);
453 test_mlockall();
454
455 ksft_finished();
456 }
457