| /* |
| * 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 <zephyr.h> |
| #include <ztest.h> |
| #include <kernel_structs.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <app_memory/app_memdomain.h> |
| #include <misc/util.h> |
| #include <misc/stack.h> |
| #include <syscall_handler.h> |
| #include "test_syscall.h" |
| |
| #if defined(CONFIG_ARC) |
| #include <arch/arc/v2/mpu/arc_core_mpu.h> |
| #endif |
| |
| #if defined(CONFIG_ARM) |
| #include <arch/arm/cortex_m/mpu/arm_core_mpu_dev.h> |
| #endif |
| |
| #define INFO(fmt, ...) printk(fmt, ##__VA_ARGS__) |
| #define PIPE_LEN 1 |
| #define BYTES_TO_READ_WRITE 1 |
| #define STACKSIZE (1024 + CONFIG_TEST_EXTRA_STACKSIZE) |
| |
| K_SEM_DEFINE(uthread_start_sem, 0, 1); |
| K_SEM_DEFINE(uthread_end_sem, 0, 1); |
| K_SEM_DEFINE(test_revoke_sem, 0, 1); |
| K_SEM_DEFINE(expect_fault_sem, 0, 1); |
| |
| /* |
| * Create partitions. part0 is for all variables to run |
| * ztest and this test suite. part1 is for |
| * subsequent test specifically for this new implementation. |
| */ |
| FOR_EACH(K_APPMEM_PARTITION_DEFINE, part0, part1); |
| |
| /* |
| * Create memory domains. dom0 is for the ztest and this |
| * test suite, specifically. dom1 is for a specific test |
| * in this test suite. |
| */ |
| struct k_mem_domain dom0; |
| struct k_mem_domain dom1; |
| |
| K_APP_DMEM(part0) static volatile bool give_uthread_end_sem; |
| K_APP_DMEM(part0) bool mem_access_check; |
| |
| K_APP_BMEM(part0) static volatile bool expect_fault; |
| |
| #if defined(CONFIG_X86) |
| #define REASON_HW_EXCEPTION _NANO_ERR_CPU_EXCEPTION |
| #define REASON_KERNEL_OOPS _NANO_ERR_KERNEL_OOPS |
| #elif defined(CONFIG_ARM) |
| #define REASON_HW_EXCEPTION _NANO_ERR_HW_EXCEPTION |
| #define REASON_KERNEL_OOPS _NANO_ERR_HW_EXCEPTION |
| #elif defined(CONFIG_ARC) |
| #define REASON_HW_EXCEPTION _NANO_ERR_HW_EXCEPTION |
| #define REASON_KERNEL_OOPS _NANO_ERR_KERNEL_OOPS |
| #else |
| #error "Not implemented for this architecture" |
| #endif |
| K_APP_BMEM(part0) static volatile unsigned int expected_reason; |
| |
| /* |
| * We need something that can act as a memory barrier |
| * from usermode threads to ensure expect_fault and |
| * expected_reason has been updated. We'll just make an |
| * arbitrary system call to force one. |
| */ |
| #define BARRIER() k_sem_give(&expect_fault_sem) |
| |
| /* ARM is a special case, in that k_thread_abort() does indeed return |
| * instead of calling z_swap() directly. The PendSV exception is queued |
| * and immediately fires upon completing the exception path; the faulting |
| * thread is never run again. |
| */ |
| #if !(defined(CONFIG_ARM) || defined(CONFIG_ARC)) |
| FUNC_NORETURN |
| #endif |
| void z_SysFatalErrorHandler(unsigned int reason, const NANO_ESF *pEsf) |
| { |
| INFO("Caught system error -- reason %d\n", reason); |
| /* |
| * If there is a user thread waiting for notification to exit, |
| * give it that notification. |
| */ |
| if (give_uthread_end_sem) { |
| give_uthread_end_sem = false; |
| k_sem_give(&uthread_end_sem); |
| } |
| |
| if (expect_fault && expected_reason == reason) { |
| expect_fault = false; |
| expected_reason = 0; |
| BARRIER(); |
| ztest_test_pass(); |
| } else { |
| zassert_unreachable("Unexpected fault during test"); |
| } |
| #if !(defined(CONFIG_ARM) || defined(CONFIG_ARC)) |
| CODE_UNREACHABLE; |
| #endif |
| } |
| |
| /** |
| * @brief Test to check if the thread is in user mode |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void is_usermode(void) |
| { |
| /* Confirm that we are in fact running in user mode. */ |
| expect_fault = false; |
| BARRIER(); |
| zassert_true(_is_user_context(), "thread left in kernel mode"); |
| } |
| |
| /** |
| * @brief Test to write to a control register |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void write_control(void) |
| { |
| /* Try to write to a control register. */ |
| #if defined(CONFIG_X86) |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| __asm__ volatile ( |
| "mov %cr0, %eax;\n\t" |
| "and $0xfffeffff, %eax;\n\t" |
| "mov %eax, %cr0;\n\t" |
| ); |
| zassert_unreachable("Write to control register did not fault"); |
| #elif defined(CONFIG_ARM) |
| unsigned int msr_value; |
| |
| expect_fault = false; |
| BARRIER(); |
| __asm__ volatile ( |
| "mrs %0, CONTROL;\n\t" |
| "bic %0, #1;\n\t" |
| "msr CONTROL, %0;\n\t" |
| "mrs %0, CONTROL;\n\t" |
| : "=r" (msr_value):: |
| ); |
| zassert_true((msr_value & 1), |
| "Write to control register was successful"); |
| #elif defined(CONFIG_ARC) |
| unsigned int er_status; |
| |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| /* _ARC_V2_ERSTATUS is privilege aux reg */ |
| __asm__ volatile ( |
| "lr %0, [0x402]\n" |
| : "=r" (er_status):: |
| ); |
| #else |
| #error "Not implemented for this architecture" |
| zassert_unreachable("Write to control register did not fault"); |
| #endif |
| } |
| |
| /** |
| * @brief Test to disable memory protection |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void disable_mmu_mpu(void) |
| { |
| /* Try to disable memory protections. */ |
| #if defined(CONFIG_X86) |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| __asm__ volatile ( |
| "mov %cr0, %eax;\n\t" |
| "and $0x7ffeffff, %eax;\n\t" |
| "mov %eax, %cr0;\n\t" |
| ); |
| #elif defined(CONFIG_ARM) |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| arm_core_mpu_disable(); |
| #elif defined(CONFIG_ARC) |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| arc_core_mpu_disable(); |
| #else |
| #error "Not implemented for this architecture" |
| #endif |
| zassert_unreachable("Disable MMU/MPU did not fault"); |
| } |
| |
| /** |
| * @brief Test to read from kernel RAM |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void read_kernram(void) |
| { |
| /* Try to read from kernel RAM. */ |
| void *p; |
| |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| p = _current->init_data; |
| printk("%p\n", p); |
| zassert_unreachable("Read from kernel RAM did not fault"); |
| } |
| |
| /** |
| * @brief Test to write to kernel RAM |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void write_kernram(void) |
| { |
| /* Try to write to kernel RAM. */ |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| _current->init_data = NULL; |
| zassert_unreachable("Write to kernel RAM did not fault"); |
| } |
| |
| extern int _k_neg_eagain; |
| |
| #include <linker/linker-defs.h> |
| |
| /** |
| * @brief Test to write kernel RO |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void write_kernro(void) |
| { |
| /* Try to write to kernel RO. */ |
| const char *const ptr = (const char *const)&_k_neg_eagain; |
| |
| zassert_true(ptr < _image_rodata_end && |
| ptr >= _image_rodata_start, |
| "_k_neg_eagain is not in rodata"); |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| _k_neg_eagain = -EINVAL; |
| zassert_unreachable("Write to kernel RO did not fault"); |
| } |
| |
| /** |
| * @brief Test to write to kernel text section |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void write_kerntext(void) |
| { |
| /* Try to write to kernel text. */ |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| memset(&z_is_thread_essential, 0, 4); |
| zassert_unreachable("Write to kernel text did not fault"); |
| } |
| |
| static int kernel_data; |
| |
| /** |
| * @brief Testto read from kernel data section |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void read_kernel_data(void) |
| { |
| /* Try to read from embedded kernel data. */ |
| int value; |
| |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| value = kernel_data; |
| printk("%d\n", value); |
| zassert_unreachable("Read from data did not fault"); |
| } |
| |
| /** |
| * @brief Test to write to kernel data section |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void write_kernel_data(void) |
| { |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| kernel_data = 1; |
| zassert_unreachable("Write to data did not fault"); |
| } |
| |
| /* |
| * volatile to avoid compiler mischief. |
| */ |
| K_APP_DMEM(part0) volatile int *priv_stack_ptr; |
| #if defined(CONFIG_X86) |
| /* |
| * We can't inline this in the code or make it static |
| * or local without triggering a warning on -Warray-bounds. |
| */ |
| K_APP_DMEM(part0) size_t size = MMU_PAGE_SIZE; |
| #elif defined(CONFIG_ARC) |
| K_APP_DMEM(part0) s32_t size = (0 - CONFIG_PRIVILEGED_STACK_SIZE - |
| STACK_GUARD_SIZE); |
| #endif |
| |
| /** |
| * @brief Test to read provileged stack |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void read_priv_stack(void) |
| { |
| /* Try to read from privileged stack. */ |
| #if defined(CONFIG_X86) || defined(CONFIG_ARC) |
| int s[1]; |
| |
| s[0] = 0; |
| priv_stack_ptr = &s[0]; |
| priv_stack_ptr = (int *)((unsigned char *)priv_stack_ptr - size); |
| #elif defined(CONFIG_ARM) |
| /* priv_stack_ptr set by test_main() */ |
| #else |
| #error "Not implemented for this architecture" |
| #endif |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| printk("%d\n", *priv_stack_ptr); |
| zassert_unreachable("Read from privileged stack did not fault"); |
| } |
| |
| /** |
| * @brief Test to write to privilege stack |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void write_priv_stack(void) |
| { |
| /* Try to write to privileged stack. */ |
| #if defined(CONFIG_X86) || defined(CONFIG_ARC) |
| int s[1]; |
| |
| s[0] = 0; |
| priv_stack_ptr = &s[0]; |
| priv_stack_ptr = (int *)((unsigned char *)priv_stack_ptr - size); |
| #elif defined(CONFIG_ARM) |
| /* priv_stack_ptr set by test_main() */ |
| #else |
| #error "Not implemented for this architecture" |
| #endif |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| *priv_stack_ptr = 42; |
| zassert_unreachable("Write to privileged stack did not fault"); |
| } |
| |
| |
| K_APP_BMEM(part0) static struct k_sem sem; |
| |
| /** |
| * @brief Test to pass a user object to system call |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void pass_user_object(void) |
| { |
| /* Try to pass a user object to a system call. */ |
| expect_fault = true; |
| expected_reason = REASON_KERNEL_OOPS; |
| BARRIER(); |
| k_sem_init(&sem, 0, 1); |
| zassert_unreachable("Pass a user object to a syscall did not fault"); |
| } |
| |
| static struct k_sem ksem; |
| |
| /** |
| * @brief Test to pass object to a system call without permissions |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void pass_noperms_object(void) |
| { |
| /* Try to pass a object to a system call w/o permissions. */ |
| expect_fault = true; |
| expected_reason = REASON_KERNEL_OOPS; |
| BARRIER(); |
| k_sem_init(&ksem, 0, 1); |
| zassert_unreachable("Pass an unauthorized object to a " |
| "syscall did not fault"); |
| } |
| |
| struct k_thread kthread_thread; |
| |
| K_THREAD_STACK_DEFINE(kthread_stack, STACKSIZE); |
| |
| void thread_body(void) |
| { |
| } |
| |
| /** |
| * @brief Test to start kernel thread from usermode |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void start_kernel_thread(void) |
| { |
| /* Try to start a kernel thread from a usermode thread */ |
| expect_fault = true; |
| expected_reason = REASON_KERNEL_OOPS; |
| BARRIER(); |
| k_thread_create(&kthread_thread, kthread_stack, STACKSIZE, |
| (k_thread_entry_t)thread_body, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(1), K_INHERIT_PERMS, |
| K_NO_WAIT); |
| zassert_unreachable("Create a kernel thread did not fault"); |
| } |
| |
| struct k_thread uthread_thread; |
| K_THREAD_STACK_DEFINE(uthread_stack, STACKSIZE); |
| |
| static void uthread_body(void) |
| { |
| /* Notify our creator that we are alive. */ |
| k_sem_give(&uthread_start_sem); |
| /* Request notification of when we should exit. */ |
| give_uthread_end_sem = true; |
| /* Wait until notified by the fault handler or by the creator. */ |
| k_sem_take(&uthread_end_sem, K_FOREVER); |
| } |
| |
| /** |
| * @brief Test to read from another thread's stack |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void read_other_stack(void) |
| { |
| /* Try to read from another thread's stack. */ |
| unsigned int *ptr; |
| |
| k_thread_create(&uthread_thread, uthread_stack, STACKSIZE, |
| (k_thread_entry_t)uthread_body, NULL, NULL, NULL, |
| -1, K_USER | K_INHERIT_PERMS, |
| K_NO_WAIT); |
| |
| /* Ensure that the other thread has begun. */ |
| k_sem_take(&uthread_start_sem, K_FOREVER); |
| |
| /* Try to directly read the stack of the other thread. */ |
| ptr = (unsigned int *)Z_THREAD_STACK_BUFFER(uthread_stack); |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| printk("%u\n", *ptr); |
| |
| /* Shouldn't be reached, but if so, let the other thread exit */ |
| if (give_uthread_end_sem) { |
| give_uthread_end_sem = false; |
| k_sem_give(&uthread_end_sem); |
| } |
| zassert_unreachable("Read from other thread stack did not fault"); |
| } |
| |
| /** |
| * @brief Test to write to other thread's stack |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void write_other_stack(void) |
| { |
| /* Try to write to another thread's stack. */ |
| unsigned int *ptr; |
| |
| k_thread_create(&uthread_thread, uthread_stack, STACKSIZE, |
| (k_thread_entry_t)uthread_body, NULL, NULL, NULL, |
| -1, K_USER | K_INHERIT_PERMS, |
| K_NO_WAIT); |
| |
| /* Ensure that the other thread has begun. */ |
| k_sem_take(&uthread_start_sem, K_FOREVER); |
| |
| /* Try to directly write the stack of the other thread. */ |
| ptr = (unsigned int *) Z_THREAD_STACK_BUFFER(uthread_stack); |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| *ptr = 0U; |
| |
| /* Shouldn't be reached, but if so, let the other thread exit */ |
| if (give_uthread_end_sem) { |
| give_uthread_end_sem = false; |
| k_sem_give(&uthread_end_sem); |
| } |
| zassert_unreachable("Write to other thread stack did not fault"); |
| } |
| |
| /** |
| * @brief Test to revoke access to kobject without permission |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void revoke_noperms_object(void) |
| { |
| /* Attempt to revoke access to kobject w/o permissions*/ |
| expect_fault = true; |
| expected_reason = REASON_KERNEL_OOPS; |
| BARRIER(); |
| k_object_release(&ksem); |
| |
| zassert_unreachable("Revoke access to unauthorized object " |
| "did not fault"); |
| } |
| |
| /** |
| * @brief Test to access object after revoking access |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void access_after_revoke(void) |
| { |
| k_object_release(&test_revoke_sem); |
| |
| /* Try to access an object after revoking access to it */ |
| expect_fault = true; |
| expected_reason = REASON_KERNEL_OOPS; |
| BARRIER(); |
| k_sem_take(&test_revoke_sem, K_NO_WAIT); |
| |
| zassert_unreachable("Using revoked object did not fault"); |
| } |
| |
| static void umode_enter_func(void) |
| { |
| if (_is_user_context()) { |
| /* |
| * Have to explicitly call ztest_test_pass() because |
| * k_thread_user_mode_enter() does not return. We have |
| * to signal a pass status or else run_test() will hang |
| * forever waiting on test_end_signal semaphore. |
| */ |
| ztest_test_pass(); |
| } else { |
| zassert_unreachable("Thread did not enter user mode"); |
| } |
| } |
| |
| /** |
| * @brief Test to check enter to usermode |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void user_mode_enter(void) |
| { |
| expect_fault = false; |
| BARRIER(); |
| k_thread_user_mode_enter((k_thread_entry_t)umode_enter_func, |
| NULL, NULL, NULL); |
| } |
| |
| /* Define and initialize pipe. */ |
| K_PIPE_DEFINE(kpipe, PIPE_LEN, BYTES_TO_READ_WRITE); |
| K_APP_BMEM(part0) static size_t bytes_written_read; |
| |
| /** |
| * @brief Test to write to kobject using pipe |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void write_kobject_user_pipe(void) |
| { |
| /* |
| * Attempt to use system call from k_pipe_get to write over |
| * a kernel object. |
| */ |
| expect_fault = true; |
| expected_reason = REASON_KERNEL_OOPS; |
| BARRIER(); |
| k_pipe_get(&kpipe, &uthread_start_sem, BYTES_TO_READ_WRITE, |
| &bytes_written_read, 1, K_NO_WAIT); |
| |
| zassert_unreachable("System call memory write validation " |
| "did not fault"); |
| } |
| |
| /** |
| * @brief Test to read from kobject using pipe |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void read_kobject_user_pipe(void) |
| { |
| /* |
| * Attempt to use system call from k_pipe_put to read a |
| * kernel object. |
| */ |
| expect_fault = true; |
| expected_reason = REASON_KERNEL_OOPS; |
| BARRIER(); |
| k_pipe_put(&kpipe, &uthread_start_sem, BYTES_TO_READ_WRITE, |
| &bytes_written_read, 1, K_NO_WAIT); |
| |
| zassert_unreachable("System call memory read validation " |
| "did not fault"); |
| } |
| |
| /* Create bool in part1 partitions */ |
| K_APP_DMEM(part1) bool thread_bool; |
| |
| static void shared_mem_thread(void) |
| { |
| /* |
| * Try to access thread_bool_1 in denied memory |
| * domain. |
| */ |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| BARRIER(); |
| thread_bool = false; |
| zassert_unreachable("Thread accessed global in other " |
| "memory domain\n"); |
| } |
| |
| /** |
| * @brief Test to access other memory domain |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void access_other_memdomain(void) |
| { |
| struct k_mem_partition *parts[] = {&part0}; |
| /* |
| * Following tests the ability for a thread to access data |
| * in a domain that it is denied. |
| */ |
| |
| k_mem_domain_init(&dom1, ARRAY_SIZE(parts), parts); |
| |
| /* remove current thread from domain dom0 and add to dom1 */ |
| k_mem_domain_remove_thread(k_current_get()); |
| k_mem_domain_add_thread(&dom1, k_current_get()); |
| |
| /* Create user mode thread */ |
| k_thread_create(&uthread_thread, uthread_stack, STACKSIZE, |
| (k_thread_entry_t)shared_mem_thread, NULL, |
| NULL, NULL, -1, K_USER | K_INHERIT_PERMS, K_NO_WAIT); |
| |
| k_thread_abort(k_current_get()); |
| |
| } |
| |
| |
| #if defined(CONFIG_ARM) |
| extern u8_t *z_priv_stack_find(void *obj); |
| extern k_thread_stack_t ztest_thread_stack[]; |
| #endif |
| |
| struct k_mem_domain add_thread_drop_dom; |
| struct k_mem_domain add_part_drop_dom; |
| struct k_mem_domain remove_thread_drop_dom; |
| struct k_mem_domain remove_part_drop_dom; |
| |
| struct k_mem_domain add_thread_ctx_dom; |
| struct k_mem_domain add_part_ctx_dom; |
| struct k_mem_domain remove_thread_ctx_dom; |
| struct k_mem_domain remove_part_ctx_dom; |
| |
| K_APPMEM_PARTITION_DEFINE(access_part); |
| K_APP_BMEM(access_part) volatile bool test_bool; |
| |
| static void user_half(void *arg1, void *arg2, void *arg3) |
| { |
| test_bool = 1; |
| if (!expect_fault) { |
| ztest_test_pass(); |
| } else { |
| ztest_test_fail(); |
| } |
| } |
| |
| /** |
| * Show that changing between memory domains and dropping to user mode works |
| * as expected. |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void domain_add_thread_drop_to_user(void) |
| { |
| struct k_mem_partition *parts[] = {&part0, &access_part, |
| &ztest_mem_partition}; |
| |
| expect_fault = false; |
| k_mem_domain_init(&add_thread_drop_dom, ARRAY_SIZE(parts), parts); |
| k_mem_domain_remove_thread(k_current_get()); |
| |
| k_sleep(1); |
| k_mem_domain_add_thread(&add_thread_drop_dom, k_current_get()); |
| |
| k_thread_user_mode_enter(user_half, NULL, NULL, NULL); |
| } |
| |
| /* Show that adding a partition to a domain and then dropping to user mode |
| * works as expected. |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void domain_add_part_drop_to_user(void) |
| { |
| struct k_mem_partition *parts[] = {&part0, &ztest_mem_partition}; |
| |
| expect_fault = false; |
| k_mem_domain_init(&add_part_drop_dom, ARRAY_SIZE(parts), parts); |
| k_mem_domain_remove_thread(k_current_get()); |
| k_mem_domain_add_thread(&add_part_drop_dom, k_current_get()); |
| |
| k_sleep(1); |
| k_mem_domain_add_partition(&add_part_drop_dom, &access_part); |
| |
| k_thread_user_mode_enter(user_half, NULL, NULL, NULL); |
| } |
| |
| /* Show that self-removing from a memory domain and then dropping to user |
| * mode faults as expected. |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void domain_remove_thread_drop_to_user(void) |
| { |
| struct k_mem_partition *parts[] = {&part0, &access_part, |
| &ztest_mem_partition}; |
| |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| k_mem_domain_init(&remove_thread_drop_dom, ARRAY_SIZE(parts), parts); |
| k_mem_domain_remove_thread(k_current_get()); |
| k_mem_domain_add_thread(&remove_thread_drop_dom, k_current_get()); |
| |
| k_sleep(1); |
| k_mem_domain_remove_thread(k_current_get()); |
| |
| k_thread_user_mode_enter(user_half, NULL, NULL, NULL); |
| } |
| |
| /** |
| * Show that self-removing a partition from a domain we are a membed of, |
| * and then dropping to user mode faults as expected. |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void domain_remove_part_drop_to_user(void) |
| { |
| struct k_mem_partition *parts[] = {&part0, &access_part, |
| &ztest_mem_partition}; |
| |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| k_mem_domain_init(&remove_part_drop_dom, ARRAY_SIZE(parts), parts); |
| k_mem_domain_remove_thread(k_current_get()); |
| k_mem_domain_add_thread(&remove_part_drop_dom, k_current_get()); |
| |
| k_sleep(1); |
| k_mem_domain_remove_partition(&remove_part_drop_dom, &access_part); |
| |
| k_thread_user_mode_enter(user_half, NULL, NULL, NULL); |
| } |
| |
| static void user_ctx_switch_half(void *arg1, void *arg2, void *arg3) |
| { |
| test_bool = 1; |
| k_sem_give(&uthread_end_sem); |
| } |
| |
| static void spawn_user(void) |
| { |
| k_sem_reset(&uthread_end_sem); |
| k_object_access_grant(&uthread_end_sem, k_current_get()); |
| |
| k_thread_create(&kthread_thread, kthread_stack, STACKSIZE, |
| user_ctx_switch_half, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(1), K_INHERIT_PERMS | K_USER, |
| K_NO_WAIT); |
| |
| k_sem_take(&uthread_end_sem, K_FOREVER); |
| if (expect_fault) { |
| ztest_test_fail(); |
| } |
| } |
| |
| /** |
| * Show that changing between memory domains and then switching to another |
| * thread in the same domain works as expected. |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void domain_add_thread_context_switch(void) |
| { |
| struct k_mem_partition *parts[] = {&part0, &access_part, |
| &ztest_mem_partition}; |
| |
| expect_fault = false; |
| k_mem_domain_init(&add_thread_ctx_dom, ARRAY_SIZE(parts), parts); |
| k_mem_domain_remove_thread(k_current_get()); |
| |
| k_sleep(1); |
| k_mem_domain_add_thread(&add_thread_ctx_dom, k_current_get()); |
| |
| spawn_user(); |
| } |
| |
| /* Show that adding a partition to a domain and then switching to another |
| * user thread in the same domain works as expected. |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void domain_add_part_context_switch(void) |
| { |
| struct k_mem_partition *parts[] = {&part0, &ztest_mem_partition}; |
| |
| expect_fault = false; |
| k_mem_domain_init(&add_part_ctx_dom, ARRAY_SIZE(parts), parts); |
| k_mem_domain_remove_thread(k_current_get()); |
| k_mem_domain_add_thread(&add_part_ctx_dom, k_current_get()); |
| |
| k_sleep(1); |
| k_mem_domain_add_partition(&add_part_ctx_dom, &access_part); |
| |
| spawn_user(); |
| } |
| |
| /* Show that self-removing from a memory domain and then switching to another |
| * user thread in the same domain faults as expected. |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void domain_remove_thread_context_switch(void) |
| { |
| struct k_mem_partition *parts[] = {&part0, &access_part, |
| &ztest_mem_partition}; |
| |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| k_mem_domain_init(&remove_thread_ctx_dom, ARRAY_SIZE(parts), parts); |
| k_mem_domain_remove_thread(k_current_get()); |
| k_mem_domain_add_thread(&remove_thread_ctx_dom, k_current_get()); |
| |
| k_sleep(1); |
| k_mem_domain_remove_thread(k_current_get()); |
| |
| spawn_user(); |
| } |
| |
| /** |
| * Show that self-removing a partition from a domain we are a member of, |
| * and then switching to another user thread in the same domain faults as |
| * expected. |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| static void domain_remove_part_context_switch(void) |
| { |
| struct k_mem_partition *parts[] = {&part0, &access_part, |
| &ztest_mem_partition}; |
| |
| expect_fault = true; |
| expected_reason = REASON_HW_EXCEPTION; |
| k_mem_domain_init(&remove_part_ctx_dom, ARRAY_SIZE(parts), parts); |
| k_mem_domain_remove_thread(k_current_get()); |
| k_mem_domain_add_thread(&remove_part_ctx_dom, k_current_get()); |
| |
| k_sleep(1); |
| k_mem_domain_remove_partition(&remove_part_ctx_dom, &access_part); |
| |
| spawn_user(); |
| } |
| |
| /* |
| * Stack testing |
| */ |
| |
| #define NUM_STACKS 3 |
| #define STEST_STACKSIZE (1024 + CONFIG_TEST_EXTRA_STACKSIZE) |
| K_THREAD_STACK_DEFINE(stest_stack, STEST_STACKSIZE); |
| K_THREAD_STACK_ARRAY_DEFINE(stest_stack_array, NUM_STACKS, STEST_STACKSIZE); |
| |
| struct foo { |
| int bar; |
| K_THREAD_STACK_MEMBER(stack, STEST_STACKSIZE); |
| int baz; |
| }; |
| |
| struct foo stest_member_stack; |
| |
| void z_impl_stack_info_get(u32_t *start_addr, u32_t *size) |
| { |
| *start_addr = k_current_get()->stack_info.start; |
| *size = k_current_get()->stack_info.size; |
| } |
| |
| Z_SYSCALL_HANDLER(stack_info_get, start_addr, size) |
| { |
| Z_OOPS(Z_SYSCALL_MEMORY_WRITE(start_addr, sizeof(u32_t))); |
| Z_OOPS(Z_SYSCALL_MEMORY_WRITE(size, sizeof(u32_t))); |
| |
| z_impl_stack_info_get((u32_t *)start_addr, (u32_t *)size); |
| |
| return 0; |
| } |
| |
| int z_impl_check_perms(void *addr, size_t size, int write) |
| { |
| return z_arch_buffer_validate(addr, size, write); |
| } |
| |
| Z_SYSCALL_HANDLER(check_perms, addr, size, write) |
| { |
| return z_impl_check_perms((void *)addr, size, write); |
| } |
| |
| void stack_buffer_scenarios(k_thread_stack_t *stack_obj, size_t obj_size) |
| { |
| size_t stack_size; |
| u8_t val; |
| char *stack_start, *stack_ptr, *stack_end, *obj_start, *obj_end; |
| volatile char *pos; |
| |
| expect_fault = false; |
| |
| |
| /* Dump interesting information */ |
| |
| stack_info_get((u32_t *)&stack_start, (u32_t *)&stack_size); |
| printk(" - Thread reports buffer %p size %zu\n", stack_start, |
| stack_size); |
| |
| stack_end = stack_start + stack_size; |
| obj_end = (char *)stack_obj + obj_size; |
| obj_start = (char *)stack_obj; |
| |
| /* Assert that the created stack object, with the reserved data |
| * removed, can hold a thread buffer of STEST_STACKSIZE |
| */ |
| zassert_true(STEST_STACKSIZE <= (obj_size - K_THREAD_STACK_RESERVED), |
| "bad stack size in object"); |
| |
| /* Check that the stack info in the thread marks a region |
| * completely contained within the stack object |
| */ |
| zassert_true(stack_end <= obj_end, |
| "stack size in thread struct out of bounds (overflow)"); |
| zassert_true(stack_start >= obj_start, |
| "stack size in thread struct out of bounds (underflow)"); |
| |
| /* Check that the entire stack buffer is read/writable */ |
| printk(" - check read/write to stack buffer\n"); |
| |
| /* Address of this stack variable is guaranteed to part of |
| * the active stack, and close to the actual stack pointer. |
| * Some CPUs have hardware stack overflow detection which |
| * faults on memory access within the stack buffer but below |
| * the stack pointer. |
| * |
| * First test does direct read & write starting at the estimated |
| * stack pointer up to the highest addresses in the buffer |
| */ |
| stack_ptr = &val; |
| for (pos = stack_ptr; pos < stack_end; pos++) { |
| /* pos is volatile so this doesn't get optimized out */ |
| val = *pos; |
| *pos = val; |
| } |
| |
| if (z_arch_is_user_context()) { |
| /* If we're in user mode, check every byte in the stack buffer |
| * to ensure that the thread has permissions on it. |
| */ |
| for (pos = stack_start; pos < stack_end; pos++) { |
| zassert_false(check_perms((void *)pos, 1, 1), |
| "bad MPU/MMU permission on stack buffer at address %p", |
| pos); |
| } |
| |
| /* Bounds check the user accessible area, it shouldn't extend |
| * before or after the stack. Because of memory protection HW |
| * alignment constraints, we test the end of the stack object |
| * and not the buffer. |
| */ |
| zassert_true(check_perms(obj_start - 1, 1, 0), |
| "user mode access to memory before start of stack object"); |
| zassert_true(check_perms(obj_end, 1, 0), |
| "user mode access past end of stack object"); |
| } |
| |
| |
| /* This API is being removed just whine about it for now */ |
| if (Z_THREAD_STACK_BUFFER(stack_obj) != stack_start) { |
| printk("WARNING: Z_THREAD_STACK_BUFFER() reports %p\n", |
| Z_THREAD_STACK_BUFFER(stack_obj)); |
| } |
| |
| if (z_arch_is_user_context()) { |
| zassert_true(stack_size <= obj_size - K_THREAD_STACK_RESERVED, |
| "bad stack size in thread struct"); |
| } |
| |
| |
| k_sem_give(&uthread_end_sem); |
| } |
| |
| void stest_thread_entry(void *p1, void *p2, void *p3) |
| { |
| bool drop = (bool)p3; |
| |
| if (drop) { |
| k_thread_user_mode_enter(stest_thread_entry, p1, p2, |
| (void *)false); |
| } else { |
| stack_buffer_scenarios((k_thread_stack_t *)p1, (size_t)p2); |
| } |
| } |
| |
| void stest_thread_launch(void *stack_obj, size_t obj_size, u32_t flags, |
| bool drop) |
| { |
| k_thread_create(&uthread_thread, stack_obj, STEST_STACKSIZE, |
| stest_thread_entry, stack_obj, (void *)obj_size, |
| (void *)drop, |
| -1, flags, K_NO_WAIT); |
| k_sem_take(&uthread_end_sem, K_FOREVER); |
| |
| stack_analyze("test_thread", (char *)uthread_thread.stack_info.start, |
| uthread_thread.stack_info.size); |
| } |
| |
| void scenario_entry(void *stack_obj, size_t obj_size) |
| { |
| printk("Stack object %p[%zu]\n", stack_obj, obj_size); |
| printk(" - Testing supervisor mode\n"); |
| stest_thread_launch(stack_obj, obj_size, 0, false); |
| printk(" - Testing user mode (direct launch)\n"); |
| stest_thread_launch(stack_obj, obj_size, K_USER | K_INHERIT_PERMS, |
| false); |
| printk(" - Testing user mode (drop)\n"); |
| stest_thread_launch(stack_obj, obj_size, K_INHERIT_PERMS, |
| true); |
| } |
| |
| void test_stack_buffer(void) |
| { |
| printk("Reserved space: %u\n", K_THREAD_STACK_RESERVED); |
| printk("Provided stack size: %u\n", STEST_STACKSIZE); |
| scenario_entry(stest_stack, sizeof(stest_stack)); |
| |
| for (int i = 0; i < NUM_STACKS; i++) { |
| scenario_entry(stest_stack_array[i], |
| sizeof(stest_stack_array[i])); |
| } |
| |
| scenario_entry(&stest_member_stack.stack, |
| sizeof(stest_member_stack.stack)); |
| |
| } |
| |
| void z_impl_missing_syscall(void) |
| { |
| /* Shouldn't ever get here; no handler function compiled */ |
| k_panic(); |
| } |
| |
| void test_unimplemented_syscall(void) |
| { |
| expect_fault = true; |
| expected_reason = REASON_KERNEL_OOPS; |
| |
| missing_syscall(); |
| } |
| |
| void test_bad_syscall(void) |
| { |
| expect_fault = true; |
| expected_reason = REASON_KERNEL_OOPS; |
| |
| z_arch_syscall_invoke0(INT_MAX); |
| |
| } |
| |
| static struct k_sem recycle_sem; |
| |
| |
| void test_object_recycle(void) |
| { |
| struct _k_object *ko; |
| int perms_count = 0; |
| |
| ko = z_object_find(&recycle_sem); |
| (void)memset(ko->perms, 0xFF, sizeof(ko->perms)); |
| |
| z_object_recycle(&recycle_sem); |
| zassert_true(ko != NULL, "kernel object not found"); |
| zassert_true(ko->flags & K_OBJ_FLAG_INITIALIZED, |
| "object wasn't marked as initialized"); |
| |
| for (int i = 0; i < CONFIG_MAX_THREAD_BYTES; i++) { |
| perms_count += popcount(ko->perms[i]); |
| } |
| |
| zassert_true(perms_count == 1, "invalid number of thread permissions"); |
| } |
| |
| void test_main(void) |
| { |
| struct k_mem_partition *parts[] = {&part0, &part1, |
| &ztest_mem_partition}; |
| |
| k_mem_domain_remove_thread(k_current_get()); |
| k_mem_domain_init(&dom0, ARRAY_SIZE(parts), parts); |
| k_mem_domain_add_thread(&dom0, k_current_get()); |
| |
| #if defined(CONFIG_ARM) |
| priv_stack_ptr = (int *)z_priv_stack_find(ztest_thread_stack) - |
| MPU_GUARD_ALIGN_AND_SIZE; |
| |
| #endif |
| k_thread_access_grant(k_current_get(), |
| &kthread_thread, &kthread_stack, |
| &uthread_thread, &uthread_stack, |
| &uthread_start_sem, &uthread_end_sem, |
| &test_revoke_sem, &kpipe, &expect_fault_sem); |
| ztest_test_suite(userspace, |
| ztest_user_unit_test(is_usermode), |
| ztest_user_unit_test(write_control), |
| ztest_user_unit_test(disable_mmu_mpu), |
| ztest_user_unit_test(read_kernram), |
| ztest_user_unit_test(write_kernram), |
| ztest_user_unit_test(write_kernro), |
| ztest_user_unit_test(write_kerntext), |
| ztest_user_unit_test(read_kernel_data), |
| ztest_user_unit_test(write_kernel_data), |
| ztest_user_unit_test(read_priv_stack), |
| ztest_user_unit_test(write_priv_stack), |
| ztest_user_unit_test(pass_user_object), |
| ztest_user_unit_test(pass_noperms_object), |
| ztest_user_unit_test(start_kernel_thread), |
| ztest_user_unit_test(read_other_stack), |
| ztest_user_unit_test(write_other_stack), |
| ztest_user_unit_test(revoke_noperms_object), |
| ztest_user_unit_test(access_after_revoke), |
| ztest_unit_test(user_mode_enter), |
| ztest_user_unit_test(write_kobject_user_pipe), |
| ztest_user_unit_test(read_kobject_user_pipe), |
| ztest_unit_test(access_other_memdomain), |
| ztest_unit_test(domain_add_thread_drop_to_user), |
| ztest_unit_test(domain_add_part_drop_to_user), |
| ztest_unit_test(domain_remove_part_drop_to_user), |
| ztest_unit_test(domain_remove_thread_drop_to_user), |
| ztest_unit_test(domain_add_thread_context_switch), |
| ztest_unit_test(domain_add_part_context_switch), |
| ztest_unit_test(domain_remove_part_context_switch), |
| ztest_unit_test(domain_remove_thread_context_switch), |
| ztest_unit_test(test_stack_buffer), |
| ztest_user_unit_test(test_unimplemented_syscall), |
| ztest_user_unit_test(test_bad_syscall), |
| ztest_unit_test(test_object_recycle) |
| ); |
| ztest_run_test_suite(userspace); |
| } |