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
14struct vm_boundaries {
15	unsigned long start;
16	unsigned long end;
17};
18
19static 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	}
64out:
65	fclose(file);
66	return ret;
67}
68
69#define VMFLAGS "VmFlags:"
70
71static 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
98out:
99	free(line);
100	fclose(smaps);
101	return ret;
102}
103
104#define SIZE "Size:"
105#define RSS  "Rss:"
106#define LOCKED "lo"
107
108static 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
138out:
139	if (smaps)
140		fclose(smaps);
141	free(line);
142	return value;
143}
144
145static 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
165static 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
180static 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
190static 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
223unmap:
224	munmap(map, 2 * page_size);
225out:
226	return ret;
227}
228
229static 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
240static 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
253static 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);
289unmap:
290	munmap(map, 2 * page_size);
291out:
292	return ret;
293}
294
295static 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;
325unmap:
326	munmap(map, 2 * page_size);
327out:
328	return ret;
329}
330
331static 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
402unmap:
403	munmap(map, 2 * page_size);
404out:
405	munlockall();
406	return ret;
407}
408
409static 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;
490out:
491	munmap(map, 3 * page_size);
492	return ret;
493}
494
495static 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
509int 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