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 struct vm_boundaries {
13 	unsigned long start;
14 	unsigned long end;
15 };
16 
get_vm_area(unsigned long addr,struct vm_boundaries * area)17 static int get_vm_area(unsigned long addr, struct vm_boundaries *area)
18 {
19 	FILE *file;
20 	int ret = 1;
21 	char line[1024] = {0};
22 	char *end_addr;
23 	char *stop;
24 	unsigned long start;
25 	unsigned long end;
26 
27 	if (!area)
28 		return ret;
29 
30 	file = fopen("/proc/self/maps", "r");
31 	if (!file) {
32 		perror("fopen");
33 		return ret;
34 	}
35 
36 	memset(area, 0, sizeof(struct vm_boundaries));
37 
38 	while(fgets(line, 1024, file)) {
39 		end_addr = strchr(line, '-');
40 		if (!end_addr) {
41 			printf("cannot parse /proc/self/maps\n");
42 			goto out;
43 		}
44 		*end_addr = '\0';
45 		end_addr++;
46 		stop = strchr(end_addr, ' ');
47 		if (!stop) {
48 			printf("cannot parse /proc/self/maps\n");
49 			goto out;
50 		}
51 		stop = '\0';
52 
53 		sscanf(line, "%lx", &start);
54 		sscanf(end_addr, "%lx", &end);
55 
56 		if (start <= addr && end > addr) {
57 			area->start = start;
58 			area->end = end;
59 			ret = 0;
60 			goto out;
61 		}
62 	}
63 out:
64 	fclose(file);
65 	return ret;
66 }
67 
get_pageflags(unsigned long addr)68 static uint64_t get_pageflags(unsigned long addr)
69 {
70 	FILE *file;
71 	uint64_t pfn;
72 	unsigned long offset;
73 
74 	file = fopen("/proc/self/pagemap", "r");
75 	if (!file) {
76 		perror("fopen pagemap");
77 		_exit(1);
78 	}
79 
80 	offset = addr / getpagesize() * sizeof(pfn);
81 
82 	if (fseek(file, offset, SEEK_SET)) {
83 		perror("fseek pagemap");
84 		_exit(1);
85 	}
86 
87 	if (fread(&pfn, sizeof(pfn), 1, file) != 1) {
88 		perror("fread pagemap");
89 		_exit(1);
90 	}
91 
92 	fclose(file);
93 	return pfn;
94 }
95 
get_kpageflags(unsigned long pfn)96 static uint64_t get_kpageflags(unsigned long pfn)
97 {
98 	uint64_t flags;
99 	FILE *file;
100 
101 	file = fopen("/proc/kpageflags", "r");
102 	if (!file) {
103 		perror("fopen kpageflags");
104 		_exit(1);
105 	}
106 
107 	if (fseek(file, pfn * sizeof(flags), SEEK_SET)) {
108 		perror("fseek kpageflags");
109 		_exit(1);
110 	}
111 
112 	if (fread(&flags, sizeof(flags), 1, file) != 1) {
113 		perror("fread kpageflags");
114 		_exit(1);
115 	}
116 
117 	fclose(file);
118 	return flags;
119 }
120 
121 #define VMFLAGS "VmFlags:"
122 
is_vmflag_set(unsigned long addr,const char * vmflag)123 static bool is_vmflag_set(unsigned long addr, const char *vmflag)
124 {
125 	char *line = NULL;
126 	char *flags;
127 	size_t size = 0;
128 	bool ret = false;
129 	FILE *smaps;
130 
131 	smaps = seek_to_smaps_entry(addr);
132 	if (!smaps) {
133 		printf("Unable to parse /proc/self/smaps\n");
134 		goto out;
135 	}
136 
137 	while (getline(&line, &size, smaps) > 0) {
138 		if (!strstr(line, VMFLAGS)) {
139 			free(line);
140 			line = NULL;
141 			size = 0;
142 			continue;
143 		}
144 
145 		flags = line + strlen(VMFLAGS);
146 		ret = (strstr(flags, vmflag) != NULL);
147 		goto out;
148 	}
149 
150 out:
151 	free(line);
152 	fclose(smaps);
153 	return ret;
154 }
155 
156 #define SIZE "Size:"
157 #define RSS  "Rss:"
158 #define LOCKED "lo"
159 
is_vma_lock_on_fault(unsigned long addr)160 static bool is_vma_lock_on_fault(unsigned long addr)
161 {
162 	bool ret = false;
163 	bool locked;
164 	FILE *smaps = NULL;
165 	unsigned long vma_size, vma_rss;
166 	char *line = NULL;
167 	char *value;
168 	size_t size = 0;
169 
170 	locked = is_vmflag_set(addr, LOCKED);
171 	if (!locked)
172 		goto out;
173 
174 	smaps = seek_to_smaps_entry(addr);
175 	if (!smaps) {
176 		printf("Unable to parse /proc/self/smaps\n");
177 		goto out;
178 	}
179 
180 	while (getline(&line, &size, smaps) > 0) {
181 		if (!strstr(line, SIZE)) {
182 			free(line);
183 			line = NULL;
184 			size = 0;
185 			continue;
186 		}
187 
188 		value = line + strlen(SIZE);
189 		if (sscanf(value, "%lu kB", &vma_size) < 1) {
190 			printf("Unable to parse smaps entry for Size\n");
191 			goto out;
192 		}
193 		break;
194 	}
195 
196 	while (getline(&line, &size, smaps) > 0) {
197 		if (!strstr(line, RSS)) {
198 			free(line);
199 			line = NULL;
200 			size = 0;
201 			continue;
202 		}
203 
204 		value = line + strlen(RSS);
205 		if (sscanf(value, "%lu kB", &vma_rss) < 1) {
206 			printf("Unable to parse smaps entry for Rss\n");
207 			goto out;
208 		}
209 		break;
210 	}
211 
212 	ret = locked && (vma_rss < vma_size);
213 out:
214 	free(line);
215 	if (smaps)
216 		fclose(smaps);
217 	return ret;
218 }
219 
220 #define PRESENT_BIT     0x8000000000000000ULL
221 #define PFN_MASK        0x007FFFFFFFFFFFFFULL
222 #define UNEVICTABLE_BIT (1UL << 18)
223 
lock_check(char * map)224 static int lock_check(char *map)
225 {
226 	unsigned long page_size = getpagesize();
227 	uint64_t page1_flags, page2_flags;
228 
229 	page1_flags = get_pageflags((unsigned long)map);
230 	page2_flags = get_pageflags((unsigned long)map + page_size);
231 
232 	/* Both pages should be present */
233 	if (((page1_flags & PRESENT_BIT) == 0) ||
234 	    ((page2_flags & PRESENT_BIT) == 0)) {
235 		printf("Failed to make both pages present\n");
236 		return 1;
237 	}
238 
239 	page1_flags = get_kpageflags(page1_flags & PFN_MASK);
240 	page2_flags = get_kpageflags(page2_flags & PFN_MASK);
241 
242 	/* Both pages should be unevictable */
243 	if (((page1_flags & UNEVICTABLE_BIT) == 0) ||
244 	    ((page2_flags & UNEVICTABLE_BIT) == 0)) {
245 		printf("Failed to make both pages unevictable\n");
246 		return 1;
247 	}
248 
249 	if (!is_vmflag_set((unsigned long)map, LOCKED)) {
250 		printf("VMA flag %s is missing on page 1\n", LOCKED);
251 		return 1;
252 	}
253 
254 	if (!is_vmflag_set((unsigned long)map + page_size, LOCKED)) {
255 		printf("VMA flag %s is missing on page 2\n", LOCKED);
256 		return 1;
257 	}
258 
259 	return 0;
260 }
261 
unlock_lock_check(char * map)262 static int unlock_lock_check(char *map)
263 {
264 	unsigned long page_size = getpagesize();
265 	uint64_t page1_flags, page2_flags;
266 
267 	page1_flags = get_pageflags((unsigned long)map);
268 	page2_flags = get_pageflags((unsigned long)map + page_size);
269 	page1_flags = get_kpageflags(page1_flags & PFN_MASK);
270 	page2_flags = get_kpageflags(page2_flags & PFN_MASK);
271 
272 	if ((page1_flags & UNEVICTABLE_BIT) || (page2_flags & UNEVICTABLE_BIT)) {
273 		printf("A page is still marked unevictable after unlock\n");
274 		return 1;
275 	}
276 
277 	if (is_vmflag_set((unsigned long)map, LOCKED)) {
278 		printf("VMA flag %s is present on page 1 after unlock\n", LOCKED);
279 		return 1;
280 	}
281 
282 	if (is_vmflag_set((unsigned long)map + page_size, LOCKED)) {
283 		printf("VMA flag %s is present on page 2 after unlock\n", LOCKED);
284 		return 1;
285 	}
286 
287 	return 0;
288 }
289 
test_mlock_lock()290 static int test_mlock_lock()
291 {
292 	char *map;
293 	int ret = 1;
294 	unsigned long page_size = getpagesize();
295 
296 	map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
297 		   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
298 	if (map == MAP_FAILED) {
299 		perror("test_mlock_locked mmap");
300 		goto out;
301 	}
302 
303 	if (mlock2_(map, 2 * page_size, 0)) {
304 		if (errno == ENOSYS) {
305 			printf("Cannot call new mlock family, skipping test\n");
306 			_exit(0);
307 		}
308 		perror("mlock2(0)");
309 		goto unmap;
310 	}
311 
312 	if (lock_check(map))
313 		goto unmap;
314 
315 	/* Now unlock and recheck attributes */
316 	if (munlock(map, 2 * page_size)) {
317 		perror("munlock()");
318 		goto unmap;
319 	}
320 
321 	ret = unlock_lock_check(map);
322 
323 unmap:
324 	munmap(map, 2 * page_size);
325 out:
326 	return ret;
327 }
328 
onfault_check(char * map)329 static int onfault_check(char *map)
330 {
331 	unsigned long page_size = getpagesize();
332 	uint64_t page1_flags, page2_flags;
333 
334 	page1_flags = get_pageflags((unsigned long)map);
335 	page2_flags = get_pageflags((unsigned long)map + page_size);
336 
337 	/* Neither page should be present */
338 	if ((page1_flags & PRESENT_BIT) || (page2_flags & PRESENT_BIT)) {
339 		printf("Pages were made present by MLOCK_ONFAULT\n");
340 		return 1;
341 	}
342 
343 	*map = 'a';
344 	page1_flags = get_pageflags((unsigned long)map);
345 	page2_flags = get_pageflags((unsigned long)map + page_size);
346 
347 	/* Only page 1 should be present */
348 	if ((page1_flags & PRESENT_BIT) == 0) {
349 		printf("Page 1 is not present after fault\n");
350 		return 1;
351 	} else if (page2_flags & PRESENT_BIT) {
352 		printf("Page 2 was made present\n");
353 		return 1;
354 	}
355 
356 	page1_flags = get_kpageflags(page1_flags & PFN_MASK);
357 
358 	/* Page 1 should be unevictable */
359 	if ((page1_flags & UNEVICTABLE_BIT) == 0) {
360 		printf("Failed to make faulted page unevictable\n");
361 		return 1;
362 	}
363 
364 	if (!is_vma_lock_on_fault((unsigned long)map)) {
365 		printf("VMA is not marked for lock on fault\n");
366 		return 1;
367 	}
368 
369 	if (!is_vma_lock_on_fault((unsigned long)map + page_size)) {
370 		printf("VMA is not marked for lock on fault\n");
371 		return 1;
372 	}
373 
374 	return 0;
375 }
376 
unlock_onfault_check(char * map)377 static int unlock_onfault_check(char *map)
378 {
379 	unsigned long page_size = getpagesize();
380 	uint64_t page1_flags;
381 
382 	page1_flags = get_pageflags((unsigned long)map);
383 	page1_flags = get_kpageflags(page1_flags & PFN_MASK);
384 
385 	if (page1_flags & UNEVICTABLE_BIT) {
386 		printf("Page 1 is still marked unevictable after unlock\n");
387 		return 1;
388 	}
389 
390 	if (is_vma_lock_on_fault((unsigned long)map) ||
391 	    is_vma_lock_on_fault((unsigned long)map + page_size)) {
392 		printf("VMA is still lock on fault after unlock\n");
393 		return 1;
394 	}
395 
396 	return 0;
397 }
398 
test_mlock_onfault()399 static int test_mlock_onfault()
400 {
401 	char *map;
402 	int ret = 1;
403 	unsigned long page_size = getpagesize();
404 
405 	map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
406 		   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
407 	if (map == MAP_FAILED) {
408 		perror("test_mlock_locked mmap");
409 		goto out;
410 	}
411 
412 	if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
413 		if (errno == ENOSYS) {
414 			printf("Cannot call new mlock family, skipping test\n");
415 			_exit(0);
416 		}
417 		perror("mlock2(MLOCK_ONFAULT)");
418 		goto unmap;
419 	}
420 
421 	if (onfault_check(map))
422 		goto unmap;
423 
424 	/* Now unlock and recheck attributes */
425 	if (munlock(map, 2 * page_size)) {
426 		if (errno == ENOSYS) {
427 			printf("Cannot call new mlock family, skipping test\n");
428 			_exit(0);
429 		}
430 		perror("munlock()");
431 		goto unmap;
432 	}
433 
434 	ret = unlock_onfault_check(map);
435 unmap:
436 	munmap(map, 2 * page_size);
437 out:
438 	return ret;
439 }
440 
test_lock_onfault_of_present()441 static int test_lock_onfault_of_present()
442 {
443 	char *map;
444 	int ret = 1;
445 	unsigned long page_size = getpagesize();
446 	uint64_t page1_flags, page2_flags;
447 
448 	map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
449 		   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
450 	if (map == MAP_FAILED) {
451 		perror("test_mlock_locked mmap");
452 		goto out;
453 	}
454 
455 	*map = 'a';
456 
457 	if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
458 		if (errno == ENOSYS) {
459 			printf("Cannot call new mlock family, skipping test\n");
460 			_exit(0);
461 		}
462 		perror("mlock2(MLOCK_ONFAULT)");
463 		goto unmap;
464 	}
465 
466 	page1_flags = get_pageflags((unsigned long)map);
467 	page2_flags = get_pageflags((unsigned long)map + page_size);
468 	page1_flags = get_kpageflags(page1_flags & PFN_MASK);
469 	page2_flags = get_kpageflags(page2_flags & PFN_MASK);
470 
471 	/* Page 1 should be unevictable */
472 	if ((page1_flags & UNEVICTABLE_BIT) == 0) {
473 		printf("Failed to make present page unevictable\n");
474 		goto unmap;
475 	}
476 
477 	if (!is_vma_lock_on_fault((unsigned long)map) ||
478 	    !is_vma_lock_on_fault((unsigned long)map + page_size)) {
479 		printf("VMA with present pages is not marked lock on fault\n");
480 		goto unmap;
481 	}
482 	ret = 0;
483 unmap:
484 	munmap(map, 2 * page_size);
485 out:
486 	return ret;
487 }
488 
test_munlockall()489 static int test_munlockall()
490 {
491 	char *map;
492 	int ret = 1;
493 	unsigned long page_size = getpagesize();
494 
495 	map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
496 		   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
497 
498 	if (map == MAP_FAILED) {
499 		perror("test_munlockall mmap");
500 		goto out;
501 	}
502 
503 	if (mlockall(MCL_CURRENT)) {
504 		perror("mlockall(MCL_CURRENT)");
505 		goto out;
506 	}
507 
508 	if (lock_check(map))
509 		goto unmap;
510 
511 	if (munlockall()) {
512 		perror("munlockall()");
513 		goto unmap;
514 	}
515 
516 	if (unlock_lock_check(map))
517 		goto unmap;
518 
519 	munmap(map, 2 * page_size);
520 
521 	map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
522 		   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
523 
524 	if (map == MAP_FAILED) {
525 		perror("test_munlockall second mmap");
526 		goto out;
527 	}
528 
529 	if (mlockall(MCL_CURRENT | MCL_ONFAULT)) {
530 		perror("mlockall(MCL_CURRENT | MCL_ONFAULT)");
531 		goto unmap;
532 	}
533 
534 	if (onfault_check(map))
535 		goto unmap;
536 
537 	if (munlockall()) {
538 		perror("munlockall()");
539 		goto unmap;
540 	}
541 
542 	if (unlock_onfault_check(map))
543 		goto unmap;
544 
545 	if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
546 		perror("mlockall(MCL_CURRENT | MCL_FUTURE)");
547 		goto out;
548 	}
549 
550 	if (lock_check(map))
551 		goto unmap;
552 
553 	if (munlockall()) {
554 		perror("munlockall()");
555 		goto unmap;
556 	}
557 
558 	ret = unlock_lock_check(map);
559 
560 unmap:
561 	munmap(map, 2 * page_size);
562 out:
563 	munlockall();
564 	return ret;
565 }
566 
test_vma_management(bool call_mlock)567 static int test_vma_management(bool call_mlock)
568 {
569 	int ret = 1;
570 	void *map;
571 	unsigned long page_size = getpagesize();
572 	struct vm_boundaries page1;
573 	struct vm_boundaries page2;
574 	struct vm_boundaries page3;
575 
576 	map = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
577 		   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
578 	if (map == MAP_FAILED) {
579 		perror("mmap()");
580 		return ret;
581 	}
582 
583 	if (call_mlock && mlock2_(map, 3 * page_size, MLOCK_ONFAULT)) {
584 		if (errno == ENOSYS) {
585 			printf("Cannot call new mlock family, skipping test\n");
586 			_exit(0);
587 		}
588 		perror("mlock(ONFAULT)\n");
589 		goto out;
590 	}
591 
592 	if (get_vm_area((unsigned long)map, &page1) ||
593 	    get_vm_area((unsigned long)map + page_size, &page2) ||
594 	    get_vm_area((unsigned long)map + page_size * 2, &page3)) {
595 		printf("couldn't find mapping in /proc/self/maps\n");
596 		goto out;
597 	}
598 
599 	/*
600 	 * Before we unlock a portion, we need to that all three pages are in
601 	 * the same VMA.  If they are not we abort this test (Note that this is
602 	 * not a failure)
603 	 */
604 	if (page1.start != page2.start || page2.start != page3.start) {
605 		printf("VMAs are not merged to start, aborting test\n");
606 		ret = 0;
607 		goto out;
608 	}
609 
610 	if (munlock(map + page_size, page_size)) {
611 		perror("munlock()");
612 		goto out;
613 	}
614 
615 	if (get_vm_area((unsigned long)map, &page1) ||
616 	    get_vm_area((unsigned long)map + page_size, &page2) ||
617 	    get_vm_area((unsigned long)map + page_size * 2, &page3)) {
618 		printf("couldn't find mapping in /proc/self/maps\n");
619 		goto out;
620 	}
621 
622 	/* All three VMAs should be different */
623 	if (page1.start == page2.start || page2.start == page3.start) {
624 		printf("failed to split VMA for munlock\n");
625 		goto out;
626 	}
627 
628 	/* Now unlock the first and third page and check the VMAs again */
629 	if (munlock(map, page_size * 3)) {
630 		perror("munlock()");
631 		goto out;
632 	}
633 
634 	if (get_vm_area((unsigned long)map, &page1) ||
635 	    get_vm_area((unsigned long)map + page_size, &page2) ||
636 	    get_vm_area((unsigned long)map + page_size * 2, &page3)) {
637 		printf("couldn't find mapping in /proc/self/maps\n");
638 		goto out;
639 	}
640 
641 	/* Now all three VMAs should be the same */
642 	if (page1.start != page2.start || page2.start != page3.start) {
643 		printf("failed to merge VMAs after munlock\n");
644 		goto out;
645 	}
646 
647 	ret = 0;
648 out:
649 	munmap(map, 3 * page_size);
650 	return ret;
651 }
652 
test_mlockall(int (test_function)(bool call_mlock))653 static int test_mlockall(int (test_function)(bool call_mlock))
654 {
655 	int ret = 1;
656 
657 	if (mlockall(MCL_CURRENT | MCL_ONFAULT | MCL_FUTURE)) {
658 		perror("mlockall");
659 		return ret;
660 	}
661 
662 	ret = test_function(false);
663 	munlockall();
664 	return ret;
665 }
666 
main(int argc,char ** argv)667 int main(int argc, char **argv)
668 {
669 	int ret = 0;
670 	ret += test_mlock_lock();
671 	ret += test_mlock_onfault();
672 	ret += test_munlockall();
673 	ret += test_lock_onfault_of_present();
674 	ret += test_vma_management(true);
675 	ret += test_mlockall(test_vma_management);
676 	return ret;
677 }
678