| /* |
| * Copyright (c) 2020 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr.h> |
| #include <ztest.h> |
| #include <syscall_handler.h> |
| #include <kernel_internal.h> |
| |
| #include "test_syscall.h" |
| |
| /* |
| * Stack testing |
| */ |
| struct k_thread test_thread; |
| #define NUM_STACKS 3 |
| #define STEST_STACKSIZE (512 + CONFIG_TEST_EXTRA_STACK_SIZE) |
| K_THREAD_STACK_DEFINE(user_stack, STEST_STACKSIZE); |
| K_THREAD_STACK_ARRAY_DEFINE(user_stack_array, NUM_STACKS, STEST_STACKSIZE); |
| K_KERNEL_STACK_DEFINE(kern_stack, STEST_STACKSIZE); |
| K_KERNEL_STACK_ARRAY_DEFINE(kern_stack_array, NUM_STACKS, STEST_STACKSIZE); |
| |
| struct foo { |
| int bar; |
| |
| K_KERNEL_STACK_MEMBER(stack, STEST_STACKSIZE); |
| int baz; |
| }; |
| |
| __kstackmem struct foo stest_member_stack; |
| |
| void z_impl_stack_info_get(char **start_addr, size_t *size) |
| { |
| *start_addr = (char *)k_current_get()->stack_info.start; |
| *size = k_current_get()->stack_info.size; |
| } |
| |
| #ifdef CONFIG_USERSPACE |
| static inline void z_vrfy_stack_info_get(char **start_addr, |
| size_t *size) |
| { |
| Z_OOPS(Z_SYSCALL_MEMORY_WRITE(start_addr, sizeof(uintptr_t))); |
| Z_OOPS(Z_SYSCALL_MEMORY_WRITE(size, sizeof(size_t))); |
| |
| z_impl_stack_info_get(start_addr, size); |
| } |
| #include <syscalls/stack_info_get_mrsh.c> |
| |
| int z_impl_check_perms(void *addr, size_t size, int write) |
| { |
| return arch_buffer_validate(addr, size, write); |
| } |
| |
| static inline int z_vrfy_check_perms(void *addr, size_t size, int write) |
| { |
| return z_impl_check_perms((void *)addr, size, write); |
| } |
| #include <syscalls/check_perms_mrsh.c> |
| #endif /* CONFIG_USERSPACE */ |
| |
| /* Global data structure with object information, used by |
| * stack_buffer_scenarios |
| */ |
| ZTEST_BMEM struct scenario_data { |
| k_thread_stack_t *stack; |
| |
| /* If this was declared with K_THREAD_STACK_DEFINE and not |
| * K_KERNEL_STACK_DEFINE |
| */ |
| bool is_user; |
| |
| /* Stack size stored in kernel object metadata if a user stack */ |
| size_t metadata_size; |
| |
| /* Return value of sizeof(stack) */ |
| size_t object_size; |
| |
| /* Return value of K_{THREAD|KERNEL}_STACK_SIZEOF(stack) */ |
| size_t reported_size; |
| |
| /* Original size argument passed to K_{THREAD|KERNEL}_STACK_DECLARE */ |
| size_t declared_size; |
| |
| /* Whether this stack is part of an array of thread stacks */ |
| bool is_array; |
| } scenario_data; |
| |
| void stack_buffer_scenarios(void) |
| { |
| k_thread_stack_t *stack_obj = scenario_data.stack; |
| size_t obj_size = scenario_data.object_size; |
| size_t stack_size, unused, carveout, reserved, alignment, adjusted; |
| uint8_t val = 0; |
| char *stack_start, *stack_ptr, *stack_end, *obj_start, *obj_end; |
| char *stack_buf; |
| volatile char *pos; |
| int ret, expected; |
| uintptr_t base = (uintptr_t)stack_obj; |
| bool is_usermode; |
| long int end_space; |
| |
| #ifdef CONFIG_USERSPACE |
| is_usermode = arch_is_user_context(); |
| #else |
| is_usermode = false; |
| #endif |
| /* Dump interesting information */ |
| stack_info_get(&stack_start, &stack_size); |
| printk(" - Thread reports buffer %p size %zu\n", stack_start, |
| stack_size); |
| |
| #ifdef CONFIG_USERSPACE |
| if (scenario_data.is_user) { |
| reserved = K_THREAD_STACK_RESERVED; |
| stack_buf = Z_THREAD_STACK_BUFFER(stack_obj); |
| alignment = Z_THREAD_STACK_OBJ_ALIGN(stack_size); |
| } else |
| #endif |
| { |
| reserved = K_KERNEL_STACK_RESERVED; |
| stack_buf = Z_KERNEL_STACK_BUFFER(stack_obj); |
| alignment = Z_KERNEL_STACK_OBJ_ALIGN; |
| } |
| |
| 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 - 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 base of the stack is aligned properly. */ |
| zassert_true(base % alignment == 0, |
| "stack base address %p not aligned to %zu", |
| stack_obj, alignment); |
| |
| /* 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 |
| * Starting from &val which is close enough to stack pointer |
| */ |
| 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; |
| } |
| |
| #ifdef CONFIG_USERSPACE |
| if (is_usermode) { |
| /* 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(stack_start - 1, 1, 0), |
| "user mode access to memory %p before start of stack object", |
| obj_start - 1); |
| zassert_true(check_perms(stack_end, 1, 0), |
| "user mode access to memory %p past end of stack object", |
| obj_end); |
| zassert_true(stack_size <= obj_size - reserved, |
| "bad stack size %zu in thread struct", |
| stack_size); |
| } |
| #endif |
| carveout = stack_start - stack_buf; |
| printk(" - Carved-out space in buffer: %zu\n", carveout); |
| zassert_true(carveout < stack_size, |
| "Suspicious carve-out space reported"); |
| /* 0 unless this is a stack array */ |
| end_space = obj_end - stack_end; |
| printk(" - Unused objects space: %ld\n", end_space); |
| |
| /* For all stacks, when k_thread_create() is called with a stack object, |
| * it is equivalent to pass either the original requested stack size, or |
| * the return value of K_*_STACK_SIZEOF() for that stack object. |
| * |
| * When the stack is actually instantiated, both expand to fill any space |
| * rounded up, except rounding space for array members. |
| */ |
| if (!scenario_data.is_array) { |
| /* These should be exactly the same. We have an equivalence relation: |
| * For some stack declared with: |
| * |
| * K_THREAD_STACK_DEFINE(my_stack, X); |
| * Z_THREAD_STACK_SIZE_ADJUST(X) - K_THREAD_STACK_RESERVED == |
| * K_THREAD_STACK_SIZEOF(my_stack) |
| * |
| * K_KERNEL_STACK_DEFINE(my_kern_stack, Y): |
| * Z_KERNEL_STACK_SIZE_ADJUST(Y) - K_KERNEL_STACK_RESERVED == |
| * K_KERNEL_STACK_SIZEOF(my_stack) |
| */ |
| #ifdef CONFIG_USERSPACE |
| /* Not defined if user mode disabled, all stacks are kernel stacks */ |
| if (scenario_data.is_user) { |
| adjusted = Z_THREAD_STACK_SIZE_ADJUST(scenario_data.declared_size); |
| } else |
| #endif |
| { |
| adjusted = Z_KERNEL_STACK_SIZE_ADJUST(scenario_data.declared_size); |
| } |
| adjusted -= reserved; |
| |
| zassert_equal(end_space, 0, "unexpected unused space\n"); |
| } else { |
| /* For arrays there may be unused space per-object. This is because |
| * every single array member must be aligned to the value returned |
| * by Z_{KERNEL|THREAD}_STACK_OBJ_ALIGN. |
| * |
| * If we define: |
| * |
| * K_{THREAD|KERNEL}_STACK_ARRAY_DEFINE(my_stack_array, num_stacks, X); |
| * |
| * We do not auto-expand usable space to cover this unused area. Doing |
| * this would require some way for the kernel to know that a stack object |
| * pointer passed in is an array member, which is currently not possible. |
| * |
| * The equivalence here is computable with: |
| * K_THREAD_STACK_SIZEOF(my_stack_array[0]) == |
| * K_THREAD_STACK_LEN(X) - K_THREAD_STACK_RESERVED; |
| */ |
| |
| if (scenario_data.is_user) { |
| adjusted = K_THREAD_STACK_LEN(scenario_data.declared_size); |
| } else { |
| adjusted = Z_KERNEL_STACK_LEN(scenario_data.declared_size); |
| } |
| adjusted -= reserved; |
| |
| /* At least make sure it's not negative, that means stack_info isn't set |
| * right |
| */ |
| zassert_true(end_space >= 0, "bad stack bounds in stack_info"); |
| } |
| |
| zassert_true(adjusted == scenario_data.reported_size, |
| "size mismatch: adjusted %zu vs. reported %zu", |
| adjusted, scenario_data.reported_size); |
| |
| ret = k_thread_stack_space_get(k_current_get(), &unused); |
| if (!is_usermode && IS_ENABLED(CONFIG_NO_UNUSED_STACK_INSPECTION)) { |
| expected = -ENOTSUP; |
| } else { |
| expected = 0; |
| } |
| |
| zassert_equal(ret, expected, "unexpected return value %d", ret); |
| if (ret == 0) { |
| printk("self-reported unused stack space: %zu\n", unused); |
| } |
| } |
| |
| void stest_thread_entry(void *p1, void *p2, void *p3) |
| { |
| bool drop = (bool)p1; |
| |
| if (drop) { |
| k_thread_user_mode_enter(stest_thread_entry, (void *)false, |
| p2, p3); |
| } else { |
| stack_buffer_scenarios(); |
| } |
| } |
| |
| void stest_thread_launch(uint32_t flags, bool drop) |
| { |
| int ret; |
| size_t unused; |
| |
| k_thread_create(&test_thread, scenario_data.stack, STEST_STACKSIZE, |
| stest_thread_entry, |
| (void *)drop, NULL, NULL, |
| -1, flags, K_NO_WAIT); |
| k_thread_join(&test_thread, K_FOREVER); |
| |
| ret = k_thread_stack_space_get(&test_thread, &unused); |
| zassert_equal(ret, 0, "failed to calculate unused stack space\n"); |
| printk("target thread unused stack space: %zu\n", unused); |
| } |
| |
| void scenario_entry(void *stack_obj, size_t obj_size, size_t reported_size, |
| size_t declared_size, bool is_array) |
| { |
| bool is_user; |
| size_t metadata_size; |
| |
| #ifdef CONFIG_USERSPACE |
| struct z_object *zo; |
| |
| zo = z_object_find(stack_obj); |
| if (zo != NULL) { |
| is_user = true; |
| #ifdef CONFIG_GEN_PRIV_STACKS |
| metadata_size = zo->data.stack_data->size; |
| #else |
| metadata_size = zo->data.stack_size; |
| #endif /* CONFIG_GEN_PRIV_STACKS */ |
| printk("stack may host user thread, size in metadata is %zu\n", |
| metadata_size); |
| } else |
| #endif /* CONFIG_USERSPACE */ |
| { |
| metadata_size = 0; |
| is_user = false; |
| } |
| |
| scenario_data.stack = stack_obj; |
| scenario_data.object_size = obj_size; |
| scenario_data.is_user = is_user; |
| scenario_data.metadata_size = metadata_size; |
| scenario_data.reported_size = reported_size; |
| scenario_data.declared_size = declared_size; |
| scenario_data.is_array = is_array; |
| |
| printk("Stack object %p[%zu]\n", stack_obj, obj_size); |
| printk(" - Testing supervisor mode\n"); |
| stest_thread_launch(0, false); |
| |
| #ifdef CONFIG_USERSPACE |
| if (is_user) { |
| printk(" - Testing user mode (direct launch)\n"); |
| stest_thread_launch(K_USER | K_INHERIT_PERMS, false); |
| printk(" - Testing user mode (drop)\n"); |
| stest_thread_launch(K_INHERIT_PERMS, true); |
| } |
| #endif /* CONFIG_USERSPACE */ |
| } |
| |
| /** |
| * @brief Test kernel provides user thread read/write access to its own stack |
| * memory buffer |
| * |
| * @details Thread can access its own stack memory buffer and perform |
| * read/write operations. |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| void test_stack_buffer(void) |
| { |
| printk("Reserved space (thread stacks): %zu\n", |
| K_THREAD_STACK_RESERVED); |
| printk("Reserved space (kernel stacks): %zu\n", |
| K_KERNEL_STACK_RESERVED); |
| |
| printk("CONFIG_ISR_STACK_SIZE %zu\n", (size_t)CONFIG_ISR_STACK_SIZE); |
| for (int i = 0; i < CONFIG_MP_NUM_CPUS; i++) { |
| printk("irq stack %d: %p size %zu\n", |
| i, &z_interrupt_stacks[i], |
| sizeof(z_interrupt_stacks[i])); |
| } |
| |
| printk("Provided stack size: %u\n", STEST_STACKSIZE); |
| |
| printk("\ntesting user_stack\n"); |
| scenario_entry(user_stack, sizeof(user_stack), K_THREAD_STACK_SIZEOF(user_stack), |
| STEST_STACKSIZE, false); |
| |
| for (int i = 0; i < NUM_STACKS; i++) { |
| printk("\ntesting user_stack_array[%d]\n", i); |
| scenario_entry(user_stack_array[i], |
| sizeof(user_stack_array[i]), |
| K_THREAD_STACK_SIZEOF(user_stack_array[i]), |
| STEST_STACKSIZE, true); |
| } |
| |
| printk("\ntesting kern_stack\n"); |
| scenario_entry(kern_stack, sizeof(kern_stack), K_KERNEL_STACK_SIZEOF(kern_stack), |
| STEST_STACKSIZE, false); |
| |
| for (int i = 0; i < NUM_STACKS; i++) { |
| printk("\ntesting kern_stack_array[%d]\n", i); |
| scenario_entry(kern_stack_array[i], |
| sizeof(kern_stack_array[i]), |
| K_KERNEL_STACK_SIZEOF(kern_stack_array[i]), |
| STEST_STACKSIZE, true); |
| } |
| |
| printk("\ntesting stest_member_stack\n"); |
| scenario_entry(&stest_member_stack.stack, |
| sizeof(stest_member_stack.stack), |
| K_KERNEL_STACK_SIZEOF(stest_member_stack.stack), |
| STEST_STACKSIZE, false); |
| } |
| |
| void no_op_entry(void *p1, void *p2, void *p3) |
| { |
| |
| printk("hi! bye!\n"); |
| |
| #ifdef CONFIG_DYNAMIC_OBJECTS |
| /* Allocate a dynamic kernel object, which gets freed on thread |
| * cleanup since this thread has the only reference. |
| */ |
| struct k_sem *dyn_sem = k_object_alloc(K_OBJ_SEM); |
| k_sem_init(dyn_sem, 1, 1); |
| printk("allocated semaphore %p\n", dyn_sem); |
| #endif |
| /* thread self-aborts, triggering idle thread cleanup */ |
| } |
| |
| /** |
| * @brief Show that the idle thread stack size is correct |
| * |
| * The idle thread has to occasionally clean up self-exiting threads. |
| * Exercise this and show that we didn't overflow, reporting out stack |
| * usage. |
| * |
| * @ingroup kernel_memprotect_tests |
| */ |
| void test_idle_stack(void) |
| { |
| if (IS_ENABLED(CONFIG_KERNEL_COHERENCE)) { |
| /* Stacks on coherence platforms aren't coherent, and |
| * the idle stack may have been initialized on a |
| * different CPU! |
| */ |
| ztest_test_skip(); |
| } |
| |
| int ret; |
| #ifdef CONFIG_SMP |
| /* 1cpu test case, so all other CPUs are spinning with co-op |
| * threads blocking them. _current_cpu triggers an assertion. |
| */ |
| struct k_thread *idle = arch_curr_cpu()->idle_thread; |
| #else |
| struct k_thread *idle = _current_cpu->idle_thread; |
| #endif |
| size_t unused_bytes; |
| |
| /* Spwawn a child thread which self-exits */ |
| k_thread_create(&test_thread, kern_stack, STEST_STACKSIZE, |
| no_op_entry, |
| NULL, NULL, NULL, |
| -1, 0, K_NO_WAIT); |
| |
| k_thread_join(&test_thread, K_FOREVER); |
| |
| /* Also sleep for a bit, which also exercises the idle thread |
| * in case some PM hooks will run |
| */ |
| k_sleep(K_MSEC(1)); |
| |
| /* Now measure idle thread stack usage */ |
| ret = k_thread_stack_space_get(idle, &unused_bytes); |
| zassert_true(ret == 0, "failed to obtain stack space"); |
| zassert_true(unused_bytes > 0, "idle thread stack size %d too low", |
| CONFIG_IDLE_STACK_SIZE); |
| printk("unused idle thread stack size: %zu/%d (%zu used)\n", |
| unused_bytes, CONFIG_IDLE_STACK_SIZE, |
| CONFIG_IDLE_STACK_SIZE - unused_bytes); |
| |
| } |
| |
| void test_main(void) |
| { |
| k_thread_system_pool_assign(k_current_get()); |
| |
| /* Run a thread that self-exits, triggering idle cleanup */ |
| ztest_test_suite(userspace_thread_stack, |
| ztest_1cpu_unit_test(test_stack_buffer), |
| ztest_1cpu_unit_test(test_idle_stack) |
| ); |
| ztest_run_test_suite(userspace_thread_stack); |
| } |