tests/arch/x86: Add tests for shadow stack

Ensure they behave as expected.

Signed-off-by: Ederson de Souza <ederson.desouza@intel.com>
This commit is contained in:
Ederson de Souza
2025-05-07 14:00:07 -07:00
committed by Benjamin Cabé
parent 948c884054
commit eb6da2ec20
3 changed files with 146 additions and 9 deletions

View File

@@ -1,2 +1,3 @@
CONFIG_ZTEST=y
CONFIG_X86_CET=y
CONFIG_IRQ_OFFLOAD=y

View File

@@ -5,32 +5,38 @@
*/
#include <zephyr/ztest.h>
#include <zephyr/arch/exception.h>
#include <zephyr/irq_offload.h>
#include <zephyr/kernel/thread_stack.h>
#define IV_CTRL_PROTECTION_EXCEPTION 21
#define CTRL_PROTECTION_ERRORCODE_NEAR_RET 1
#define CTRL_PROTECTION_ERRORCODE_ENDBRANCH 3
extern int should_work(int a);
extern int should_not_work(int a);
#define STACKSIZE 1024
#define THREAD_PRIORITY 5
static bool expect_fault;
static int expect_code;
K_SEM_DEFINE(error_handler_sem, 0, 1);
volatile bool expect_fault;
volatile int expect_code;
volatile int expect_reason;
void k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf *pEsf)
{
if (expect_fault) {
#ifdef CONFIG_X86_64
zassert_equal(pEsf->vector, IV_CTRL_PROTECTION_EXCEPTION,
"unexpected exception");
zassert_equal(pEsf->vector, expect_reason, "unexpected exception");
zassert_equal(pEsf->code, expect_code, "unexpected error code");
#else
zassert_equal(z_x86_exception_vector, IV_CTRL_PROTECTION_EXCEPTION,
"unexpected exception");
zassert_equal(z_x86_exception_vector, expect_reason, "unexpected exception");
zassert_equal(pEsf->errorCode, expect_code, "unexpected error code");
#endif
printk("fatal error expected as part of test case\n");
expect_fault = false;
k_sem_give(&error_handler_sem);
} else {
printk("fatal error was unexpected, aborting\n");
TC_END_REPORT(TC_FAIL);
@@ -38,6 +44,123 @@ void k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf *pEsf)
}
}
#ifdef CONFIG_HW_SHADOW_STACK
void thread_a_entry(void *p1, void *p2, void *p3);
K_SEM_DEFINE(thread_a_sem, 0, 1);
K_THREAD_DEFINE(thread_a, STACKSIZE, thread_a_entry, NULL, NULL, NULL,
THREAD_PRIORITY, 0, -1);
void thread_b_entry(void *p1, void *p2, void *p3);
K_SEM_DEFINE(thread_b_sem, 0, 1);
K_SEM_DEFINE(thread_b_irq_sem, 0, 1);
K_THREAD_DEFINE(thread_b, STACKSIZE, thread_b_entry, NULL, NULL, NULL,
THREAD_PRIORITY, 0, -1);
static bool is_shstk_enabled(void)
{
long cur;
cur = z_x86_msr_read(X86_S_CET_MSR);
return (cur & X86_S_CET_MSR_SHSTK_EN) == X86_S_CET_MSR_SHSTK_EN;
}
void thread_c_entry(void *p1, void *p2, void *p3)
{
zassert_true(is_shstk_enabled(), "shadow stack not enabled on static thread");
}
K_THREAD_DEFINE(thread_c, STACKSIZE, thread_c_entry, NULL, NULL, NULL,
THREAD_PRIORITY, 0, 0);
void __attribute__((optimize("O0"))) foo(void)
{
printk("foo called\n");
}
void __attribute__((optimize("O0"))) fail(void)
{
long a[] = {0};
printk("should fail after this\n");
*(a + 2) = (long)&foo;
}
struct k_work work;
void work_handler(struct k_work *wrk)
{
printk("work handler\n");
zassert_true(is_shstk_enabled(), "shadow stack not enabled");
}
ZTEST(cet, test_shstk_work_q)
{
k_work_init(&work, work_handler);
k_work_submit(&work);
}
void intr_handler(const void *p)
{
printk("interrupt handler\n");
if (p != NULL) {
/* Test one nested level. It should just work. */
printk("trying interrupt handler\n");
irq_offload(intr_handler, NULL);
k_sem_give((struct k_sem *)p);
} else {
printk("interrupt handler nested\n");
}
}
void thread_b_entry(void *p1, void *p2, void *p3)
{
k_sem_take(&thread_b_sem, K_FOREVER);
irq_offload(intr_handler, &thread_b_irq_sem);
k_sem_take(&thread_b_irq_sem, K_FOREVER);
}
ZTEST(cet, test_shstk_irq)
{
k_thread_start(thread_b);
k_sem_give(&thread_b_sem);
k_thread_join(thread_b, K_FOREVER);
}
void thread_a_entry(void *p1, void *p2, void *p3)
{
k_sem_take(&thread_a_sem, K_FOREVER);
fail();
zassert_unreachable("should not reach here");
}
ZTEST(cet, test_shstk)
{
k_thread_start(thread_a);
expect_fault = true;
expect_code = CTRL_PROTECTION_ERRORCODE_NEAR_RET;
expect_reason = IV_CTRL_PROTECTION_EXCEPTION;
k_sem_give(&thread_a_sem);
k_sem_take(&error_handler_sem, K_FOREVER);
k_thread_abort(thread_a);
}
#endif /* CONFIG_HW_SHADOW_STACK */
#ifdef CONFIG_X86_CET_IBT
extern int should_work(int a);
extern int should_not_work(int a);
/* Round trip to trick optimisations and ensure the calls are indirect */
int do_call(int (*func)(int), int a)
{
@@ -50,8 +173,10 @@ ZTEST(cet, test_ibt)
expect_fault = true;
expect_code = CTRL_PROTECTION_ERRORCODE_ENDBRANCH;
expect_reason = IV_CTRL_PROTECTION_EXCEPTION;
do_call(should_not_work, 1);
zassert_unreachable("should_not_work did not fault");
}
#endif /* CONFIG_X86_CET_IBT */
ZTEST_SUITE(cet, NULL, NULL, NULL, NULL, NULL);

View File

@@ -17,3 +17,14 @@ tests:
filter: CONFIG_X86_64
extra_configs:
- CONFIG_X86_CET_IBT=y
arch.x86.cet.kernel.shstk64:
arch_allow: x86
filter: CONFIG_X86_64
extra_configs:
- CONFIG_HW_SHADOW_STACK=y
arch.x86.cet.kernel.shstk32:
arch_allow: x86
filter: not CONFIG_X86_64
extra_configs:
- CONFIG_DEBUG_COREDUMP=y
- CONFIG_HW_SHADOW_STACK=y