1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 /*
3 * Copyright (C) 2005-2006 IBM Corporation.
4 * Copyright (c) Linux Test Project, 2023
5 * Author: David Gibson & Adam Litke
6 */
7
8 /*\
9 * [Description]
10 *
11 * This test uses mprotect to change protection of hugepage mapping and
12 * perform read/write operation. It checks if the operation results in
13 * expected behaviour as per the protection.
14 */
15
16 #include <setjmp.h>
17 #include "hugetlb.h"
18
19 #define MNTPOINT "hugetlbfs/"
20 #define RANDOM_CONSTANT 0x1234ABCD
21 #define FLAGS_DESC(x) x, #x
22
23 static int fd = -1;
24 static sigjmp_buf sig_escape;
25 static void *sig_expected = MAP_FAILED;
26 static long hpage_size;
27 static void *addr;
28
29 static struct tcase {
30 char *tname;
31 unsigned long len1;
32 int prot1;
33 char *prot1_str;
34 unsigned long len2;
35 int prot2;
36 char *prot2_str;
37 } tcases[] = {
38 {"R->RW", 1, FLAGS_DESC(PROT_READ), 1, FLAGS_DESC(PROT_READ|PROT_WRITE)},
39 {"RW->R", 1, FLAGS_DESC(PROT_READ | PROT_WRITE), 1, FLAGS_DESC(PROT_READ)},
40 {"R->RW 1/2", 2, FLAGS_DESC(PROT_READ), 1, FLAGS_DESC(PROT_READ | PROT_WRITE)},
41 {"RW->R 1/2", 2, FLAGS_DESC(PROT_READ | PROT_WRITE), 1, FLAGS_DESC(PROT_READ)},
42 {"NONE->R", 1, FLAGS_DESC(PROT_NONE), 1, FLAGS_DESC(PROT_READ)},
43 {"NONE->RW", 1, FLAGS_DESC(PROT_NONE), 1, FLAGS_DESC(PROT_READ | PROT_WRITE)},
44 };
45
sig_handler(int signum,siginfo_t * si,void * uc)46 static void sig_handler(int signum, siginfo_t *si, void *uc)
47 {
48 (void)uc;
49
50 if (signum == SIGSEGV) {
51 tst_res(TINFO, "SIGSEGV at %p (sig_expected=%p)", si->si_addr,
52 sig_expected);
53 if (si->si_addr == sig_expected)
54 siglongjmp(sig_escape, 1);
55 tst_res(TFAIL, "SIGSEGV somewhere unexpected");
56 } else {
57 tst_res(TFAIL, "Unexpected signal %s", strsignal(signum));
58 }
59 }
60
test_read(void * p)61 static int test_read(void *p)
62 {
63 volatile unsigned long *pl = p;
64 unsigned long x;
65
66 if (sigsetjmp(sig_escape, 1)) {
67 /* We got a SEGV */
68 sig_expected = MAP_FAILED;
69 return -1;
70 }
71
72 sig_expected = p;
73 barrier();
74 x = *pl;
75 tst_res(TINFO, "Read back %lu", x);
76 barrier();
77 sig_expected = MAP_FAILED;
78 return 0;
79 }
80
test_write(void * p,unsigned long val)81 static int test_write(void *p, unsigned long val)
82 {
83 volatile unsigned long *pl = p;
84 unsigned long x;
85
86 if (sigsetjmp(sig_escape, 1)) {
87 /* We got a SEGV */
88 sig_expected = MAP_FAILED;
89 return -1;
90 }
91
92 sig_expected = p;
93 barrier();
94 *pl = val;
95 x = *pl;
96 barrier();
97 sig_expected = MAP_FAILED;
98
99 return (x != val);
100 }
101
test_prot(void * p,int prot,char * prot_str)102 static int test_prot(void *p, int prot, char *prot_str)
103 {
104 int r, w;
105
106 r = test_read(p);
107 tst_res(TINFO, "On Read: %d", r);
108 w = test_write(p, RANDOM_CONSTANT);
109 tst_res(TINFO, "On Write: %d", w);
110
111 if (prot & PROT_READ) {
112 if (r != 0) {
113 tst_res(TFAIL, "read failed on mmap(prot %s)", prot_str);
114 return -1;
115 }
116 } else {
117 if (r != -1) {
118 tst_res(TFAIL, "read succeeded on mmap(prot %s)", prot_str);
119 return -1;
120 }
121 }
122
123 if (prot & PROT_WRITE) {
124 switch (w) {
125 case -1:
126 tst_res(TFAIL, "write failed on mmap(prot %s)", prot_str);
127 return -1;
128 case 0:
129 break;
130 case 1:
131 tst_res(TFAIL, "write mismatch on mmap(prot %s)", prot_str);
132 return -1;
133 default:
134 tst_res(TWARN, "Bug in test");
135 return -1;
136 }
137 } else {
138 switch (w) {
139 case -1:
140 break;
141 case 0:
142 tst_res(TFAIL, "write succeeded on mmap(prot %s)", prot_str);
143 return -1;
144 case 1:
145 tst_res(TFAIL, "write mismatch on mmap(prot %s)", prot_str);
146 return -1;
147 default:
148 tst_res(TWARN, "Bug in test");
149 break;
150 }
151 }
152
153 return 0;
154 }
155
run_test(unsigned int i)156 static void run_test(unsigned int i)
157 {
158 void *p;
159 int ret;
160 struct tcase *tc = &tcases[i];
161
162 tst_res(TINFO, "Test Name: %s", tc->tname);
163
164 p = SAFE_MMAP(NULL, tc->len1*hpage_size, tc->prot1, MAP_SHARED, fd, 0);
165
166 ret = test_prot(p, tc->prot1, tc->prot1_str);
167 if (ret)
168 goto cleanup;
169
170 ret = mprotect(p, tc->len2*hpage_size, tc->prot2);
171 if (ret != 0) {
172 tst_res(TFAIL|TERRNO, "%s: mprotect(prot %s)",
173 tc->tname, tc->prot2_str);
174 goto cleanup;
175 }
176
177 ret = test_prot(p, tc->prot2, tc->prot2_str);
178 if (ret)
179 goto cleanup;
180
181 if (tc->len2 < tc->len1)
182 ret = test_prot(p + tc->len2*hpage_size, tc->prot1, tc->prot1_str);
183
184 tst_res(TPASS, "Successfully tested mprotect %s", tc->tname);
185
186 cleanup:
187 SAFE_MUNMAP(p, tc->len1*hpage_size);
188 }
189
setup(void)190 static void setup(void)
191 {
192 struct sigaction sa = {
193 .sa_sigaction = sig_handler,
194 .sa_flags = SA_SIGINFO,
195 };
196
197 hpage_size = tst_get_hugepage_size();
198 SAFE_SIGACTION(SIGSEGV, &sa, NULL);
199
200 fd = tst_creat_unlinked(MNTPOINT, 0);
201 addr = SAFE_MMAP(NULL, 2*hpage_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
202 memset(addr, 0, hpage_size);
203 SAFE_MUNMAP(addr, hpage_size);
204 }
205
cleanup(void)206 static void cleanup(void)
207 {
208 SAFE_MUNMAP(addr+hpage_size, hpage_size);
209 if (fd >= 0)
210 SAFE_CLOSE(fd);
211 }
212
213 static struct tst_test test = {
214 .tcnt = ARRAY_SIZE(tcases),
215 .needs_root = 1,
216 .mntpoint = MNTPOINT,
217 .needs_hugetlbfs = 1,
218 .needs_tmpdir = 1,
219 .setup = setup,
220 .cleanup = cleanup,
221 .test = run_test,
222 .hugepages = {2, TST_NEEDS},
223 };
224