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
13struct vm_boundaries {
14	unsigned long start;
15	unsigned long end;
16};
17
18static 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	}
50out:
51	fclose(file);
52	return ret;
53}
54
55#define VMFLAGS "VmFlags:"
56
57static 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
84out:
85	free(line);
86	fclose(smaps);
87	return ret;
88}
89
90#define SIZE "Size:"
91#define RSS  "Rss:"
92#define LOCKED "lo"
93
94static 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
124out:
125	if (smaps)
126		fclose(smaps);
127	free(line);
128	return value;
129}
130
131static 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
151static 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
166static 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
176static 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
203static 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
214static 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
227static 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
254static 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
277static 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
303static 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
343static 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
419static 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
428int 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