Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/compilation_on_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,14 @@ jobs:
cmake --build . --config Release --parallel 4
working-directory: wamr-compiler

# The macOS runner ships Apple/Xcode GNU bison 2.3, which cannot parse the
# component-model wave-parser grammar (it uses Bison 2.4+ %code blocks).
# Put Homebrew bison (3.x) ahead of /usr/bin/bison on PATH.
- name: Setup modern bison
run: |
brew install bison
echo "$(brew --prefix bison)/bin" >> "$GITHUB_PATH"

- name: Build Sample [wasm-c-api]
run: |
VERBOSE=1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,23 +213,21 @@ wasm_component_parse_instances_section(const uint8_t **payload,

for (uint32_t j = 0; j < inline_expr_len; j++) {
// inlineexport ::= n:<exportname> si:<sortidx>
WASMComponentCoreName *name = wasm_runtime_malloc(
sizeof(WASMComponentCoreName));
if (!name) {
set_error_buf_ex(error_buf, error_buf_size,
"Failed to allocate memory "
"for component export name");
if (consumed_len)
*consumed_len = (uint32_t)(p - *payload);
return false;
}
/* parse_core_name() allocates the result itself and
* only writes *out on success. Pre-allocating here
* leaked that struct on success and, worse, left
* `name` pointing at uninitialized memory whose
* ->name field free_core_name() then dereferenced
* on the failure path (heap-use-after-free found by
* the component-parser fuzz target). Pass NULL and
* let parse_core_name own the allocation + its own
* cleanup on failure. */
WASMComponentCoreName *name = NULL;

// Parse export name (component-level name)
bool name_parse_success = parse_core_name(
&p, end, &name, error_buf, error_buf_size);
if (!name_parse_success) {
free_core_name(name);
wasm_runtime_free(name);
if (consumed_len)
*consumed_len = (uint32_t)(p - *payload);
return false;
Expand Down Expand Up @@ -539,7 +537,8 @@ wasm_resolve_instance(struct WASMComponentInstSection *instance_section,
fail_inst:
inst_ok = false;
done_inst:
if (instance_expression.args) wasm_runtime_free(instance_expression.args);
if (instance_expression.args)
wasm_runtime_free(instance_expression.args);
instance_expression.args = NULL;
if (!inst_ok)
return false;
Expand Down
75 changes: 73 additions & 2 deletions core/iwasm/common/component-model/wasm_component_types_section.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ static WASMComponentTypeInstance primitive_type_error_context = {
static void
free_component_instance_decl(WASMComponentInstDecl *decl);

static void
free_component_decl(WASMComponentComponentDecl *decl);

// Free helpers for nested component/instance/resource types
static void
free_component_types_entry(WASMComponentTypes *type);
Expand Down Expand Up @@ -1418,13 +1421,31 @@ parse_param_list(const uint8_t **payload, const uint8_t *end,

// Allocate memory for the param list
if (param_count > 0) {
/* Each param occupies at least one byte in the payload, so a count
* larger than the remaining input is malformed. Rejecting it here
* also bounds the allocation and prevents the size_t multiply below
* from overflowing on 32-bit targets (where it would wrap and
* under-allocate). */
if (param_count > (uint32_t)(end - p)) {
set_error_buf_ex(error_buf, error_buf_size,
"param count %u exceeds remaining input",
param_count);
return false;
}
(*out)->params = wasm_runtime_malloc(sizeof(WASMComponentLabelValType)
* param_count);
if (!(*out)->params) {
set_error_buf_ex(error_buf, error_buf_size,
"Failed to allocate memory for param list");
return false;
}
/* Zero the array so that if parse_labelvaltype fails partway through
* the loop below, the unparsed tail stays NULL. The cleanup path
* (free_component_types_entry) walks all `count` entries and would
* otherwise dereference uninitialized label / value_type pointers
* (found by the component-parser fuzz target). */
memset((*out)->params, 0,
sizeof(WASMComponentLabelValType) * param_count);

// Parse the param list
for (uint32_t i = 0; i < param_count; i++) {
Expand Down Expand Up @@ -1722,14 +1743,20 @@ parse_component_decl_export(const uint8_t **payload, const uint8_t *end,
return false;
}

/* Ownership of export_name now belongs to *out; on any later error
* path it is freed by the centralized cleanup
* (free_component_instance_decl -> free_component_export_name), which
* walks (*out)->export_name. Freeing it locally below as well caused a
* double-free / heap-use-after-free (found by the component-parser
* fuzz target). */
(*out)->export_name = export_name;

WASMComponentExternDesc *extern_desc =
wasm_runtime_malloc(sizeof(WASMComponentExternDesc));
if (!extern_desc) {
set_error_buf_ex(error_buf, error_buf_size,
"Failed to allocate memory for extern desc");
wasm_runtime_free(export_name);
/* export_name is owned by *out; the caller frees it. */
return false;
}
memset(extern_desc, 0, sizeof(WASMComponentExternDesc));
Expand All @@ -1738,8 +1765,9 @@ parse_component_decl_export(const uint8_t **payload, const uint8_t *end,
if (!parse_extern_desc(&p, end, extern_desc, error_buf, error_buf_size)) {
set_error_buf_ex(error_buf, error_buf_size,
"Failed to parse extern desc");
/* extern_desc is not yet owned by *out -> free it here.
* export_name IS owned by *out -> the caller frees it. */
wasm_runtime_free(extern_desc);
wasm_runtime_free(export_name);
return false;
}

Expand Down Expand Up @@ -1874,13 +1902,31 @@ parse_component_type(const uint8_t **payload, const uint8_t *end,

// Allocate memory for the component list
if (component_count > 0) {
/* Each componentdecl is at least one byte, so a count larger than the
* remaining input is malformed. This also bounds the allocation and
* prevents the size_t multiply below from overflowing on 32-bit
* targets. */
if (component_count > (uint32_t)(end - p)) {
set_error_buf_ex(error_buf, error_buf_size,
"component count %u exceeds remaining input",
component_count);
return false;
}
(*out)->component_decls = wasm_runtime_malloc(
sizeof(WASMComponentComponentDecl) * component_count);
if (!(*out)->component_decls) {
set_error_buf_ex(error_buf, error_buf_size,
"Failed to allocate memory for component list");
return false;
}
/* Zero the array: (*out)->count is already the full declared count, so
* if parse_component_decl fails partway the caller's cleanup
* (free_component_component_type -> free_component_decl) still walks
* all `count` entries. Without this the unparsed tail is uninitialized
* and free_component_decl dereferences a wild decl->tag (found by the
* component-parser fuzz target). */
memset((*out)->component_decls, 0,
sizeof(WASMComponentComponentDecl) * component_count);

// Parse the component list
for (uint32_t i = 0; i < component_count; i++) {
Expand All @@ -1898,6 +1944,13 @@ parse_component_type(const uint8_t **payload, const uint8_t *end,
error_buf_size)) {
set_error_buf_ex(error_buf, error_buf_size,
"Failed to parse component %d", i);
/* parse_component_decl may have populated decl contents
* (e.g. an import's import_name) before failing on a later
* sub-parse such as the extern-desc; free those contents
* first, mirroring the parse_component_instance_type fail
* path. component_decls[i] is still zeroed, so the
* centralized cleanup never reaches this transient shell. */
free_component_decl(component_decl);
wasm_runtime_free(component_decl);
return false;
}
Expand Down Expand Up @@ -1951,13 +2004,31 @@ parse_component_instance_type(const uint8_t **payload, const uint8_t *end,

// Allocate memory for the instance list
if (instance_count > 0) {
/* Each instancedecl is at least one byte, so a count larger than the
* remaining input is malformed. This also bounds the allocation and
* prevents the size_t multiply below from overflowing on 32-bit
* targets. */
if (instance_count > (uint32_t)(end - p)) {
set_error_buf_ex(error_buf, error_buf_size,
"instance count %u exceeds remaining input",
instance_count);
goto fail;
}
instance_decls =
wasm_runtime_malloc(sizeof(WASMComponentInstDecl) * instance_count);
if (!instance_decls) {
set_error_buf_ex(error_buf, error_buf_size,
"Failed to allocate memory for instance list");
goto fail;
}
/* Zero the array so a parse failure partway through the loop leaves the
* unparsed tail as zeroed decls. This function recurses (an
* instancedecl may itself be an instance type), and the cleanup walk
* (free_component_instance_decl) reads decl->tag for every entry; an
* uninitialized tail produced a wild dereference at arbitrary nesting
* depth (found by the component-parser fuzz target). */
memset(instance_decls, 0,
sizeof(WASMComponentInstDecl) * instance_count);

// Parse the instance list
for (uint32_t i = 0; i < instance_count; i++) {
Expand Down
47 changes: 37 additions & 10 deletions product-mini/platforms/posix/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
#include "bh_read_file.h"
#include "wasm_export.h"
#if WASM_ENABLE_COMPONENT_MODEL != 0
/* The component-model headers below pull in core-module types from the
* interpreter's wasm.h. That header lives in core/iwasm/interpreter, which is
* only on the include path when WAMR_BUILD_INTERP=1, so keep this include
* inside the component-model guard: a pure AOT-only build (INTERP=0) does not
* build the component model and must not reference wasm.h. */
#include "wasm.h"
#include "wasm_component.h"
#include "wasm_component_runtime.h"
#include "component-model/wasm_component_host_resource.h"
#include "wasm_component_export.h"
#include "component-model/wasm_component_validate.h"
#endif
#include "wasm.h"
#if WASM_ENABLE_LIBC_WASI != 0
#include "../common/libc_wasi.c"
#endif
Expand Down Expand Up @@ -646,7 +651,7 @@ execute_wasm_module(uint8 *wasm_file_buf, uint32 wasm_file_size,
#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
bool disable_bounds_checks,
#endif
char *error_buf, int32 *ret_value,
char *error_buf, uint32 error_buf_size, int32 *ret_value,
wasm_module_t *wasm_module_out,
wasm_module_inst_t *wasm_module_inst_out, int argc,
char *argv[]
Expand Down Expand Up @@ -682,14 +687,14 @@ execute_wasm_module(uint8 *wasm_file_buf, uint32 wasm_file_size,
#endif

if (!(wasm_module = wasm_runtime_load(wasm_file_buf, wasm_file_size,
error_buf, sizeof(error_buf)))) {
error_buf, error_buf_size))) {
printf("%s\n", error_buf);
return false;
}

#if WASM_ENABLE_DYNAMIC_AOT_DEBUG != 0
if (!wasm_runtime_set_module_name(wasm_module, wasm_file, error_buf,
sizeof(error_buf))) {
error_buf_size)) {
printf("set aot module name failed in dynamic aot debug mode, %s\n",
error_buf);
wasm_runtime_unload(wasm_module);
Expand All @@ -711,11 +716,18 @@ execute_wasm_module(uint8 *wasm_file_buf, uint32 wasm_file_size,
libc_wasi_set_init_args(inst_args, argc, argv, wasi_parse_ctx);
#endif

wasm_module_inst = wasm_runtime_instantiate_ex2(
wasm_module, inst_args, error_buf, sizeof(error_buf));
wasm_module_inst = wasm_runtime_instantiate_ex2(wasm_module, inst_args,
error_buf, error_buf_size);
wasm_runtime_instantiation_args_destroy(inst_args);
if (!wasm_module_inst) {
LOG_ERROR("%s\n", error_buf);
/* Print the instantiation error directly (matching upstream and the
* module-load error path above). LOG_ERROR routes through bh_log,
* which prefixes a "[time - tid]:" banner that pollutes iwasm's
* stdout; the spec-test harness reads that stream for the
* "webassembly> " prompt / expected trap text, so the banner makes
* data/elem/start (and the ba-issues regression suite) spuriously
* fail. */
printf("%s\n", error_buf);
wasm_runtime_unload(wasm_module);
return false;
}
Expand Down Expand Up @@ -956,7 +968,9 @@ main(int argc, char *argv[])
bool component_loaded = false;
bool component_func_invoke = false; // 'true' if component function is invoked
#endif
#if WASM_ENABLE_COMPONENT_MODEL != 0
bool module_func_invoke = false; // 'true' if module function is invoked
#endif
RunningMode running_mode = 0;
RuntimeInitArgs init_args;
char error_buf[128] = { 0 };
Expand Down Expand Up @@ -1003,7 +1017,9 @@ main(int argc, char *argv[])
return print_help();
}
func_name = argv[0];
#if WASM_ENABLE_COMPONENT_MODEL != 0
module_func_invoke = true;
#endif
}
#if WASM_ENABLE_COMPONENT_MODEL != 0
else if (!strcmp(argv[0], "-i") || !strcmp(argv[0], "--invoke")) {
Expand Down Expand Up @@ -1368,7 +1384,17 @@ main(int argc, char *argv[])
}
else
#endif
if (is_wasm_module(header)) {
if (is_wasm_module(header)
#if WASM_ENABLE_AOT != 0
/* AoT files carry the "\0aot" magic, so is_wasm_module() (which
* only matches the "\0asm" bytecode magic) rejects them. Route
* them through the same execute_wasm_module()/wasm_runtime_load()
* path, which dispatches AoT internally — otherwise `iwasm
* foo.aot` falls through to the "Unknown WASM file type" error. */
|| get_package_type(wasm_file_buf, wasm_file_size)
== Wasm_Module_AoT
#endif
) {
#if WASM_ENABLE_COMPONENT_MODEL != 0
if (component_func_invoke) {
ret = print_help();
Expand Down Expand Up @@ -1396,8 +1422,9 @@ main(int argc, char *argv[])
#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
disable_bounds_checks,
#endif
error_buf, &ret, &wasm_module,
&wasm_module_inst, app_argc, app_argv
error_buf, sizeof(error_buf), &ret,
&wasm_module, &wasm_module_inst, app_argc,
app_argv
#if WASM_ENABLE_LIBC_WASI != 0
,
&wasi_parse_ctx
Expand Down
8 changes: 8 additions & 0 deletions tests/fuzz/component-fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
build/
corpus/
corpus_verify/
findings/
crash-*
oom-*
leak-*
timeout-*
Loading