Add wasm_runtime_detect_native_stack_overflow_size (#3355)

- Add a few API (https://github.com/bytecodealliance/wasm-micro-runtime/issues/3325)
   ```c
   wasm_runtime_detect_native_stack_overflow_size
   wasm_runtime_detect_native_stack_overflow
   ```
- Adapt the runtime to use them
- Adapt samples/native-stack-overflow to use them
- Add a few missing overflow checks in the interpreters
- Build and run the sample on the CI
This commit is contained in:
YAMAMOTO Takashi 2024-04-26 17:00:58 +09:00 committed by GitHub
parent 1b5ff93656
commit 410ee580ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 201 additions and 53 deletions

View File

@ -491,6 +491,13 @@ jobs:
./iwasm wasm-apps/trap.aot | grep "#" > call_stack_aot.txt ./iwasm wasm-apps/trap.aot | grep "#" > call_stack_aot.txt
bash -x ../symbolicate.sh 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: test:
needs: needs:
[ [

View File

@ -379,3 +379,12 @@ jobs:
./iwasm wasm-apps/trap.wasm | grep "#" > call_stack.txt ./iwasm wasm-apps/trap.wasm | grep "#" > call_stack.txt
./iwasm wasm-apps/trap.aot | grep "#" > call_stack_aot.txt ./iwasm wasm-apps/trap.aot | grep "#" > call_stack_aot.txt
bash -x ../symbolicate.sh 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

View File

@ -548,6 +548,13 @@ jobs:
./build.sh ./build.sh
./run.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] - name: Build Sample [native-lib]
run: | run: |
mkdir build && cd build mkdir build && cd build
@ -567,6 +574,7 @@ jobs:
python3 ./sample_test_run.py $(pwd)/out python3 ./sample_test_run.py $(pwd)/out
exit $? exit $?
working-directory: ./wamr-app-framework/samples/simple working-directory: ./wamr-app-framework/samples/simple
test: test:
needs: needs:
[ [

View File

@ -1967,8 +1967,6 @@ invoke_native_with_hw_bound_check(WASMExecEnv *exec_env, void *func_ptr,
AOTModuleInstance *module_inst = (AOTModuleInstance *)exec_env->module_inst; AOTModuleInstance *module_inst = (AOTModuleInstance *)exec_env->module_inst;
WASMExecEnv *exec_env_tls = wasm_runtime_get_exec_env_tls(); WASMExecEnv *exec_env_tls = wasm_runtime_get_exec_env_tls();
WASMJmpBuf jmpbuf_node = { 0 }, *jmpbuf_node_pop; 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 #ifdef BH_PLATFORM_WINDOWS
int result; int result;
bool has_exception; 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 /* Check native stack overflow firstly to ensure we have enough
native stack to run the following codes before actually calling native stack to run the following codes before actually calling
the aot function in invokeNative function. */ the aot function in invokeNative function. */
RECORD_STACK_USAGE(exec_env, (uint8 *)&module_inst); if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
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);
return false; 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 exec_env->native_stack_boundary must have been set, we don't set
it again */ it again */
RECORD_STACK_USAGE(exec_env, (uint8 *)&module_inst); if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
if ((uint8 *)&module_inst < exec_env->native_stack_boundary) {
aot_set_exception_with_id(module_inst, EXCE_NATIVE_STACK_OVERFLOW);
goto fail; goto fail;
} }

View File

@ -7015,3 +7015,59 @@ wasm_runtime_get_module_name(wasm_module_t module)
return ""; 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;
}

View File

@ -1189,6 +1189,13 @@ wasm_runtime_end_blocking_op(WASMExecEnv *exec_env);
void void
wasm_runtime_interrupt_blocking_op(WASMExecEnv *exec_env); 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 #if WASM_ENABLE_LINUX_PERF != 0
bool bool
wasm_runtime_get_linux_perf(void); wasm_runtime_get_linux_perf(void);

View File

@ -1756,6 +1756,57 @@ wasm_runtime_set_module_name(wasm_module_t module, const char *name,
WASM_RUNTIME_API_EXTERN const char * WASM_RUNTIME_API_EXTERN const char *
wasm_runtime_get_module_name(wasm_module_t module); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -1159,6 +1159,10 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst,
uint8 *frame_ref; uint8 *frame_ref;
#endif #endif
if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
return;
}
all_cell_num = local_cell_num; all_cell_num = local_cell_num;
#if WASM_ENABLE_GC != 0 #if WASM_ENABLE_GC != 0
all_cell_num += (local_cell_num + 3) / 4; 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_boundary = 0;
uintptr_t aux_stack_origin_bottom = 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) { if (!sub_func_inst) {
snprintf(buf, sizeof(buf), snprintf(buf, sizeof(buf),
"failed to call unlinked import function (%s, %s)", "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; 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 !(defined(OS_ENABLE_HW_BOUND_CHECK) \ /*
&& WASM_DISABLE_STACK_HW_BOUND_CHECK == 0) * wasm_runtime_detect_native_stack_overflow is done by
if ((uint8 *)&prev_frame < exec_env->native_stack_boundary) { * call_wasm_with_hw_bound_check.
wasm_set_exception((WASMModuleInstance *)exec_env->module_inst, */
"native stack overflow"); #else
if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
return; return;
} }
#endif #endif

View File

@ -1167,6 +1167,10 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst,
all_cell_num += (local_cell_num + 3) / 4; all_cell_num += (local_cell_num + 3) / 4;
#endif #endif
if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
return;
}
if (!(frame = if (!(frame =
ALLOC_FRAME(exec_env, wasm_interp_interp_frame_size(all_cell_num), ALLOC_FRAME(exec_env, wasm_interp_interp_frame_size(all_cell_num),
prev_frame))) 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_boundary = 0;
uintptr_t aux_stack_origin_bottom = 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) { if (!sub_func_inst) {
snprintf(buf, sizeof(buf), snprintf(buf, sizeof(buf),
"failed to call unlinked import function (%s, %s)", "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; 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 !(defined(OS_ENABLE_HW_BOUND_CHECK) \ /*
&& WASM_DISABLE_STACK_HW_BOUND_CHECK == 0) * wasm_runtime_detect_native_stack_overflow is done by
if ((uint8 *)&prev_frame < exec_env->native_stack_boundary) { * call_wasm_with_hw_bound_check.
wasm_set_exception((WASMModuleInstance *)exec_env->module_inst, */
"native stack overflow"); #else
if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
return; return;
} }
#endif #endif

View File

@ -3139,8 +3139,6 @@ call_wasm_with_hw_bound_check(WASMModuleInstance *module_inst,
{ {
WASMExecEnv *exec_env_tls = wasm_runtime_get_exec_env_tls(); WASMExecEnv *exec_env_tls = wasm_runtime_get_exec_env_tls();
WASMJmpBuf jmpbuf_node = { 0 }, *jmpbuf_node_pop; 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); WASMRuntimeFrame *prev_frame = wasm_exec_env_get_cur_frame(exec_env);
uint8 *prev_top = exec_env->wasm_stack.top; uint8 *prev_top = exec_env->wasm_stack.top;
#ifdef BH_PLATFORM_WINDOWS #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 /* Check native stack overflow firstly to ensure we have enough
native stack to run the following codes before actually calling native stack to run the following codes before actually calling
the aot function in invokeNative function. */ the aot function in invokeNative function. */
RECORD_STACK_USAGE(exec_env, (uint8 *)&exec_env_tls); if (!wasm_runtime_detect_native_stack_overflow(exec_env)) {
if ((uint8 *)&exec_env_tls
< exec_env->native_stack_boundary + page_size * guard_page_count) {
wasm_set_exception(module_inst, "native stack overflow");
return; return;
} }

View File

@ -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") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-usage")
endif () 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 # build out vmlib
set (WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) set (WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake) include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake)

View File

@ -69,7 +69,7 @@ echo "#################### build wasm apps done"
echo "#################### aot-compile" echo "#################### aot-compile"
WAMRC=${WAMR_DIR}/wamr-compiler/build/wamrc 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)" 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}

View File

@ -114,7 +114,7 @@ main(int argc, char **argv)
"--------\n"); "--------\n");
unsigned int stack; unsigned int stack;
unsigned int prevstack; unsigned int prevstack = 0; /* appease GCC -Wmaybe-uninitialized */
unsigned int stack_range_start = 0; unsigned int stack_range_start = 0;
unsigned int stack_range_end = 4096 * 6; unsigned int stack_range_end = 4096 * 6;
unsigned int step = 16; unsigned int step = 16;

View File

@ -9,10 +9,6 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#if defined(__APPLE__)
#include <Availability.h>
#endif
#include "wasm_export.h" #include "wasm_export.h"
#include "bh_platform.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 *boundary = os_thread_get_stack_boundary();
void *fp = __builtin_frame_address(0); void *fp = __builtin_frame_address(0);
ptrdiff_t diff = fp - boundary; 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), * because this function performs recursive calls depending on
"native stack overflow 2"); * 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; return 0;
} }
if (diff > stack) { 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 __attribute__((noinline)) static uint32_t
consume_stack1(wasm_exec_env_t exec_env, void *base, uint32_t stack) consume_stack1(wasm_exec_env_t exec_env, void *base, uint32_t stack)
#if defined(__clang__)
__attribute__((disable_tail_calls)) __attribute__((disable_tail_calls))
#endif
{ {
void *fp = __builtin_frame_address(0); void *fp = __builtin_frame_address(0);
ptrdiff_t diff = (unsigned char *)base - (unsigned char *)fp; ptrdiff_t diff = (unsigned char *)base - (unsigned char *)fp;
assert(diff > 0); 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) { if (diff > stack) {
return diff; return diff;
} }
@ -93,6 +78,12 @@ consume_stack1(wasm_exec_env_t exec_env, void *base, uint32_t stack)
uint32_t uint32_t
host_consume_stack(wasm_exec_env_t exec_env, uint32_t stack) 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); void *base = __builtin_frame_address(0);
return consume_stack1(exec_env, base, stack); return consume_stack1(exec_env, base, stack);
} }