diff --git a/.github/workflows/compilation_on_android_ubuntu.yml b/.github/workflows/compilation_on_android_ubuntu.yml index 6b2a1a1148..21437ffc09 100644 --- a/.github/workflows/compilation_on_android_ubuntu.yml +++ b/.github/workflows/compilation_on_android_ubuntu.yml @@ -491,6 +491,13 @@ jobs: ./iwasm wasm-apps/trap.aot | grep "#" > call_stack_aot.txt bash -x ../symbolicate.sh + - name: Build Sample [native-stack-overflow] + run: | + cd samples/native-stack-overflow + ./build.sh + ./run.sh test1 + ./run.sh test2 + test: needs: [ diff --git a/.github/workflows/compilation_on_macos.yml b/.github/workflows/compilation_on_macos.yml index c0b565bff7..559f25ed79 100644 --- a/.github/workflows/compilation_on_macos.yml +++ b/.github/workflows/compilation_on_macos.yml @@ -379,3 +379,12 @@ jobs: ./iwasm wasm-apps/trap.wasm | grep "#" > call_stack.txt ./iwasm wasm-apps/trap.aot | grep "#" > call_stack_aot.txt bash -x ../symbolicate.sh + + # skip on arm64 (macos-14) for now + - name: Build Sample [native-stack-overflow] + if: matrix.os != 'macos-14' + run: | + cd samples/native-stack-overflow + ./build.sh + ./run.sh test1 + ./run.sh test2 diff --git a/.github/workflows/nightly_run.yml b/.github/workflows/nightly_run.yml index 4335822bd9..3e4e89caf6 100644 --- a/.github/workflows/nightly_run.yml +++ b/.github/workflows/nightly_run.yml @@ -548,6 +548,13 @@ jobs: ./build.sh ./run.sh + - name: Build Sample [native-stack-overflow] + run: | + cd samples/native-stack-overflow + ./build.sh + ./run.sh test1 + ./run.sh test2 + - name: Build Sample [native-lib] run: | mkdir build && cd build @@ -567,6 +574,7 @@ jobs: python3 ./sample_test_run.py $(pwd)/out exit $? working-directory: ./wamr-app-framework/samples/simple + test: needs: [ diff --git a/core/iwasm/aot/aot_runtime.c b/core/iwasm/aot/aot_runtime.c index 09bf4b1e6e..f34decb5ea 100644 --- a/core/iwasm/aot/aot_runtime.c +++ b/core/iwasm/aot/aot_runtime.c @@ -1967,8 +1967,6 @@ invoke_native_with_hw_bound_check(WASMExecEnv *exec_env, void *func_ptr, AOTModuleInstance *module_inst = (AOTModuleInstance *)exec_env->module_inst; WASMExecEnv *exec_env_tls = wasm_runtime_get_exec_env_tls(); WASMJmpBuf jmpbuf_node = { 0 }, *jmpbuf_node_pop; - uint32 page_size = os_getpagesize(); - uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT; #ifdef BH_PLATFORM_WINDOWS int result; bool has_exception; @@ -1979,10 +1977,7 @@ invoke_native_with_hw_bound_check(WASMExecEnv *exec_env, void *func_ptr, /* Check native stack overflow firstly to ensure we have enough native stack to run the following codes before actually calling the aot function in invokeNative function. */ - RECORD_STACK_USAGE(exec_env, (uint8 *)&module_inst); - if ((uint8 *)&module_inst - < exec_env->native_stack_boundary + page_size * guard_page_count) { - aot_set_exception_with_id(module_inst, EXCE_NATIVE_STACK_OVERFLOW); + if (!wasm_runtime_detect_native_stack_overflow(exec_env)) { return false; } @@ -2790,9 +2785,7 @@ aot_call_indirect(WASMExecEnv *exec_env, uint32 tbl_idx, uint32 table_elem_idx, exec_env->native_stack_boundary must have been set, we don't set it again */ - RECORD_STACK_USAGE(exec_env, (uint8 *)&module_inst); - if ((uint8 *)&module_inst < exec_env->native_stack_boundary) { - aot_set_exception_with_id(module_inst, EXCE_NATIVE_STACK_OVERFLOW); + if (!wasm_runtime_detect_native_stack_overflow(exec_env)) { goto fail; } diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c index 665141fb00..2b481710ce 100644 --- a/core/iwasm/common/wasm_runtime_common.c +++ b/core/iwasm/common/wasm_runtime_common.c @@ -7015,3 +7015,59 @@ wasm_runtime_get_module_name(wasm_module_t module) return ""; } + +/* + * wasm_runtime_detect_native_stack_overflow + * + * - raise "native stack overflow" exception if available native stack + * at this point is less than WASM_STACK_GUARD_SIZE. in that case, + * return false. + * + * - update native_stack_top_min. + */ +bool +wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env) +{ + uint8 *boundary = exec_env->native_stack_boundary; + RECORD_STACK_USAGE(exec_env, (uint8 *)&boundary); + if (boundary == NULL) { + /* the platfrom doesn't support os_thread_get_stack_boundary */ + return true; + } +#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0 + uint32 page_size = os_getpagesize(); + uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT; + boundary = boundary + page_size * guard_page_count; +#endif + if ((uint8 *)&boundary < boundary) { + wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env), + "native stack overflow"); + return false; + } + return true; +} + +bool +wasm_runtime_detect_native_stack_overflow_size(WASMExecEnv *exec_env, + uint32 requested_size) +{ + uint8 *boundary = exec_env->native_stack_boundary; + RECORD_STACK_USAGE(exec_env, (uint8 *)&boundary); + if (boundary == NULL) { + /* the platfrom doesn't support os_thread_get_stack_boundary */ + return true; + } +#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0 + uint32 page_size = os_getpagesize(); + uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT; + boundary = boundary + page_size * guard_page_count; +#endif + /* adjust the boundary for the requested size */ + boundary = boundary - WASM_STACK_GUARD_SIZE + requested_size; + if ((uint8 *)&boundary < boundary) { + wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env), + "native stack overflow"); + return false; + } + return true; +} diff --git a/core/iwasm/common/wasm_runtime_common.h b/core/iwasm/common/wasm_runtime_common.h index 62c35473af..abaa330111 100644 --- a/core/iwasm/common/wasm_runtime_common.h +++ b/core/iwasm/common/wasm_runtime_common.h @@ -1189,6 +1189,13 @@ wasm_runtime_end_blocking_op(WASMExecEnv *exec_env); void wasm_runtime_interrupt_blocking_op(WASMExecEnv *exec_env); +WASM_RUNTIME_API_EXTERN bool +wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env); + +WASM_RUNTIME_API_EXTERN bool +wasm_runtime_detect_native_stack_overflow_size(WASMExecEnv *exec_env, + uint32 requested_size); + #if WASM_ENABLE_LINUX_PERF != 0 bool wasm_runtime_get_linux_perf(void); diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h index 104a9e83c1..c7513396fc 100644 --- a/core/iwasm/include/wasm_export.h +++ b/core/iwasm/include/wasm_export.h @@ -1756,6 +1756,57 @@ wasm_runtime_set_module_name(wasm_module_t module, const char *name, WASM_RUNTIME_API_EXTERN const char * wasm_runtime_get_module_name(wasm_module_t module); +/* + * wasm_runtime_detect_native_stack_overflow + * + * Detect native stack shortage. + * Ensure that the calling thread still has a reasonable amount of + * native stack (WASM_STACK_GUARD_SIZE bytes) available. + * + * If enough stack is left, this function returns true. + * Otherwise, this function raises a "native stack overflow" trap and + * returns false. + * + * Note: please do not expect a very strict detection. it's a good idea + * to give some margins. wasm_runtime_detect_native_stack_overflow itself + * requires a small amount of stack to run. + */ +WASM_RUNTIME_API_EXTERN bool +wasm_runtime_detect_native_stack_overflow(wasm_exec_env_t exec_env); + +/* + * wasm_runtime_detect_native_stack_overflow_size + * + * Similar to wasm_runtime_detect_native_stack_overflow, + * but use the caller-specified size instead of WASM_STACK_GUARD_SIZE. + * + * An expected usage: + * ```c + * __attribute__((noinline)) // inlining can break the stack check + * void stack_hog(void) + * { + * // consume a lot of stack here + * } + * + * void + * stack_hog_wrapper(exec_env) { + * // the amount of stack stack_hog would consume, + * // plus a small margin + * uint32_t size = 10000000; + * + * if (!wasm_runtime_detect_native_stack_overflow_size(exec_env, size)) { + * // wasm_runtime_detect_native_stack_overflow_size has raised + * // a trap. + * return; + * } + * stack_hog(); + * } + * ``` + */ +WASM_RUNTIME_API_EXTERN bool +wasm_runtime_detect_native_stack_overflow_size(wasm_exec_env_t exec_env, + uint32_t required_size); + #ifdef __cplusplus } #endif diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c index 026e9dd419..2763bc2bea 100644 --- a/core/iwasm/interpreter/wasm_interp_classic.c +++ b/core/iwasm/interpreter/wasm_interp_classic.c @@ -1159,6 +1159,10 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst, uint8 *frame_ref; #endif + if (!wasm_runtime_detect_native_stack_overflow(exec_env)) { + return; + } + all_cell_num = local_cell_num; #if WASM_ENABLE_GC != 0 all_cell_num += (local_cell_num + 3) / 4; @@ -1290,6 +1294,14 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, uintptr_t aux_stack_origin_boundary = 0; uintptr_t aux_stack_origin_bottom = 0; + /* + * perform stack overflow check before calling + * wasm_interp_call_func_bytecode recursively. + */ + if (!wasm_runtime_detect_native_stack_overflow(exec_env)) { + return; + } + if (!sub_func_inst) { snprintf(buf, sizeof(buf), "failed to call unlinked import function (%s, %s)", @@ -7108,12 +7120,13 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, } argc = function->param_cell_num; - RECORD_STACK_USAGE(exec_env, (uint8 *)&prev_frame); -#if !(defined(OS_ENABLE_HW_BOUND_CHECK) \ - && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0) - if ((uint8 *)&prev_frame < exec_env->native_stack_boundary) { - wasm_set_exception((WASMModuleInstance *)exec_env->module_inst, - "native stack overflow"); +#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0 + /* + * wasm_runtime_detect_native_stack_overflow is done by + * call_wasm_with_hw_bound_check. + */ +#else + if (!wasm_runtime_detect_native_stack_overflow(exec_env)) { return; } #endif diff --git a/core/iwasm/interpreter/wasm_interp_fast.c b/core/iwasm/interpreter/wasm_interp_fast.c index 417e3b0163..b861b271eb 100644 --- a/core/iwasm/interpreter/wasm_interp_fast.c +++ b/core/iwasm/interpreter/wasm_interp_fast.c @@ -1167,6 +1167,10 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst, all_cell_num += (local_cell_num + 3) / 4; #endif + if (!wasm_runtime_detect_native_stack_overflow(exec_env)) { + return; + } + if (!(frame = ALLOC_FRAME(exec_env, wasm_interp_interp_frame_size(all_cell_num), prev_frame))) @@ -1275,6 +1279,14 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, uintptr_t aux_stack_origin_boundary = 0; uintptr_t aux_stack_origin_bottom = 0; + /* + * perform stack overflow check before calling + * wasm_interp_call_func_bytecode recursively. + */ + if (!wasm_runtime_detect_native_stack_overflow(exec_env)) { + return; + } + if (!sub_func_inst) { snprintf(buf, sizeof(buf), "failed to call unlinked import function (%s, %s)", @@ -6081,12 +6093,13 @@ wasm_interp_call_wasm(WASMModuleInstance *module_inst, WASMExecEnv *exec_env, } argc = function->param_cell_num; - RECORD_STACK_USAGE(exec_env, (uint8 *)&prev_frame); -#if !(defined(OS_ENABLE_HW_BOUND_CHECK) \ - && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0) - if ((uint8 *)&prev_frame < exec_env->native_stack_boundary) { - wasm_set_exception((WASMModuleInstance *)exec_env->module_inst, - "native stack overflow"); +#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0 + /* + * wasm_runtime_detect_native_stack_overflow is done by + * call_wasm_with_hw_bound_check. + */ +#else + if (!wasm_runtime_detect_native_stack_overflow(exec_env)) { return; } #endif diff --git a/core/iwasm/interpreter/wasm_runtime.c b/core/iwasm/interpreter/wasm_runtime.c index cf480fb218..7495e2a173 100644 --- a/core/iwasm/interpreter/wasm_runtime.c +++ b/core/iwasm/interpreter/wasm_runtime.c @@ -3139,8 +3139,6 @@ call_wasm_with_hw_bound_check(WASMModuleInstance *module_inst, { WASMExecEnv *exec_env_tls = wasm_runtime_get_exec_env_tls(); WASMJmpBuf jmpbuf_node = { 0 }, *jmpbuf_node_pop; - uint32 page_size = os_getpagesize(); - uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT; WASMRuntimeFrame *prev_frame = wasm_exec_env_get_cur_frame(exec_env); uint8 *prev_top = exec_env->wasm_stack.top; #ifdef BH_PLATFORM_WINDOWS @@ -3153,10 +3151,7 @@ call_wasm_with_hw_bound_check(WASMModuleInstance *module_inst, /* Check native stack overflow firstly to ensure we have enough native stack to run the following codes before actually calling the aot function in invokeNative function. */ - RECORD_STACK_USAGE(exec_env, (uint8 *)&exec_env_tls); - if ((uint8 *)&exec_env_tls - < exec_env->native_stack_boundary + page_size * guard_page_count) { - wasm_set_exception(module_inst, "native stack overflow"); + if (!wasm_runtime_detect_native_stack_overflow(exec_env)) { return; } diff --git a/samples/native-stack-overflow/CMakeLists.txt b/samples/native-stack-overflow/CMakeLists.txt index 9858feea1f..efe5b5dc05 100644 --- a/samples/native-stack-overflow/CMakeLists.txt +++ b/samples/native-stack-overflow/CMakeLists.txt @@ -72,6 +72,11 @@ if (CMAKE_C_COMPILER_ID MATCHES "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GRE set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-usage") endif () +# GCC doesn't have disable_tail_calls attribute +if (CMAKE_C_COMPILER_ID MATCHES "GNU") +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-optimize-sibling-calls") +endif () + # build out vmlib set (WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake) diff --git a/samples/native-stack-overflow/build.sh b/samples/native-stack-overflow/build.sh index df31131a5b..ac9f410f45 100755 --- a/samples/native-stack-overflow/build.sh +++ b/samples/native-stack-overflow/build.sh @@ -69,7 +69,7 @@ echo "#################### build wasm apps done" echo "#################### aot-compile" WAMRC=${WAMR_DIR}/wamr-compiler/build/wamrc -${WAMRC} -o ${OUT_DIR}/wasm-apps/${OUT_FILE}.aot ${OUT_DIR}/wasm-apps/${OUT_FILE} +${WAMRC} -o ${OUT_DIR}/wasm-apps/${OUT_FILE}.aot --size-level=0 ${OUT_DIR}/wasm-apps/${OUT_FILE} echo "#################### aot-compile (--bounds-checks=1)" -${WAMRC} -o ${OUT_DIR}/wasm-apps/${OUT_FILE}.aot.bounds-checks --bounds-checks=1 ${OUT_DIR}/wasm-apps/${OUT_FILE} +${WAMRC} -o ${OUT_DIR}/wasm-apps/${OUT_FILE}.aot.bounds-checks --size-level=0 --bounds-checks=1 ${OUT_DIR}/wasm-apps/${OUT_FILE} diff --git a/samples/native-stack-overflow/src/main.c b/samples/native-stack-overflow/src/main.c index b17dd4612d..0719293233 100644 --- a/samples/native-stack-overflow/src/main.c +++ b/samples/native-stack-overflow/src/main.c @@ -114,7 +114,7 @@ main(int argc, char **argv) "--------\n"); unsigned int stack; - unsigned int prevstack; + unsigned int prevstack = 0; /* appease GCC -Wmaybe-uninitialized */ unsigned int stack_range_start = 0; unsigned int stack_range_end = 4096 * 6; unsigned int step = 16; diff --git a/samples/native-stack-overflow/src/native_impl.c b/samples/native-stack-overflow/src/native_impl.c index 9e6cec5616..7037e1029b 100644 --- a/samples/native-stack-overflow/src/native_impl.c +++ b/samples/native-stack-overflow/src/native_impl.c @@ -9,10 +9,6 @@ #include #include -#if defined(__APPLE__) -#include -#endif - #include "wasm_export.h" #include "bh_platform.h" @@ -45,9 +41,12 @@ host_consume_stack_and_call_indirect(wasm_exec_env_t exec_env, uint32_t funcidx, void *boundary = os_thread_get_stack_boundary(); void *fp = __builtin_frame_address(0); ptrdiff_t diff = fp - boundary; - if ((unsigned char *)fp < (unsigned char *)boundary + 1024 * 5) { - wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env), - "native stack overflow 2"); + /* + * because this function performs recursive calls depending on + * the user input, we don't have an apriori knowledge how much stack + * we need. perform the overflow check on each iteration. + */ + if (!wasm_runtime_detect_native_stack_overflow(exec_env)) { return 0; } if (diff > stack) { @@ -63,27 +62,13 @@ host_consume_stack_and_call_indirect(wasm_exec_env_t exec_env, uint32_t funcidx, __attribute__((noinline)) static uint32_t consume_stack1(wasm_exec_env_t exec_env, void *base, uint32_t stack) +#if defined(__clang__) __attribute__((disable_tail_calls)) +#endif { void *fp = __builtin_frame_address(0); ptrdiff_t diff = (unsigned char *)base - (unsigned char *)fp; assert(diff > 0); - char buf[16]; - /* - * note: we prefer to use memset_s here because, unlike memset, - * memset_s is not allowed to be optimized away. - * - * memset_s is available for macOS 10.13+ according to: - * https://developer.apple.com/documentation/kernel/2876438-memset_s - */ -#if defined(__STDC_LIB_EXT1__) \ - || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) \ - && __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_3) - memset_s(buf, sizeof(buf), 0, sizeof(buf)); -#else -#warning memset_s is not available - memset(buf, 0, sizeof(buf)); -#endif if (diff > stack) { return diff; } @@ -93,6 +78,12 @@ consume_stack1(wasm_exec_env_t exec_env, void *base, uint32_t stack) uint32_t host_consume_stack(wasm_exec_env_t exec_env, uint32_t stack) { + /* + * this function consumes a bit more than "stack" bytes. + */ + if (!wasm_runtime_detect_native_stack_overflow_size(exec_env, 64 + stack)) { + return 0; + } void *base = __builtin_frame_address(0); return consume_stack1(exec_env, base, stack); }