Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 | /* * 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_STACKSIZE) 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, ztest_1cpu_unit_test(test_stack_buffer), ztest_1cpu_unit_test(test_idle_stack) ); ztest_run_test_suite(userspace); } |