| // SPDX-License-Identifier: GPL-2.0-only |
| |
| #include "../../kselftest.h" |
| #include <sys/signal.h> |
| #include <asm/ucontext.h> |
| #include <linux/prctl.h> |
| #include <errno.h> |
| #include <linux/ptrace.h> |
| #include <sys/wait.h> |
| #include <linux/elf.h> |
| #include <sys/uio.h> |
| #include <asm-generic/unistd.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) { |
| 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; |
| } |
| |
| long ptrace(int request, pid_t pid, void *addr, void *data); |
| |
| bool cfi_ptrace_test(void) |
| { |
| pid_t pid; |
| int status, ret = 0; |
| unsigned long ptrace_test_num = 0, total_ptrace_tests = 2; |
| |
| struct user_cfi_state cfi_reg; |
| struct iovec iov; |
| |
| pid = fork(); |
| |
| if (pid == -1) { |
| ksft_exit_fail_msg("%s: fork failed\n", __func__); |
| exit(1); |
| } |
| |
| if (pid == 0) { |
| /* allow to be traced */ |
| ptrace(PTRACE_TRACEME, 0, NULL, NULL); |
| raise(SIGSTOP); |
| asm volatile ("la a5, 1f\n" |
| "jalr a5\n" |
| "nop\n" |
| "nop\n" |
| "1: nop\n" |
| : : : "a5"); |
| exit(11); |
| /* child shouldn't go beyond here */ |
| } |
| |
| /* parent's code goes here */ |
| iov.iov_base = &cfi_reg; |
| iov.iov_len = sizeof(cfi_reg); |
| |
| while (ptrace_test_num < total_ptrace_tests) { |
| memset(&cfi_reg, 0, sizeof(cfi_reg)); |
| waitpid(pid, &status, 0); |
| if (WIFSTOPPED(status)) { |
| errno = 0; |
| ret = ptrace(PTRACE_GETREGSET, pid, (void *)NT_RISCV_USER_CFI, &iov); |
| if (ret == -1 && errno) |
| ksft_exit_fail_msg("%s: PTRACE_GETREGSET failed\n", __func__); |
| } else { |
| ksft_exit_fail_msg("%s: child didn't stop, failed\n", __func__); |
| } |
| |
| switch (ptrace_test_num) { |
| #define CFI_ENABLE_MASK (PTRACE_CFI_BRANCH_LANDING_PAD_EN_STATE | \ |
| PTRACE_CFI_SHADOW_STACK_EN_STATE | \ |
| PTRACE_CFI_SHADOW_STACK_PTR_STATE) |
| case 0: |
| if ((cfi_reg.cfi_status.cfi_state & CFI_ENABLE_MASK) != CFI_ENABLE_MASK) |
| ksft_exit_fail_msg("%s: ptrace_getregset failed, %llu\n", __func__, |
| cfi_reg.cfi_status.cfi_state); |
| if (!cfi_reg.shstk_ptr) |
| ksft_exit_fail_msg("%s: NULL shadow stack pointer, test failed\n", |
| __func__); |
| break; |
| case 1: |
| if (!(cfi_reg.cfi_status.cfi_state & |
| PTRACE_CFI_BRANCH_EXPECTED_LANDING_PAD_STATE)) |
| ksft_exit_fail_msg("%s: elp must have been set\n", __func__); |
| /* clear elp state. not interested in anything else */ |
| cfi_reg.cfi_status.cfi_state = 0; |
| |
| ret = ptrace(PTRACE_SETREGSET, pid, (void *)NT_RISCV_USER_CFI, &iov); |
| if (ret == -1 && errno) |
| ksft_exit_fail_msg("%s: PTRACE_GETREGSET failed\n", __func__); |
| break; |
| default: |
| ksft_exit_fail_msg("%s: unreachable switch case\n", __func__); |
| break; |
| } |
| ptrace(PTRACE_CONT, pid, NULL, NULL); |
| ptrace_test_num++; |
| } |
| |
| waitpid(pid, &status, 0); |
| if (WEXITSTATUS(status) != 11) |
| ksft_print_msg("%s, bad return code from child\n", __func__); |
| |
| ksft_print_msg("%s, ptrace test succeeded\n", __func__); |
| 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"); |
| |
| /* Test unknown PR_CFI bits */ |
| ret = my_syscall5(__NR_prctl, PR_SET_CFI, PR_CFI_BRANCH_LANDING_PADS, |
| PR_CFI_ENABLE | 0xffff0, 0, 0); |
| if (!ret) |
| ksft_exit_fail_msg("PR_SET_CFI accepted reserved branch landing pad bits\n"); |
| |
| /* |
| * Landing pad test. Not a lot of kernel changes to support landing |
| * pads for user mode except lighting up a bit in senvcfg via a prctl. |
| * Enable landing pad support throughout the execution of the test binary. |
| */ |
| ret = my_syscall5(__NR_prctl, PR_GET_CFI, PR_CFI_BRANCH_LANDING_PADS, &lpad_status, 0, 0); |
| if (ret) |
| ksft_exit_fail_msg("Get landing pad status failed with %d\n", ret); |
| |
| if (!(lpad_status & PR_CFI_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"); |
| cfi_ptrace_test(); |
| |
| execute_shadow_stack_tests(); |
| |
| return 0; |
| } |
| |
| #pragma GCC pop_options |