Skip to content

Commit 7470b5a

Browse files
SiFiveHollandpalmer-dabbelt
authored andcommitted
riscv: selftests: Add a pointer masking test
This test covers the behavior of the PR_SET_TAGGED_ADDR_CTRL and PR_GET_TAGGED_ADDR_CTRL prctl() operations, their effects on the userspace ABI, and their effects on the system call ABI. Reviewed-by: Charlie Jenkins <charlie@rivosinc.com> Tested-by: Charlie Jenkins <charlie@rivosinc.com> Signed-off-by: Samuel Holland <samuel.holland@sifive.com> Link: https://lore.kernel.org/r/20241016202814.4061541-8-samuel.holland@sifive.com Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
1 parent 7884448 commit 7470b5a

File tree

4 files changed

+344
-1
lines changed

4 files changed

+344
-1
lines changed

tools/testing/selftests/riscv/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
ARCH ?= $(shell uname -m 2>/dev/null || echo not)
66

77
ifneq (,$(filter $(ARCH),riscv))
8-
RISCV_SUBTARGETS ?= hwprobe vector mm sigreturn
8+
RISCV_SUBTARGETS ?= abi hwprobe mm sigreturn vector
99
else
1010
RISCV_SUBTARGETS :=
1111
endif
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pointer_masking
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# SPDX-License-Identifier: GPL-2.0
2+
3+
CFLAGS += -I$(top_srcdir)/tools/include
4+
5+
TEST_GEN_PROGS := pointer_masking
6+
7+
include ../../lib.mk
8+
9+
$(OUTPUT)/pointer_masking: pointer_masking.c
10+
$(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
3+
#include <errno.h>
4+
#include <fcntl.h>
5+
#include <setjmp.h>
6+
#include <signal.h>
7+
#include <stdbool.h>
8+
#include <sys/prctl.h>
9+
#include <sys/wait.h>
10+
#include <unistd.h>
11+
12+
#include "../../kselftest.h"
13+
14+
#ifndef PR_PMLEN_SHIFT
15+
#define PR_PMLEN_SHIFT 24
16+
#endif
17+
#ifndef PR_PMLEN_MASK
18+
#define PR_PMLEN_MASK (0x7fUL << PR_PMLEN_SHIFT)
19+
#endif
20+
21+
static int dev_zero;
22+
23+
static int pipefd[2];
24+
25+
static sigjmp_buf jmpbuf;
26+
27+
static void sigsegv_handler(int sig)
28+
{
29+
siglongjmp(jmpbuf, 1);
30+
}
31+
32+
static int min_pmlen;
33+
static int max_pmlen;
34+
35+
static inline bool valid_pmlen(int pmlen)
36+
{
37+
return pmlen == 0 || pmlen == 7 || pmlen == 16;
38+
}
39+
40+
static void test_pmlen(void)
41+
{
42+
ksft_print_msg("Testing available PMLEN values\n");
43+
44+
for (int request = 0; request <= 16; request++) {
45+
int pmlen, ret;
46+
47+
ret = prctl(PR_SET_TAGGED_ADDR_CTRL, request << PR_PMLEN_SHIFT, 0, 0, 0);
48+
if (ret)
49+
goto pr_set_error;
50+
51+
ret = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
52+
ksft_test_result(ret >= 0, "PMLEN=%d PR_GET_TAGGED_ADDR_CTRL\n", request);
53+
if (ret < 0)
54+
goto pr_get_error;
55+
56+
pmlen = (ret & PR_PMLEN_MASK) >> PR_PMLEN_SHIFT;
57+
ksft_test_result(pmlen >= request, "PMLEN=%d constraint\n", request);
58+
ksft_test_result(valid_pmlen(pmlen), "PMLEN=%d validity\n", request);
59+
60+
if (min_pmlen == 0)
61+
min_pmlen = pmlen;
62+
if (max_pmlen < pmlen)
63+
max_pmlen = pmlen;
64+
65+
continue;
66+
67+
pr_set_error:
68+
ksft_test_result_skip("PMLEN=%d PR_GET_TAGGED_ADDR_CTRL\n", request);
69+
pr_get_error:
70+
ksft_test_result_skip("PMLEN=%d constraint\n", request);
71+
ksft_test_result_skip("PMLEN=%d validity\n", request);
72+
}
73+
74+
if (max_pmlen == 0)
75+
ksft_exit_fail_msg("Failed to enable pointer masking\n");
76+
}
77+
78+
static int set_tagged_addr_ctrl(int pmlen, bool tagged_addr_abi)
79+
{
80+
int arg, ret;
81+
82+
arg = pmlen << PR_PMLEN_SHIFT | tagged_addr_abi;
83+
ret = prctl(PR_SET_TAGGED_ADDR_CTRL, arg, 0, 0, 0);
84+
if (!ret) {
85+
ret = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
86+
if (ret == arg)
87+
return 0;
88+
}
89+
90+
return ret < 0 ? -errno : -ENODATA;
91+
}
92+
93+
static void test_dereference_pmlen(int pmlen)
94+
{
95+
static volatile int i;
96+
volatile int *p;
97+
int ret;
98+
99+
ret = set_tagged_addr_ctrl(pmlen, false);
100+
if (ret)
101+
return ksft_test_result_error("PMLEN=%d setup (%d)\n", pmlen, ret);
102+
103+
i = pmlen;
104+
105+
if (pmlen) {
106+
p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen));
107+
108+
/* These dereferences should succeed. */
109+
if (sigsetjmp(jmpbuf, 1))
110+
return ksft_test_result_fail("PMLEN=%d valid tag\n", pmlen);
111+
if (*p != pmlen)
112+
return ksft_test_result_fail("PMLEN=%d bad value\n", pmlen);
113+
++*p;
114+
}
115+
116+
p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen - 1));
117+
118+
/* These dereferences should raise SIGSEGV. */
119+
if (sigsetjmp(jmpbuf, 1))
120+
return ksft_test_result_pass("PMLEN=%d dereference\n", pmlen);
121+
++*p;
122+
ksft_test_result_fail("PMLEN=%d invalid tag\n", pmlen);
123+
}
124+
125+
static void test_dereference(void)
126+
{
127+
ksft_print_msg("Testing userspace pointer dereference\n");
128+
129+
signal(SIGSEGV, sigsegv_handler);
130+
131+
test_dereference_pmlen(0);
132+
test_dereference_pmlen(min_pmlen);
133+
test_dereference_pmlen(max_pmlen);
134+
135+
signal(SIGSEGV, SIG_DFL);
136+
}
137+
138+
static void execve_child_sigsegv_handler(int sig)
139+
{
140+
exit(42);
141+
}
142+
143+
static int execve_child(void)
144+
{
145+
static volatile int i;
146+
volatile int *p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - 7));
147+
148+
signal(SIGSEGV, execve_child_sigsegv_handler);
149+
150+
/* This dereference should raise SIGSEGV. */
151+
return *p;
152+
}
153+
154+
static void test_fork_exec(void)
155+
{
156+
int ret, status;
157+
158+
ksft_print_msg("Testing fork/exec behavior\n");
159+
160+
ret = set_tagged_addr_ctrl(min_pmlen, false);
161+
if (ret)
162+
return ksft_test_result_error("setup (%d)\n", ret);
163+
164+
if (fork()) {
165+
wait(&status);
166+
ksft_test_result(WIFEXITED(status) && WEXITSTATUS(status) == 42,
167+
"dereference after fork\n");
168+
} else {
169+
static volatile int i = 42;
170+
volatile int *p;
171+
172+
p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - min_pmlen));
173+
174+
/* This dereference should succeed. */
175+
exit(*p);
176+
}
177+
178+
if (fork()) {
179+
wait(&status);
180+
ksft_test_result(WIFEXITED(status) && WEXITSTATUS(status) == 42,
181+
"dereference after fork+exec\n");
182+
} else {
183+
/* Will call execve_child(). */
184+
execve("/proc/self/exe", (char *const []) { "", NULL }, NULL);
185+
}
186+
}
187+
188+
static void test_tagged_addr_abi_sysctl(void)
189+
{
190+
char value;
191+
int fd;
192+
193+
ksft_print_msg("Testing tagged address ABI sysctl\n");
194+
195+
fd = open("/proc/sys/abi/tagged_addr_disabled", O_WRONLY);
196+
if (fd < 0) {
197+
ksft_test_result_skip("failed to open sysctl file\n");
198+
ksft_test_result_skip("failed to open sysctl file\n");
199+
return;
200+
}
201+
202+
value = '1';
203+
pwrite(fd, &value, 1, 0);
204+
ksft_test_result(set_tagged_addr_ctrl(min_pmlen, true) == -EINVAL,
205+
"sysctl disabled\n");
206+
207+
value = '0';
208+
pwrite(fd, &value, 1, 0);
209+
ksft_test_result(set_tagged_addr_ctrl(min_pmlen, true) == 0,
210+
"sysctl enabled\n");
211+
212+
set_tagged_addr_ctrl(0, false);
213+
214+
close(fd);
215+
}
216+
217+
static void test_tagged_addr_abi_pmlen(int pmlen)
218+
{
219+
int i, *p, ret;
220+
221+
i = ~pmlen;
222+
223+
if (pmlen) {
224+
p = (int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen));
225+
226+
ret = set_tagged_addr_ctrl(pmlen, false);
227+
if (ret)
228+
return ksft_test_result_error("PMLEN=%d ABI disabled setup (%d)\n",
229+
pmlen, ret);
230+
231+
ret = write(pipefd[1], p, sizeof(*p));
232+
if (ret >= 0 || errno != EFAULT)
233+
return ksft_test_result_fail("PMLEN=%d ABI disabled write\n", pmlen);
234+
235+
ret = read(dev_zero, p, sizeof(*p));
236+
if (ret >= 0 || errno != EFAULT)
237+
return ksft_test_result_fail("PMLEN=%d ABI disabled read\n", pmlen);
238+
239+
if (i != ~pmlen)
240+
return ksft_test_result_fail("PMLEN=%d ABI disabled value\n", pmlen);
241+
242+
ret = set_tagged_addr_ctrl(pmlen, true);
243+
if (ret)
244+
return ksft_test_result_error("PMLEN=%d ABI enabled setup (%d)\n",
245+
pmlen, ret);
246+
247+
ret = write(pipefd[1], p, sizeof(*p));
248+
if (ret != sizeof(*p))
249+
return ksft_test_result_fail("PMLEN=%d ABI enabled write\n", pmlen);
250+
251+
ret = read(dev_zero, p, sizeof(*p));
252+
if (ret != sizeof(*p))
253+
return ksft_test_result_fail("PMLEN=%d ABI enabled read\n", pmlen);
254+
255+
if (i)
256+
return ksft_test_result_fail("PMLEN=%d ABI enabled value\n", pmlen);
257+
258+
i = ~pmlen;
259+
} else {
260+
/* The tagged address ABI cannot be enabled when PMLEN == 0. */
261+
ret = set_tagged_addr_ctrl(pmlen, true);
262+
if (ret != -EINVAL)
263+
return ksft_test_result_error("PMLEN=%d ABI setup (%d)\n",
264+
pmlen, ret);
265+
}
266+
267+
p = (int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen - 1));
268+
269+
ret = write(pipefd[1], p, sizeof(*p));
270+
if (ret >= 0 || errno != EFAULT)
271+
return ksft_test_result_fail("PMLEN=%d invalid tag write (%d)\n", pmlen, errno);
272+
273+
ret = read(dev_zero, p, sizeof(*p));
274+
if (ret >= 0 || errno != EFAULT)
275+
return ksft_test_result_fail("PMLEN=%d invalid tag read\n", pmlen);
276+
277+
if (i != ~pmlen)
278+
return ksft_test_result_fail("PMLEN=%d invalid tag value\n", pmlen);
279+
280+
ksft_test_result_pass("PMLEN=%d tagged address ABI\n", pmlen);
281+
}
282+
283+
static void test_tagged_addr_abi(void)
284+
{
285+
ksft_print_msg("Testing tagged address ABI\n");
286+
287+
test_tagged_addr_abi_pmlen(0);
288+
test_tagged_addr_abi_pmlen(min_pmlen);
289+
test_tagged_addr_abi_pmlen(max_pmlen);
290+
}
291+
292+
static struct test_info {
293+
unsigned int nr_tests;
294+
void (*test_fn)(void);
295+
} tests[] = {
296+
{ .nr_tests = 17 * 3, test_pmlen },
297+
{ .nr_tests = 3, test_dereference },
298+
{ .nr_tests = 2, test_fork_exec },
299+
{ .nr_tests = 2, test_tagged_addr_abi_sysctl },
300+
{ .nr_tests = 3, test_tagged_addr_abi },
301+
};
302+
303+
int main(int argc, char **argv)
304+
{
305+
unsigned int plan = 0;
306+
int ret;
307+
308+
/* Check if this is the child process after execve(). */
309+
if (!argv[0][0])
310+
return execve_child();
311+
312+
dev_zero = open("/dev/zero", O_RDWR);
313+
if (dev_zero < 0)
314+
return 1;
315+
316+
/* Write to a pipe so the kernel must dereference the buffer pointer. */
317+
ret = pipe(pipefd);
318+
if (ret)
319+
return 1;
320+
321+
ksft_print_header();
322+
323+
for (int i = 0; i < ARRAY_SIZE(tests); i++)
324+
plan += tests[i].nr_tests;
325+
326+
ksft_set_plan(plan);
327+
328+
for (int i = 0; i < ARRAY_SIZE(tests); i++)
329+
tests[i].test_fn();
330+
331+
ksft_finished();
332+
}

0 commit comments

Comments
 (0)