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