From b02eaa0b0eca7c6bcd28efd9792c4cfd399227fe Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Tue, 16 Apr 2024 21:39:21 +0900 Subject: [PATCH] Add native-stack-overflow sample This is a test code to examine native stack overflow detection logic. The current output on my environment (macOS amd64): ```shell ====== Interpreter stack size | fail? | leak? | exception --------------------------------------------------------------------------- 0 - 14704 | failed | leaked | Exception: native stack overflow 14704 - 17904 | failed | ok | Exception: native stack overflow 17904 - 24576 | ok | ok | ====== AOT stack size | fail? | leak? | exception --------------------------------------------------------------------------- 0 - 18176 | failed | leaked | Exception: native stack overflow 18176 - 24576 | ok | ok | ====== AOT WAMR_DISABLE_HW_BOUND_CHECK=1 stack size | fail? | leak? | exception --------------------------------------------------------------------------- 0 - 1968 | failed | ok | Exception: native stack overflow 1968 - 24576 | ok | ok | ``` This is a preparation to work on relevant issues, including: https://github.com/bytecodealliance/wasm-micro-runtime/issues/3325 https://github.com/bytecodealliance/wasm-micro-runtime/issues/3320 https://github.com/bytecodealliance/wasm-micro-runtime/issues/3314 https://github.com/bytecodealliance/wasm-micro-runtime/issues/3297 --- samples/native-stack-overflow/.gitignore | 1 + samples/native-stack-overflow/CMakeLists.txt | 87 +++++++++ samples/native-stack-overflow/README.md | 4 + samples/native-stack-overflow/build.sh | 75 ++++++++ samples/native-stack-overflow/clean.sh | 1 + samples/native-stack-overflow/run.sh | 12 ++ samples/native-stack-overflow/src/main.c | 178 ++++++++++++++++++ .../native-stack-overflow/src/native_impl.c | 71 +++++++ .../native-stack-overflow/wasm-apps/testapp.c | 54 ++++++ 9 files changed, 483 insertions(+) create mode 100644 samples/native-stack-overflow/.gitignore create mode 100644 samples/native-stack-overflow/CMakeLists.txt create mode 100644 samples/native-stack-overflow/README.md create mode 100755 samples/native-stack-overflow/build.sh create mode 100755 samples/native-stack-overflow/clean.sh create mode 100755 samples/native-stack-overflow/run.sh create mode 100644 samples/native-stack-overflow/src/main.c create mode 100644 samples/native-stack-overflow/src/native_impl.c create mode 100644 samples/native-stack-overflow/wasm-apps/testapp.c diff --git a/samples/native-stack-overflow/.gitignore b/samples/native-stack-overflow/.gitignore new file mode 100644 index 000000000..0fa8a76bd --- /dev/null +++ b/samples/native-stack-overflow/.gitignore @@ -0,0 +1 @@ +/out/ \ No newline at end of file diff --git a/samples/native-stack-overflow/CMakeLists.txt b/samples/native-stack-overflow/CMakeLists.txt new file mode 100644 index 000000000..cdc6accf5 --- /dev/null +++ b/samples/native-stack-overflow/CMakeLists.txt @@ -0,0 +1,87 @@ +# Copyright (C) 2019 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required (VERSION 3.14) + +include(CheckPIESupported) + +if (NOT WAMR_BUILD_PLATFORM STREQUAL "windows") + project (native-stack-overflow) +else() + project (native-stack-overflow C ASM) +endif() + +################ runtime settings ################ +string (TOLOWER ${CMAKE_HOST_SYSTEM_NAME} WAMR_BUILD_PLATFORM) +if (APPLE) + add_definitions(-DBH_PLATFORM_DARWIN) +endif () + +# Reset default linker flags +set (CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") +set (CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") + +# WAMR features switch + +# Set WAMR_BUILD_TARGET, currently values supported: +# "X86_64", "AMD_64", "X86_32", "AARCH64[sub]", "ARM[sub]", "THUMB[sub]", +# "MIPS", "XTENSA", "RISCV64[sub]", "RISCV32[sub]" +if (NOT DEFINED WAMR_BUILD_TARGET) + if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)") + set (WAMR_BUILD_TARGET "AARCH64") + elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64") + set (WAMR_BUILD_TARGET "RISCV64") + elseif (CMAKE_SIZEOF_VOID_P EQUAL 8) + # Build as X86_64 by default in 64-bit platform + set (WAMR_BUILD_TARGET "X86_64") + elseif (CMAKE_SIZEOF_VOID_P EQUAL 4) + # Build as X86_32 by default in 32-bit platform + set (WAMR_BUILD_TARGET "X86_32") + else () + message(SEND_ERROR "Unsupported build target platform!") + endif () +endif () + +if (NOT CMAKE_BUILD_TYPE) + set (CMAKE_BUILD_TYPE Debug) +endif () + +set (WAMR_BUILD_INTERP 1) +set (WAMR_BUILD_AOT 1) +set (WAMR_BUILD_JIT 0) +set (WAMR_BUILD_LIBC_BUILTIN 0) +set (WAMR_BUILD_LIBC_WASI 1) + +if (NOT MSVC) + # linker flags + if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang")) + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") + endif () + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wformat -Wformat-security") + if (WAMR_BUILD_TARGET MATCHES "X86_.*" OR WAMR_BUILD_TARGET STREQUAL "AMD_64") + if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang")) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mindirect-branch-register") + endif () + endif () +endif () + +# build out vmlib +set (WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) +include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake) + +add_library(vmlib ${WAMR_RUNTIME_LIB_SOURCE}) + +################ application related ################ +include_directories(${CMAKE_CURRENT_LIST_DIR}/src) +include (${SHARED_DIR}/utils/uncommon/shared_uncommon.cmake) + +add_executable (native-stack-overflow src/main.c src/native_impl.c ${UNCOMMON_SHARED_SOURCE}) + +check_pie_supported() +set_target_properties (native-stack-overflow PROPERTIES POSITION_INDEPENDENT_CODE ON) + +if (APPLE) + target_link_libraries (native-stack-overflow vmlib -lm -ldl -lpthread) +else () + target_link_libraries (native-stack-overflow vmlib -lm -ldl -lpthread -lrt) +endif () diff --git a/samples/native-stack-overflow/README.md b/samples/native-stack-overflow/README.md new file mode 100644 index 000000000..431c5876e --- /dev/null +++ b/samples/native-stack-overflow/README.md @@ -0,0 +1,4 @@ +The "native-stack-overflow" sample project +========================================== + +This sample examines native stack overflow detection mechanisms. diff --git a/samples/native-stack-overflow/build.sh b/samples/native-stack-overflow/build.sh new file mode 100755 index 000000000..3961098a9 --- /dev/null +++ b/samples/native-stack-overflow/build.sh @@ -0,0 +1,75 @@ +# +# Copyright (C) 2019 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# + +#!/bin/bash + +CURR_DIR=$PWD +WAMR_DIR=${PWD}/../.. +OUT_DIR=${PWD}/out + +WASM_APPS=${PWD}/wasm-apps + + +rm -rf ${OUT_DIR} +mkdir ${OUT_DIR} +mkdir ${OUT_DIR}/wasm-apps + + +echo "##################### build (default)" +cd ${CURR_DIR} +mkdir -p cmake_build +cd cmake_build +cmake .. +make -j 4 +if [ $? != 0 ];then + echo "BUILD_FAIL native-stack-overflow exit as $?\n" + exit 2 +fi +cp -a native-stack-overflow ${OUT_DIR} + +echo "##################### build (WAMR_DISABLE_HW_BOUND_CHECK=1)" +cd ${CURR_DIR} +mkdir -p cmake_build_disable_hw_bound +cd cmake_build_disable_hw_bound +cmake -D WAMR_DISABLE_HW_BOUND_CHECK=1 .. +make -j 4 +if [ $? != 0 ];then + echo "BUILD_FAIL native-stack-overflow exit as $?\n" + exit 2 +fi +cp -a native-stack-overflow ${OUT_DIR}/native-stack-overflow.WAMR_DISABLE_HW_BOUND_CHECK + +echo + +echo "##################### build wasm apps" + +cd ${WASM_APPS} + +for i in `ls *.c` +do +APP_SRC="$i" +OUT_FILE=${i%.*}.wasm + +# use WAMR SDK to build out the .wasm binary +/opt/wasi-sdk/bin/clang \ + -mexec-model=reactor \ + -Os -z stack-size=4096 -Wl,--initial-memory=65536 \ + -Wl,--allow-undefined \ + -o ${OUT_DIR}/wasm-apps/${OUT_FILE} ${APP_SRC} + +if [ -f ${OUT_DIR}/wasm-apps/${OUT_FILE} ]; then + echo "build ${OUT_FILE} success" +else + echo "build ${OUT_FILE} fail" +fi +done +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} + +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} diff --git a/samples/native-stack-overflow/clean.sh b/samples/native-stack-overflow/clean.sh new file mode 100755 index 000000000..5eba9bc37 --- /dev/null +++ b/samples/native-stack-overflow/clean.sh @@ -0,0 +1 @@ +rm -r cmake_build cmake_build_disable_hw_bound out diff --git a/samples/native-stack-overflow/run.sh b/samples/native-stack-overflow/run.sh new file mode 100755 index 000000000..aa2baa570 --- /dev/null +++ b/samples/native-stack-overflow/run.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +echo "====== Interpreter" +out/native-stack-overflow out/wasm-apps/testapp.wasm + +echo +echo "====== AOT" +out/native-stack-overflow out/wasm-apps/testapp.wasm.aot + +echo +echo "====== AOT WAMR_DISABLE_HW_BOUND_CHECK=1" +out/native-stack-overflow.WAMR_DISABLE_HW_BOUND_CHECK out/wasm-apps/testapp.wasm.aot.bounds-checks diff --git a/samples/native-stack-overflow/src/main.c b/samples/native-stack-overflow/src/main.c new file mode 100644 index 000000000..fa86e4df2 --- /dev/null +++ b/samples/native-stack-overflow/src/main.c @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2024 Midokura Japan KK. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "wasm_export.h" +#include "bh_read_file.h" + +uint32_t +host_consume_stack_and_call_indirect(wasm_exec_env_t exec_env, uint32_t funcidx, + uint32_t x, uint32_t stack); +uint32_t +host_consume_stack(wasm_exec_env_t exec_env, uint32_t stack); + +extern unsigned int nest; + +static NativeSymbol native_symbols[] = { + { "host_consume_stack_and_call_indirect", + host_consume_stack_and_call_indirect, "(iii)i", NULL }, + { "host_consume_stack", host_consume_stack, "(i)i", NULL }, +}; + +struct record { + bool failed; + bool leaked; + char exception[128]; /* EXCEPTION_BUF_LEN */ +}; + +void +print_record(unsigned int start, unsigned int end, const struct record *rec) +{ + printf("%5u - %5u | %6s | %6s | %s\n", start, end, + rec->failed ? "failed" : "ok", rec->leaked ? "leaked" : "ok", + rec->exception); +} + +int +main(int argc, char **argv) +{ + char *buffer; + char error_buf[128]; + + if (argc != 2) { + return 2; + } + char *module_path = argv[1]; + + wasm_module_t module = NULL; + uint32 buf_size; + uint32 stack_size = 4096; + /* + * disable app heap. + * - we use wasi + * - https://github.com/bytecodealliance/wasm-micro-runtime/issues/2275 + */ + uint32 heap_size = 0; + + RuntimeInitArgs init_args; + memset(&init_args, 0, sizeof(RuntimeInitArgs)); + init_args.mem_alloc_type = Alloc_With_System_Allocator; + init_args.n_native_symbols = sizeof(native_symbols) / sizeof(NativeSymbol); + init_args.native_module_name = "env"; + init_args.native_symbols = native_symbols; + if (!wasm_runtime_full_init(&init_args)) { + printf("wasm_runtime_full_init failed.\n"); + return -1; + } + + buffer = bh_read_file_to_buffer(module_path, &buf_size); + if (!buffer) { + printf("bh_read_file_to_buffer failed\n"); + goto fail; + } + + module = wasm_runtime_load((uint8 *)buffer, buf_size, error_buf, + sizeof(error_buf)); + if (!module) { + printf("wasm_runtime_load failed: %s\n", error_buf); + goto fail; + } + + /* header */ + printf(" stack size | fail? | leak? | exception\n"); + printf("-------------------------------------------------------------------" + "--------\n"); + + unsigned int stack; + unsigned int prevstack; + unsigned int stack_range_start = 0; + unsigned int stack_range_end = 4096 * 6; + unsigned int step = 16; + struct record rec0; + struct record rec1; + struct record *rec = &rec0; + struct record *prevrec = &rec1; + bool have_prevrec = false; + for (stack = stack_range_start; stack < stack_range_end; stack += step) { + wasm_module_inst_t module_inst = NULL; + wasm_exec_env_t exec_env = NULL; + bool failed = true; + const char *exception = NULL; + nest = 0; + + module_inst = wasm_runtime_instantiate(module, stack_size, heap_size, + error_buf, sizeof(error_buf)); + if (!module_inst) { + printf("wasm_runtime_instantiate failed: %s\n", error_buf); + goto fail2; + } + + exec_env = wasm_runtime_create_exec_env(module_inst, stack_size); + if (!exec_env) { + printf("wasm_runtime_create_exec_env failed\n"); + goto fail2; + } + + const char *funcname = "test"; + wasm_function_inst_t func = + wasm_runtime_lookup_function(module_inst, funcname); + if (!func) { + printf("wasm_runtime_lookup_function failed for %s\n", funcname); + goto fail2; + } + + /* note: the function type is (ii)i */ + uint32_t wasm_argv[] = { + stack, + 30, + }; + uint32_t wasm_argc = 2; + if (!wasm_runtime_call_wasm(exec_env, func, wasm_argc, wasm_argv)) { + exception = wasm_runtime_get_exception(module_inst); + goto fail2; + } + failed = false; + fail2: + /* + * note: non-zero "nest" here demonstrates resource leak on longjmp + * from signal handler. + * cf. + * https://github.com/bytecodealliance/wasm-micro-runtime/issues/3320 + */ + memset(rec, 0, sizeof(*rec)); + rec->failed = failed; + rec->leaked = nest != 0; + strncpy(rec->exception, exception ? exception : "", + sizeof(rec->exception)); + if (have_prevrec && memcmp(prevrec, rec, sizeof(*rec))) { + print_record(prevstack, stack, prevrec); + have_prevrec = false; + } + if (!have_prevrec) { + prevstack = stack; + struct record *tmp = prevrec; + prevrec = rec; + rec = tmp; + have_prevrec = true; + } + if (exec_env) { + wasm_runtime_destroy_exec_env(exec_env); + } + if (module_inst) { + wasm_runtime_deinstantiate(module_inst); + } + } + if (have_prevrec) { + print_record(prevstack, stack, prevrec); + } + +fail: + if (module) { + wasm_runtime_unload(module); + } + if (buffer) { + BH_FREE(buffer); + } + wasm_runtime_destroy(); +} diff --git a/samples/native-stack-overflow/src/native_impl.c b/samples/native-stack-overflow/src/native_impl.c new file mode 100644 index 000000000..6eb349c68 --- /dev/null +++ b/samples/native-stack-overflow/src/native_impl.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 Midokura Japan KK. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include + +#include "wasm_export.h" +#include "bh_platform.h" + +/* + * this "nest" var has two purposes: + * - prevent tail-call optimization + * - detect possible resource leak + */ +unsigned int nest = 0; +ptrdiff_t prev_diff = 0; + +uint32_t +call_indirect(wasm_exec_env_t exec_env, uint32_t funcidx, uint32_t x) +{ + uint32_t argv[1] = { + x, + }; + uint32_t argc = 1; + if (!wasm_runtime_call_indirect(exec_env, funcidx, argc, argv)) { + /* failed */ + return 0; + } + return argv[0]; +} + +uint32_t +host_consume_stack_and_call_indirect(wasm_exec_env_t exec_env, uint32_t funcidx, + uint32_t x, uint32_t stack) +{ + void *boundary = os_thread_get_stack_boundary(); + void *fp = __builtin_frame_address(0); + ptrdiff_t diff = fp - boundary; + if (diff > stack) { + prev_diff = diff; + nest++; + uint32_t ret = + host_consume_stack_and_call_indirect(exec_env, funcidx, x, stack); + nest--; + return ret; + } + return call_indirect(exec_env, funcidx, x); +} + +static uint32_t +consume_stack1(wasm_exec_env_t exec_env, void *base, uint32_t stack) +{ + void *fp = __builtin_frame_address(0); + ptrdiff_t diff = (unsigned char *)base - (unsigned char *)fp; + assert(diff > 0); + char buf[16]; + memset_s(buf, sizeof(buf), 0, sizeof(buf)); + if (diff > stack) { + return diff; + } + return consume_stack1(exec_env, base, stack); +} + +uint32_t +host_consume_stack(wasm_exec_env_t exec_env, uint32_t stack) +{ + void *base = __builtin_frame_address(0); + return consume_stack1(exec_env, base, stack); +} diff --git a/samples/native-stack-overflow/wasm-apps/testapp.c b/samples/native-stack-overflow/wasm-apps/testapp.c new file mode 100644 index 000000000..bc517c397 --- /dev/null +++ b/samples/native-stack-overflow/wasm-apps/testapp.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 Midokura Japan KK. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include + +uint32_t +host_consume_stack_and_call_indirect(int (*)(int), uint32_t, uint32_t); +uint32_t host_consume_stack(uint32_t); + +int +cb(int x) +{ + return x * x; +} + +int +consume_stack_cb(int x) +{ + /* + * intentions: + * + * - consume native stack by making recursive calls + * + * - avoid tail-call optimization (either by the C compiler or + * aot-compiler) + */ + if (x == 0) { + return 0; + } + return consume_stack_cb(x - 1) + 1; +} + +int +host_consume_stack_cb(int x) +{ + return host_consume_stack(x); +} + +__attribute__((export_name("test"))) uint32_t +test(uint32_t native_stack, uint32_t recurse_count) +{ + uint32_t ret; + ret = host_consume_stack_and_call_indirect(cb, 321, native_stack); + ret = host_consume_stack_and_call_indirect(consume_stack_cb, recurse_count, + native_stack); +#if 0 /* notyet */ + ret = host_consume_stack_and_call_indirect(host_consume_stack_cb, 1000000, + native_stack); +#endif + return 42; +}