diff --git a/tests/kernel/protection/Makefile b/tests/kernel/protection/Makefile new file mode 100644 index 000000000000..783c4018ad3d --- /dev/null +++ b/tests/kernel/protection/Makefile @@ -0,0 +1,4 @@ +BOARD ?= frdm_k64f +CONF_FILE = prj.conf + +include ${ZEPHYR_BASE}/Makefile.test diff --git a/tests/kernel/protection/README.rst b/tests/kernel/protection/README.rst new file mode 100644 index 000000000000..06f46f6e5fef --- /dev/null +++ b/tests/kernel/protection/README.rst @@ -0,0 +1,84 @@ +.. _protection_tests: + +Protection tests +################################# + +Overview +******** +This test case verifies that protection is provided +against the following security issues: + +* Write to read-only data. +* Write to text. +* Execute from data. +* Execute from stack. +* Execute from heap. + +Building and Running +******************** + +This project can be built and executed as follows: + +.. code-block:: console + + $ cd tests/protection + $ make BOARD= + +Connect the board to your host computer using the USB port. +Flash the generated zephyr.bin on the board. +Reset the board. + +Sample Output +============= + +.. code-block:: console + + ***** BOOTING ZEPHYR OS v1.8.99 - BUILD: Jun 19 2017 12:44:27 ***** + Running test suite test_protection + tc_start() - write_ro + trying to write to rodata at 0x00003124 + ***** BUS FAULT ***** + Executing thread ID (thread): 0x200001bc + Faulting instruction address: 0x88c + Imprecise data bus error + Caught system error -- reason 0 + =================================================================== + PASS - write_ro. + tc_start() - write_text + trying to write to text at 0x000006c0 + ***** BUS FAULT ***** + Executing thread ID (thread): 0x200001bc + Faulting instruction address: 0xd60 + Imprecise data bus error + Caught system error -- reason 0 + =================================================================== + PASS - write_text. + tc_start() - exec_data + trying to call code written to 0x2000041d + ***** BUS FAULT ***** + Executing thread ID (thread): 0x200001bc + Faulting instruction address: 0x2000041c + Imprecise data bus error + Caught system error -- reason 0 + =================================================================== + PASS - exec_data. + tc_start() - exec_stack + trying to call code written to 0x20000929 + ***** BUS FAULT ***** + Executing thread ID (thread): 0x200001bc + Faulting instruction address: 0x20000928 + Imprecise data bus error + Caught system error -- reason 0 + =================================================================== + PASS - exec_stack. + tc_start() - exec_heap + trying to call code written to 0x20000455 + ***** BUS FAULT ***** + Executing thread ID (thread): 0x200001bc + Faulting instruction address: 0x20000454 + Imprecise data bus error + Caught system error -- reason 0 + =================================================================== + PASS - exec_heap. + =================================================================== + PROJECT EXECUTION SUCCESSFUL diff --git a/tests/kernel/protection/prj.conf b/tests/kernel/protection/prj.conf new file mode 100644 index 000000000000..a3b3b01065cf --- /dev/null +++ b/tests/kernel/protection/prj.conf @@ -0,0 +1,2 @@ +CONFIG_HEAP_MEM_POOL_SIZE=256 +CONFIG_ZTEST=y diff --git a/tests/kernel/protection/src/Makefile b/tests/kernel/protection/src/Makefile new file mode 100644 index 000000000000..e1f454f63be8 --- /dev/null +++ b/tests/kernel/protection/src/Makefile @@ -0,0 +1,5 @@ +include $(ZEPHYR_BASE)/tests/Makefile.test + +ccflags-y += -I${ZEPHYR_BASE}/tests/include + +obj-y = targets.o main.o diff --git a/tests/kernel/protection/src/main.c b/tests/kernel/protection/src/main.c new file mode 100644 index 000000000000..258e0b0246cc --- /dev/null +++ b/tests/kernel/protection/src/main.c @@ -0,0 +1,170 @@ +/* + * Parts derived from tests/kernel/fatal/src/main.c, which has the + * following copyright and license: + * + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include "targets.h" + +#define INFO(fmt, ...) printk(fmt, ##__VA_ARGS__) + +/* ARM is a special case, in that k_thread_abort() does indeed return + * instead of calling _Swap() directly. The PendSV exception is queued + * and immediately fires upon completing the exception path; the faulting + * thread is never run again. + */ +#ifndef CONFIG_ARM +FUNC_NORETURN +#endif +void _SysFatalErrorHandler(unsigned int reason, const NANO_ESF *pEsf) +{ + INFO("Caught system error -- reason %d\n", reason); + ztest_test_pass(); +#ifndef CONFIG_ARM + CODE_UNREACHABLE; +#endif +} + +#ifdef CONFIG_CPU_CORTEX_M +#include +/* Must clear LSB of function address to access as data. */ +#define FUNC_TO_PTR(x) (void *)((uintptr_t)(x) & ~0x1) +/* Must set LSB of function address to call in Thumb mode. */ +#define PTR_TO_FUNC(x) (int (*)(int))((uintptr_t)(x) | 0x1) +/* Flush preceding data writes and instruction fetches. */ +#define DO_BARRIERS() do { __DSB(); __ISB(); } while (0) +#else +#define FUNC_TO_PTR(x) (void *)(x) +#define PTR_TO_FUNC(x) (int (*)(int))(x) +#define DO_BARRIERS() do { } while (0) +#endif + +static int __attribute__((noinline)) add_one(int i) +{ + return (i + 1); +} + +static void execute_from_buffer(u8_t *dst) +{ + void *src = FUNC_TO_PTR(add_one); + int (*func)(int i) = PTR_TO_FUNC(dst); + int i = 1; + + /* Copy add_one() code to destination buffer. */ + memcpy(dst, src, BUF_SIZE); + DO_BARRIERS(); + + /* + * Try executing from buffer we just filled. + * Optimally, this triggers a fault. + * If not, we check to see if the function + * returned the expected result as confirmation + * that we truly executed the code we wrote. + */ + INFO("trying to call code written to %p\n", func); + i = func(i); + INFO("returned from code at %p\n", func); + if (i == 2) { + INFO("Execute from target buffer succeeded!\n"); + } else { + INFO("Did not get expected return value!\n"); + } +} + +static void write_ro(void) +{ + u32_t *ptr = (u32_t *)&rodata_var; + + /* + * Try writing to rodata. Optimally, this triggers a fault. + * If not, we check to see if the rodata value actually changed. + */ + INFO("trying to write to rodata at %p\n", ptr); + *ptr = ~RODATA_VALUE; + + DO_BARRIERS(); + + if (*ptr == RODATA_VALUE) { + INFO("rodata value still the same\n"); + } else if (*ptr == ~RODATA_VALUE) { + INFO("rodata modified!\n"); + } else { + INFO("something went wrong!\n"); + } + + zassert_unreachable("Write to rodata did not fault"); +} + +static void write_text(void) +{ + void *src = FUNC_TO_PTR(add_one); + void *dst = FUNC_TO_PTR(overwrite_target); + int i = 1; + + /* + * Try writing to a function in the text section. + * Optimally, this triggers a fault. + * If not, we try calling the function after overwriting + * to see if it returns the expected result as + * confirmation that we truly executed the code we wrote. + */ + INFO("trying to write to text at %p\n", dst); + memcpy(dst, src, BUF_SIZE); + DO_BARRIERS(); + i = overwrite_target(i); + if (i == 2) { + INFO("Overwrite of text succeeded!\n"); + } else { + INFO("Did not get expected return value!\n"); + } + + zassert_unreachable("Write to text did not fault"); +} + +static void exec_data(void) +{ + execute_from_buffer(data_buf); + zassert_unreachable("Execute from data did not fault"); +} + +static void exec_stack(void) +{ + u8_t stack_buf[BUF_SIZE] __aligned(sizeof(int)); + + execute_from_buffer(stack_buf); + zassert_unreachable("Execute from stack did not fault"); +} + +#if (CONFIG_HEAP_MEM_POOL_SIZE > 0) +static void exec_heap(void) +{ + u8_t *heap_buf = k_malloc(BUF_SIZE); + + execute_from_buffer(heap_buf); + k_free(heap_buf); + zassert_unreachable("Execute from heap did not fault"); +} +#endif + +void test_main(void *unused1, void *unused2, void *unused3) +{ + ztest_test_suite(test_protection, + ztest_unit_test(write_ro), + ztest_unit_test(write_text), + ztest_unit_test(exec_data), + ztest_unit_test(exec_stack) +#if (CONFIG_HEAP_MEM_POOL_SIZE > 0) + , ztest_unit_test(exec_heap) +#endif + ); + ztest_run_test_suite(test_protection); +} diff --git a/tests/kernel/protection/src/targets.c b/tests/kernel/protection/src/targets.c new file mode 100644 index 000000000000..8aad7d700c0f --- /dev/null +++ b/tests/kernel/protection/src/targets.c @@ -0,0 +1,14 @@ +#include +#include + +#include "targets.h" + +const u32_t rodata_var = RODATA_VALUE; + +u8_t data_buf[BUF_SIZE] __aligned(sizeof(int)); + +int overwrite_target(int i) +{ + printk("text not modified\n"); + return (i - 1); +} diff --git a/tests/kernel/protection/src/targets.h b/tests/kernel/protection/src/targets.h new file mode 100644 index 000000000000..71b71c2dcf79 --- /dev/null +++ b/tests/kernel/protection/src/targets.h @@ -0,0 +1,12 @@ +#ifndef _PROT_TEST_TARGETS_H_ +#define _PROT_TEST_TARGETS_H_ + +#define RODATA_VALUE 0xF00FF00F +extern const u32_t rodata_var; + +#define BUF_SIZE 16 +extern u8_t data_buf[BUF_SIZE]; + +extern int overwrite_target(int i); + +#endif diff --git a/tests/kernel/protection/testcase.ini b/tests/kernel/protection/testcase.ini new file mode 100644 index 000000000000..b9edbaa0a45b --- /dev/null +++ b/tests/kernel/protection/testcase.ini @@ -0,0 +1,3 @@ +[test] +tags = core security ignore_faults +filter = CONFIG_CPU_HAS_MPU or CONFIG_X86_MMU