From 18092f86ccdd0a9a0a1917b7bcb7c0161f47333d Mon Sep 17 00:00:00 2001 From: Huang Qi Date: Tue, 4 Jul 2023 16:21:30 +0800 Subject: [PATCH] Make memory access boundary check behavior configurable (#2289) Allow to use `cmake -DWAMR_CONFIGURABLE_BOUNDS_CHECKS=1` to build iwasm, and then run `iwasm --disable-bounds-checks` to disable the memory access boundary checks. And add two APIs: `wasm_runtime_set_bounds_checks` and `wasm_runtime_is_bounds_checks_enabled` --- build-scripts/config_common.cmake | 4 ++ core/config.h | 5 ++ core/iwasm/aot/aot_runtime.h | 4 ++ core/iwasm/common/wasm_memory.c | 60 +++++++++++++++++++- core/iwasm/common/wasm_runtime_common.c | 48 ++++++++++++++++ core/iwasm/common/wasm_runtime_common.h | 11 ++++ core/iwasm/include/wasm_export.h | 19 +++++++ core/iwasm/interpreter/wasm_interp_classic.c | 44 ++++++++------ core/iwasm/interpreter/wasm_interp_fast.c | 27 +++++---- core/iwasm/interpreter/wasm_runtime.h | 4 ++ doc/perf_tune.md | 15 +++++ product-mini/platforms/nuttx/wamr.mk | 6 ++ product-mini/platforms/posix/main.c | 17 ++++++ 13 files changed, 234 insertions(+), 30 deletions(-) diff --git a/build-scripts/config_common.cmake b/build-scripts/config_common.cmake index 19f0772c..6b4247e0 100644 --- a/build-scripts/config_common.cmake +++ b/build-scripts/config_common.cmake @@ -396,3 +396,7 @@ if (WAMR_DISABLE_WRITE_GS_BASE EQUAL 1) add_definitions (-DWASM_DISABLE_WRITE_GS_BASE=1) message (" Write linear memory base addr to x86 GS register disabled") endif () +if (WAMR_CONFIGUABLE_BOUNDS_CHECKS EQUAL 1) + add_definitions (-DWASM_CONFIGURABLE_BOUNDS_CHECKS=1) + message (" Configurable bounds checks enabled") +endif () diff --git a/core/config.h b/core/config.h index 0d99d580..f33290f0 100644 --- a/core/config.h +++ b/core/config.h @@ -456,4 +456,9 @@ #define WASM_DISABLE_WRITE_GS_BASE 0 #endif +/* Configurable bounds checks */ +#ifndef WASM_CONFIGURABLE_BOUNDS_CHECKS +#define WASM_CONFIGURABLE_BOUNDS_CHECKS 0 +#endif + #endif /* end of _CONFIG_H_ */ diff --git a/core/iwasm/aot/aot_runtime.h b/core/iwasm/aot/aot_runtime.h index 5aaac41b..8d679d55 100644 --- a/core/iwasm/aot/aot_runtime.h +++ b/core/iwasm/aot/aot_runtime.h @@ -90,6 +90,10 @@ typedef struct AOTFunctionInstance { typedef struct AOTModuleInstanceExtra { DefPointer(const uint32 *, stack_sizes); CApiFuncImport *c_api_func_imports; +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 + /* Disable bounds checks or not */ + bool disable_bounds_checks; +#endif } AOTModuleInstanceExtra; #if defined(OS_ENABLE_HW_BOUND_CHECK) && defined(BH_PLATFORM_WINDOWS) diff --git a/core/iwasm/common/wasm_memory.c b/core/iwasm/common/wasm_memory.c index 310dab6d..b2ec2a89 100644 --- a/core/iwasm/common/wasm_memory.c +++ b/core/iwasm/common/wasm_memory.c @@ -5,6 +5,7 @@ #include "wasm_runtime_common.h" #include "../interpreter/wasm_runtime.h" +#include "../aot/aot_runtime.h" #include "bh_platform.h" #include "mem_alloc.h" @@ -87,6 +88,16 @@ wasm_memory_init_with_allocator(void *_malloc_func, void *_realloc_func, } #endif +static inline bool +is_bounds_checks_enabled(WASMModuleInstanceCommon *module_inst) +{ +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 + return wasm_runtime_is_bounds_checks_enabled(module_inst); +#else + return true; +#endif +} + bool wasm_runtime_memory_init(mem_alloc_type_t mem_alloc_type, const MemAllocOption *alloc_option) @@ -269,6 +280,10 @@ wasm_runtime_validate_app_addr(WASMModuleInstanceCommon *module_inst_comm, bh_assert(module_inst_comm->module_type == Wasm_Module_Bytecode || module_inst_comm->module_type == Wasm_Module_AoT); + if (!is_bounds_checks_enabled(module_inst_comm)) { + return true; + } + memory_inst = wasm_get_default_memory(module_inst); if (!memory_inst) { goto fail; @@ -299,6 +314,10 @@ wasm_runtime_validate_app_str_addr(WASMModuleInstanceCommon *module_inst_comm, bh_assert(module_inst_comm->module_type == Wasm_Module_Bytecode || module_inst_comm->module_type == Wasm_Module_AoT); + if (!is_bounds_checks_enabled(module_inst_comm)) { + return true; + } + if (!wasm_runtime_get_app_addr_range(module_inst_comm, app_str_offset, NULL, &app_end_offset)) goto fail; @@ -327,6 +346,10 @@ wasm_runtime_validate_native_addr(WASMModuleInstanceCommon *module_inst_comm, bh_assert(module_inst_comm->module_type == Wasm_Module_Bytecode || module_inst_comm->module_type == Wasm_Module_AoT); + if (!is_bounds_checks_enabled(module_inst_comm)) { + return true; + } + memory_inst = wasm_get_default_memory(module_inst); if (!memory_inst) { goto fail; @@ -354,10 +377,13 @@ wasm_runtime_addr_app_to_native(WASMModuleInstanceCommon *module_inst_comm, WASMModuleInstance *module_inst = (WASMModuleInstance *)module_inst_comm; WASMMemoryInstance *memory_inst; uint8 *addr; + bool bounds_checks; bh_assert(module_inst_comm->module_type == Wasm_Module_Bytecode || module_inst_comm->module_type == Wasm_Module_AoT); + bounds_checks = is_bounds_checks_enabled(module_inst_comm); + memory_inst = wasm_get_default_memory(module_inst); if (!memory_inst) { return NULL; @@ -365,8 +391,17 @@ wasm_runtime_addr_app_to_native(WASMModuleInstanceCommon *module_inst_comm, addr = memory_inst->memory_data + app_offset; - if (memory_inst->memory_data <= addr && addr < memory_inst->memory_data_end) + if (bounds_checks) { + if (memory_inst->memory_data <= addr + && addr < memory_inst->memory_data_end) { + + return addr; + } + } + /* If bounds checks is disabled, return the address directly */ + else if (app_offset != 0) { return addr; + } return NULL; } @@ -378,17 +413,27 @@ wasm_runtime_addr_native_to_app(WASMModuleInstanceCommon *module_inst_comm, WASMModuleInstance *module_inst = (WASMModuleInstance *)module_inst_comm; WASMMemoryInstance *memory_inst; uint8 *addr = (uint8 *)native_ptr; + bool bounds_checks; bh_assert(module_inst_comm->module_type == Wasm_Module_Bytecode || module_inst_comm->module_type == Wasm_Module_AoT); + bounds_checks = is_bounds_checks_enabled(module_inst_comm); + memory_inst = wasm_get_default_memory(module_inst); if (!memory_inst) { return 0; } - if (memory_inst->memory_data <= addr && addr < memory_inst->memory_data_end) + if (bounds_checks) { + if (memory_inst->memory_data <= addr + && addr < memory_inst->memory_data_end) + return (uint32)(addr - memory_inst->memory_data); + } + /* If bounds checks is disabled, return the offset directly */ + else if (addr != NULL) { return (uint32)(addr - memory_inst->memory_data); + } return 0; } @@ -460,6 +505,7 @@ wasm_check_app_addr_and_convert(WASMModuleInstance *module_inst, bool is_str, { WASMMemoryInstance *memory_inst = wasm_get_default_memory(module_inst); uint8 *native_addr; + bool bounds_checks; if (!memory_inst) { goto fail; @@ -467,6 +513,15 @@ wasm_check_app_addr_and_convert(WASMModuleInstance *module_inst, bool is_str, native_addr = memory_inst->memory_data + app_buf_addr; + bounds_checks = is_bounds_checks_enabled((wasm_module_inst_t)module_inst); + + if (!bounds_checks) { + if (app_buf_addr == 0) { + native_addr = NULL; + } + goto success; + } + /* No need to check the app_offset and buf_size if memory access boundary check with hardware trap is enabled */ #ifndef OS_ENABLE_HW_BOUND_CHECK @@ -492,6 +547,7 @@ wasm_check_app_addr_and_convert(WASMModuleInstance *module_inst, bool is_str, } #endif +success: *p_native_addr = (void *)native_addr; return true; fail: diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c index b2923db3..5467065f 100644 --- a/core/iwasm/common/wasm_runtime_common.c +++ b/core/iwasm/common/wasm_runtime_common.c @@ -2482,6 +2482,54 @@ wasm_runtime_get_custom_data(WASMModuleInstanceCommon *module_inst_comm) return module_inst->custom_data; } +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 +void +wasm_runtime_set_bounds_checks(WASMModuleInstanceCommon *module_inst, + bool enable) +{ + /* Alwary disable bounds checks if hw bounds checks enabled */ +#ifdef OS_ENABLE_HW_BOUND_CHECK + enable = false; +#endif +#if WASM_ENABLE_INTERP != 0 + if (module_inst->module_type == Wasm_Module_Bytecode) { + ((WASMModuleInstanceExtra *)((WASMModuleInstance *)module_inst)->e) + ->disable_bounds_checks = enable ? false : true; + } +#endif + +#if WASM_ENABLE_AOT != 0 + if (module_inst->module_type == Wasm_Module_AoT) { + ((AOTModuleInstanceExtra *)((AOTModuleInstance *)module_inst)->e) + ->disable_bounds_checks = enable ? false : true; + } +#endif +} + +bool +wasm_runtime_is_bounds_checks_enabled(WASMModuleInstanceCommon *module_inst) +{ + +#if WASM_ENABLE_INTERP != 0 + if (module_inst->module_type == Wasm_Module_Bytecode) { + return !((WASMModuleInstanceExtra *)((WASMModuleInstance *)module_inst) + ->e) + ->disable_bounds_checks; + } +#endif + +#if WASM_ENABLE_AOT != 0 + if (module_inst->module_type == Wasm_Module_AoT) { + return !((AOTModuleInstanceExtra *)((WASMModuleInstance *)module_inst) + ->e) + ->disable_bounds_checks; + } +#endif + + return true; +} +#endif + uint32 wasm_runtime_module_malloc_internal(WASMModuleInstanceCommon *module_inst, WASMExecEnv *exec_env, uint32 size, diff --git a/core/iwasm/common/wasm_runtime_common.h b/core/iwasm/common/wasm_runtime_common.h index 283d2ed5..60a32cb8 100644 --- a/core/iwasm/common/wasm_runtime_common.h +++ b/core/iwasm/common/wasm_runtime_common.h @@ -593,6 +593,17 @@ wasm_runtime_set_user_data(WASMExecEnv *exec_env, void *user_data); WASM_RUNTIME_API_EXTERN void * wasm_runtime_get_user_data(WASMExecEnv *exec_env); +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 +/* See wasm_export.h for description */ +WASM_RUNTIME_API_EXTERN void +wasm_runtime_set_bounds_checks(WASMModuleInstanceCommon *module_inst, + bool enable); + +/* See wasm_export.h for description */ +WASM_RUNTIME_API_EXTERN bool +wasm_runtime_is_bounds_checks_enabled(WASMModuleInstanceCommon *module_inst); +#endif + #ifdef OS_ENABLE_HW_BOUND_CHECK /* Access exception check guard page to trigger the signal handler */ void diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h index 28b952e8..4d02e027 100644 --- a/core/iwasm/include/wasm_export.h +++ b/core/iwasm/include/wasm_export.h @@ -914,6 +914,25 @@ wasm_runtime_set_custom_data(wasm_module_inst_t module_inst, WASM_RUNTIME_API_EXTERN void * wasm_runtime_get_custom_data(wasm_module_inst_t module_inst); +/** + * Set the memory bounds checks flag of a WASM module instance. + * + * @param module_inst the WASM module instance + * @param enable the flag to enable/disable the memory bounds checks + */ +WASM_RUNTIME_API_EXTERN void +wasm_runtime_set_bounds_checks(wasm_module_inst_t module_inst, + bool enable); +/** + * Check if the memory bounds checks flag is enabled for a WASM module instance. + * + * @param module_inst the WASM module instance + * + * @return true if the memory bounds checks flag is enabled, false otherwise + */ +WASM_RUNTIME_API_EXTERN bool +wasm_runtime_is_bounds_checks_enabled( + wasm_module_inst_t module_inst); /** * Allocate memory from the heap of WASM module instance * diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c index 6defb046..d63b0a0e 100644 --- a/core/iwasm/interpreter/wasm_interp_classic.c +++ b/core/iwasm/interpreter/wasm_interp_classic.c @@ -41,26 +41,28 @@ typedef float64 CellType_F64; #if !defined(OS_ENABLE_HW_BOUND_CHECK) \ || WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS == 0 -#define CHECK_MEMORY_OVERFLOW(bytes) \ - do { \ - uint64 offset1 = (uint64)offset + (uint64)addr; \ - if (offset1 + bytes <= (uint64)get_linear_mem_size()) \ - /* If offset1 is in valid range, maddr must also \ - be in valid range, no need to check it again. */ \ - maddr = memory->memory_data + offset1; \ - else \ - goto out_of_bounds; \ +#define CHECK_MEMORY_OVERFLOW(bytes) \ + do { \ + uint64 offset1 = (uint64)offset + (uint64)addr; \ + if (disable_bounds_checks \ + || offset1 + bytes <= (uint64)get_linear_mem_size()) \ + /* If offset1 is in valid range, maddr must also \ + be in valid range, no need to check it again. */ \ + maddr = memory->memory_data + offset1; \ + else \ + goto out_of_bounds; \ } while (0) -#define CHECK_BULK_MEMORY_OVERFLOW(start, bytes, maddr) \ - do { \ - uint64 offset1 = (uint32)(start); \ - if (offset1 + bytes <= (uint64)get_linear_mem_size()) \ - /* App heap space is not valid space for \ - bulk memory operation */ \ - maddr = memory->memory_data + offset1; \ - else \ - goto out_of_bounds; \ +#define CHECK_BULK_MEMORY_OVERFLOW(start, bytes, maddr) \ + do { \ + uint64 offset1 = (uint32)(start); \ + if (disable_bounds_checks \ + || offset1 + bytes <= (uint64)get_linear_mem_size()) \ + /* App heap space is not valid space for \ + bulk memory operation */ \ + maddr = memory->memory_data + offset1; \ + else \ + goto out_of_bounds; \ } while (0) #else #define CHECK_MEMORY_OVERFLOW(bytes) \ @@ -1174,6 +1176,12 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, uint8 local_type, *global_addr; uint32 cache_index, type_index, param_cell_num, cell_num; uint8 value_type; +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 + bool disable_bounds_checks = !wasm_runtime_is_bounds_checks_enabled( + (WASMModuleInstanceCommon *)module); +#else + bool disable_bounds_checks = false; +#endif #if WASM_ENABLE_DEBUG_INTERP != 0 uint8 *frame_ip_orig = NULL; diff --git a/core/iwasm/interpreter/wasm_interp_fast.c b/core/iwasm/interpreter/wasm_interp_fast.c index 63d30028..0852ab2f 100644 --- a/core/iwasm/interpreter/wasm_interp_fast.c +++ b/core/iwasm/interpreter/wasm_interp_fast.c @@ -35,7 +35,8 @@ typedef float64 CellType_F64; #define CHECK_MEMORY_OVERFLOW(bytes) \ do { \ uint64 offset1 = (uint64)offset + (uint64)addr; \ - if (offset1 + bytes <= (uint64)get_linear_mem_size()) \ + if (disable_bounds_checks \ + || offset1 + bytes <= (uint64)get_linear_mem_size()) \ /* If offset1 is in valid range, maddr must also \ be in valid range, no need to check it again. */ \ maddr = memory->memory_data + offset1; \ @@ -43,15 +44,15 @@ typedef float64 CellType_F64; goto out_of_bounds; \ } while (0) -#define CHECK_BULK_MEMORY_OVERFLOW(start, bytes, maddr) \ - do { \ - uint64 offset1 = (uint32)(start); \ - if (offset1 + bytes <= get_linear_mem_size()) \ - /* App heap space is not valid space for \ - bulk memory operation */ \ - maddr = memory->memory_data + offset1; \ - else \ - goto out_of_bounds; \ +#define CHECK_BULK_MEMORY_OVERFLOW(start, bytes, maddr) \ + do { \ + uint64 offset1 = (uint32)(start); \ + if (disable_bounds_checks || offset1 + bytes <= get_linear_mem_size()) \ + /* App heap space is not valid space for \ + bulk memory operation */ \ + maddr = memory->memory_data + offset1; \ + else \ + goto out_of_bounds; \ } while (0) #else #define CHECK_MEMORY_OVERFLOW(bytes) \ @@ -1199,6 +1200,12 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, uint8 *maddr = NULL; uint32 local_idx, local_offset, global_idx; uint8 opcode, local_type, *global_addr; +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 + bool disable_bounds_checks = !wasm_runtime_is_bounds_checks_enabled( + (WASMModuleInstanceCommon *)module); +#else + bool disable_bounds_checks = false; +#endif #if WASM_ENABLE_LABELS_AS_VALUES != 0 #define HANDLE_OPCODE(op) &&HANDLE_##op diff --git a/core/iwasm/interpreter/wasm_runtime.h b/core/iwasm/interpreter/wasm_runtime.h index ef9f0b71..d5665c24 100644 --- a/core/iwasm/interpreter/wasm_runtime.h +++ b/core/iwasm/interpreter/wasm_runtime.h @@ -241,6 +241,10 @@ typedef struct WASMModuleInstanceExtra { && WASM_ENABLE_LAZY_JIT != 0) WASMModuleInstance *next; #endif +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 + /* Disable bounds checks or not */ + bool disable_bounds_checks; +#endif } WASMModuleInstanceExtra; struct AOTFuncPerfProfInfo; diff --git a/doc/perf_tune.md b/doc/perf_tune.md index 05a6433a..2cc42898 100644 --- a/doc/perf_tune.md +++ b/doc/perf_tune.md @@ -72,3 +72,18 @@ wasm_runtime_dump_pgo_prof_data_to_buf(wasm_module_inst_t module_inst, char *buf 6. Run the optimized aot_file: `iwasm `. Developer can refer to the `test_pgo.sh` files under each benchmark folder for more details, e.g. [test_pgo.sh](../tests/benchmarks/coremark/test_pgo.sh) of CoreMark benchmark. + +## 6. Disable the memory boundary check + +Please notice that this method is not a general solution since it may lead to security issues. And only boost the performance for some platforms in AOT mode and don't support hardware trap for memory boundary check. + +1. Build WAMR with `-DWAMR_CONFIGUABLE_BOUNDS_CHECKS=1` option. + +2. Compile AOT module by wamrc with `--bounds-check=0` option. + +3. Run the AOT module by iwasm with `--disable-bounds-checks` option. + +> Note: The size of AOT file will be much smaller than the default, and some tricks are possible such as let the wasm application access the memory of host os directly. +Please notice that if this option is enabled, the wasm spec test will fail since it requires the memory boundary check. For example, the runtime will crash when accessing the memory out of the boundary in some cases instead of throwing an exception as the spec requires. + +You should only use this method for well tested wasm applications and make sure the memory access is safe. diff --git a/product-mini/platforms/nuttx/wamr.mk b/product-mini/platforms/nuttx/wamr.mk index 78cf3eea..da5cdcc2 100644 --- a/product-mini/platforms/nuttx/wamr.mk +++ b/product-mini/platforms/nuttx/wamr.mk @@ -230,6 +230,12 @@ else CFLAGS += -DWASM_ENABLE_LIBC_BUILTIN=0 endif +ifeq ($(CONFIG_INTERPRETERS_WAMR_CONFIGUABLE_BOUNDS_CHECKS),y) +CFLAGS += -DWASM_CONFIGUABLE_BOUNDS_CHECKS=1 +else +CFLAGS += -DWASM_CONFIGUABLE_BOUNDS_CHECKS=0 +endif + ifeq ($(CONFIG_INTERPRETERS_WAMR_LIBC_WASI),y) CFLAGS += -DWASM_ENABLE_LIBC_WASI=1 CFLAGS += -I$(IWASM_ROOT)/libraries/libc-wasi/sandboxed-system-primitives/src diff --git a/product-mini/platforms/posix/main.c b/product-mini/platforms/posix/main.c index 752d235e..fec8ec66 100644 --- a/product-mini/platforms/posix/main.c +++ b/product-mini/platforms/posix/main.c @@ -65,6 +65,9 @@ print_help() #endif printf(" --repl Start a very simple REPL (read-eval-print-loop) mode\n" " that runs commands in the form of \"FUNC ARG...\"\n"); +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 + printf(" --disable-bounds-checks Disable bounds checks for memory accesses\n"); +#endif #if WASM_ENABLE_LIBC_WASI != 0 printf(" --env= Pass wasi environment variables with \"key=value\"\n"); printf(" to the program, for example:\n"); @@ -481,6 +484,9 @@ main(int argc, char *argv[]) #endif bool is_repl_mode = false; bool is_xip_file = false; +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 + bool disable_bounds_checks = false; +#endif #if WASM_ENABLE_LIBC_WASI != 0 const char *dir_list[8] = { NULL }; uint32 dir_list_size = 0; @@ -545,6 +551,11 @@ main(int argc, char *argv[]) else if (!strcmp(argv[0], "--repl")) { is_repl_mode = true; } +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 + else if (!strcmp(argv[0], "--disable-bounds-checks")) { + disable_bounds_checks = true; + } +#endif else if (!strncmp(argv[0], "--stack-size=", 13)) { if (argv[0][13] == '\0') return print_help(); @@ -832,6 +843,12 @@ main(int argc, char *argv[]) goto fail3; } +#if WASM_CONFIGUABLE_BOUNDS_CHECKS != 0 + if (disable_bounds_checks) { + wasm_runtime_set_bounds_checks(wasm_module_inst, false); + } +#endif + #if WASM_ENABLE_DEBUG_INTERP != 0 if (ip_addr != NULL) { wasm_exec_env_t exec_env =