milos-linux/tools/testing/selftests/riscv/cfi/cfitests.c
Richard Patel 15b4155138 riscv: cfi: reject unknown flags in PR_SET_CFI
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>
2026-06-06 20:17:05 -06:00

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