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