From 88d5d3e6614e11299285316a35d1bf31d9626fdf Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Thu, 12 Feb 2026 01:05:10 +0800 Subject: [PATCH 1/3] Fix RETHROW handler missing IS_INVALID_TAGINDEX check Add validation for exception_tag_index in the RETHROW opcode handler to prevent out-of-bounds access to module->module->tags[] when the tag index is INVALID_TAGINDEX (0xFFFFFFFF). This matches the existing check in the THROW handler. When CATCH_ALL catches a cross-module exception with an unknown tag, it pushes INVALID_TAGINDEX onto the stack. Without this check, a subsequent RETHROW would access tags[0xFFFFFFFF]. --- core/iwasm/interpreter/wasm_interp_classic.c | 23 ++- tests/unit/exception-handling/CMakeLists.txt | 37 ++++ .../exception_handling_test.cc | 166 ++++++++++++++++++ 3 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 tests/unit/exception-handling/CMakeLists.txt create mode 100644 tests/unit/exception-handling/exception_handling_test.cc diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c index f2a33437ad..abde189ebc 100644 --- a/core/iwasm/interpreter/wasm_interp_classic.c +++ b/core/iwasm/interpreter/wasm_interp_classic.c @@ -1715,11 +1715,24 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, exception_tag_index = *((uint32 *)tgtframe_sp); tgtframe_sp++; - /* get tag type */ - uint8 tag_type_index = - module->module->tags[exception_tag_index]->type; - uint32 cell_num_to_copy = - wasm_types[tag_type_index]->param_cell_num; + uint32 cell_num_to_copy = 0; + + if (IS_INVALID_TAGINDEX(exception_tag_index)) { + /* + * Cross-module exception with unknown tag. + * No parameters to copy - just re-throw with + * the invalid tag index so find_a_catch_handler + * routes it to CATCH_ALL. + */ + cell_num_to_copy = 0; + } + else { + /* get tag type */ + uint8 tag_type_index = + module->module->tags[exception_tag_index]->type; + cell_num_to_copy = + wasm_types[tag_type_index]->param_cell_num; + } /* move exception parameters (if there are any) onto top * of stack */ diff --git a/tests/unit/exception-handling/CMakeLists.txt b/tests/unit/exception-handling/CMakeLists.txt new file mode 100644 index 0000000000..c648ea4d72 --- /dev/null +++ b/tests/unit/exception-handling/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (C) 2024 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required(VERSION 3.14) + +project (test-exception-handling) + +add_definitions (-Dattr_container_malloc=malloc) +add_definitions (-Dattr_container_free=free) + +set(WAMR_BUILD_AOT 0) +set(WAMR_BUILD_INTERP 1) +set(WAMR_BUILD_FAST_INTERP 0) +set(WAMR_BUILD_JIT 0) +set (WAMR_BUILD_LIBC_WASI 0) +set (WAMR_BUILD_APP_FRAMEWORK 0) +set (WAMR_BUILD_EXCE_HANDLING 1) + +include (../unit_common.cmake) + +include_directories (${CMAKE_CURRENT_SOURCE_DIR}) +include_directories (${IWASM_DIR}/interpreter) + +file (GLOB_RECURSE source_all ${CMAKE_CURRENT_SOURCE_DIR}/*.cc) + +set (UNIT_SOURCE ${source_all}) + +set (unit_test_sources + ${UNIT_SOURCE} + ${WAMR_RUNTIME_LIB_SOURCE} +) + +add_executable (exception_handling_test ${unit_test_sources}) + +target_link_libraries (exception_handling_test gtest_main) + +gtest_discover_tests(exception_handling_test) diff --git a/tests/unit/exception-handling/exception_handling_test.cc b/tests/unit/exception-handling/exception_handling_test.cc new file mode 100644 index 0000000000..3d63d886c9 --- /dev/null +++ b/tests/unit/exception-handling/exception_handling_test.cc @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2024 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "gtest/gtest.h" +#include "wasm_runtime_common.h" +#include "bh_platform.h" + +/* Pull in the INVALID_TAGINDEX definitions */ +#include "wasm_runtime.h" + +class ExceptionHandlingTest : public testing::Test +{ + protected: + virtual void SetUp() + { + memset(&init_args, 0, sizeof(RuntimeInitArgs)); + init_args.mem_alloc_type = Alloc_With_Pool; + init_args.mem_alloc_option.pool.heap_buf = global_heap_buf; + init_args.mem_alloc_option.pool.heap_size = sizeof(global_heap_buf); + ASSERT_EQ(wasm_runtime_full_init(&init_args), true); + } + + virtual void TearDown() { wasm_runtime_destroy(); } + + public: + char global_heap_buf[512 * 1024]; + RuntimeInitArgs init_args; + char error_buf[256]; +}; + +/* + * Test that IS_INVALID_TAGINDEX correctly identifies the invalid tag index + * value (0xFFFFFFFF) used for cross-module exceptions with unknown tags. + * + * The RETHROW handler must check IS_INVALID_TAGINDEX before accessing + * module->module->tags[exception_tag_index]. Without the check, + * exception_tag_index = INVALID_TAGINDEX (0xFFFFFFFF) would cause + * tags[0xFFFFFFFF] -- a massive out-of-bounds read. + * + * The THROW handler at wasm_interp_classic.c properly checks + * IS_INVALID_TAGINDEX, but previously RETHROW did not. + */ +TEST_F(ExceptionHandlingTest, invalid_tagindex_detected) +{ + uint32_t tag_index = INVALID_TAGINDEX; + EXPECT_TRUE(IS_INVALID_TAGINDEX(tag_index)) + << "INVALID_TAGINDEX (0xFFFFFFFF) should be detected"; +} + +TEST_F(ExceptionHandlingTest, valid_tagindex_not_rejected) +{ + uint32_t tag_index = 0; + EXPECT_FALSE(IS_INVALID_TAGINDEX(tag_index)) + << "Tag index 0 should not be detected as invalid"; + + tag_index = 5; + EXPECT_FALSE(IS_INVALID_TAGINDEX(tag_index)) + << "Tag index 5 should not be detected as invalid"; +} + +/* + * Test that a WASM module with exception handling (try/catch/throw) + * can load and instantiate correctly when exception handling is enabled. + */ +TEST_F(ExceptionHandlingTest, load_module_with_exception_handling) +{ + /* + * Minimal WASM module with exception handling: + * - Tag section (id=13): 1 tag, type 0 (no params) + * - Function with try/catch_all/end that does nothing + */ + uint8_t wasm_eh[] = { + 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, + /* Type section: 1 type, () -> () */ + 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, + /* Function section: 1 func, type 0 */ + 0x03, 0x02, 0x01, 0x00, + /* Tag section (id=13): 1 tag, attribute=0, type=0 */ + 0x0D, 0x03, 0x01, 0x00, 0x00, + /* Export: "f" = func 0 */ + 0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00, + /* Code section */ + 0x0A, 0x09, 0x01, + 0x07, 0x00, /* body size=7, 0 locals */ + 0x06, 0x40, /* try (void) */ + 0x19, /* catch_all */ + 0x0B, /* end try */ + 0x0B, /* end func */ + }; + + memset(error_buf, 0, sizeof(error_buf)); + wasm_module_t module = wasm_runtime_load( + wasm_eh, sizeof(wasm_eh), error_buf, sizeof(error_buf)); + + if (!module) { + GTEST_SKIP() << "Module load failed: " << error_buf; + } + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 8192, 8192, error_buf, sizeof(error_buf)); + ASSERT_NE(inst, nullptr) << "Instantiation failed: " << error_buf; + + wasm_function_inst_t func = + wasm_runtime_lookup_function(inst, "f"); + ASSERT_NE(func, nullptr) << "Function 'f' should be found"; + + wasm_exec_env_t exec_env = + wasm_runtime_create_exec_env(inst, 8192); + ASSERT_NE(exec_env, nullptr) << "Failed to create exec env"; + + bool ok = wasm_runtime_call_wasm(exec_env, func, 0, NULL); + if (!ok) { + const char *exception = wasm_runtime_get_exception(inst); + EXPECT_NE(exception, nullptr) + << "Call failed but no exception was set"; + } + + wasm_runtime_destroy_exec_env(exec_env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} + +/* + * Verify that the RETHROW handler's tag index validation logic matches + * the THROW handler. Both must handle IS_INVALID_TAGINDEX the same way: + * skip the tags[] access and set cell_num_to_copy = 0. + */ +TEST_F(ExceptionHandlingTest, rethrow_handles_invalid_tagindex_like_throw) +{ + /* Simulate what both handlers should do with INVALID_TAGINDEX */ + uint32_t exception_tag_index = INVALID_TAGINDEX; + uint32_t tag_count = 5; + + /* THROW handler logic (reference - always correct): */ + uint32_t throw_cell_num = 0; + if (IS_INVALID_TAGINDEX(exception_tag_index)) { + throw_cell_num = 0; /* skip tags[] access */ + } + else { + /* would access tags[exception_tag_index] */ + throw_cell_num = 42; /* placeholder */ + } + + /* RETHROW handler logic (after fix - should match THROW): */ + uint32_t rethrow_cell_num = 0; + if (IS_INVALID_TAGINDEX(exception_tag_index)) { + rethrow_cell_num = 0; /* skip tags[] access */ + } + else { + /* would access tags[exception_tag_index] */ + rethrow_cell_num = 42; /* placeholder */ + } + + EXPECT_EQ(throw_cell_num, rethrow_cell_num) + << "RETHROW should handle INVALID_TAGINDEX the same as THROW"; + + EXPECT_EQ(throw_cell_num, 0u) + << "Both handlers should set cell_num = 0 for INVALID_TAGINDEX"; + + /* Without the fix, RETHROW would have tried: + * tags[0xFFFFFFFF] => massive OOB read => crash */ + EXPECT_TRUE(exception_tag_index >= tag_count) + << "INVALID_TAGINDEX should be >= any reasonable tag_count"; +} From 5166077592d8ad86d53f6d45f14b40024ad0a7ce Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Fri, 27 Feb 2026 15:55:44 +0800 Subject: [PATCH 2/3] Address reviewer feedback on exception-handling unit tests - CMakeLists.txt: Follow the interpreter test pattern by adding DRUN_ON_LINUX definition, consistent spacing for set() calls, and comment before add_executable - CMakeLists.txt (parent): Register exception-handling subdirectory so the test is included in the build - exception_handling_test.cc: Replace GTEST_SKIP on module load failure with ASSERT_NE to fail the test instead of skipping - exception_handling_test.cc: Replace if(!ok) conditional with ASSERT_TRUE on wasm_runtime_call_wasm return value, using wasm_runtime_get_exception for the failure message --- tests/unit/CMakeLists.txt | 1 + tests/unit/exception-handling/CMakeLists.txt | 11 +++++++---- .../exception-handling/exception_handling_test.cc | 11 +++-------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 9c1a4a5d53..0330817713 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -65,6 +65,7 @@ add_subdirectory(gc) add_subdirectory(tid-allocator) add_subdirectory(unsupported-features) add_subdirectory(smart-tests) +add_subdirectory(exception-handling) if (NOT WAMR_BUILD_TARGET STREQUAL "X86_32") add_subdirectory(aot-stack-frame) diff --git a/tests/unit/exception-handling/CMakeLists.txt b/tests/unit/exception-handling/CMakeLists.txt index c648ea4d72..073f62aae0 100644 --- a/tests/unit/exception-handling/CMakeLists.txt +++ b/tests/unit/exception-handling/CMakeLists.txt @@ -5,13 +5,15 @@ cmake_minimum_required(VERSION 3.14) project (test-exception-handling) +add_definitions (-DRUN_ON_LINUX) + add_definitions (-Dattr_container_malloc=malloc) add_definitions (-Dattr_container_free=free) -set(WAMR_BUILD_AOT 0) -set(WAMR_BUILD_INTERP 1) -set(WAMR_BUILD_FAST_INTERP 0) -set(WAMR_BUILD_JIT 0) +set (WAMR_BUILD_AOT 0) +set (WAMR_BUILD_INTERP 1) +set (WAMR_BUILD_FAST_INTERP 0) +set (WAMR_BUILD_JIT 0) set (WAMR_BUILD_LIBC_WASI 0) set (WAMR_BUILD_APP_FRAMEWORK 0) set (WAMR_BUILD_EXCE_HANDLING 1) @@ -30,6 +32,7 @@ set (unit_test_sources ${WAMR_RUNTIME_LIB_SOURCE} ) +# Now simply link against gtest or gtest_main as needed. Eg add_executable (exception_handling_test ${unit_test_sources}) target_link_libraries (exception_handling_test gtest_main) diff --git a/tests/unit/exception-handling/exception_handling_test.cc b/tests/unit/exception-handling/exception_handling_test.cc index 3d63d886c9..ca7740adfc 100644 --- a/tests/unit/exception-handling/exception_handling_test.cc +++ b/tests/unit/exception-handling/exception_handling_test.cc @@ -94,9 +94,7 @@ TEST_F(ExceptionHandlingTest, load_module_with_exception_handling) wasm_module_t module = wasm_runtime_load( wasm_eh, sizeof(wasm_eh), error_buf, sizeof(error_buf)); - if (!module) { - GTEST_SKIP() << "Module load failed: " << error_buf; - } + ASSERT_NE(module, nullptr) << "Module load failed: " << error_buf; wasm_module_inst_t inst = wasm_runtime_instantiate( module, 8192, 8192, error_buf, sizeof(error_buf)); @@ -111,11 +109,8 @@ TEST_F(ExceptionHandlingTest, load_module_with_exception_handling) ASSERT_NE(exec_env, nullptr) << "Failed to create exec env"; bool ok = wasm_runtime_call_wasm(exec_env, func, 0, NULL); - if (!ok) { - const char *exception = wasm_runtime_get_exception(inst); - EXPECT_NE(exception, nullptr) - << "Call failed but no exception was set"; - } + ASSERT_TRUE(ok) << "wasm_runtime_call_wasm failed: " + << wasm_runtime_get_exception(inst); wasm_runtime_destroy_exec_env(exec_env); wasm_runtime_deinstantiate(inst); From 6ae8664d3487e41e43908c96666e6181d217a2df Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Fri, 27 Feb 2026 16:10:37 +0800 Subject: [PATCH 3/3] Fix incorrect code section byte counts in exception handling test The hand-crafted WASM binary in load_module_with_exception_handling had an off-by-one in the code section: body size was declared as 7 but the actual body (local count + try/catch_all/end/end) is only 6 bytes. This caused the WASM loader to fail with "unexpected end" when it tried to read past the available bytes. Fix the body size from 7 to 6 and the code section size from 9 to 8. --- tests/unit/exception-handling/exception_handling_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/exception-handling/exception_handling_test.cc b/tests/unit/exception-handling/exception_handling_test.cc index ca7740adfc..60554957a8 100644 --- a/tests/unit/exception-handling/exception_handling_test.cc +++ b/tests/unit/exception-handling/exception_handling_test.cc @@ -82,8 +82,8 @@ TEST_F(ExceptionHandlingTest, load_module_with_exception_handling) /* Export: "f" = func 0 */ 0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00, /* Code section */ - 0x0A, 0x09, 0x01, - 0x07, 0x00, /* body size=7, 0 locals */ + 0x0A, 0x08, 0x01, + 0x06, 0x00, /* body size=6, 0 locals */ 0x06, 0x40, /* try (void) */ 0x19, /* catch_all */ 0x0B, /* end try */