Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,7 @@
basic-gcs
libc-gcs
gcs-locking
gcs-stress
gcs-stress-thread
gcspushm
gcsstr
@@ -0,0 +1,30 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2023 ARM Limited
#
# In order to avoid interaction with the toolchain and dynamic linker the
# portions of these tests that interact with the GCS are implemented using
# nolibc.
#
TEST_GEN_PROGS := basic-gcs libc-gcs gcs-locking gcs-stress gcspushm gcsstr
TEST_GEN_PROGS_EXTENDED := gcs-stress-thread
LDLIBS+=-lpthread
include ../../lib.mk
$(OUTPUT)/basic-gcs: basic-gcs.c
$(CC) $(CFLAGS) -fno-asynchronous-unwind-tables -fno-ident -s -nostdlib -nostdinc \
-static -I../../../../include/nolibc -include ../../../../include/nolibc/nolibc.h \
-I../../../../../usr/include \
-std=gnu99 -I../.. -g \
-ffreestanding $^ -o $@ -lgcc
$(OUTPUT)/gcs-stress-thread: gcs-stress-thread.S
$(CC) -nostdlib $^ -o $@
$(OUTPUT)/gcspushm: gcspushm.S
$(CC) -nostdlib $^ -o $@
$(OUTPUT)/gcsstr: gcsstr.S
$(CC) -nostdlib $^ -o $@
@@ -0,0 +1,412 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2023 ARM Limited.
*/
#include <limits.h>
#include <stdbool.h>
#include <linux/prctl.h>
#include <sys/mman.h>
#include <asm/mman.h>
#include <asm/hwcap.h>
#include <linux/sched.h>
#include "kselftest.h"
#include "gcs-util.h"
/* nolibc doesn't have sysconf(), just hard code the maximum */
static size_t page_size = 65536;
static __attribute__((noinline)) void valid_gcs_function(void)
{
/* Do something the compiler can't optimise out */
syscall(__NR_prctl, PR_SVE_GET_VL);
}
static inline int gcs_set_status(unsigned long mode)
{
bool enabling = mode & PR_SHADOW_STACK_ENABLE;
int ret;
unsigned long new_mode;
/*
* The prctl takes 1 argument but we need to ensure that the
* other 3 values passed in registers to the syscall are zero
* since the kernel validates them.
*/
ret = syscall(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, mode, 0, 0, 0);
if (ret == 0) {
ret = syscall(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &new_mode, 0, 0, 0);
if (ret == 0) {
if (new_mode != mode) {
ksft_print_msg("Mode set to %lx not %lx\n",
new_mode, mode);
ret = -EINVAL;
}
} else {
ksft_print_msg("Failed to validate mode: %d\n", errno);
}
if (enabling != chkfeat_gcs()) {
ksft_print_msg("%senabled by prctl but %senabled in CHKFEAT\n",
enabling ? "" : "not ",
chkfeat_gcs() ? "" : "not ");
ret = -EINVAL;
}
}
return ret;
}
/* Try to read the status */
static bool read_status(void)
{
unsigned long state;
int ret;
ret = syscall(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &state, 0, 0, 0);
if (ret != 0) {
ksft_print_msg("Failed to read state: %d\n", errno);
return false;
}
return state & PR_SHADOW_STACK_ENABLE;
}
/* Just a straight enable */
static bool base_enable(void)
{
int ret;
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
if (ret) {
ksft_print_msg("PR_SHADOW_STACK_ENABLE failed %d\n", ret);
return false;
}
return true;
}
/* Check we can read GCSPR_EL0 when GCS is enabled */
static bool read_gcspr_el0(void)
{
unsigned long *gcspr_el0;
ksft_print_msg("GET GCSPR\n");
gcspr_el0 = get_gcspr();
ksft_print_msg("GCSPR_EL0 is %p\n", gcspr_el0);
return true;
}
/* Also allow writes to stack */
static bool enable_writeable(void)
{
int ret;
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE);
if (ret) {
ksft_print_msg("PR_SHADOW_STACK_ENABLE writeable failed: %d\n", ret);
return false;
}
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
if (ret) {
ksft_print_msg("failed to restore plain enable %d\n", ret);
return false;
}
return true;
}
/* Also allow writes to stack */
static bool enable_push_pop(void)
{
int ret;
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH);
if (ret) {
ksft_print_msg("PR_SHADOW_STACK_ENABLE with push failed: %d\n",
ret);
return false;
}
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
if (ret) {
ksft_print_msg("failed to restore plain enable %d\n", ret);
return false;
}
return true;
}
/* Enable GCS and allow everything */
static bool enable_all(void)
{
int ret;
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH |
PR_SHADOW_STACK_WRITE);
if (ret) {
ksft_print_msg("PR_SHADOW_STACK_ENABLE with everything failed: %d\n",
ret);
return false;
}
ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
if (ret) {
ksft_print_msg("failed to restore plain enable %d\n", ret);
return false;
}
return true;
}
static bool enable_invalid(void)
{
int ret = gcs_set_status(ULONG_MAX);
if (ret == 0) {
ksft_print_msg("GCS_SET_STATUS %lx succeeded\n", ULONG_MAX);
return false;
}
return true;
}
/* Map a GCS */
static bool map_guarded_stack(void)
{
int ret;
uint64_t *buf;
uint64_t expected_cap;
int elem;
bool pass = true;
buf = (void *)syscall(__NR_map_shadow_stack, 0, page_size,
SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN);
if (buf == MAP_FAILED) {
ksft_print_msg("Failed to map %lu byte GCS: %d\n",
page_size, errno);
return false;
}
ksft_print_msg("Mapped GCS at %p-%p\n", buf,
(void *)((uint64_t)buf + page_size));
/* The top of the newly allocated region should be 0 */
elem = (page_size / sizeof(uint64_t)) - 1;
if (buf[elem]) {
ksft_print_msg("Last entry is 0x%llx not 0x0\n", buf[elem]);
pass = false;
}
/* Then a valid cap token */
elem--;
expected_cap = ((uint64_t)buf + page_size - 16);
expected_cap &= GCS_CAP_ADDR_MASK;
expected_cap |= GCS_CAP_VALID_TOKEN;
if (buf[elem] != expected_cap) {
ksft_print_msg("Cap entry is 0x%llx not 0x%llx\n",
buf[elem], expected_cap);
pass = false;
}
ksft_print_msg("cap token is 0x%llx\n", buf[elem]);
/* The rest should be zeros */
for (elem = 0; elem < page_size / sizeof(uint64_t) - 2; elem++) {
if (!buf[elem])
continue;
ksft_print_msg("GCS slot %d is 0x%llx not 0x0\n",
elem, buf[elem]);
pass = false;
}
ret = munmap(buf, page_size);
if (ret != 0) {
ksft_print_msg("Failed to unmap %ld byte GCS: %d\n",
page_size, errno);
pass = false;
}
return pass;
}
/* A fork()ed process can run */
static bool test_fork(void)
{
unsigned long child_mode;
int ret, status;
pid_t pid;
bool pass = true;
pid = fork();
if (pid == -1) {
ksft_print_msg("fork() failed: %d\n", errno);
pass = false;
goto out;
}
if (pid == 0) {
/* In child, make sure we can call a function, read
* the GCS pointer and status and then exit */
valid_gcs_function();
get_gcspr();
ret = syscall(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &child_mode, 0, 0, 0);
if (ret == 0 && !(child_mode & PR_SHADOW_STACK_ENABLE)) {
ksft_print_msg("GCS not enabled in child\n");
ret = -EINVAL;
}
exit(ret);
}
/*
* In parent, check we can still do function calls then block
* for the child.
*/
valid_gcs_function();
ksft_print_msg("Waiting for child %d\n", pid);
ret = waitpid(pid, &status, 0);
if (ret == -1) {
ksft_print_msg("Failed to wait for child: %d\n",
errno);
return false;
}
if (!WIFEXITED(status)) {
ksft_print_msg("Child exited due to signal %d\n",
WTERMSIG(status));
pass = false;
} else {
if (WEXITSTATUS(status)) {
ksft_print_msg("Child exited with status %d\n",
WEXITSTATUS(status));
pass = false;
}
}
out:
return pass;
}
/* A vfork()ed process can run and exit */
static bool test_vfork(void)
{
unsigned long child_mode;
int ret, status;
pid_t pid;
bool pass = true;
pid = vfork();
if (pid == -1) {
ksft_print_msg("vfork() failed: %d\n", errno);
pass = false;
goto out;
}
if (pid == 0) {
/*
* In child, make sure we can call a function, read
* the GCS pointer and status and then exit.
*/
valid_gcs_function();
get_gcspr();
ret = syscall(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &child_mode, 0, 0, 0);
if (ret == 0 && !(child_mode & PR_SHADOW_STACK_ENABLE)) {
ksft_print_msg("GCS not enabled in child\n");
ret = EXIT_FAILURE;
}
_exit(ret);
}
/*
* In parent, check we can still do function calls then check
* on the child.
*/
valid_gcs_function();
ksft_print_msg("Waiting for child %d\n", pid);
ret = waitpid(pid, &status, 0);
if (ret == -1) {
ksft_print_msg("Failed to wait for child: %d\n",
errno);
return false;
}
if (!WIFEXITED(status)) {
ksft_print_msg("Child exited due to signal %d\n",
WTERMSIG(status));
pass = false;
} else if (WEXITSTATUS(status)) {
ksft_print_msg("Child exited with status %d\n",
WEXITSTATUS(status));
pass = false;
}
out:
return pass;
}
typedef bool (*gcs_test)(void);
static struct {
char *name;
gcs_test test;
bool needs_enable;
} tests[] = {
{ "read_status", read_status },
{ "base_enable", base_enable, true },
{ "read_gcspr_el0", read_gcspr_el0 },
{ "enable_writeable", enable_writeable, true },
{ "enable_push_pop", enable_push_pop, true },
{ "enable_all", enable_all, true },
{ "enable_invalid", enable_invalid, true },
{ "map_guarded_stack", map_guarded_stack },
{ "fork", test_fork },
{ "vfork", test_vfork },
};
int main(void)
{
int i, ret;
unsigned long gcs_mode;
ksft_print_header();
if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
ksft_exit_skip("SKIP GCS not supported\n");
ret = syscall(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode, 0, 0, 0);
if (ret != 0)
ksft_exit_fail_msg("Failed to read GCS state: %d\n", errno);
if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) {
gcs_mode = PR_SHADOW_STACK_ENABLE;
ret = syscall(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, gcs_mode, 0, 0, 0);
if (ret != 0)
ksft_exit_fail_msg("Failed to enable GCS: %d\n", errno);
}
ksft_set_plan(ARRAY_SIZE(tests));
for (i = 0; i < ARRAY_SIZE(tests); i++) {
ksft_test_result((*tests[i].test)(), "%s\n", tests[i].name);
}
/* One last test: disable GCS, we can do this one time */
ret = syscall(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0);
if (ret != 0)
ksft_print_msg("Failed to disable GCS: %d\n", errno);
ksft_finished();
return 0;
}
@@ -0,0 +1,199 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2023 ARM Limited.
*
* Tests for GCS mode locking. These tests rely on both having GCS
* unconfigured on entry and on the kselftest harness running each
* test in a fork()ed process which will have it's own mode.
*/
#include <limits.h>
#include <sys/auxv.h>
#include <sys/prctl.h>
#include <asm/hwcap.h>
#include "kselftest_harness.h"
#include "gcs-util.h"
#define my_syscall2(num, arg1, arg2) \
({ \
register long _num __asm__ ("x8") = (num); \
register long _arg1 __asm__ ("x0") = (long)(arg1); \
register long _arg2 __asm__ ("x1") = (long)(arg2); \
register long _arg3 __asm__ ("x2") = 0; \
register long _arg4 __asm__ ("x3") = 0; \
register long _arg5 __asm__ ("x4") = 0; \
\
__asm__ volatile ( \
"svc #0\n" \
: "=r"(_arg1) \
: "r"(_arg1), "r"(_arg2), \
"r"(_arg3), "r"(_arg4), \
"r"(_arg5), "r"(_num) \
: "memory", "cc" \
); \
_arg1; \
})
/* No mode bits are rejected for locking */
TEST(lock_all_modes)
{
int ret;
ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, ULONG_MAX, 0, 0, 0);
ASSERT_EQ(ret, 0);
}
FIXTURE(valid_modes)
{
};
FIXTURE_VARIANT(valid_modes)
{
unsigned long mode;
};
FIXTURE_VARIANT_ADD(valid_modes, enable)
{
.mode = PR_SHADOW_STACK_ENABLE,
};
FIXTURE_VARIANT_ADD(valid_modes, enable_write)
{
.mode = PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE,
};
FIXTURE_VARIANT_ADD(valid_modes, enable_push)
{
.mode = PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH,
};
FIXTURE_VARIANT_ADD(valid_modes, enable_write_push)
{
.mode = PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE |
PR_SHADOW_STACK_PUSH,
};
FIXTURE_SETUP(valid_modes)
{
}
FIXTURE_TEARDOWN(valid_modes)
{
}
/* We can set the mode at all */
TEST_F(valid_modes, set)
{
int ret;
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
variant->mode);
ASSERT_EQ(ret, 0);
_exit(0);
}
/* Enabling, locking then disabling is rejected */
TEST_F(valid_modes, enable_lock_disable)
{
unsigned long mode;
int ret;
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
variant->mode);
ASSERT_EQ(ret, 0);
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ASSERT_EQ(mode, variant->mode);
ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, variant->mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, 0);
ASSERT_EQ(ret, -EBUSY);
_exit(0);
}
/* Locking then enabling is rejected */
TEST_F(valid_modes, lock_enable)
{
unsigned long mode;
int ret;
ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, variant->mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
variant->mode);
ASSERT_EQ(ret, -EBUSY);
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ASSERT_EQ(mode, 0);
_exit(0);
}
/* Locking then changing other modes is fine */
TEST_F(valid_modes, lock_enable_disable_others)
{
unsigned long mode;
int ret;
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
variant->mode);
ASSERT_EQ(ret, 0);
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ASSERT_EQ(mode, variant->mode);
ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, variant->mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
PR_SHADOW_STACK_ALL_MODES);
ASSERT_EQ(ret, 0);
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ASSERT_EQ(mode, PR_SHADOW_STACK_ALL_MODES);
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
variant->mode);
ASSERT_EQ(ret, 0);
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ASSERT_EQ(mode, variant->mode);
_exit(0);
}
int main(int argc, char **argv)
{
unsigned long mode;
int ret;
if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
ksft_exit_skip("SKIP GCS not supported\n");
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
if (ret) {
ksft_print_msg("Failed to read GCS state: %d\n", ret);
return EXIT_FAILURE;
}
if (mode & PR_SHADOW_STACK_ENABLE) {
ksft_print_msg("GCS was enabled, test unsupported\n");
return KSFT_SKIP;
}
return test_harness_run(argc, argv);
}
@@ -0,0 +1,311 @@
// Program that loops for ever doing lots of recursions and system calls,
// intended to be used as part of a stress test for GCS context switching.
//
// Copyright 2015-2023 Arm Ltd
#include <asm/unistd.h>
#define sa_sz 32
#define sa_flags 8
#define sa_handler 0
#define sa_mask_sz 8
#define si_code 8
#define SIGINT 2
#define SIGABRT 6
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGTERM 15
#define SEGV_CPERR 10
#define SA_NODEFER 1073741824
#define SA_SIGINFO 4
#define ucontext_regs 184
#define PR_SET_SHADOW_STACK_STATUS 75
# define PR_SHADOW_STACK_ENABLE (1UL << 0)
#define GCSPR_EL0 S3_3_C2_C5_1
.macro function name
.macro endfunction
.type \name, @function
.purgem endfunction
.endm
\name:
.endm
// Print a single character x0 to stdout
// Clobbers x0-x2,x8
function putc
str x0, [sp, #-16]!
mov x0, #1 // STDOUT_FILENO
mov x1, sp
mov x2, #1
mov x8, #__NR_write
svc #0
add sp, sp, #16
ret
endfunction
.globl putc
// Print a NUL-terminated string starting at address x0 to stdout
// Clobbers x0-x3,x8
function puts
mov x1, x0
mov x2, #0
0: ldrb w3, [x0], #1
cbz w3, 1f
add x2, x2, #1
b 0b
1: mov w0, #1 // STDOUT_FILENO
mov x8, #__NR_write
svc #0
ret
endfunction
.globl puts
// Utility macro to print a literal string
// Clobbers x0-x4,x8
.macro puts string
.pushsection .rodata.str1.1, "aMS", @progbits, 1
.L__puts_literal\@: .string "\string"
.popsection
ldr x0, =.L__puts_literal\@
bl puts
.endm
// Print an unsigned decimal number x0 to stdout
// Clobbers x0-x4,x8
function putdec
mov x1, sp
str x30, [sp, #-32]! // Result can't be > 20 digits
mov x2, #0
strb w2, [x1, #-1]! // Write the NUL terminator
mov x2, #10
0: udiv x3, x0, x2 // div-mod loop to generate the digits
msub x0, x3, x2, x0
add w0, w0, #'0'
strb w0, [x1, #-1]!
mov x0, x3
cbnz x3, 0b
ldrb w0, [x1]
cbnz w0, 1f
mov w0, #'0' // Print "0" for 0, not ""
strb w0, [x1, #-1]!
1: mov x0, x1
bl puts
ldr x30, [sp], #32
ret
endfunction
.globl putdec
// Print an unsigned decimal number x0 to stdout, followed by a newline
// Clobbers x0-x5,x8
function putdecn
mov x5, x30
bl putdec
mov x0, #'\n'
bl putc
ret x5
endfunction
.globl putdecn
// Fill x1 bytes starting at x0 with 0.
// Clobbers x1, x2.
function memclr
mov w2, #0
endfunction
.globl memclr
// fall through to memfill
// Trivial memory fill: fill x1 bytes starting at address x0 with byte w2
// Clobbers x1
function memfill
cmp x1, #0
b.eq 1f
0: strb w2, [x0], #1
subs x1, x1, #1
b.ne 0b
1: ret
endfunction
.globl memfill
// w0: signal number
// x1: sa_action
// w2: sa_flags
// Clobbers x0-x6,x8
function setsignal
str x30, [sp, #-((sa_sz + 15) / 16 * 16 + 16)]!
mov w4, w0
mov x5, x1
mov w6, w2
add x0, sp, #16
mov x1, #sa_sz
bl memclr
mov w0, w4
add x1, sp, #16
str w6, [x1, #sa_flags]
str x5, [x1, #sa_handler]
mov x2, #0
mov x3, #sa_mask_sz
mov x8, #__NR_rt_sigaction
svc #0
cbz w0, 1f
puts "sigaction failure\n"
b abort
1: ldr x30, [sp], #((sa_sz + 15) / 16 * 16 + 16)
ret
endfunction
function tickle_handler
// Perhaps collect GCSPR_EL0 here in future?
ret
endfunction
function terminate_handler
mov w21, w0
mov x20, x2
puts "Terminated by signal "
mov w0, w21
bl putdec
puts ", no error\n"
mov x0, #0
mov x8, #__NR_exit
svc #0
endfunction
function segv_handler
// stash the siginfo_t *
mov x20, x1
// Disable GCS, we don't want additional faults logging things
mov x0, PR_SET_SHADOW_STACK_STATUS
mov x1, xzr
mov x2, xzr
mov x3, xzr
mov x4, xzr
mov x5, xzr
mov x8, #__NR_prctl
svc #0
puts "Got SIGSEGV code "
ldr x21, [x20, #si_code]
mov x0, x21
bl putdec
// GCS faults should have si_code SEGV_CPERR
cmp x21, #SEGV_CPERR
bne 1f
puts " (GCS violation)"
1:
mov x0, '\n'
bl putc
b abort
endfunction
// Recurse x20 times
.macro recurse id
function recurse\id
stp x29, x30, [sp, #-16]!
mov x29, sp
cmp x20, 0
beq 1f
sub x20, x20, 1
bl recurse\id
1:
ldp x29, x30, [sp], #16
// Do a syscall immediately prior to returning to try to provoke
// scheduling and migration at a point where coherency issues
// might trigger.
mov x8, #__NR_getpid
svc #0
ret
endfunction
.endm
// Generate and use two copies so we're changing the GCS contents
recurse 1
recurse 2
.globl _start
function _start
// Run with GCS
mov x0, PR_SET_SHADOW_STACK_STATUS
mov x1, PR_SHADOW_STACK_ENABLE
mov x2, xzr
mov x3, xzr
mov x4, xzr
mov x5, xzr
mov x8, #__NR_prctl
svc #0
cbz x0, 1f
puts "Failed to enable GCS\n"
b abort
1:
mov w0, #SIGTERM
adr x1, terminate_handler
mov w2, #SA_SIGINFO
bl setsignal
mov w0, #SIGUSR1
adr x1, tickle_handler
mov w2, #SA_SIGINFO
orr w2, w2, #SA_NODEFER
bl setsignal
mov w0, #SIGSEGV
adr x1, segv_handler
mov w2, #SA_SIGINFO
orr w2, w2, #SA_NODEFER
bl setsignal
puts "Running\n"
loop:
// Small recursion depth so we're frequently flipping between
// the two recursors and changing what's on the stack
mov x20, #5
bl recurse1
mov x20, #5
bl recurse2
b loop
endfunction
abort:
mov x0, #255
mov x8, #__NR_exit
svc #0
@@ -0,0 +1,530 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2022-3 ARM Limited.
*/
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 199309L
#include <errno.h>
#include <getopt.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/auxv.h>
#include <sys/epoll.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <asm/hwcap.h>
#include "kselftest.h"
struct child_data {
char *name, *output;
pid_t pid;
int stdout;
bool output_seen;
bool exited;
int exit_status;
int exit_signal;
};
static int epoll_fd;
static struct child_data *children;
static struct epoll_event *evs;
static int tests;
static int num_children;
static bool terminate;
static int startup_pipe[2];
static int num_processors(void)
{
long nproc = sysconf(_SC_NPROCESSORS_CONF);
if (nproc < 0) {
perror("Unable to read number of processors\n");
exit(EXIT_FAILURE);
}
return nproc;
}
static void start_thread(struct child_data *child, int id)
{
int ret, pipefd[2], i;
struct epoll_event ev;
ret = pipe(pipefd);
if (ret != 0)
ksft_exit_fail_msg("Failed to create stdout pipe: %s (%d)\n",
strerror(errno), errno);
child->pid = fork();
if (child->pid == -1)
ksft_exit_fail_msg("fork() failed: %s (%d)\n",
strerror(errno), errno);
if (!child->pid) {
/*
* In child, replace stdout with the pipe, errors to
* stderr from here as kselftest prints to stdout.
*/
ret = dup2(pipefd[1], 1);
if (ret == -1) {
fprintf(stderr, "dup2() %d\n", errno);
exit(EXIT_FAILURE);
}
/*
* Duplicate the read side of the startup pipe to
* FD 3 so we can close everything else.
*/
ret = dup2(startup_pipe[0], 3);
if (ret == -1) {
fprintf(stderr, "dup2() %d\n", errno);
exit(EXIT_FAILURE);
}
/*
* Very dumb mechanism to clean open FDs other than
* stdio. We don't want O_CLOEXEC for the pipes...
*/
for (i = 4; i < 8192; i++)
close(i);
/*
* Read from the startup pipe, there should be no data
* and we should block until it is closed. We just
* carry on on error since this isn't super critical.
*/
ret = read(3, &i, sizeof(i));
if (ret < 0)
fprintf(stderr, "read(startp pipe) failed: %s (%d)\n",
strerror(errno), errno);
if (ret > 0)
fprintf(stderr, "%d bytes of data on startup pipe\n",
ret);
close(3);
ret = execl("gcs-stress-thread", "gcs-stress-thread", NULL);
fprintf(stderr, "execl(gcs-stress-thread) failed: %d (%s)\n",
errno, strerror(errno));
exit(EXIT_FAILURE);
} else {
/*
* In parent, remember the child and close our copy of the
* write side of stdout.
*/
close(pipefd[1]);
child->stdout = pipefd[0];
child->output = NULL;
child->exited = false;
child->output_seen = false;
ev.events = EPOLLIN | EPOLLHUP;
ev.data.ptr = child;
ret = asprintf(&child->name, "Thread-%d", id);
if (ret == -1)
ksft_exit_fail_msg("asprintf() failed\n");
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, child->stdout, &ev);
if (ret < 0) {
ksft_exit_fail_msg("%s EPOLL_CTL_ADD failed: %s (%d)\n",
child->name, strerror(errno), errno);
}
}
ksft_print_msg("Started %s\n", child->name);
num_children++;
}
static bool child_output_read(struct child_data *child)
{
char read_data[1024];
char work[1024];
int ret, len, cur_work, cur_read;
ret = read(child->stdout, read_data, sizeof(read_data));
if (ret < 0) {
if (errno == EINTR)
return true;
ksft_print_msg("%s: read() failed: %s (%d)\n",
child->name, strerror(errno),
errno);
return false;
}
len = ret;
child->output_seen = true;
/* Pick up any partial read */
if (child->output) {
strncpy(work, child->output, sizeof(work) - 1);
cur_work = strnlen(work, sizeof(work));
free(child->output);
child->output = NULL;
} else {
cur_work = 0;
}
cur_read = 0;
while (cur_read < len) {
work[cur_work] = read_data[cur_read++];
if (work[cur_work] == '\n') {
work[cur_work] = '\0';
ksft_print_msg("%s: %s\n", child->name, work);
cur_work = 0;
} else {
cur_work++;
}
}
if (cur_work) {
work[cur_work] = '\0';
ret = asprintf(&child->output, "%s", work);
if (ret == -1)
ksft_exit_fail_msg("Out of memory\n");
}
return false;
}
static void child_output(struct child_data *child, uint32_t events,
bool flush)
{
bool read_more;
if (events & EPOLLIN) {
do {
read_more = child_output_read(child);
} while (read_more);
}
if (events & EPOLLHUP) {
close(child->stdout);
child->stdout = -1;
flush = true;
}
if (flush && child->output) {
ksft_print_msg("%s: %s<EOF>\n", child->name, child->output);
free(child->output);
child->output = NULL;
}
}
static void child_tickle(struct child_data *child)
{
if (child->output_seen && !child->exited)
kill(child->pid, SIGUSR1);
}
static void child_stop(struct child_data *child)
{
if (!child->exited)
kill(child->pid, SIGTERM);
}
static void child_cleanup(struct child_data *child)
{
pid_t ret;
int status;
bool fail = false;
if (!child->exited) {
do {
ret = waitpid(child->pid, &status, 0);
if (ret == -1 && errno == EINTR)
continue;
if (ret == -1) {
ksft_print_msg("waitpid(%d) failed: %s (%d)\n",
child->pid, strerror(errno),
errno);
fail = true;
break;
}
if (WIFEXITED(status)) {
child->exit_status = WEXITSTATUS(status);
child->exited = true;
}
if (WIFSIGNALED(status)) {
child->exit_signal = WTERMSIG(status);
ksft_print_msg("%s: Exited due to signal %d\n",
child->name, child->exit_signal);
fail = true;
child->exited = true;
}
} while (!child->exited);
}
if (!child->output_seen) {
ksft_print_msg("%s no output seen\n", child->name);
fail = true;
}
if (child->exit_status != 0) {
ksft_print_msg("%s exited with error code %d\n",
child->name, child->exit_status);
fail = true;
}
ksft_test_result(!fail, "%s\n", child->name);
}
static void handle_child_signal(int sig, siginfo_t *info, void *context)
{
int i;
bool found = false;
for (i = 0; i < num_children; i++) {
if (children[i].pid == info->si_pid) {
children[i].exited = true;
children[i].exit_status = info->si_status;
found = true;
break;
}
}
if (!found)
ksft_print_msg("SIGCHLD for unknown PID %d with status %d\n",
info->si_pid, info->si_status);
}
static void handle_exit_signal(int sig, siginfo_t *info, void *context)
{
int i;
/* If we're already exiting then don't signal again */
if (terminate)
return;
ksft_print_msg("Got signal, exiting...\n");
terminate = true;
/*
* This should be redundant, the main loop should clean up
* after us, but for safety stop everything we can here.
*/
for (i = 0; i < num_children; i++)
child_stop(&children[i]);
}
/* Handle any pending output without blocking */
static void drain_output(bool flush)
{
int ret = 1;
int i;
while (ret > 0) {
ret = epoll_wait(epoll_fd, evs, tests, 0);
if (ret < 0) {
if (errno == EINTR)
continue;
ksft_print_msg("epoll_wait() failed: %s (%d)\n",
strerror(errno), errno);
}
for (i = 0; i < ret; i++)
child_output(evs[i].data.ptr, evs[i].events, flush);
}
}
static const struct option options[] = {
{ "timeout", required_argument, NULL, 't' },
{ }
};
int main(int argc, char **argv)
{
int seen_children;
bool all_children_started = false;
int gcs_threads;
int timeout = 10;
int ret, cpus, i, c;
struct sigaction sa;
while ((c = getopt_long(argc, argv, "t:", options, NULL)) != -1) {
switch (c) {
case 't':
ret = sscanf(optarg, "%d", &timeout);
if (ret != 1)
ksft_exit_fail_msg("Failed to parse timeout %s\n",
optarg);
break;
default:
ksft_exit_fail_msg("Unknown argument\n");
}
}
cpus = num_processors();
tests = 0;
if (getauxval(AT_HWCAP) & HWCAP_GCS) {
/* One extra thread, trying to trigger migrations */
gcs_threads = cpus + 1;
tests += gcs_threads;
} else {
gcs_threads = 0;
}
ksft_print_header();
ksft_set_plan(tests);
ksft_print_msg("%d CPUs, %d GCS threads\n",
cpus, gcs_threads);
if (!tests)
ksft_exit_skip("No tests scheduled\n");
if (timeout > 0)
ksft_print_msg("Will run for %ds\n", timeout);
else
ksft_print_msg("Will run until terminated\n");
children = calloc(sizeof(*children), tests);
if (!children)
ksft_exit_fail_msg("Unable to allocate child data\n");
ret = epoll_create1(EPOLL_CLOEXEC);
if (ret < 0)
ksft_exit_fail_msg("epoll_create1() failed: %s (%d)\n",
strerror(errno), ret);
epoll_fd = ret;
/* Create a pipe which children will block on before execing */
ret = pipe(startup_pipe);
if (ret != 0)
ksft_exit_fail_msg("Failed to create startup pipe: %s (%d)\n",
strerror(errno), errno);
/* Get signal handers ready before we start any children */
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = handle_exit_signal;
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sigemptyset(&sa.sa_mask);
ret = sigaction(SIGINT, &sa, NULL);
if (ret < 0)
ksft_print_msg("Failed to install SIGINT handler: %s (%d)\n",
strerror(errno), errno);
ret = sigaction(SIGTERM, &sa, NULL);
if (ret < 0)
ksft_print_msg("Failed to install SIGTERM handler: %s (%d)\n",
strerror(errno), errno);
sa.sa_sigaction = handle_child_signal;
ret = sigaction(SIGCHLD, &sa, NULL);
if (ret < 0)
ksft_print_msg("Failed to install SIGCHLD handler: %s (%d)\n",
strerror(errno), errno);
evs = calloc(tests, sizeof(*evs));
if (!evs)
ksft_exit_fail_msg("Failed to allocate %d epoll events\n",
tests);
for (i = 0; i < gcs_threads; i++)
start_thread(&children[i], i);
/*
* All children started, close the startup pipe and let them
* run.
*/
close(startup_pipe[0]);
close(startup_pipe[1]);
timeout *= 10;
for (;;) {
/* Did we get a signal asking us to exit? */
if (terminate)
break;
/*
* Timeout is counted in 100ms with no output, the
* tests print during startup then are silent when
* running so this should ensure they all ran enough
* to install the signal handler, this is especially
* useful in emulation where we will both be slow and
* likely to have a large set of VLs.
*/
ret = epoll_wait(epoll_fd, evs, tests, 100);
if (ret < 0) {
if (errno == EINTR)
continue;
ksft_exit_fail_msg("epoll_wait() failed: %s (%d)\n",
strerror(errno), errno);
}
/* Output? */
if (ret > 0) {
for (i = 0; i < ret; i++) {
child_output(evs[i].data.ptr, evs[i].events,
false);
}
continue;
}
/* Otherwise epoll_wait() timed out */
/*
* If the child processes have not produced output they
* aren't actually running the tests yet.
*/
if (!all_children_started) {
seen_children = 0;
for (i = 0; i < num_children; i++)
if (children[i].output_seen ||
children[i].exited)
seen_children++;
if (seen_children != num_children) {
ksft_print_msg("Waiting for %d children\n",
num_children - seen_children);
continue;
}
all_children_started = true;
}
ksft_print_msg("Sending signals, timeout remaining: %d00ms\n",
timeout);
for (i = 0; i < num_children; i++)
child_tickle(&children[i]);
/* Negative timeout means run indefinitely */
if (timeout < 0)
continue;
if (--timeout == 0)
break;
}
ksft_print_msg("Finishing up...\n");
terminate = true;
for (i = 0; i < tests; i++)
child_stop(&children[i]);
drain_output(false);
for (i = 0; i < tests; i++)
child_cleanup(&children[i]);
drain_output(true);
ksft_finished();
}
@@ -0,0 +1,94 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2023 ARM Limited.
*/
#ifndef GCS_UTIL_H
#define GCS_UTIL_H
#include <stdbool.h>
#ifndef __NR_map_shadow_stack
#define __NR_map_shadow_stack 453
#endif
#ifndef __NR_prctl
#define __NR_prctl 167
#endif
#ifndef NT_ARM_GCS
#define NT_ARM_GCS 0x410
#endif
/* Shadow Stack/Guarded Control Stack interface */
#define PR_GET_SHADOW_STACK_STATUS 74
#define PR_SET_SHADOW_STACK_STATUS 75
#define PR_LOCK_SHADOW_STACK_STATUS 76
# define PR_SHADOW_STACK_ENABLE (1UL << 0)
# define PR_SHADOW_STACK_WRITE (1UL << 1)
# define PR_SHADOW_STACK_PUSH (1UL << 2)
#define PR_SHADOW_STACK_ALL_MODES \
PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE | PR_SHADOW_STACK_PUSH
#define SHADOW_STACK_SET_TOKEN (1ULL << 0) /* Set up a restore token in the shadow stack */
#define SHADOW_STACK_SET_MARKER (1ULL << 1) /* Set up a top of stack merker in the shadow stack */
#define GCS_CAP_ADDR_MASK (0xfffffffffffff000UL)
#define GCS_CAP_TOKEN_MASK (0x0000000000000fffUL)
#define GCS_CAP_VALID_TOKEN 1
#define GCS_CAP_IN_PROGRESS_TOKEN 5
#define GCS_CAP(x) (((unsigned long)(x) & GCS_CAP_ADDR_MASK) | \
GCS_CAP_VALID_TOKEN)
static inline unsigned long *get_gcspr(void)
{
unsigned long *gcspr;
asm volatile(
"mrs %0, S3_3_C2_C5_1"
: "=r" (gcspr)
:
: "cc");
return gcspr;
}
static inline void __attribute__((always_inline)) gcsss1(unsigned long *Xt)
{
asm volatile (
"sys #3, C7, C7, #2, %0\n"
:
: "rZ" (Xt)
: "memory");
}
static inline unsigned long __attribute__((always_inline)) *gcsss2(void)
{
unsigned long *Xt;
asm volatile(
"SYSL %0, #3, C7, C7, #3\n"
: "=r" (Xt)
:
: "memory");
return Xt;
}
static inline bool chkfeat_gcs(void)
{
register long val __asm__ ("x16") = 1;
/* CHKFEAT x16 */
asm volatile(
"hint #0x28\n"
: "=r" (val)
: "r" (val));
return val != 1;
}
#endif
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright 2024 Arm Limited
//
// Give ourselves GCS push permissions then use them
#include <asm/unistd.h>
/* Shadow Stack/Guarded Control Stack interface */
#define PR_GET_SHADOW_STACK_STATUS 74
#define PR_SET_SHADOW_STACK_STATUS 75
#define PR_LOCK_SHADOW_STACK_STATUS 76
# define PR_SHADOW_STACK_ENABLE (1UL << 0)
# define PR_SHADOW_STACK_WRITE (1UL << 1)
# define PR_SHADOW_STACK_PUSH (1UL << 2)
#define KSFT_SKIP 4
.macro function name
.macro endfunction
.type \name, @function
.purgem endfunction
.endm
\name:
.endm
// Print a single character x0 to stdout
// Clobbers x0-x2,x8
function putc
str x0, [sp, #-16]!
mov x0, #1 // STDOUT_FILENO
mov x1, sp
mov x2, #1
mov x8, #__NR_write
svc #0
add sp, sp, #16
ret
endfunction
.globl putc
// Print a NUL-terminated string starting at address x0 to stdout
// Clobbers x0-x3,x8
function puts
mov x1, x0
mov x2, #0
0: ldrb w3, [x0], #1
cbz w3, 1f
add x2, x2, #1
b 0b
1: mov w0, #1 // STDOUT_FILENO
mov x8, #__NR_write
svc #0
ret
endfunction
.globl puts
// Utility macro to print a literal string
// Clobbers x0-x4,x8
.macro puts string
.pushsection .rodata.str1.1, "aMS", @progbits, 1
.L__puts_literal\@: .string "\string"
.popsection
ldr x0, =.L__puts_literal\@
bl puts
.endm
.globl _start
function _start
// Run with GCS
mov x0, PR_SET_SHADOW_STACK_STATUS
mov x1, PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH
mov x2, xzr
mov x3, xzr
mov x4, xzr
mov x5, xzr
mov x8, #__NR_prctl
svc #0
cbz x0, 1f
puts "Failed to enable GCS with push permission\n"
mov x0, #KSFT_SKIP
b 2f
1:
sys #3, c7, c7, #0, x0 // GCSPUSHM
sysl x0, #3, c7, c7, #1 // GCSPOPM
mov x0, #0
2:
mov x8, #__NR_exit
svc #0
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright 2024 Arm Limited
//
// Give ourselves GCS write permissions then use them
#include <asm/unistd.h>
/* Shadow Stack/Guarded Control Stack interface */
#define PR_GET_SHADOW_STACK_STATUS 74
#define PR_SET_SHADOW_STACK_STATUS 75
#define PR_LOCK_SHADOW_STACK_STATUS 76
# define PR_SHADOW_STACK_ENABLE (1UL << 0)
# define PR_SHADOW_STACK_WRITE (1UL << 1)
# define PR_SHADOW_STACK_PUSH (1UL << 2)
#define GCSPR_EL0 S3_3_C2_C5_1
#define KSFT_SKIP 4
.macro function name
.macro endfunction
.type \name, @function
.purgem endfunction
.endm
\name:
.endm
// Print a single character x0 to stdout
// Clobbers x0-x2,x8
function putc
str x0, [sp, #-16]!
mov x0, #1 // STDOUT_FILENO
mov x1, sp
mov x2, #1
mov x8, #__NR_write
svc #0
add sp, sp, #16
ret
endfunction
.globl putc
// Print a NUL-terminated string starting at address x0 to stdout
// Clobbers x0-x3,x8
function puts
mov x1, x0
mov x2, #0
0: ldrb w3, [x0], #1
cbz w3, 1f
add x2, x2, #1
b 0b
1: mov w0, #1 // STDOUT_FILENO
mov x8, #__NR_write
svc #0
ret
endfunction
.globl puts
// Utility macro to print a literal string
// Clobbers x0-x4,x8
.macro puts string
.pushsection .rodata.str1.1, "aMS", @progbits, 1
.L__puts_literal\@: .string "\string"
.popsection
ldr x0, =.L__puts_literal\@
bl puts
.endm
.globl _start
function _start
// Run with GCS
mov x0, PR_SET_SHADOW_STACK_STATUS
mov x1, PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE
mov x2, xzr
mov x3, xzr
mov x4, xzr
mov x5, xzr
mov x8, #__NR_prctl
svc #0
cbz x0, 1f
puts "Failed to enable GCS with write permission\n"
mov x0, #KSFT_SKIP
b 2f
1:
mrs x0, GCSPR_EL0
sub x0, x0, #8
.inst 0xd91f1c01 // GCSSTR x1, x0
mov x0, #0
2:
mov x8, #__NR_exit
svc #0
@@ -0,0 +1,729 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2023 ARM Limited.
*/
#define _GNU_SOURCE
#include <pthread.h>
#include <stdbool.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/uio.h>
#include <asm/hwcap.h>
#include <asm/mman.h>
#include <asm/ptrace.h>
#include <linux/compiler.h>
#include "kselftest_harness.h"
#include "gcs-util.h"
#define my_syscall2(num, arg1, arg2) \
({ \
register long _num __asm__ ("x8") = (num); \
register long _arg1 __asm__ ("x0") = (long)(arg1); \
register long _arg2 __asm__ ("x1") = (long)(arg2); \
register long _arg3 __asm__ ("x2") = 0; \
register long _arg4 __asm__ ("x3") = 0; \
register long _arg5 __asm__ ("x4") = 0; \
\
__asm__ volatile ( \
"svc #0\n" \
: "=r"(_arg1) \
: "r"(_arg1), "r"(_arg2), \
"r"(_arg3), "r"(_arg4), \
"r"(_arg5), "r"(_num) \
: "memory", "cc" \
); \
_arg1; \
})
static noinline void gcs_recurse(int depth)
{
if (depth)
gcs_recurse(depth - 1);
/* Prevent tail call optimization so we actually recurse */
asm volatile("dsb sy" : : : "memory");
}
/* Smoke test that a function call and return works*/
TEST(can_call_function)
{
gcs_recurse(0);
}
static void *gcs_test_thread(void *arg)
{
int ret;
unsigned long mode;
/*
* Some libcs don't seem to fill unused arguments with 0 but
* the kernel validates this so we supply all 5 arguments.
*/
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
if (ret != 0) {
ksft_print_msg("PR_GET_SHADOW_STACK_STATUS failed: %d\n", ret);
return NULL;
}
if (!(mode & PR_SHADOW_STACK_ENABLE)) {
ksft_print_msg("GCS not enabled in thread, mode is %lu\n",
mode);
return NULL;
}
/* Just in case... */
gcs_recurse(0);
/* Use a non-NULL value to indicate a pass */
return &gcs_test_thread;
}
/* Verify that if we start a new thread it has GCS enabled */
TEST(gcs_enabled_thread)
{
pthread_t thread;
void *thread_ret;
int ret;
ret = pthread_create(&thread, NULL, gcs_test_thread, NULL);
ASSERT_TRUE(ret == 0);
if (ret != 0)
return;
ret = pthread_join(thread, &thread_ret);
ASSERT_TRUE(ret == 0);
if (ret != 0)
return;
ASSERT_TRUE(thread_ret != NULL);
}
/* Read the GCS until we find the terminator */
TEST(gcs_find_terminator)
{
unsigned long *gcs, *cur;
gcs = get_gcspr();
cur = gcs;
while (*cur)
cur++;
ksft_print_msg("GCS in use from %p-%p\n", gcs, cur);
/*
* We should have at least whatever called into this test so
* the two pointer should differ.
*/
ASSERT_TRUE(gcs != cur);
}
/*
* We can access a GCS via ptrace
*
* This could usefully have a fixture but note that each test is
* fork()ed into a new child which causes issues. Might be better to
* lift at least some of this out into a separate, non-harness, test
* program.
*/
TEST(ptrace_read_write)
{
pid_t child, pid;
int ret, status;
siginfo_t si;
uint64_t val, rval, gcspr;
struct user_gcs child_gcs;
struct iovec iov, local_iov, remote_iov;
child = fork();
if (child == -1) {
ksft_print_msg("fork() failed: %d (%s)\n",
errno, strerror(errno));
ASSERT_NE(child, -1);
}
if (child == 0) {
/*
* In child, make sure there's something on the stack and
* ask to be traced.
*/
gcs_recurse(0);
if (ptrace(PTRACE_TRACEME, -1, NULL, NULL))
ksft_exit_fail_msg("PTRACE_TRACEME %s",
strerror(errno));
if (raise(SIGSTOP))
ksft_exit_fail_msg("raise(SIGSTOP) %s",
strerror(errno));
return;
}
ksft_print_msg("Child: %d\n", child);
/* Attach to the child */
while (1) {
int sig;
pid = wait(&status);
if (pid == -1) {
ksft_print_msg("wait() failed: %s",
strerror(errno));
goto error;
}
/*
* This should never happen but it's hard to flag in
* the framework.
*/
if (pid != child)
continue;
if (WIFEXITED(status) || WIFSIGNALED(status))
ksft_exit_fail_msg("Child died unexpectedly\n");
if (!WIFSTOPPED(status))
goto error;
sig = WSTOPSIG(status);
if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) {
if (errno == ESRCH) {
ASSERT_NE(errno, ESRCH);
return;
}
if (errno == EINVAL) {
sig = 0; /* bust group-stop */
goto cont;
}
ksft_print_msg("PTRACE_GETSIGINFO: %s\n",
strerror(errno));
goto error;
}
if (sig == SIGSTOP && si.si_code == SI_TKILL &&
si.si_pid == pid)
break;
cont:
if (ptrace(PTRACE_CONT, pid, NULL, sig)) {
if (errno == ESRCH) {
ASSERT_NE(errno, ESRCH);
return;
}
ksft_print_msg("PTRACE_CONT: %s\n", strerror(errno));
goto error;
}
}
/* Where is the child GCS? */
iov.iov_base = &child_gcs;
iov.iov_len = sizeof(child_gcs);
ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_GCS, &iov);
if (ret != 0) {
ksft_print_msg("Failed to read child GCS state: %s (%d)\n",
strerror(errno), errno);
goto error;
}
/* We should have inherited GCS over fork(), confirm */
if (!(child_gcs.features_enabled & PR_SHADOW_STACK_ENABLE)) {
ASSERT_TRUE(child_gcs.features_enabled &
PR_SHADOW_STACK_ENABLE);
goto error;
}
gcspr = child_gcs.gcspr_el0;
ksft_print_msg("Child GCSPR 0x%lx, flags %llx, locked %llx\n",
gcspr, child_gcs.features_enabled,
child_gcs.features_locked);
/* Ideally we'd cross check with the child memory map */
errno = 0;
val = ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL);
ret = errno;
if (ret != 0)
ksft_print_msg("PTRACE_PEEKDATA failed: %s (%d)\n",
strerror(ret), ret);
EXPECT_EQ(ret, 0);
/* The child should be in a function, the GCSPR shouldn't be 0 */
EXPECT_NE(val, 0);
/* Same thing via process_vm_readv() */
local_iov.iov_base = &rval;
local_iov.iov_len = sizeof(rval);
remote_iov.iov_base = (void *)gcspr;
remote_iov.iov_len = sizeof(rval);
ret = process_vm_readv(child, &local_iov, 1, &remote_iov, 1, 0);
if (ret == -1)
ksft_print_msg("process_vm_readv() failed: %s (%d)\n",
strerror(errno), errno);
EXPECT_EQ(ret, sizeof(rval));
EXPECT_EQ(val, rval);
/* Write data via a peek */
ret = ptrace(PTRACE_POKEDATA, child, (void *)gcspr, NULL);
if (ret == -1)
ksft_print_msg("PTRACE_POKEDATA failed: %s (%d)\n",
strerror(errno), errno);
EXPECT_EQ(ret, 0);
EXPECT_EQ(0, ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL));
/* Restore what we had before */
ret = ptrace(PTRACE_POKEDATA, child, (void *)gcspr, val);
if (ret == -1)
ksft_print_msg("PTRACE_POKEDATA failed: %s (%d)\n",
strerror(errno), errno);
EXPECT_EQ(ret, 0);
EXPECT_EQ(val, ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL));
/* That's all, folks */
kill(child, SIGKILL);
return;
error:
kill(child, SIGKILL);
ASSERT_FALSE(true);
}
FIXTURE(map_gcs)
{
unsigned long *stack;
};
FIXTURE_VARIANT(map_gcs)
{
size_t stack_size;
unsigned long flags;
};
FIXTURE_VARIANT_ADD(map_gcs, s2k_cap_marker)
{
.stack_size = 2 * 1024,
.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
};
FIXTURE_VARIANT_ADD(map_gcs, s2k_cap)
{
.stack_size = 2 * 1024,
.flags = SHADOW_STACK_SET_TOKEN,
};
FIXTURE_VARIANT_ADD(map_gcs, s2k_marker)
{
.stack_size = 2 * 1024,
.flags = SHADOW_STACK_SET_MARKER,
};
FIXTURE_VARIANT_ADD(map_gcs, s2k)
{
.stack_size = 2 * 1024,
.flags = 0,
};
FIXTURE_VARIANT_ADD(map_gcs, s4k_cap_marker)
{
.stack_size = 4 * 1024,
.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
};
FIXTURE_VARIANT_ADD(map_gcs, s4k_cap)
{
.stack_size = 4 * 1024,
.flags = SHADOW_STACK_SET_TOKEN,
};
FIXTURE_VARIANT_ADD(map_gcs, s3k_marker)
{
.stack_size = 4 * 1024,
.flags = SHADOW_STACK_SET_MARKER,
};
FIXTURE_VARIANT_ADD(map_gcs, s4k)
{
.stack_size = 4 * 1024,
.flags = 0,
};
FIXTURE_VARIANT_ADD(map_gcs, s16k_cap_marker)
{
.stack_size = 16 * 1024,
.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
};
FIXTURE_VARIANT_ADD(map_gcs, s16k_cap)
{
.stack_size = 16 * 1024,
.flags = SHADOW_STACK_SET_TOKEN,
};
FIXTURE_VARIANT_ADD(map_gcs, s16k_marker)
{
.stack_size = 16 * 1024,
.flags = SHADOW_STACK_SET_MARKER,
};
FIXTURE_VARIANT_ADD(map_gcs, s16k)
{
.stack_size = 16 * 1024,
.flags = 0,
};
FIXTURE_VARIANT_ADD(map_gcs, s64k_cap_marker)
{
.stack_size = 64 * 1024,
.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
};
FIXTURE_VARIANT_ADD(map_gcs, s64k_cap)
{
.stack_size = 64 * 1024,
.flags = SHADOW_STACK_SET_TOKEN,
};
FIXTURE_VARIANT_ADD(map_gcs, s64k_marker)
{
.stack_size = 64 * 1024,
.flags = SHADOW_STACK_SET_MARKER,
};
FIXTURE_VARIANT_ADD(map_gcs, s64k)
{
.stack_size = 64 * 1024,
.flags = 0,
};
FIXTURE_VARIANT_ADD(map_gcs, s128k_cap_marker)
{
.stack_size = 128 * 1024,
.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
};
FIXTURE_VARIANT_ADD(map_gcs, s128k_cap)
{
.stack_size = 128 * 1024,
.flags = SHADOW_STACK_SET_TOKEN,
};
FIXTURE_VARIANT_ADD(map_gcs, s128k_marker)
{
.stack_size = 128 * 1024,
.flags = SHADOW_STACK_SET_MARKER,
};
FIXTURE_VARIANT_ADD(map_gcs, s128k)
{
.stack_size = 128 * 1024,
.flags = 0,
};
FIXTURE_SETUP(map_gcs)
{
self->stack = (void *)syscall(__NR_map_shadow_stack, 0,
variant->stack_size,
variant->flags);
ASSERT_FALSE(self->stack == MAP_FAILED);
ksft_print_msg("Allocated stack from %p-%p\n", self->stack,
self->stack + variant->stack_size);
}
FIXTURE_TEARDOWN(map_gcs)
{
int ret;
if (self->stack != MAP_FAILED) {
ret = munmap(self->stack, variant->stack_size);
ASSERT_EQ(ret, 0);
}
}
/* The stack has a cap token */
TEST_F(map_gcs, stack_capped)
{
unsigned long *stack = self->stack;
size_t cap_index;
cap_index = (variant->stack_size / sizeof(unsigned long));
switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {
case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:
cap_index -= 2;
break;
case SHADOW_STACK_SET_TOKEN:
cap_index -= 1;
break;
case SHADOW_STACK_SET_MARKER:
case 0:
/* No cap, no test */
return;
}
ASSERT_EQ(stack[cap_index], GCS_CAP(&stack[cap_index]));
}
/* The top of the stack is 0 */
TEST_F(map_gcs, stack_terminated)
{
unsigned long *stack = self->stack;
size_t term_index;
if (!(variant->flags & SHADOW_STACK_SET_MARKER))
return;
term_index = (variant->stack_size / sizeof(unsigned long)) - 1;
ASSERT_EQ(stack[term_index], 0);
}
/* Writes should fault */
TEST_F_SIGNAL(map_gcs, not_writeable, SIGSEGV)
{
self->stack[0] = 0;
}
/* Put it all together, we can safely switch to and from the stack */
TEST_F(map_gcs, stack_switch)
{
size_t cap_index;
cap_index = (variant->stack_size / sizeof(unsigned long));
unsigned long *orig_gcspr_el0, *pivot_gcspr_el0;
/* Skip over the stack terminator and point at the cap */
switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {
case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:
cap_index -= 2;
break;
case SHADOW_STACK_SET_TOKEN:
cap_index -= 1;
break;
case SHADOW_STACK_SET_MARKER:
case 0:
/* No cap, no test */
return;
}
pivot_gcspr_el0 = &self->stack[cap_index];
/* Pivot to the new GCS */
ksft_print_msg("Pivoting to %p from %p, target has value 0x%lx\n",
pivot_gcspr_el0, get_gcspr(),
*pivot_gcspr_el0);
gcsss1(pivot_gcspr_el0);
orig_gcspr_el0 = gcsss2();
ksft_print_msg("Pivoted to %p from %p, target has value 0x%lx\n",
get_gcspr(), orig_gcspr_el0,
*pivot_gcspr_el0);
ksft_print_msg("Pivoted, GCSPR_EL0 now %p\n", get_gcspr());
/* New GCS must be in the new buffer */
ASSERT_TRUE((unsigned long)get_gcspr() > (unsigned long)self->stack);
ASSERT_TRUE((unsigned long)get_gcspr() <=
(unsigned long)self->stack + variant->stack_size);
/* We should be able to use all but 2 slots of the new stack */
ksft_print_msg("Recursing %zu levels\n", cap_index - 1);
gcs_recurse(cap_index - 1);
/* Pivot back to the original GCS */
gcsss1(orig_gcspr_el0);
pivot_gcspr_el0 = gcsss2();
gcs_recurse(0);
ksft_print_msg("Pivoted back to GCSPR_EL0 0x%p\n", get_gcspr());
}
/* We fault if we try to go beyond the end of the stack */
TEST_F_SIGNAL(map_gcs, stack_overflow, SIGSEGV)
{
size_t cap_index;
cap_index = (variant->stack_size / sizeof(unsigned long));
unsigned long *orig_gcspr_el0, *pivot_gcspr_el0;
/* Skip over the stack terminator and point at the cap */
switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {
case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:
cap_index -= 2;
break;
case SHADOW_STACK_SET_TOKEN:
cap_index -= 1;
break;
case SHADOW_STACK_SET_MARKER:
case 0:
/* No cap, no test but we need to SEGV to avoid a false fail */
orig_gcspr_el0 = get_gcspr();
*orig_gcspr_el0 = 0;
return;
}
pivot_gcspr_el0 = &self->stack[cap_index];
/* Pivot to the new GCS */
ksft_print_msg("Pivoting to %p from %p, target has value 0x%lx\n",
pivot_gcspr_el0, get_gcspr(),
*pivot_gcspr_el0);
gcsss1(pivot_gcspr_el0);
orig_gcspr_el0 = gcsss2();
ksft_print_msg("Pivoted to %p from %p, target has value 0x%lx\n",
pivot_gcspr_el0, orig_gcspr_el0,
*pivot_gcspr_el0);
ksft_print_msg("Pivoted, GCSPR_EL0 now %p\n", get_gcspr());
/* New GCS must be in the new buffer */
ASSERT_TRUE((unsigned long)get_gcspr() > (unsigned long)self->stack);
ASSERT_TRUE((unsigned long)get_gcspr() <=
(unsigned long)self->stack + variant->stack_size);
/* Now try to recurse, we should fault doing this. */
ksft_print_msg("Recursing %zu levels...\n", cap_index + 1);
gcs_recurse(cap_index + 1);
ksft_print_msg("...done\n");
/* Clean up properly to try to guard against spurious passes. */
gcsss1(orig_gcspr_el0);
pivot_gcspr_el0 = gcsss2();
ksft_print_msg("Pivoted back to GCSPR_EL0 0x%p\n", get_gcspr());
}
FIXTURE(map_invalid_gcs)
{
};
FIXTURE_VARIANT(map_invalid_gcs)
{
size_t stack_size;
};
FIXTURE_SETUP(map_invalid_gcs)
{
}
FIXTURE_TEARDOWN(map_invalid_gcs)
{
}
/* GCS must be larger than 16 bytes */
FIXTURE_VARIANT_ADD(map_invalid_gcs, too_small)
{
.stack_size = 8,
};
/* GCS size must be 16 byte aligned */
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_1) { .stack_size = 1024 + 1 };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_2) { .stack_size = 1024 + 2 };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_3) { .stack_size = 1024 + 3 };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_4) { .stack_size = 1024 + 4 };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_5) { .stack_size = 1024 + 5 };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_6) { .stack_size = 1024 + 6 };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_7) { .stack_size = 1024 + 7 };
TEST_F(map_invalid_gcs, do_map)
{
void *stack;
stack = (void *)syscall(__NR_map_shadow_stack, 0,
variant->stack_size, 0);
ASSERT_TRUE(stack == MAP_FAILED);
if (stack != MAP_FAILED)
munmap(stack, variant->stack_size);
}
FIXTURE(invalid_mprotect)
{
unsigned long *stack;
size_t stack_size;
};
FIXTURE_VARIANT(invalid_mprotect)
{
unsigned long flags;
};
FIXTURE_SETUP(invalid_mprotect)
{
self->stack_size = sysconf(_SC_PAGE_SIZE);
self->stack = (void *)syscall(__NR_map_shadow_stack, 0,
self->stack_size, 0);
ASSERT_FALSE(self->stack == MAP_FAILED);
ksft_print_msg("Allocated stack from %p-%p\n", self->stack,
self->stack + self->stack_size);
}
FIXTURE_TEARDOWN(invalid_mprotect)
{
int ret;
if (self->stack != MAP_FAILED) {
ret = munmap(self->stack, self->stack_size);
ASSERT_EQ(ret, 0);
}
}
FIXTURE_VARIANT_ADD(invalid_mprotect, exec)
{
.flags = PROT_EXEC,
};
TEST_F(invalid_mprotect, do_map)
{
int ret;
ret = mprotect(self->stack, self->stack_size, variant->flags);
ASSERT_EQ(ret, -1);
}
TEST_F(invalid_mprotect, do_map_read)
{
int ret;
ret = mprotect(self->stack, self->stack_size,
variant->flags | PROT_READ);
ASSERT_EQ(ret, -1);
}
int main(int argc, char **argv)
{
unsigned long gcs_mode;
int ret;
if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
ksft_exit_skip("SKIP GCS not supported\n");
/*
* Force shadow stacks on, our tests *should* be fine with or
* without libc support and with or without this having ended
* up tagged for GCS and enabled by the dynamic linker. We
* can't use the libc prctl() function since we can't return
* from enabling the stack.
*/
ret = my_syscall2(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode);
if (ret) {
ksft_print_msg("Failed to read GCS state: %d\n", ret);
return EXIT_FAILURE;
}
if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) {
gcs_mode = PR_SHADOW_STACK_ENABLE;
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
gcs_mode);
if (ret) {
ksft_print_msg("Failed to configure GCS: %d\n", ret);
return EXIT_FAILURE;
}
}
/* Avoid returning in case libc doesn't understand GCS */
exit(test_harness_run(argc, argv));
}