Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions BM/cet/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
cet_app
cet_thread
glibc_shstk_test
glibc_shstk_test_legacy
glibc_supported_shstk_test
quick_test
shstk_alloc
Expand All @@ -8,5 +10,6 @@ shstk_cpu
shstk_cpu_legacy
shstk_huge_page
shstk_unlock_test
sig_stack
test_shadow_stack
wrss
10 changes: 7 additions & 3 deletions BM/cet/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ execute_process(COMMAND gcc --version OUTPUT_VARIABLE GCC_VERSION)
string(REGEX MATCH "([0-9]+)\\." GCC_VER_MAJOR ${GCC_VERSION})
if(GCC_VER_MAJOR GREATER_EQUAL 8)
set(BIN shstk_alloc test_shadow_stack quick_test wrss shstk_huge_page
shstk_unlock_test shstk_cp cet_app glibc_shstk_test shstk_cpu shstk_cpu_legacy)
shstk_unlock_test shstk_cp cet_app glibc_shstk_test shstk_cpu shstk_cpu_legacy
glibc_shstk_test_legacy sig_stack cet_thread)
else()
message(WARNING "GCC version is less than 8, skipping build cet.")
return()
Expand All @@ -39,14 +40,17 @@ foreach(target ${BIN})
if(${target} STREQUAL "cet_app")
add_executable(${target} cet_driver/cet_app.c)
target_include_directories(${target} PRIVATE cet_driver)
elseif(${target} STREQUAL "glibc_shstk_test_legacy")
add_executable(${target} glibc_shstk_test.c)
target_compile_options(${target} PRIVATE ${NOCETFLAGS})
else()
add_executable(${target} ${target}.c)
if(${target} MATCHES "quick_test|shstk_huge_page|glibc_shstk_test")
if(${target} MATCHES "quick_test|shstk_huge_page|glibc_shstk_test|sig_stack|cet_thread")
target_compile_options(${target} PRIVATE ${CETFLAGS})
else()
target_compile_options(${target} PRIVATE ${NOCETFLAGS})
endif()
if(${target} STREQUAL "test_shadow_stack")
if(${target} STREQUAL "test_shadow_stack" OR ${target} STREQUAL "cet_thread")
target_link_libraries(${target} PRIVATE pthread)
endif()
endif()
Expand Down
11 changes: 10 additions & 1 deletion BM/cet/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ IS_KER_SRC = $(shell [ -d $(KER_SRC) ] && echo true)
ifeq ($(GCC_GE_8),true)
BIN := shstk_alloc test_shadow_stack quick_test wrss shstk_huge_page \
shstk_unlock_test shstk_cp cet_app glibc_shstk_test shstk_cpu \
shstk_cpu_legacy
shstk_cpu_legacy glibc_shstk_test_legacy sig_stack cet_thread

$(info GCC major version: ${GCC_VER_MAJOR})
else
Expand Down Expand Up @@ -58,6 +58,15 @@ cet_app:
glibc_shstk_test: glibc_shstk_test.c
gcc $(CETFLAGS) $^ -o $@

glibc_shstk_test_legacy: glibc_shstk_test.c
gcc $(NOCETFLAGS) $^ -o $@

sig_stack: sig_stack.c
gcc $(CETFLAGS) $^ -o $@

cet_thread: cet_thread.c
gcc -pthread $(CETFLAGS) $^ -o $@

shstk_cpu: shstk_cpu.c
gcc $(NOCETFLAGS) $^ -o $@

Expand Down
21 changes: 21 additions & 0 deletions BM/cet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,26 @@ SHSTK enabled binary:
2. Write one incorrect value into shadow stack
3. The expected SISEGV should be received after ret instruction

## Kernel space IBT tests
The kernel IBT cases (`kmod_ibt_*`) load the `cet_driver` kernel module to
exercise Indirect Branch Tracking in kernel space.

