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