1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (C) 2024 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
4 */
5
6 /*\
7 * [Description]
8 *
9 * This is a smoke test that verifies if mseal() protects specific VMA portions
10 * of a process. According to documentation, the syscall should protect memory
11 * from the following actions:
12 *
13 * - unmapping, moving to another location, and shrinking the size, via munmap()
14 * and mremap()
15 * - moving or expanding a different VMA into the current location, via mremap()
16 * - modifying a VMA via mmap(MAP_FIXED)
17 * - mprotect() and pkey_mprotect()
18 * - destructive madvice() behaviors (e.g. MADV_DONTNEED) for anonymous memory,
19 * when users don't have write permission to the memory
20 *
21 * Any of the described actions is recognized via EPERM errno.
22 *
23 * TODO: support both raw syscall and libc wrapper via .test_variants.
24 */
25
26 #define _GNU_SOURCE
27
28 #include "tst_test.h"
29 #include "lapi/syscalls.h"
30 #include "lapi/pkey.h"
31
32 #define MEMPAGES 8
33 #define MEMSEAL 2
34
35 static void *mem_addr;
36 static int mem_size;
37 static int mem_offset;
38 static int mem_alignment;
39
sys_mseal(void * start,size_t len)40 static inline int sys_mseal(void *start, size_t len)
41 {
42 return tst_syscall(__NR_mseal, start, len, 0);
43 }
44
test_mprotect(void)45 static void test_mprotect(void)
46 {
47 TST_EXP_FAIL(mprotect(mem_addr, mem_size, PROT_NONE), EPERM);
48 }
49
test_pkey_mprotect(void)50 static void test_pkey_mprotect(void)
51 {
52 int pkey;
53
54 check_pkey_support();
55
56 pkey = pkey_alloc(0, 0);
57 if (pkey == -1)
58 tst_brk(TBROK | TERRNO, "pkey_alloc failed");
59
60 TST_EXP_FAIL(pkey_mprotect(
61 mem_addr, mem_size,
62 PROT_NONE,
63 pkey),
64 EPERM);
65
66 if (pkey_free(pkey) == -1)
67 tst_brk(TBROK | TERRNO, "pkey_free() error");
68 }
69
test_madvise(void)70 static void test_madvise(void)
71 {
72 TST_EXP_FAIL(madvise(mem_addr, mem_size, MADV_DONTNEED), EPERM);
73 }
74
test_munmap(void)75 static void test_munmap(void)
76 {
77 TST_EXP_FAIL(munmap(mem_addr, mem_size), EPERM);
78 }
79
test_mremap_resize(void)80 static void test_mremap_resize(void)
81 {
82 void *new_addr;
83 size_t new_size = 2 * mem_alignment;
84
85 new_addr = SAFE_MMAP(NULL, mem_size,
86 PROT_READ,
87 MAP_ANONYMOUS | MAP_PRIVATE,
88 -1, 0);
89
90 TST_EXP_FAIL_PTR_VOID(mremap(mem_addr, mem_size, new_size,
91 MREMAP_MAYMOVE | MREMAP_FIXED,
92 new_addr),
93 EPERM);
94
95 SAFE_MUNMAP(new_addr, new_size);
96 }
97
test_mmap_change_prot(void)98 static void test_mmap_change_prot(void)
99 {
100 TST_EXP_FAIL_PTR_VOID(mmap(mem_addr, mem_size,
101 PROT_READ,
102 MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,
103 -1, 0), EPERM);
104 }
105
106 static struct tcase {
107 void (*func_test)(void);
108 int prot;
109 char *message;
110 } tcases[] = {
111 {test_mprotect, PROT_READ | PROT_WRITE, "mprotect() availability"},
112 {test_pkey_mprotect, PROT_READ | PROT_WRITE, "pkey_mprotect() availability"},
113 {test_madvise, PROT_READ, "madvise() availability"},
114 {test_munmap, PROT_READ | PROT_WRITE, "munmap() availability from child"},
115 {test_mremap_resize, PROT_READ | PROT_WRITE, "mremap() address move/resize"},
116 {test_mmap_change_prot, PROT_READ | PROT_WRITE, "mmap() protection change"},
117 };
118
run(unsigned int n)119 static void run(unsigned int n)
120 {
121 /* the reason why we spawn a child is that mseal() will
122 * protect VMA until process will call _exit()
123 */
124 if (!SAFE_FORK()) {
125 struct tcase *tc = &tcases[n];
126
127 mem_addr = SAFE_MMAP(NULL, mem_size,
128 tc->prot,
129 MAP_ANONYMOUS | MAP_PRIVATE,
130 -1, 0);
131
132 tst_res(TINFO, "Testing %s", tc->message);
133
134 TST_EXP_PASS(sys_mseal(mem_addr + mem_offset, mem_alignment));
135
136 tc->func_test();
137 _exit(0);
138 }
139 }
140
setup(void)141 static void setup(void)
142 {
143 mem_alignment = getpagesize();
144 mem_size = mem_alignment * MEMPAGES;
145 mem_offset = mem_alignment * MEMSEAL;
146 }
147
148 static struct tst_test test = {
149 .test = run,
150 .tcnt = ARRAY_SIZE(tcases),
151 .setup = setup,
152 .forks_child = 1,
153 };
154
155