Message ID | 20241008-v5_user_cfi_series-v6-0-60d9fe073f37@rivosinc.com |
---|---|
Headers | show |
Series | riscv control-flow integrity for usermode | expand |
On Wed, Oct 9, 2024 at 7:46 AM Deepak Gupta <debug@rivosinc.com> wrote: > > Adds kselftest for RISC-V control flow integrity implementation for user > mode. There is not a lot going on in kernel for enabling landing pad for > user mode. cfi selftest are intended to be compiled with zicfilp and > zicfiss enabled compiler. Thus kselftest simply checks if landing pad and > shadow stack for the binary and process are enabled or not. selftest then > register a signal handler for SIGSEGV. Any control flow violation are > reported as SIGSEGV with si_code = SEGV_CPERR. Test will fail on receiving > any SEGV_CPERR. Shadow stack part has more changes in kernel and thus there > are separate tests for that > > - Exercise `map_shadow_stack` syscall > - `fork` test to make sure COW works for shadow stack pages > - gup tests > Kernel uses FOLL_FORCE when access happens to memory via > /proc/<pid>/mem. Not breaking that for shadow stack. > - signal test. Make sure signal delivery results in token creation on > shadow stack and consumes (and verifies) token on sigreturn > - shadow stack protection test. attempts to write using regular store > instruction on shadow stack memory must result in access faults > > Test outut > ========== > > """ > TAP version 13 > 1..5 > This is to ensure shadow stack is indeed enabled and working > This is to ensure shadow stack is indeed enabled and working > ok 1 shstk fork test > ok 2 map shadow stack syscall > ok 3 shadow stack gup tests > ok 4 shadow stack signal tests > ok 5 memory protections of shadow stack memory > """ > > Signed-off-by: Deepak Gupta <debug@rivosinc.com> > --- > tools/testing/selftests/riscv/Makefile | 2 +- > tools/testing/selftests/riscv/cfi/.gitignore | 3 + > tools/testing/selftests/riscv/cfi/Makefile | 10 + > tools/testing/selftests/riscv/cfi/cfi_rv_test.h | 84 +++++ > tools/testing/selftests/riscv/cfi/riscv_cfi_test.c | 78 +++++ > tools/testing/selftests/riscv/cfi/shadowstack.c | 373 +++++++++++++++++++++ > tools/testing/selftests/riscv/cfi/shadowstack.h | 37 ++ > 7 files changed, 586 insertions(+), 1 deletion(-) > > diff --git a/tools/testing/selftests/riscv/Makefile b/tools/testing/selftests/riscv/Makefile > index 7ce03d832b64..6e142fe004ab 100644 > --- a/tools/testing/selftests/riscv/Makefile > +++ b/tools/testing/selftests/riscv/Makefile > @@ -5,7 +5,7 @@ > ARCH ?= $(shell uname -m 2>/dev/null || echo not) > > ifneq (,$(filter $(ARCH),riscv)) > -RISCV_SUBTARGETS ?= hwprobe vector mm sigreturn > +RISCV_SUBTARGETS ?= hwprobe vector mm sigreturn cfi > else > RISCV_SUBTARGETS := > endif > diff --git a/tools/testing/selftests/riscv/cfi/.gitignore b/tools/testing/selftests/riscv/cfi/.gitignore > new file mode 100644 > index 000000000000..82545863bac6 > --- /dev/null > +++ b/tools/testing/selftests/riscv/cfi/.gitignore > @@ -0,0 +1,3 @@ > +cfitests > +riscv_cfi_test > +shadowstack > diff --git a/tools/testing/selftests/riscv/cfi/Makefile b/tools/testing/selftests/riscv/cfi/Makefile > new file mode 100644 > index 000000000000..b65f7ff38a32 > --- /dev/null > +++ b/tools/testing/selftests/riscv/cfi/Makefile > @@ -0,0 +1,10 @@ > +CFLAGS += -I$(top_srcdir)/tools/include > + > +CFLAGS += -march=rv64gc_zicfilp_zicfiss > + > +TEST_GEN_PROGS := cfitests > + > +include ../../lib.mk > + > +$(OUTPUT)/cfitests: riscv_cfi_test.c shadowstack.c > + $(CC) -o$@ $(CFLAGS) $(LDFLAGS) $^ > diff --git a/tools/testing/selftests/riscv/cfi/cfi_rv_test.h b/tools/testing/selftests/riscv/cfi/cfi_rv_test.h > new file mode 100644 > index 000000000000..0fefdc33f71e > --- /dev/null > +++ b/tools/testing/selftests/riscv/cfi/cfi_rv_test.h > @@ -0,0 +1,84 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > + > +#ifndef SELFTEST_RISCV_CFI_H > +#define SELFTEST_RISCV_CFI_H > +#include <stddef.h> > +#include <sys/types.h> > +#include "shadowstack.h" > + > +#define RISCV_CFI_SELFTEST_COUNT RISCV_SHADOW_STACK_TESTS > + > +#define CHILD_EXIT_CODE_SSWRITE 10 > +#define CHILD_EXIT_CODE_SIG_TEST 11 > + > +#define my_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ > +({ \ > + register long _num __asm__ ("a7") = (num); \ > + register long _arg1 __asm__ ("a0") = (long)(arg1); \ > + register long _arg2 __asm__ ("a1") = (long)(arg2); \ > + register long _arg3 __asm__ ("a2") = (long)(arg3); \ > + register long _arg4 __asm__ ("a3") = (long)(arg4); \ > + register long _arg5 __asm__ ("a4") = (long)(arg5); \ > + \ > + __asm__ volatile( \ > + "ecall\n" \ > + : "+r" \ > + (_arg1) \ > + : "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ > + "r"(_num) \ > + : "memory", "cc" \ > + ); \ > + _arg1; \ > +}) > + > +#define my_syscall3(num, arg1, arg2, arg3) \ > +({ \ > + register long _num __asm__ ("a7") = (num); \ > + register long _arg1 __asm__ ("a0") = (long)(arg1); \ > + register long _arg2 __asm__ ("a1") = (long)(arg2); \ > + register long _arg3 __asm__ ("a2") = (long)(arg3); \ > + \ > + __asm__ volatile( \ > + "ecall\n" \ > + : "+r" (_arg1) \ > + : "r"(_arg2), "r"(_arg3), \ > + "r"(_num) \ > + : "memory", "cc" \ > + ); \ > + _arg1; \ > +}) > + > +#ifndef __NR_prctl > +#define __NR_prctl 167 > +#endif > + > +#ifndef __NR_map_shadow_stack > +#define __NR_map_shadow_stack 453 > +#endif > + > +#define CSR_SSP 0x011 > + > +#ifdef __ASSEMBLY__ > +#define __ASM_STR(x) x > +#else > +#define __ASM_STR(x) #x > +#endif > + > +#define csr_read(csr) \ > +({ \ > + register unsigned long __v; \ > + __asm__ __volatile__ ("csrr %0, " __ASM_STR(csr) \ > + : "=r" (__v) : \ > + : "memory"); \ > + __v; \ > +}) > + > +#define csr_write(csr, val) \ > +({ \ > + unsigned long __v = (unsigned long) (val); \ > + __asm__ __volatile__ ("csrw " __ASM_STR(csr) ", %0" \ > + : : "rK" (__v) \ > + : "memory"); \ > +}) > + > +#endif > diff --git a/tools/testing/selftests/riscv/cfi/riscv_cfi_test.c b/tools/testing/selftests/riscv/cfi/riscv_cfi_test.c > new file mode 100644 > index 000000000000..720a001f7c31 > --- /dev/null > +++ b/tools/testing/selftests/riscv/cfi/riscv_cfi_test.c > @@ -0,0 +1,78 @@ > +// SPDX-License-Identifier: GPL-2.0-only > + > +#include "../../kselftest.h" > +#include <signal.h> > +#include <asm/ucontext.h> > +#include <linux/prctl.h> > +#include "cfi_rv_test.h" > + > +/* do not optimize cfi related test functions */ > +#pragma GCC push_options > +#pragma GCC optimize("O0") > + > +void sigsegv_handler(int signum, siginfo_t *si, void *uc) > +{ > + struct ucontext *ctx = (struct ucontext *) uc; > + > + if (si->si_code == SEGV_CPERR) { Hi Deepak, I got some errors when building this test, I suppose they should be fixed in the next version. riscv_cfi_test.c: In function 'sigsegv_handler': riscv_cfi_test.c:17:28: error: 'SEGV_CPERR' undeclared (first use in this function); did you mean 'SEGV_ACCERR'? 17 | if (si->si_code == SEGV_CPERR) { | ^~~~~~~~~~ | SEGV_ACCERR > + ksft_print_msg("Control flow violation happened somewhere\n"); > + ksft_print_msg("PC where violation happened %lx\n", ctx->uc_mcontext.gregs[0]); > + exit(-1); > + } > + > + /* all other cases are expected to be of shadow stack write case */ > + exit(CHILD_EXIT_CODE_SSWRITE); > +} > + > +bool register_signal_handler(void) > +{ > + struct sigaction sa = {}; > + > + sa.sa_sigaction = sigsegv_handler; > + sa.sa_flags = SA_SIGINFO; > + if (sigaction(SIGSEGV, &sa, NULL)) { > + ksft_print_msg("Registering signal handler for landing pad violation failed\n"); > + return false; > + } > + > + return true; > +} > + > +int main(int argc, char *argv[]) > +{ > + int ret = 0; > + unsigned long lpad_status = 0, ss_status = 0; > + > + ksft_print_header(); > + > + ksft_print_msg("Starting risc-v tests\n"); > + > + /* > + * Landing pad test. Not a lot of kernel changes to support landing > + * pad for user mode except lighting up a bit in senvcfg via a prctl > + * Enable landing pad through out the execution of test binary > + */ > + ret = my_syscall5(__NR_prctl, PR_GET_INDIR_BR_LP_STATUS, &lpad_status, 0, 0, 0); > + if (ret) > + ksft_exit_fail_msg("Get landing pad status failed with %d\n", ret); > + > + if (!(lpad_status & PR_INDIR_BR_LP_ENABLE)) > + ksft_exit_fail_msg("Landing pad is not enabled, should be enabled via glibc\n"); > + > + ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &ss_status, 0, 0, 0); > + if (ret) > + ksft_exit_fail_msg("Get shadow stack failed with %d\n", ret); > + > + if (!(ss_status & PR_SHADOW_STACK_ENABLE)) > + ksft_exit_fail_msg("Shadow stack is not enabled, should be enabled via glibc\n"); > + > + if (!register_signal_handler()) > + ksft_exit_fail_msg("Registering signal handler for SIGSEGV failed\n"); > + > + ksft_print_msg("Landing pad and shadow stack are enabled for binary\n"); > + execute_shadow_stack_tests(); > + > + return 0; > +} > + > +#pragma GCC pop_options > diff --git a/tools/testing/selftests/riscv/cfi/shadowstack.c b/tools/testing/selftests/riscv/cfi/shadowstack.c > new file mode 100644 > index 000000000000..9d5301914578 > --- /dev/null > +++ b/tools/testing/selftests/riscv/cfi/shadowstack.c > @@ -0,0 +1,373 @@ > +// SPDX-License-Identifier: GPL-2.0-only > + > +#include "../../kselftest.h" > +#include <sys/wait.h> > +#include <signal.h> > +#include <fcntl.h> > +#include <asm-generic/unistd.h> > +#include <sys/mman.h> > +#include "shadowstack.h" > +#include "cfi_rv_test.h" > + > +/* do not optimize shadow stack related test functions */ > +#pragma GCC push_options > +#pragma GCC optimize("O0") > + > +void zar(void) > +{ > + unsigned long ssp = 0; > + > + ssp = csr_read(CSR_SSP); > + ksft_print_msg("Spewing out shadow stack ptr: %lx\n" > + " This is to ensure shadow stack is indeed enabled and working\n", > + ssp); > +} > + > +void bar(void) > +{ > + zar(); > +} > + > +void foo(void) > +{ > + bar(); > +} > + > +void zar_child(void) > +{ > + unsigned long ssp = 0; > + > + ssp = csr_read(CSR_SSP); > + ksft_print_msg("Spewing out shadow stack ptr: %lx\n" > + " This is to ensure shadow stack is indeed enabled and working\n", > + ssp); > +} > + > +void bar_child(void) > +{ > + zar_child(); > +} > + > +void foo_child(void) > +{ > + bar_child(); > +} > + > +typedef void (call_func_ptr)(void); > +/* > + * call couple of functions to test push pop. > + */ > +int shadow_stack_call_tests(call_func_ptr fn_ptr, bool parent) > +{ > + ksft_print_msg("Exercising dummy calls for sspush and sspopchk in" > + " context of %s\n", parent ? "parent" : "child"); > + > + (fn_ptr)(); > + > + return 0; > +} > + > +/* forks a thread, and ensure shadow stacks fork out */ > +bool shadow_stack_fork_test(unsigned long test_num, void *ctx) > +{ > + int pid = 0, child_status = 0, parent_pid = 0, ret = 0; > + unsigned long ss_status = 0; > + > + ksft_print_msg("Exercising shadow stack fork test\n"); > + > + ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &ss_status, 0, 0, 0); > + if (ret) { > + ksft_exit_skip("Shadow stack get status prctl failed with errorcode %d\n", ret); > + return false; > + } > + > + if (!(ss_status & PR_SHADOW_STACK_ENABLE)) > + ksft_exit_skip("Shadow stack is not enabled, should be enabled via glibc\n"); > + > + parent_pid = getpid(); > + pid = fork(); > + > + if (pid) { > + ksft_print_msg("Parent pid %d and child pid %d\n", parent_pid, pid); > + shadow_stack_call_tests(&foo, true); > + } else > + shadow_stack_call_tests(&foo_child, false); > + > + if (pid) { > + ksft_print_msg("Waiting on child to finish\n"); > + wait(&child_status); > + } else { > + /* exit child gracefully */ > + exit(0); > + } > + > + if (pid && WIFSIGNALED(child_status)) { > + ksft_print_msg("Child faulted, fork test failed\n"); > + return false; > + } > + > + return true; > +} > + > +/* exercise `map_shadow_stack`, pivot to it and call some functions to ensure it works */ > +#define SHADOW_STACK_ALLOC_SIZE 4096 > +bool shadow_stack_map_test(unsigned long test_num, void *ctx) > +{ > + unsigned long shdw_addr; > + int ret = 0; > + > + ksft_print_msg("Exercising shadow stack map test\n"); > + > + shdw_addr = my_syscall3(__NR_map_shadow_stack, NULL, SHADOW_STACK_ALLOC_SIZE, 0); > + > + if (((long) shdw_addr) <= 0) { > + ksft_print_msg("map_shadow_stack failed with error code %d\n", (int) shdw_addr); > + return false; > + } > + > + ret = munmap((void *) shdw_addr, SHADOW_STACK_ALLOC_SIZE); > + > + if (ret) { > + ksft_print_msg("munmap failed with error code %d\n", ret); > + return false; > + } > + > + return true; > +} > + > +/* > + * shadow stack protection tests. map a shadow stack and > + * validate all memory protections work on it > + */ > +bool shadow_stack_protection_test(unsigned long test_num, void *ctx) > +{ > + unsigned long shdw_addr; > + unsigned long *write_addr = NULL; > + int ret = 0, pid = 0, child_status = 0; > + > + ksft_print_msg("Exercising shadow stack protection test\n"); > + > + shdw_addr = my_syscall3(__NR_map_shadow_stack, NULL, SHADOW_STACK_ALLOC_SIZE, 0); > + > + if (((long) shdw_addr) <= 0) { > + ksft_print_msg("map_shadow_stack failed with error code %d\n", (int) shdw_addr); > + return false; > + } > + > + write_addr = (unsigned long *) shdw_addr; > + pid = fork(); > + > + /* no child was created, return false */ > + if (pid == -1) > + return false; > + > + /* > + * try to perform a store from child on shadow stack memory > + * it should result in SIGSEGV > + */ > + if (!pid) { > + /* below write must lead to SIGSEGV */ > + *write_addr = 0xdeadbeef; > + } else { > + wait(&child_status); > + } > + > + /* test fail, if 0xdeadbeef present on shadow stack address */ > + if (*write_addr == 0xdeadbeef) { > + ksft_print_msg("Write suceeded on shadow stack memory, shadow stack protection test" > + " failed\n"); > + return false; > + } > + > + /* if child reached here, then fail */ > + if (!pid) { > + ksft_print_msg("Shadow stack protection test: child reached unreachable state\n"); > + return false; > + } > + > + /* if child exited via signal handler but not for write on ss */ > + if (WIFEXITED(child_status) && > + WEXITSTATUS(child_status) != CHILD_EXIT_CODE_SSWRITE) { > + ksft_print_msg("Shadow stack protection test: child wasn't signaled for write on" > + " shadow stack\n"); > + return false; > + } > + > + ret = munmap(write_addr, SHADOW_STACK_ALLOC_SIZE); > + if (ret) { > + ksft_print_msg("Shadow stack protection test: munmap failed with error code %d\n", > + ret); > + return false; > + } > + > + return true; > +} > + > +#define SS_MAGIC_WRITE_VAL 0xbeefdead > + > +int gup_tests(int mem_fd, unsigned long *shdw_addr) > +{ > + unsigned long val = 0; > + > + lseek(mem_fd, (unsigned long)shdw_addr, SEEK_SET); > + if (read(mem_fd, &val, sizeof(val)) < 0) { > + ksft_print_msg("Reading shadow stack mem via gup failed\n"); > + return 1; > + } > + > + val = SS_MAGIC_WRITE_VAL; > + lseek(mem_fd, (unsigned long)shdw_addr, SEEK_SET); > + if (write(mem_fd, &val, sizeof(val)) < 0) { > + ksft_print_msg("Writing shadow stack mem via gup failed\n"); > + return 1; > + } > + > + if (*shdw_addr != SS_MAGIC_WRITE_VAL) { > + ksft_print_msg("GUP write to shadow stack memory failed\n"); > + return 1; > + } > + > + return 0; > +} > + > +bool shadow_stack_gup_tests(unsigned long test_num, void *ctx) > +{ > + unsigned long shdw_addr = 0; > + unsigned long *write_addr = NULL; > + int fd = 0; > + bool ret = false; > + > + ksft_print_msg("Exercising shadow stack gup tests\n"); > + shdw_addr = my_syscall3(__NR_map_shadow_stack, NULL, SHADOW_STACK_ALLOC_SIZE, 0); > + > + if (((long) shdw_addr) <= 0) { > + ksft_print_msg("map_shadow_stack failed with error code %d\n", (int) shdw_addr); > + return false; > + } > + > + write_addr = (unsigned long *) shdw_addr; > + > + fd = open("/proc/self/mem", O_RDWR); > + if (fd == -1) > + return false; > + > + if (gup_tests(fd, write_addr)) { > + ksft_print_msg("gup tests failed\n"); > + goto out; > + } > + > + ret = true; > +out: > + if (shdw_addr && munmap(write_addr, SHADOW_STACK_ALLOC_SIZE)) { > + ksft_print_msg("munmap failed with error code %d\n", ret); > + ret = false; > + } > + > + return ret; > +} > + > +volatile bool break_loop; > + > +void sigusr1_handler(int signo) > +{ > + break_loop = true; > +} > + > +bool sigusr1_signal_test(void) > +{ > + struct sigaction sa = {}; > + > + sa.sa_handler = sigusr1_handler; > + sa.sa_flags = 0; > + sigemptyset(&sa.sa_mask); > + if (sigaction(SIGUSR1, &sa, NULL)) { > + ksft_print_msg("Registering signal handler for SIGUSR1 failed\n"); > + return false; > + } > + > + return true; > +} > +/* > + * shadow stack signal test. shadow stack must be enabled. > + * register a signal, fork another thread which is waiting > + * on signal. Send a signal from parent to child, verify > + * that signal was received by child. If not test fails > + */ > +bool shadow_stack_signal_test(unsigned long test_num, void *ctx) > +{ > + int pid = 0, child_status = 0, ret = 0; > + unsigned long ss_status = 0; > + > + ksft_print_msg("Exercising shadow stack signal test\n"); > + > + ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &ss_status, 0, 0, 0); > + if (ret) { > + ksft_print_msg("Shadow stack get status prctl failed with errorcode %d\n", ret); > + return false; > + } > + > + if (!(ss_status & PR_SHADOW_STACK_ENABLE)) > + ksft_print_msg("Shadow stack is not enabled, should be enabled via glibc\n"); > + > + /* this should be caught by signal handler and do an exit */ > + if (!sigusr1_signal_test()) { > + ksft_print_msg("Registering sigusr1 handler failed\n"); > + exit(-1); > + } > + > + pid = fork(); > + > + if (pid == -1) { > + ksft_print_msg("Signal test: fork failed\n"); > + goto out; > + } > + > + if (pid == 0) { > + while (!break_loop) > + sleep(1); > + > + exit(11); > + /* child shouldn't go beyond here */ > + } > + > + /* send SIGUSR1 to child */ > + kill(pid, SIGUSR1); > + wait(&child_status); > + > +out: > + > + return (WIFEXITED(child_status) && > + WEXITSTATUS(child_status) == 11); > +} > + > +int execute_shadow_stack_tests(void) > +{ > + int ret = 0; > + unsigned long test_count = 0; > + unsigned long shstk_status = 0; > + bool test_pass = false; > + > + ksft_print_msg("Executing RISC-V shadow stack self tests\n"); > + ksft_set_plan(RISCV_SHADOW_STACK_TESTS); > + > + ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &shstk_status, 0, 0, 0); > + > + if (ret != 0) > + ksft_exit_fail_msg("Get shadow stack status failed with %d\n", ret); > + > + /* > + * If we are here that means get shadow stack status succeeded and > + * thus shadow stack support is baked in the kernel. > + */ > + while (test_count < ARRAY_SIZE(shstk_tests)) { > + test_pass = (*shstk_tests[test_count].t_func)(test_count, NULL); > + ksft_test_result(test_pass, shstk_tests[test_count].name); > + test_count++; > + } > + > + ksft_finished(); > + > + return 0; > +} > + > +#pragma GCC pop_options > diff --git a/tools/testing/selftests/riscv/cfi/shadowstack.h b/tools/testing/selftests/riscv/cfi/shadowstack.h > new file mode 100644 > index 000000000000..b43e74136a26 > --- /dev/null > +++ b/tools/testing/selftests/riscv/cfi/shadowstack.h > @@ -0,0 +1,37 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > + > +#ifndef SELFTEST_SHADOWSTACK_TEST_H > +#define SELFTEST_SHADOWSTACK_TEST_H > +#include <stddef.h> > +#include <linux/prctl.h> > + > +/* > + * a cfi test returns true for success or false for fail > + * takes a number for test number to index into array and void pointer. > + */ > +typedef bool (*shstk_test_func)(unsigned long test_num, void *); > + > +struct shadow_stack_tests { > + char *name; > + shstk_test_func t_func; > +}; > + > +bool shadow_stack_fork_test(unsigned long test_num, void *ctx); > +bool shadow_stack_map_test(unsigned long test_num, void *ctx); > +bool shadow_stack_protection_test(unsigned long test_num, void *ctx); > +bool shadow_stack_gup_tests(unsigned long test_num, void *ctx); > +bool shadow_stack_signal_test(unsigned long test_num, void *ctx); > + > +static struct shadow_stack_tests shstk_tests[] = { shadowstack.h:25:34: warning: 'shstk_tests' defined but not used [-Wunused-variable] 25 | static struct shadow_stack_tests shstk_tests[] = { > + { "shstk fork test\n", shadow_stack_fork_test }, > + { "map shadow stack syscall\n", shadow_stack_map_test }, > + { "shadow stack gup tests\n", shadow_stack_gup_tests }, > + { "shadow stack signal tests\n", shadow_stack_signal_test}, > + { "memory protections of shadow stack memory\n", shadow_stack_protection_test } > +}; > + > +#define RISCV_SHADOW_STACK_TESTS ARRAY_SIZE(shstk_tests) > + > +int execute_shadow_stack_tests(void); > + > +#endif > > -- > 2.45.0 > > > _______________________________________________ > linux-riscv mailing list > linux-riscv@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-riscv
On Fri, Oct 11, 2024 at 6:18 PM Mark Brown <broonie@kernel.org> wrote: > > On Fri, Oct 11, 2024 at 01:44:55PM +0800, Zong Li wrote: > > On Wed, Oct 9, 2024 at 7:46 AM Deepak Gupta <debug@rivosinc.com> wrote: > > > > + if (si->si_code == SEGV_CPERR) { > > > Hi Deepak, > > I got some errors when building this test, I suppose they should be > > fixed in the next version. > > > riscv_cfi_test.c: In function 'sigsegv_handler': > > riscv_cfi_test.c:17:28: error: 'SEGV_CPERR' undeclared (first use in > > this function); did you mean 'SEGV_ACCERR'? > > 17 | if (si->si_code == SEGV_CPERR) { > > | ^~~~~~~~~~ > > | SEGV_ACCERR > > > > Did you run "make headers_install" prior to building kselftest to get > the current kernel's headers available for userspace builds? Yes, I have run "make header" and "make header_install" before building the kselftest. This error happens when I cross compiled it, perhaps I can help to check if it is missing some header files or header search path.
On Sat, Oct 12, 2024 at 3:46 AM Deepak Gupta <debug@rivosinc.com> wrote: > > On Fri, Oct 11, 2024 at 07:43:30PM +0800, Zong Li wrote: > >On Fri, Oct 11, 2024 at 6:18 PM Mark Brown <broonie@kernel.org> wrote: > >> > >> On Fri, Oct 11, 2024 at 01:44:55PM +0800, Zong Li wrote: > >> > On Wed, Oct 9, 2024 at 7:46 AM Deepak Gupta <debug@rivosinc.com> wrote: > >> > >> > > + if (si->si_code == SEGV_CPERR) { > >> > >> > Hi Deepak, > >> > I got some errors when building this test, I suppose they should be > >> > fixed in the next version. > >> > >> > riscv_cfi_test.c: In function 'sigsegv_handler': > >> > riscv_cfi_test.c:17:28: error: 'SEGV_CPERR' undeclared (first use in > >> > this function); did you mean 'SEGV_ACCERR'? > >> > 17 | if (si->si_code == SEGV_CPERR) { > >> > | ^~~~~~~~~~ > >> > | SEGV_ACCERR > >> > > >> > >> Did you run "make headers_install" prior to building kselftest to get > >> the current kernel's headers available for userspace builds? > > > >Yes, I have run "make header" and "make header_install" before > >building the kselftest. This error happens when I cross compiled it, > >perhaps I can help to check if it is missing some header files or > >header search path. > > That's wierd. > > It doesn't fail for me even if I do not do `make headers_install`. But I am > building kernel and selftests with toolchain which supports shadow stack and > landing pad. It's defined in `siginfo.h`. When I built toolchain, I did point > it at the latest kernel headers. May be that's the trick. > > """ > > $ grep -nir SEGV_CPERR /scratch/debug/linux/kbuild/usr/include/* > /scratch/debug/linux/kbuild/usr/include/asm-generic/siginfo.h:240:#define SEGV_CPERR 10 /* Control protection fault */ > > $ grep -nir SEGV_CPERR /scratch/debug/open_src/sifive_cfi_toolchain/INSTALL_Sept18/sysroot/usr/* > /scratch/debug/open_src/sifive_cfi_toolchain/INSTALL_Sept18/sysroot/usr/include/asm-generic/siginfo.h:240:#define SEGV_CPERR 10 /* Control protection fault */ > /scratch/debug/open_src/sifive_cfi_toolchain/INSTALL_Sept18/sysroot/usr/include/bits/siginfo-consts.h:139: SEGV_CPERR /* Control protection fault. */ > /scratch/debug/open_src/sifive_cfi_toolchain/INSTALL_Sept18/sysroot/usr/include/bits/siginfo-consts.h:140:# define SEGV_CPERR SEGV_CPERR > > """ In my case, because the test files don't explicitly include siginfo.h, I assume it's expected that siginfo.h will be included through signal.h. Regarding the header search path, it will eventually locate signal.h in toolchain_path/sysroot/usr/include/. In my toolchain_path/sysroot/usr/include/signal.h, it doesn't include any signal.h; instead, signal.h will be included from toolchain_path/sysroot/usr/include/linux/signal.h or kernel_src/usr/include/linux/signal.h rather than toolchain/sysroot/usr/include/signal.h. I think that is why I lost the SEGV_CPERR definition. Is there any difference with you? >
On Tue, 08 Oct 2024 15:36:49 -0700, Deepak Gupta wrote: > Make an entry for cfi extensions in extensions.yaml. > > Signed-off-by: Deepak Gupta <debug@rivosinc.com> > --- > Documentation/devicetree/bindings/riscv/extensions.yaml | 14 ++++++++++++++ > 1 file changed, 14 insertions(+) > Acked-by: Rob Herring (Arm) <robh@kernel.org>
Basics and overview =================== Software with larger attack surfaces (e.g. network facing apps like databases, browsers or apps relying on browser runtimes) suffer from memory corruption issues which can be utilized by attackers to bend control flow of the program to eventually gain control (by making their payload executable). Attackers are able to perform such attacks by leveraging call-sites which rely on indirect calls or return sites which rely on obtaining return address from stack memory. To mitigate such attacks, risc-v extension zicfilp enforces that all indirect calls must land on a landing pad instruction `lpad` else cpu will raise software check exception (a new cpu exception cause code on riscv). Similarly for return flow, risc-v extension zicfiss extends architecture with - `sspush` instruction to push return address on a shadow stack - `sspopchk` instruction to pop return address from shadow stack and compare with input operand (i.e. return address on stack) - `sspopchk` to raise software check exception if comparision above was a mismatch - Protection mechanism using which shadow stack is not writeable via regular store instructions More information an details can be found at extensions github repo [1]. Equivalent to landing pad (zicfilp) on x86 is `ENDBRANCH` instruction in Intel CET [3] and branch target identification (BTI) [4] on arm. Similarly x86's Intel CET has shadow stack [5] and arm64 has guarded control stack (GCS) [6] which are very similar to risc-v's zicfiss shadow stack. x86 already supports shadow stack for user mode and arm64 support for GCS in usermode [7] is ongoing. Kernel awareness for user control flow integrity ================================================ This series picks up Samuel Holland's envcfg changes [2] as well. So if those are being applied independently, they should be removed from this series. Enabling: In order to maintain compatibility and not break anything in user mode, kernel doesn't enable control flow integrity cpu extensions on binary by default. Instead exposes a prctl interface to enable, disable and lock the shadow stack or landing pad feature for a task. This allows userspace (loader) to enumerate if all objects in its address space are compiled with shadow stack and landing pad support and accordingly enable the feature. Additionally if a subsequent `dlopen` happens on a library, user mode can take a decision again to disable the feature (if incoming library is not compiled with support) OR terminate the task (if user mode policy is strict to have all objects in address space to be compiled with control flow integirty cpu feature). prctl to enable shadow stack results in allocating shadow stack from virtual memory and activating for user address space. x86 and arm64 are also following same direction due to similar reason(s). clone/fork: On clone and fork, cfi state for task is inherited by child. Shadow stack is part of virtual memory and is a writeable memory from kernel perspective (writeable via a restricted set of instructions aka shadow stack instructions) Thus kernel changes ensure that this memory is converted into read-only when fork/clone happens and COWed when fault is taken due to sspush, sspopchk or ssamoswap. In case `CLONE_VM` is specified and shadow stack is to be enabled, kernel will automatically allocate a shadow stack for that clone call. map_shadow_stack: x86 introduced `map_shadow_stack` system call to allow user space to explicitly map shadow stack memory in its address space. It is useful to allocate shadow for different contexts managed by a single thread (green threads or contexts) risc-v implements this system call as well. signal management: If shadow stack is enabled for a task, kernel performs an asynchronous control flow diversion to deliver the signal and eventually expects userspace to issue sigreturn so that original execution can be resumed. Even though resume context is prepared by kernel, it is in user space memory and is subject to memory corruption and corruption bugs can be utilized by attacker in this race window to perform arbitrary sigreturn and eventually bypass cfi mechanism. Another issue is how to ensure that cfi related state on sigcontext area is not trampled by legacy apps or apps compiled with old kernel headers. In order to mitigate control-flow hijacting, kernel prepares a token and place it on shadow stack before signal delivery and places address of token in sigcontext structure. During sigreturn, kernel obtains address of token from sigcontext struture, reads token from shadow stack and validates it and only then allow sigreturn to succeed. Compatiblity issue is solved by adopting dynamic sigcontext management introduced for vector extension. This series re-factor the code little bit to allow future sigcontext management easy (as proposed by Andy Chiu from SiFive) config and compilation: Introduce a new risc-v config option `CONFIG_RISCV_USER_CFI`. Selecting this config option picks the kernel support for user control flow integrity. This optin is presented only if toolchain has shadow stack and landing pad support. And is on purpose guarded by toolchain support. Reason being that eventually vDSO also needs to be compiled in with shadow stack and landing pad support. vDSO compile patches are not included as of now because landing pad labeling scheme is yet to settle for usermode runtime. To get more information on kernel interactions with respect to zicfilp and zicfiss, patch series adds documentation for `zicfilp` and `zicfiss` in following: Documentation/arch/riscv/zicfiss.rst Documentation/arch/riscv/zicfilp.rst How to test this series ======================= Toolchain --------- $ git clone git@github.com:sifive/riscv-gnu-toolchain.git -b cfi-dev $ riscv-gnu-toolchain/configure --prefix=<path-to-where-to-build> --with-arch=rv64gc_zicfilp_zicfiss --enable-linux --disable-gdb --with-extra-multilib-test="rv64gc_zicfilp_zicfiss-lp64d:-static" $ make -j$(nproc) Qemu ---- $ git clone git@github.com:deepak0414/qemu.git -b zicfilp_zicfiss_ratified_master_july11 $ cd qemu $ mkdir build $ cd build $ ../configure --target-list=riscv64-softmmu $ make -j$(nproc) Opensbi ------- $ git clone git@github.com:deepak0414/opensbi.git -b v6_cfi_spec_split_opensbi $ make CROSS_COMPILE=<your riscv toolchain> -j$(nproc) PLATFORM=generic Linux ----- Running defconfig is fine. CFI is enabled by default if the toolchain supports it. $ make ARCH=riscv CROSS_COMPILE=<path-to-cfi-riscv-gnu-toolchain>/build/bin/riscv64-unknown-linux-gnu- -j$(nproc) defconfig $ make ARCH=riscv CROSS_COMPILE=<path-to-cfi-riscv-gnu-toolchain>/build/bin/riscv64-unknown-linux-gnu- -j$(nproc) Branch where user cfi enabling patches are maintained https://github.com/deepak0414/linux-riscv-cfi/tree/vdso_user_cfi_v6.12-rc1 In case you're building your own rootfs using toolchain, please make sure you pick following patch to ensure that vDSO compiled with lpad and shadow stack. "arch/riscv: compile vdso with landing pad" Running ------- Modify your qemu command to have: -bios <path-to-cfi-opensbi>/build/platform/generic/firmware/fw_dynamic.bin -cpu rv64,zicfilp=true,zicfiss=true,zimop=true,zcmop=true vDSO related Opens (in the flux) ================================= I am listing these opens for laying out plan and what to expect in future patch sets. And of course for the sake of discussion. Shadow stack and landing pad enabling in vDSO ---------------------------------------------- vDSO must have shadow stack and landing pad support compiled in for task to have shadow stack and landing pad support. This patch series doesn't enable that (yet). Enabling shadow stack support in vDSO should be straight forward (intend to do that in next versions of patch set). Enabling landing pad support in vDSO requires some collaboration with toolchain folks to follow a single label scheme for all object binaries. This is necessary to ensure that all indirect call-sites are setting correct label and target landing pads are decorated with same label scheme. How many vDSOs --------------- Shadow stack instructions are carved out of zimop (may be operations) and if CPU doesn't implement zimop, they're illegal instructions. Kernel could be running on a CPU which may or may not implement zimop. And thus kernel will have to carry 2 different vDSOs and expose the appropriate one depending on whether CPU implements zimop or not. References ========== [1] - https://github.com/riscv/riscv-cfi [2] - https://lore.kernel.org/all/20240814081126.956287-1-samuel.holland@sifive.com/ [3] - https://lwn.net/Articles/889475/ [4] - https://developer.arm.com/documentation/109576/0100/Branch-Target-Identification [5] - https://www.intel.com/content/dam/develop/external/us/en/documents/catc17-introduction-intel-cet-844137.pdf [6] - https://lwn.net/Articles/940403/ [7] - https://lore.kernel.org/all/20241001-arm64-gcs-v13-0-222b78d87eee@kernel.org/ To: Thomas Gleixner <tglx@linutronix.de> To: Ingo Molnar <mingo@redhat.com> To: Borislav Petkov <bp@alien8.de> To: Dave Hansen <dave.hansen@linux.intel.com> To: x86@kernel.org To: H. Peter Anvin <hpa@zytor.com> To: Andrew Morton <akpm@linux-foundation.org> To: Liam R. Howlett <Liam.Howlett@oracle.com> To: Vlastimil Babka <vbabka@suse.cz> To: Lorenzo Stoakes <lorenzo.stoakes@oracle.com> To: Paul Walmsley <paul.walmsley@sifive.com> To: Palmer Dabbelt <palmer@dabbelt.com> To: Albert Ou <aou@eecs.berkeley.edu> To: Conor Dooley <conor@kernel.org> To: Rob Herring <robh@kernel.org> To: Krzysztof Kozlowski <krzk+dt@kernel.org> To: Arnd Bergmann <arnd@arndb.de> To: Christian Brauner <brauner@kernel.org> To: Peter Zijlstra <peterz@infradead.org> To: Oleg Nesterov <oleg@redhat.com> To: Eric Biederman <ebiederm@xmission.com> To: Kees Cook <kees@kernel.org> To: Jonathan Corbet <corbet@lwn.net> To: Shuah Khan <shuah@kernel.org> Cc: linux-kernel@vger.kernel.org Cc: linux-fsdevel@vger.kernel.org Cc: linux-mm@kvack.org Cc: linux-riscv@lists.infradead.org Cc: devicetree@vger.kernel.org Cc: linux-arch@vger.kernel.org Cc: linux-doc@vger.kernel.org Cc: linux-kselftest@vger.kernel.org Cc: alistair.francis@wdc.com Cc: richard.henderson@linaro.org Cc: jim.shu@sifive.com Cc: andybnac@gmail.com Cc: kito.cheng@sifive.com Cc: charlie@rivosinc.com Cc: atishp@rivosinc.com Cc: evan@rivosinc.com Cc: cleger@rivosinc.com Cc: alexghiti@rivosinc.com Cc: samitolvanen@google.com Cc: broonie@kernel.org Cc: rick.p.edgecombe@intel.com Signed-off-by: Deepak Gupta <debug@rivosinc.com> --- changelog --------- v6: - Picked up Samuel Holland's changes as is with `envcfg` placed in `thread` instead of `thread_info` - fixed unaligned newline escapes in kselftest - cleaned up messages in kselftest and included test output in commit message - fixed a bug in clone path reported by Zong Li - fixed a build issue if CONFIG_RISCV_ISA_V is not selected (this was introduced due to re-factoring signal context management code) v5: - rebased on v6.12-rc1 - Fixed schema related issues in device tree file - Fixed some of the documentation related issues in zicfilp/ss.rst (style issues and added index) - added `SHADOW_STACK_SET_MARKER` so that implementation can define base of shadow stack. - Fixed warnings on definitions added in usercfi.h when CONFIG_RISCV_USER_CFI is not selected. - Adopted context header based signal handling as proposed by Andy Chiu - Added support for enabling kernel mode access to shadow stack using FWFT (https://github.com/riscv-non-isa/riscv-sbi-doc/blob/master/src/ext-firmware-features.adoc) - Link to v5: https://lore.kernel.org/r/20241001-v5_user_cfi_series-v1-0-3ba65b6e550f@rivosinc.com (Note: I had an issue in my workflow due to which version number wasn't picked up correctly while sending out patches) v4: - rebased on 6.11-rc6 - envcfg: Converged with Samuel Holland's patches for envcfg management on per- thread basis. - vma_is_shadow_stack is renamed to is_vma_shadow_stack - picked up Mark Brown's `ARCH_HAS_USER_SHADOW_STACK` patch - signal context: using extended context management to maintain compatibility. - fixed `-Wmissing-prototypes` compiler warnings for prctl functions - Documentation fixes and amending typos. - Link to v4: https://lore.kernel.org/all/20240912231650.3740732-1-debug@rivosinc.com/ v3: - envcfg logic to pick up base envcfg had a bug where `ENVCFG_CBZE` could have been picked on per task basis, even though CPU didn't implement it. Fixed in this series. - dt-bindings As suggested, split into separate commit. fixed the messaging that spec is in public review - arch_is_shadow_stack change arch_is_shadow_stack changed to vma_is_shadow_stack - hwprobe zicfiss / zicfilp if present will get enumerated in hwprobe - selftests As suggested, added object and binary filenames to .gitignore Selftest binary anyways need to be compiled with cfi enabled compiler which will make sure that landing pad and shadow stack are enabled. Thus removed separate enable/disable tests. Cleaned up tests a bit. - Link to v3: https://lore.kernel.org/lkml/20240403234054.2020347-1-debug@rivosinc.com/ v2: - Using config `CONFIG_RISCV_USER_CFI`, kernel support for riscv control flow integrity for user mode programs can be compiled in the kernel. - Enabling of control flow integrity for user programs is left to user runtime - This patch series introduces arch agnostic `prctls` to enable shadow stack and indirect branch tracking. And implements them on riscv. --- Andy Chiu (1): riscv: signal: abstract header saving for setup_sigcontext Clément Léger (1): riscv: Add Firmware Feature SBI extensions definitions Deepak Gupta (26): mm: helper `is_shadow_stack_vma` to check shadow stack vma riscv/Kconfig: enable HAVE_EXIT_THREAD for riscv dt-bindings: riscv: zicfilp and zicfiss in dt-bindings (extensions.yaml) riscv: zicfiss / zicfilp enumeration riscv: zicfiss / zicfilp extension csr and bit definitions riscv: usercfi state for task and save/restore of CSR_SSP on trap entry/exit riscv/mm : ensure PROT_WRITE leads to VM_READ | VM_WRITE riscv mm: manufacture shadow stack pte riscv mmu: teach pte_mkwrite to manufacture shadow stack PTEs riscv mmu: write protect and shadow stack riscv/mm: Implement map_shadow_stack() syscall riscv/shstk: If needed allocate a new shadow stack on clone prctl: arch-agnostic prctl for indirect branch tracking riscv: Implements arch agnostic shadow stack prctls riscv: Implements arch agnostic indirect branch tracking prctls riscv/traps: Introduce software check exception riscv/signal: save and restore of shadow stack for signal riscv/kernel: update __show_regs to print shadow stack register riscv/ptrace: riscv cfi status and state via ptrace and in core files riscv/hwprobe: zicfilp / zicfiss enumeration in hwprobe riscv: enable kernel access to shadow stack memory via FWFT sbi call riscv: kernel command line option to opt out of user cfi riscv: create a config for shadow stack and landing pad instr support riscv: Documentation for landing pad / indirect branch tracking riscv: Documentation for shadow stack on riscv kselftest/riscv: kselftest for user mode cfi Mark Brown (2): mm: Introduce ARCH_HAS_USER_SHADOW_STACK prctl: arch-agnostic prctl for shadow stack Samuel Holland (3): riscv: Enable cbo.zero only when all harts support Zicboz riscv: Add support for per-thread envcfg CSR values riscv: Call riscv_user_isa_enable() only on the boot hart Documentation/arch/riscv/index.rst | 2 + Documentation/arch/riscv/zicfilp.rst | 115 +++++ Documentation/arch/riscv/zicfiss.rst | 176 +++++++ .../devicetree/bindings/riscv/extensions.yaml | 14 + arch/riscv/Kconfig | 21 + arch/riscv/include/asm/asm-prototypes.h | 1 + arch/riscv/include/asm/cpufeature.h | 15 +- arch/riscv/include/asm/csr.h | 16 + arch/riscv/include/asm/entry-common.h | 2 + arch/riscv/include/asm/hwcap.h | 2 + arch/riscv/include/asm/mman.h | 24 + arch/riscv/include/asm/pgtable.h | 30 +- arch/riscv/include/asm/processor.h | 3 + arch/riscv/include/asm/sbi.h | 27 ++ arch/riscv/include/asm/switch_to.h | 8 + arch/riscv/include/asm/thread_info.h | 3 + arch/riscv/include/asm/usercfi.h | 89 ++++ arch/riscv/include/asm/vector.h | 3 + arch/riscv/include/uapi/asm/hwprobe.h | 2 + arch/riscv/include/uapi/asm/ptrace.h | 22 + arch/riscv/include/uapi/asm/sigcontext.h | 1 + arch/riscv/kernel/Makefile | 2 + arch/riscv/kernel/asm-offsets.c | 8 + arch/riscv/kernel/cpufeature.c | 13 +- arch/riscv/kernel/entry.S | 31 +- arch/riscv/kernel/head.S | 12 + arch/riscv/kernel/process.c | 31 +- arch/riscv/kernel/ptrace.c | 83 ++++ arch/riscv/kernel/signal.c | 140 +++++- arch/riscv/kernel/smpboot.c | 2 - arch/riscv/kernel/suspend.c | 4 +- arch/riscv/kernel/sys_hwprobe.c | 2 + arch/riscv/kernel/sys_riscv.c | 10 + arch/riscv/kernel/traps.c | 42 ++ arch/riscv/kernel/usercfi.c | 526 +++++++++++++++++++++ arch/riscv/mm/init.c | 2 +- arch/riscv/mm/pgtable.c | 17 + arch/x86/Kconfig | 1 + fs/proc/task_mmu.c | 2 +- include/linux/cpu.h | 4 + include/linux/mm.h | 5 +- include/uapi/asm-generic/mman.h | 4 + include/uapi/linux/elf.h | 1 + include/uapi/linux/prctl.h | 48 ++ kernel/sys.c | 60 +++ mm/Kconfig | 6 + mm/gup.c | 2 +- mm/mmap.c | 1 + mm/vma.h | 10 +- tools/testing/selftests/riscv/Makefile | 2 +- tools/testing/selftests/riscv/cfi/.gitignore | 3 + tools/testing/selftests/riscv/cfi/Makefile | 10 + tools/testing/selftests/riscv/cfi/cfi_rv_test.h | 84 ++++ tools/testing/selftests/riscv/cfi/riscv_cfi_test.c | 78 +++ tools/testing/selftests/riscv/cfi/shadowstack.c | 373 +++++++++++++++ tools/testing/selftests/riscv/cfi/shadowstack.h | 37 ++ 56 files changed, 2190 insertions(+), 42 deletions(-) --- base-commit: 7d9923ee3960bdbfaa7f3a4e0ac2364e770c46ff change-id: 20240930-v5_user_cfi_series-3dc332f8f5b2 -- - debug