GNU Linux-libre 4.19.286-gnu1
[releases.git] / tools / testing / selftests / vm / mlock2-tests.c
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                 stop = '\0';
54
55                 sscanf(line, "%lx", &start);
56                 sscanf(end_addr, "%lx", &end);
57
58                 if (start <= addr && end > addr) {
59                         area->start = start;
60                         area->end = end;
61                         ret = 0;
62                         goto out;
63                 }
64         }
65 out:
66         fclose(file);
67         return ret;
68 }
69
70 #define VMFLAGS "VmFlags:"
71
72 static bool is_vmflag_set(unsigned long addr, const char *vmflag)
73 {
74         char *line = NULL;
75         char *flags;
76         size_t size = 0;
77         bool ret = false;
78         FILE *smaps;
79
80         smaps = seek_to_smaps_entry(addr);
81         if (!smaps) {
82                 printf("Unable to parse /proc/self/smaps\n");
83                 goto out;
84         }
85
86         while (getline(&line, &size, smaps) > 0) {
87                 if (!strstr(line, VMFLAGS)) {
88                         free(line);
89                         line = NULL;
90                         size = 0;
91                         continue;
92                 }
93
94                 flags = line + strlen(VMFLAGS);
95                 ret = (strstr(flags, vmflag) != NULL);
96                 goto out;
97         }
98
99 out:
100         free(line);
101         fclose(smaps);
102         return ret;
103 }
104
105 #define SIZE "Size:"
106 #define RSS  "Rss:"
107 #define LOCKED "lo"
108
109 static unsigned long get_value_for_name(unsigned long addr, const char *name)
110 {
111         char *line = NULL;
112         size_t size = 0;
113         char *value_ptr;
114         FILE *smaps = NULL;
115         unsigned long value = -1UL;
116
117         smaps = seek_to_smaps_entry(addr);
118         if (!smaps) {
119                 printf("Unable to parse /proc/self/smaps\n");
120                 goto out;
121         }
122
123         while (getline(&line, &size, smaps) > 0) {
124                 if (!strstr(line, name)) {
125                         free(line);
126                         line = NULL;
127                         size = 0;
128                         continue;
129                 }
130
131                 value_ptr = line + strlen(name);
132                 if (sscanf(value_ptr, "%lu kB", &value) < 1) {
133                         printf("Unable to parse smaps entry for Size\n");
134                         goto out;
135                 }
136                 break;
137         }
138
139 out:
140         if (smaps)
141                 fclose(smaps);
142         free(line);
143         return value;
144 }
145
146 static bool is_vma_lock_on_fault(unsigned long addr)
147 {
148         bool locked;
149         unsigned long vma_size, vma_rss;
150
151         locked = is_vmflag_set(addr, LOCKED);
152         if (!locked)
153                 return false;
154
155         vma_size = get_value_for_name(addr, SIZE);
156         vma_rss = get_value_for_name(addr, RSS);
157
158         /* only one page is faulted in */
159         return (vma_rss < vma_size);
160 }
161
162 #define PRESENT_BIT     0x8000000000000000ULL
163 #define PFN_MASK        0x007FFFFFFFFFFFFFULL
164 #define UNEVICTABLE_BIT (1UL << 18)
165
166 static int lock_check(unsigned long addr)
167 {
168         bool locked;
169         unsigned long vma_size, vma_rss;
170
171         locked = is_vmflag_set(addr, LOCKED);
172         if (!locked)
173                 return false;
174
175         vma_size = get_value_for_name(addr, SIZE);
176         vma_rss = get_value_for_name(addr, RSS);
177
178         return (vma_rss == vma_size);
179 }
180
181 static int unlock_lock_check(char *map)
182 {
183         if (is_vmflag_set((unsigned long)map, LOCKED)) {
184                 printf("VMA flag %s is present on page 1 after unlock\n", LOCKED);
185                 return 1;
186         }
187
188         return 0;
189 }
190
191 static int test_mlock_lock()
192 {
193         char *map;
194         int ret = 1;
195         unsigned long page_size = getpagesize();
196
197         map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
198                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
199         if (map == MAP_FAILED) {
200                 perror("test_mlock_locked mmap");
201                 goto out;
202         }
203
204         if (mlock2_(map, 2 * page_size, 0)) {
205                 if (errno == ENOSYS) {
206                         printf("Cannot call new mlock family, skipping test\n");
207                         _exit(KSFT_SKIP);
208                 }
209                 perror("mlock2(0)");
210                 goto unmap;
211         }
212
213         if (!lock_check((unsigned long)map))
214                 goto unmap;
215
216         /* Now unlock and recheck attributes */
217         if (munlock(map, 2 * page_size)) {
218                 perror("munlock()");
219                 goto unmap;
220         }
221
222         ret = unlock_lock_check(map);
223
224 unmap:
225         munmap(map, 2 * page_size);
226 out:
227         return ret;
228 }
229
230 static int onfault_check(char *map)
231 {
232         *map = 'a';
233         if (!is_vma_lock_on_fault((unsigned long)map)) {
234                 printf("VMA is not marked for lock on fault\n");
235                 return 1;
236         }
237
238         return 0;
239 }
240
241 static int unlock_onfault_check(char *map)
242 {
243         unsigned long page_size = getpagesize();
244
245         if (is_vma_lock_on_fault((unsigned long)map) ||
246             is_vma_lock_on_fault((unsigned long)map + page_size)) {
247                 printf("VMA is still lock on fault after unlock\n");
248                 return 1;
249         }
250
251         return 0;
252 }
253
254 static int test_mlock_onfault()
255 {
256         char *map;
257         int ret = 1;
258         unsigned long page_size = getpagesize();
259
260         map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
261                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
262         if (map == MAP_FAILED) {
263                 perror("test_mlock_locked mmap");
264                 goto out;
265         }
266
267         if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
268                 if (errno == ENOSYS) {
269                         printf("Cannot call new mlock family, skipping test\n");
270                         _exit(KSFT_SKIP);
271                 }
272                 perror("mlock2(MLOCK_ONFAULT)");
273                 goto unmap;
274         }
275
276         if (onfault_check(map))
277                 goto unmap;
278
279         /* Now unlock and recheck attributes */
280         if (munlock(map, 2 * page_size)) {
281                 if (errno == ENOSYS) {
282                         printf("Cannot call new mlock family, skipping test\n");
283                         _exit(KSFT_SKIP);
284                 }
285                 perror("munlock()");
286                 goto unmap;
287         }
288
289         ret = unlock_onfault_check(map);
290 unmap:
291         munmap(map, 2 * page_size);
292 out:
293         return ret;
294 }
295
296 static int test_lock_onfault_of_present()
297 {
298         char *map;
299         int ret = 1;
300         unsigned long page_size = getpagesize();
301
302         map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
303                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
304         if (map == MAP_FAILED) {
305                 perror("test_mlock_locked mmap");
306                 goto out;
307         }
308
309         *map = 'a';
310
311         if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
312                 if (errno == ENOSYS) {
313                         printf("Cannot call new mlock family, skipping test\n");
314                         _exit(KSFT_SKIP);
315                 }
316                 perror("mlock2(MLOCK_ONFAULT)");
317                 goto unmap;
318         }
319
320         if (!is_vma_lock_on_fault((unsigned long)map) ||
321             !is_vma_lock_on_fault((unsigned long)map + page_size)) {
322                 printf("VMA with present pages is not marked lock on fault\n");
323                 goto unmap;
324         }
325         ret = 0;
326 unmap:
327         munmap(map, 2 * page_size);
328 out:
329         return ret;
330 }
331
332 static int test_munlockall()
333 {
334         char *map;
335         int ret = 1;
336         unsigned long page_size = getpagesize();
337
338         map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
339                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
340
341         if (map == MAP_FAILED) {
342                 perror("test_munlockall mmap");
343                 goto out;
344         }
345
346         if (mlockall(MCL_CURRENT)) {
347                 perror("mlockall(MCL_CURRENT)");
348                 goto out;
349         }
350
351         if (!lock_check((unsigned long)map))
352                 goto unmap;
353
354         if (munlockall()) {
355                 perror("munlockall()");
356                 goto unmap;
357         }
358
359         if (unlock_lock_check(map))
360                 goto unmap;
361
362         munmap(map, 2 * page_size);
363
364         map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
365                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
366
367         if (map == MAP_FAILED) {
368                 perror("test_munlockall second mmap");
369                 goto out;
370         }
371
372         if (mlockall(MCL_CURRENT | MCL_ONFAULT)) {
373                 perror("mlockall(MCL_CURRENT | MCL_ONFAULT)");
374                 goto unmap;
375         }
376
377         if (onfault_check(map))
378                 goto unmap;
379
380         if (munlockall()) {
381                 perror("munlockall()");
382                 goto unmap;
383         }
384
385         if (unlock_onfault_check(map))
386                 goto unmap;
387
388         if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
389                 perror("mlockall(MCL_CURRENT | MCL_FUTURE)");
390                 goto out;
391         }
392
393         if (!lock_check((unsigned long)map))
394                 goto unmap;
395
396         if (munlockall()) {
397                 perror("munlockall()");
398                 goto unmap;
399         }
400
401         ret = unlock_lock_check(map);
402
403 unmap:
404         munmap(map, 2 * page_size);
405 out:
406         munlockall();
407         return ret;
408 }
409
410 static int test_vma_management(bool call_mlock)
411 {
412         int ret = 1;
413         void *map;
414         unsigned long page_size = getpagesize();
415         struct vm_boundaries page1;
416         struct vm_boundaries page2;
417         struct vm_boundaries page3;
418
419         map = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
420                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
421         if (map == MAP_FAILED) {
422                 perror("mmap()");
423                 return ret;
424         }
425
426         if (call_mlock && mlock2_(map, 3 * page_size, MLOCK_ONFAULT)) {
427                 if (errno == ENOSYS) {
428                         printf("Cannot call new mlock family, skipping test\n");
429                         _exit(KSFT_SKIP);
430                 }
431                 perror("mlock(ONFAULT)\n");
432                 goto out;
433         }
434
435         if (get_vm_area((unsigned long)map, &page1) ||
436             get_vm_area((unsigned long)map + page_size, &page2) ||
437             get_vm_area((unsigned long)map + page_size * 2, &page3)) {
438                 printf("couldn't find mapping in /proc/self/maps\n");
439                 goto out;
440         }
441
442         /*
443          * Before we unlock a portion, we need to that all three pages are in
444          * the same VMA.  If they are not we abort this test (Note that this is
445          * not a failure)
446          */
447         if (page1.start != page2.start || page2.start != page3.start) {
448                 printf("VMAs are not merged to start, aborting test\n");
449                 ret = 0;
450                 goto out;
451         }
452
453         if (munlock(map + page_size, page_size)) {
454                 perror("munlock()");
455                 goto out;
456         }
457
458         if (get_vm_area((unsigned long)map, &page1) ||
459             get_vm_area((unsigned long)map + page_size, &page2) ||
460             get_vm_area((unsigned long)map + page_size * 2, &page3)) {
461                 printf("couldn't find mapping in /proc/self/maps\n");
462                 goto out;
463         }
464
465         /* All three VMAs should be different */
466         if (page1.start == page2.start || page2.start == page3.start) {
467                 printf("failed to split VMA for munlock\n");
468                 goto out;
469         }
470
471         /* Now unlock the first and third page and check the VMAs again */
472         if (munlock(map, page_size * 3)) {
473                 perror("munlock()");
474                 goto out;
475         }
476
477         if (get_vm_area((unsigned long)map, &page1) ||
478             get_vm_area((unsigned long)map + page_size, &page2) ||
479             get_vm_area((unsigned long)map + page_size * 2, &page3)) {
480                 printf("couldn't find mapping in /proc/self/maps\n");
481                 goto out;
482         }
483
484         /* Now all three VMAs should be the same */
485         if (page1.start != page2.start || page2.start != page3.start) {
486                 printf("failed to merge VMAs after munlock\n");
487                 goto out;
488         }
489
490         ret = 0;
491 out:
492         munmap(map, 3 * page_size);
493         return ret;
494 }
495
496 static int test_mlockall(int (test_function)(bool call_mlock))
497 {
498         int ret = 1;
499
500         if (mlockall(MCL_CURRENT | MCL_ONFAULT | MCL_FUTURE)) {
501                 perror("mlockall");
502                 return ret;
503         }
504
505         ret = test_function(false);
506         munlockall();
507         return ret;
508 }
509
510 int main(int argc, char **argv)
511 {
512         int ret = 0;
513         ret += test_mlock_lock();
514         ret += test_mlock_onfault();
515         ret += test_munlockall();
516         ret += test_lock_onfault_of_present();
517         ret += test_vma_management(true);
518         ret += test_mlockall(test_vma_management);
519         return ret;
520 }