`kmod_ibt_illegal` intentionally performs an indirect jump to a target without
an `ENDBR` instruction, which triggers a kernel-space Control Protection (#CP)
fault. The kernel defaults to `ibt_fatal=true`, so the fault ends in `BUG()`
and crashes the machine.

To run this case you MUST boot the kernel with `ibt=warn` on the cmdline. That
sets `ibt_fatal=false`, turning the fault into a recoverable `WARN` that logs
the expected `Missing ENDBR` message. Without `ibt=warn` the case is blocked
(reported as BLOCK) to avoid crashing the host; with `ibt=off` it is also
blocked because IBT is disabled.

Add `ibt=warn` and reboot, for example:
```
grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="ibt=warn"
reboot
```

## Expected result
All test results should show pass, no fail.
14 changes: 14 additions & 0 deletions BM/cet/cet_driver/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ BIN := cet_ioctl
all: $(BIN)

cet_ioctl:
@if [ ! -d "$(KERNEL_SOURCE)" ]; then \
echo "ERROR: kernel build dir $(KERNEL_SOURCE) not found."; \
echo " Install the matching kernel-devel/kernel-headers package"; \
echo " (e.g. 'dnf install kernel-devel-$(shell uname -r)' or"; \
echo " 'apt install linux-headers-$(shell uname -r)') so the"; \
echo " cet_ioctl module can be built, then re-run make."; \
exit 1; \
fi
$(MAKE) -C $(KERNEL_SOURCE) M=$(PWD) modules

clean:
Expand All @@ -21,5 +29,11 @@ clean:
# Otherwise KERNELRELEASE is defined; we've been invoked from the
# kernel build system and can use its language.
else
# The IBT/SHSTK helper functions (cet_ibt1, cet_shstk1, ...) are file-local
# but intentionally kept non-static so the compiler emits real stack frames
# and indirect jumps needed to trigger #CP. Suppress the missing prototype/
# declaration warnings so the module still builds on kernels configured with
# -Werror for them.
ccflags-y += -Wno-missing-prototypes -Wno-missing-declarations
obj-m := cet_ioctl.o
endif
4 changes: 4 additions & 0 deletions BM/cet/cet_driver/cet_ioctl.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@
})
#endif

#ifndef CPUID_LEAF_XSTATE
#define CPUID_LEAF_XSTATE 0xd
#endif
#ifndef CPUID_SUBLEAF_XSTATE_USER
#define CPUID_SUBLEAF_XSTATE_USER 0x0
#endif

#define MSR_IA32_PL3_SSP 0x000006a7 /* user shadow stack pointer */

Expand Down
13 changes: 12 additions & 1 deletion BM/cet/cet_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ load_cet_driver() {

pat=$(pwd)
echo "pat:$pat"
[[ -e "$KO_FILE" ]] || block_test "No $TEST_MOD_KO exist, please make it first"
[[ -e "$KO_FILE" ]] || block_test "No $TEST_MOD_KO exist; build it first with 'make' in cet/cet_driver (requires kernel-devel/headers for $(uname -r))"
mod_info=$(modinfo "$KO_FILE")
ker_ver=$(uname -r)
if [[ "$mod_info" == *"$ker_ver"* ]]; then
Expand Down Expand Up @@ -251,6 +251,17 @@ cet_tests() {
cet_dmesg_check "$bin_file" "$PARM" "$KEYWORD" "$CONTAIN"
;;
kmod_ibt_illegal)
# cet_ibt1() in the driver deliberately does an indirect jump to a
# target without ENDBR, triggering a kernel-space #CP. With
# CONFIG_X86_KERNEL_IBT the kernel defaults to ibt_fatal=true, so the
# fault ends in BUG() and crashes the machine. Only ibt=warn
# (ibt_fatal=false) turns it into a recoverable WARN that logs the
# expected "Missing ENDBR". Guard here to avoid crashing the host.
if grep -qw "ibt=off" /proc/cmdline; then
block_test "Kernel IBT is disabled via ibt=off; kmod_ibt_illegal needs IBT enabled"
fi
grep -qw "ibt=warn" /proc/cmdline || \
block_test "kmod_ibt_illegal triggers a fatal kernel #CP (BUG) without ibt=warn; boot with ibt=warn to run it safely"
load_cet_driver
cet_dmesg_check "$bin_file" "$PARM" "$KEYWORD" "$CONTAIN"
;;
Expand Down
154 changes: 154 additions & 0 deletions BM/cet/cet_thread.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Intel Corporation.
/*
* cet_thread.c:
*
* Author: Pengfei Xu <pengfei.xu@intel.com>
*
* - CET(Control-flow Enforcement Technology) verification in threads.
* - Parameter "s": test shadow stack violation in a new thread, which
* should be #CP blocked and get SIGSEGV.
* - Parameter "i": test indirect branch tracking violation in a new
* thread, which should be #CP blocked and get SIGSEGV.
*/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

/* Upstream kernel reports control protection fault as SEGV_CPERR. */
#ifndef SEGV_CPERR
#define SEGV_CPERR 3
#endif

void printids(const char *s)
{
pid_t pid;
pthread_t tid;

pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,
(unsigned int)tid, (unsigned int)tid);
}

