Skip to content

Commit 3d37d43

Browse files
brooniectmarinas
authored andcommitted
kselftest/arm64: Add very basic GCS test program
This test program just covers the basic GCS ABI, covering aspects of the ABI as standalone features without attempting to integrate things. Reviewed-by: Thiago Jung Bauermann <[email protected]> Tested-by: Thiago Jung Bauermann <[email protected]> Signed-off-by: Mark Brown <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Catalin Marinas <[email protected]>
1 parent 42155a8 commit 3d37d43

File tree

5 files changed

+467
-1
lines changed

5 files changed

+467
-1
lines changed

tools/testing/selftests/arm64/Makefile

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

66
ifneq (,$(filter $(ARCH),aarch64 arm64))
7-
ARM64_SUBTARGETS ?= tags signal pauth fp mte bti abi
7+
ARM64_SUBTARGETS ?= tags signal pauth fp mte bti abi gcs
88
else
99
ARM64_SUBTARGETS :=
1010
endif
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
basic-gcs
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-License-Identifier: GPL-2.0
2+
# Copyright (C) 2023 ARM Limited
3+
#
4+
# In order to avoid interaction with the toolchain and dynamic linker the
5+
# portions of these tests that interact with the GCS are implemented using
6+
# nolibc.
7+
#
8+
9+
TEST_GEN_PROGS := basic-gcs
10+
11+
include ../../lib.mk
12+
13+
$(OUTPUT)/basic-gcs: basic-gcs.c
14+
$(CC) -g -fno-asynchronous-unwind-tables -fno-ident -s -Os -nostdlib \
15+
-static -include ../../../../include/nolibc/nolibc.h \
16+
-I../../../../../usr/include \
17+
-std=gnu99 -I../.. -g \
18+
-ffreestanding -Wall $^ -o $@ -lgcc
Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Copyright (C) 2023 ARM Limited.
4+
*/
5+
6+
#include <limits.h>
7+
#include <stdbool.h>
8+
9+
#include <linux/prctl.h>
10+
11+
#include <sys/mman.h>
12+
#include <asm/mman.h>
13+
#include <linux/sched.h>
14+
15+
#include "kselftest.h"
16+
#include "gcs-util.h"
17+
18+
/* nolibc doesn't have sysconf(), just hard code the maximum */
19+
static size_t page_size = 65536;
20+
21+
static __attribute__((noinline)) void valid_gcs_function(void)
22+
{
23+
/* Do something the compiler can't optimise out */
24+
my_syscall1(__NR_prctl, PR_SVE_GET_VL);
25+
}
26+
27+
static inline int gcs_set_status(unsigned long mode)
28+
{
29+
bool enabling = mode & PR_SHADOW_STACK_ENABLE;
30+
int ret;
31+
unsigned long new_mode;
32+
33+
/*
34+
* The prctl takes 1 argument but we need to ensure that the
35+
* other 3 values passed in registers to the syscall are zero
36+
* since the kernel validates them.
37+
*/
38+
ret = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, mode,
39+
0, 0, 0);
40+
41+
if (ret == 0) {
42+
ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
43+
&new_mode, 0, 0, 0);
44+
if (ret == 0) {
45+
if (new_mode != mode) {
46+
ksft_print_msg("Mode set to %lx not %lx\n",
47+
new_mode, mode);
48+
ret = -EINVAL;
49+
}
50+
} else {
51+
ksft_print_msg("Failed to validate mode: %d\n", ret);
52+
}
53+
54+
if (enabling != chkfeat_gcs()) {
55+
ksft_print_msg("%senabled by prctl but %senabled in CHKFEAT\n",
56+
enabling ? "" : "not ",
57+
chkfeat_gcs() ? "" : "not ");
58+
ret = -EINVAL;
59+
}
60+
}
61+
62+
return ret;
63+
}
64+
65+
/* Try to read the status */
66+
static bool read_status(void)
67+
{
68+
unsigned long state;
69+
int ret;
70+
71+
ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
72+
&state, 0, 0, 0);
73+
if (ret != 0) {
74+
ksft_print_msg("Failed to read state: %d\n", ret);
75+
return false;
76+
}
77+
78+
return state & PR_SHADOW_STACK_ENABLE;
79+
}
80+
81+
/* Just a straight enable */
82+
static bool base_enable(void)
83+
{
84+
int ret;
85+
86+
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
87+
if (ret) {
88+
ksft_print_msg("PR_SHADOW_STACK_ENABLE failed %d\n", ret);
89+
return false;
90+
}
91+
92+
return true;
93+
}
94+
95+
/* Check we can read GCSPR_EL0 when GCS is enabled */
96+
static bool read_gcspr_el0(void)
97+
{
98+
unsigned long *gcspr_el0;
99+
100+
ksft_print_msg("GET GCSPR\n");
101+
gcspr_el0 = get_gcspr();
102+
ksft_print_msg("GCSPR_EL0 is %p\n", gcspr_el0);
103+
104+
return true;
105+
}
106+
107+
/* Also allow writes to stack */
108+
static bool enable_writeable(void)
109+
{
110+
int ret;
111+
112+
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE);
113+
if (ret) {
114+
ksft_print_msg("PR_SHADOW_STACK_ENABLE writeable failed: %d\n", ret);
115+
return false;
116+
}
117+
118+
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
119+
if (ret) {
120+
ksft_print_msg("failed to restore plain enable %d\n", ret);
121+
return false;
122+
}
123+
124+
return true;
125+
}
126+
127+
/* Also allow writes to stack */
128+
static bool enable_push_pop(void)
129+
{
130+
int ret;
131+
132+
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH);
133+
if (ret) {
134+
ksft_print_msg("PR_SHADOW_STACK_ENABLE with push failed: %d\n",
135+
ret);
136+
return false;
137+
}
138+
139+
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
140+
if (ret) {
141+
ksft_print_msg("failed to restore plain enable %d\n", ret);
142+
return false;
143+
}
144+
145+
return true;
146+
}
147+
148+
/* Enable GCS and allow everything */
149+
static bool enable_all(void)
150+
{
151+
int ret;
152+
153+
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH |
154+
PR_SHADOW_STACK_WRITE);
155+
if (ret) {
156+
ksft_print_msg("PR_SHADOW_STACK_ENABLE with everything failed: %d\n",
157+
ret);
158+
return false;
159+
}
160+
161+
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
162+
if (ret) {
163+
ksft_print_msg("failed to restore plain enable %d\n", ret);
164+
return false;
165+
}
166+
167+
return true;
168+
}
169+
170+
static bool enable_invalid(void)
171+
{
172+
int ret = gcs_set_status(ULONG_MAX);
173+
if (ret == 0) {
174+
ksft_print_msg("GCS_SET_STATUS %lx succeeded\n", ULONG_MAX);
175+
return false;
176+
}
177+
178+
return true;
179+
}
180+
181+
/* Map a GCS */
182+
static bool map_guarded_stack(void)
183+
{
184+
int ret;
185+
uint64_t *buf;
186+
uint64_t expected_cap;
187+
int elem;
188+
bool pass = true;
189+
190+
buf = (void *)my_syscall3(__NR_map_shadow_stack, 0, page_size,
191+
SHADOW_STACK_SET_MARKER |
192+
SHADOW_STACK_SET_TOKEN);
193+
if (buf == MAP_FAILED) {
194+
ksft_print_msg("Failed to map %lu byte GCS: %d\n",
195+
page_size, errno);
196+
return false;
197+
}
198+
ksft_print_msg("Mapped GCS at %p-%p\n", buf,
199+
(void *)((uint64_t)buf + page_size));
200+
201+
/* The top of the newly allocated region should be 0 */
202+
elem = (page_size / sizeof(uint64_t)) - 1;
203+
if (buf[elem]) {
204+
ksft_print_msg("Last entry is 0x%llx not 0x0\n", buf[elem]);
205+
pass = false;
206+
}
207+
208+
/* Then a valid cap token */
209+
elem--;
210+
expected_cap = ((uint64_t)buf + page_size - 16);
211+
expected_cap &= GCS_CAP_ADDR_MASK;
212+
expected_cap |= GCS_CAP_VALID_TOKEN;
213+
if (buf[elem] != expected_cap) {
214+
ksft_print_msg("Cap entry is 0x%llx not 0x%llx\n",
215+
buf[elem], expected_cap);
216+
pass = false;
217+
}
218+
ksft_print_msg("cap token is 0x%llx\n", buf[elem]);
219+
220+
/* The rest should be zeros */
221+
for (elem = 0; elem < page_size / sizeof(uint64_t) - 2; elem++) {
222+
if (!buf[elem])
223+
continue;
224+
ksft_print_msg("GCS slot %d is 0x%llx not 0x0\n",
225+
elem, buf[elem]);
226+
pass = false;
227+
}
228+
229+
ret = munmap(buf, page_size);
230+
if (ret != 0) {
231+
ksft_print_msg("Failed to unmap %ld byte GCS: %d\n",
232+
page_size, errno);
233+
pass = false;
234+
}
235+
236+
return pass;
237+
}
238+
239+
/* A fork()ed process can run */
240+
static bool test_fork(void)
241+
{
242+
unsigned long child_mode;
243+
int ret, status;
244+
pid_t pid;
245+
bool pass = true;
246+
247+
pid = fork();
248+
if (pid == -1) {
249+
ksft_print_msg("fork() failed: %d\n", errno);
250+
pass = false;
251+
goto out;
252+
}
253+
if (pid == 0) {
254+
/* In child, make sure we can call a function, read
255+
* the GCS pointer and status and then exit */
256+
valid_gcs_function();
257+
get_gcspr();
258+
259+
ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
260+
&child_mode, 0, 0, 0);
261+
if (ret == 0 && !(child_mode & PR_SHADOW_STACK_ENABLE)) {
262+
ksft_print_msg("GCS not enabled in child\n");
263+
ret = -EINVAL;
264+
}
265+
266+
exit(ret);
267+
}
268+
269+
/*
270+
* In parent, check we can still do function calls then block
271+
* for the child.
272+
*/
273+
valid_gcs_function();
274+
275+
ksft_print_msg("Waiting for child %d\n", pid);
276+
277+
ret = waitpid(pid, &status, 0);
278+
if (ret == -1) {
279+
ksft_print_msg("Failed to wait for child: %d\n",
280+
errno);
281+
return false;
282+
}
283+
284+
if (!WIFEXITED(status)) {
285+
ksft_print_msg("Child exited due to signal %d\n",
286+
WTERMSIG(status));
287+
pass = false;
288+
} else {
289+
if (WEXITSTATUS(status)) {
290+
ksft_print_msg("Child exited with status %d\n",
291+
WEXITSTATUS(status));
292+
pass = false;
293+
}
294+
}
295+
296+
out:
297+
298+
return pass;
299+
}
300+
301+
typedef bool (*gcs_test)(void);
302+
303+
static struct {
304+
char *name;
305+
gcs_test test;
306+
bool needs_enable;
307+
} tests[] = {
308+
{ "read_status", read_status },
309+
{ "base_enable", base_enable, true },
310+
{ "read_gcspr_el0", read_gcspr_el0 },
311+
{ "enable_writeable", enable_writeable, true },
312+
{ "enable_push_pop", enable_push_pop, true },
313+
{ "enable_all", enable_all, true },
314+
{ "enable_invalid", enable_invalid, true },
315+
{ "map_guarded_stack", map_guarded_stack },
316+
{ "fork", test_fork },
317+
};
318+
319+
int main(void)
320+
{
321+
int i, ret;
322+
unsigned long gcs_mode;
323+
324+
ksft_print_header();
325+
326+
/*
327+
* We don't have getauxval() with nolibc so treat a failure to
328+
* read GCS state as a lack of support and skip.
329+
*/
330+
ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
331+
&gcs_mode, 0, 0, 0);
332+
if (ret != 0)
333+
ksft_exit_skip("Failed to read GCS state: %d\n", ret);
334+
335+
if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) {
336+
gcs_mode = PR_SHADOW_STACK_ENABLE;
337+
ret = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
338+
gcs_mode, 0, 0, 0);
339+
if (ret != 0)
340+
ksft_exit_fail_msg("Failed to enable GCS: %d\n", ret);
341+
}
342+
343+
ksft_set_plan(ARRAY_SIZE(tests));
344+
345+
for (i = 0; i < ARRAY_SIZE(tests); i++) {
346+
ksft_test_result((*tests[i].test)(), "%s\n", tests[i].name);
347+
}
348+
349+
/* One last test: disable GCS, we can do this one time */
350+
my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0);
351+
if (ret != 0)
352+
ksft_print_msg("Failed to disable GCS: %d\n", ret);
353+
354+
ksft_finished();
355+
356+
return 0;
357+
}

0 commit comments

Comments
 (0)