Commit d30c1683 authored by Deepak Gupta's avatar Deepak Gupta Committed by Paul Walmsley
Browse files

kselftest/riscv: add kselftest for user mode CFI



Add a kselftest for RISC-V control flow integrity implementation for
user mode. There is not a lot going on in the kernel to enable landing
pad for user mode. CFI selftests are intended to be compiled with a
zicfilp and zicfiss enabled compiler. This kselftest simply checks if
landing pads and shadow stacks for the process are enabled or not and
executes ptrace selftests on CFI. The selftest then registers a
SIGSEGV signal handler.  Any control flow violations are reported as
SIGSEGV with si_code = SEGV_CPERR.  The test will fail on receiving
any SEGV_CPERR. The shadow stack part has more changes in the 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
- ptrace test: adds landing pad violation, clears ELP and continues

In case the toolchain doesn't support the CFI extension, the CFI
kselftest won't be built.

Test output
===========

"""
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
"""

Suggested-by: default avatarCharlie Jenkins <charlie@rivosinc.com>
Signed-off-by: default avatarCharlie Jenkins <charlie@rivosinc.com>
Signed-off-by: default avatarDeepak Gupta <debug@rivosinc.com>
Tested-by: Andreas Korb <andreas.korb@aisec.fraunhofer.de> # QEMU, custom CVA6
Tested-by: default avatarValentin Haudiquet <valentin.haudiquet@canonical.com>
Link: https://patch.msgid.link/20251112-v5_user_cfi_series-v23-28-b55691eacf4f@rivosinc.com


[pjw@kernel.org: updated to apply; cleaned up patch description, code comments]
Signed-off-by: default avatarPaul Walmsley <pjw@kernel.org>
parent c8350aa2
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@
ARCH ?= $(shell uname -m 2>/dev/null || echo not)

ifneq (,$(filter $(ARCH),riscv))
RISCV_SUBTARGETS ?= abi hwprobe mm sigreturn vector
RISCV_SUBTARGETS ?= abi hwprobe mm sigreturn vector cfi
else
RISCV_SUBTARGETS :=
endif
+2 −0
Original line number Diff line number Diff line
cfitests
shadowstack
+23 −0
Original line number Diff line number Diff line
CFLAGS += $(KHDR_INCLUDES)
CFLAGS += -I$(top_srcdir)/tools/include

CFLAGS += -march=rv64gc_zicfilp_zicfiss -fcf-protection=full

# Check for zicfi* extensions needs cross compiler
# which is not set until lib.mk is included
ifeq ($(LLVM)$(CC),cc)
CC := $(CROSS_COMPILE)gcc
endif


ifeq ($(shell $(CC) $(CFLAGS) -nostdlib -xc /dev/null -o /dev/null > /dev/null 2>&1; echo $$?),0)
TEST_GEN_PROGS := cfitests

$(OUTPUT)/cfitests: cfitests.c shadowstack.c
	$(CC) -o$@ $(CFLAGS) $(LDFLAGS) $^
else

$(shell echo "Toolchain doesn't support CFI, skipping CFI kselftest." >&2)
endif

include ../../lib.mk
+82 −0
Original line number Diff line number Diff line
/* 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 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
+173 −0
Original line number Diff line number Diff line
// 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_LP_EN_STATE |	\
			 PTRACE_CFI_SS_EN_STATE |	\
			 PTRACE_CFI_SS_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_ELP_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");

	/*
	 * 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_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");
	cfi_ptrace_test();

	execute_shadow_stack_tests();

	return 0;
}

#pragma GCC pop_options
Loading