int hack(void)
{
printf("%s function, which should be #cp blocked\n", __func__);
sleep(1);
return 1;
}

void *thr_shstk(void *arg)
{
unsigned long *p;

printids("new shstk thread: ");
#ifdef __x86_64__
asm("movq %%rbp,%0" : "=r"(p));
#else
asm("mov %%ebp,%0" : "=r"(p));
#endif

*(p + 1) = (unsigned long)hack;
return NULL;
}

void *thr_ibt(void *arg)
{
printids("new ibt thread: ");
#ifdef __x86_64__
asm volatile("leaq 1f, %rax");
asm volatile("jmpq *%rax");
#else
asm volatile("lea 1f, %eax");
asm volatile("jmp *%eax");
#endif
asm volatile("1:");
printf("ibt test, which should be #cp blocked\n");
return NULL;
}

void segv_handler(int signum, siginfo_t *si, void *uc)
{
int exp_code = 8, new_code = 10;

printf("si_signo:%d\n", si->si_signo);
printf("si_errno:%d\n", si->si_errno);
printf("si_code:%d\n", si->si_code);
if (si->si_code == exp_code || si->si_code == new_code ||
si->si_code == SEGV_CPERR)
printf("Got SIGSEGV(11) and si_code(%d|%d|%d) as expected\n",
exp_code, new_code, SEGV_CPERR);
else {
printf("si_code error, actual:%d, expect:%d|%d|%d.\n",
si->si_code, exp_code, new_code, SEGV_CPERR);
exit(1);
}
exit(0);
}

void usage(void)
{
printf("Usage: [s][i]\n");
printf("s: Test shadow stack in thread\n");
printf("i: Test ibt in thread\n");
}

int main(int argc, char *argv[])
{
int err, r;
pthread_t ntid;
struct sigaction sa;
char parm;

if (argc == 1) {
usage();
exit(2);
} else {
if (sscanf(argv[1], "%c", &parm) != 1)
return -EINVAL;
printf("parm:%c\n", parm);
}

r = sigemptyset(&sa.sa_mask);
if (r) {
printf("Init empty signal failed\n");
return -1;
}
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = segv_handler;
r = sigaction(SIGSEGV, &sa, NULL);
if (r) {
printf("Could not handle SIGSEGV(11)\n");
return -1;
}

switch (parm) {
case 's':
err = pthread_create(&ntid, NULL, thr_shstk, NULL);
if (err != 0) {
printf("can't create thr_shstk: %s\n", strerror(err));
exit(1);
}
break;
case 'i':
err = pthread_create(&ntid, NULL, thr_ibt, NULL);
if (err != 0) {
printf("can't create thr_ibt: %s\n", strerror(err));
exit(1);
}
break;
default:
usage();
exit(2);
}
printids("process created thread:");
sleep(2);
pthread_join(ntid, NULL);
return EXIT_FAILURE;
}
Loading
Loading