prctl(PR_SET_CFI,PR_CFI_BRANCH_LANDING_PADS) silently ignored
unknown control values. Only PR_CFI_{ENABLE,DISABLE,LOCK} should
be permitted.
This changes the behavior of the uABI (fails previously accepted bits
with EINVAL).
Fixes: 08ee155905 ("prctl: cfi: change the branch landing pad prctl()s to be more descriptive")
Signed-off-by: Richard Patel <ripatel@wii.dev>
Link: https://patch.msgid.link/20260518183918.322545-1-ripatel@wii.dev
[pjw@kernel.org: change the patch description to note that although this is a uABI change, it does not break the uABI]
Signed-off-by: Paul Walmsley <pjw@kernel.org>
180 lines
4.9 KiB
C
180 lines
4.9 KiB
C
// 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
|