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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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