wasm-micro-runtime/tests/unit/exception-handling/exception_handling_test.cc
Yi Liu 539bebed9c
Fix missing IS_INVALID_TAGINDEX check in RETHROW handler (#4837)
* 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].

* 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.
2026-03-09 10:04:18 +08:00

162 lines
5.5 KiB
C++

/*
* 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, 0x08, 0x01,
0x06, 0x00, /* body size=6, 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));
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));
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);
ASSERT_TRUE(ok) << "wasm_runtime_call_wasm failed: "
<< wasm_runtime_get_exception(inst);
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";
}