Commit message: Fix #20336 - wasm bin parser ##crash
CVE: None
CWE: CWE-787
Vulnerability description: None

Project: radare2
File: wasm.c

static void free_import_entry(RBinWasmImportEntry *entry) {
	if (entry) {
		free (entry->module_str);
		free (entry->field_str);
		free (entry);
	}
}

Project: radare2
File: wasm.c

RPVector *r_bin_wasm_get_imports(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_imports? bin->g_imports: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_IMPORT);
}

Project: radare2
File: wasm.c

void wasm_obj_free(RBinWasmObj *bin) {
	if (bin) {
		r_buf_free (bin->buf);
		r_list_free (bin->g_sections);
		r_pvector_free (bin->g_types);
		r_pvector_free (bin->g_imports);
		r_pvector_free (bin->g_funcs);
		r_pvector_free (bin->g_tables);
		r_pvector_free (bin->g_memories);
		r_pvector_free (bin->g_globals);
		r_pvector_free (bin->g_exports);
		r_pvector_free (bin->g_elements);
		r_pvector_free (bin->g_codes);
		r_pvector_free (bin->g_datas);
		r_list_free (bin->g_names);
		free (bin);
	}
}

Project: radare2
File: wasm.c

static inline RPVector *parse_unique_subsec_vec_by_id(RBinWasmObj *bin, ut8 id) {
	RBinWasmSection *sec = section_by_id_unique (bin->g_sections, id);
	if (sec) {
		return parse_sub_section_vec (bin, sec);
	}
	return false;
}

Project: radare2
File: wasm.c

RPVector *r_bin_wasm_get_memories(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_memories? bin->g_memories: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_MEMORY);
}

Project: radare2
File: wasm.c

RPVector *r_bin_wasm_get_globals(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_globals? bin->g_globals: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_GLOBAL);
}

Project: radare2
File: wasm.c

static RBinWasmMemoryEntry *parse_memory_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmMemoryEntry *ptr = R_NEW0 (RBinWasmMemoryEntry);
	if (ptr) {
		ptr->sec_i = index;
		ptr->file_offset = r_buf_tell (b);
		if (!consume_limits_r (b, bound, &ptr->limits)) {
			free (ptr);
			return NULL;
		}
	}
	return ptr;
}

Project: radare2
File: wasm.c

static size_t consume_locals_r(RBuffer *b, ut64 bound, RBinWasmCodeEntry *out) {
	r_return_val_if_fail (out, 0);
	ut32 count = out->local_count;
	if (count <= 0) {
		return 0;
	}
	out->locals = R_NEWS0 (struct r_bin_wasm_local_entry_t, count);
	if (!out->locals) {
		return 0;
	}

	ut32 i = 0;
	for (i = 0; i < count; i++) {
		struct r_bin_wasm_local_entry_t *local = &out->locals[i];
		if (!consume_u32_r (b, bound, &local->count)) {
			return 0;
		}
		if (!consume_s7_r (b, bound, &local->type)) {
			return 0;
		}
	}
	return i;
}

Project: radare2
File: wasm.c

static size_t consume_u32_r(RBuffer *b, ut64 bound, ut32 *out) {
	size_t n = 0;
	ut32 tmp = consume_r (b, bound, &n, read_u32_leb128);
	if (out) {
		*out = tmp;
	}
	return n;
}

Project: radare2
File: wasm.c

static inline bool strbuf_append_type_vec(RStrBuf *sb, RBinWasmTypeVec *vec) {
	if (!r_strbuf_append (sb, "(")) {
		return false;
	}
	ut32 i;
	for (i = 0; i < vec->count; i++) {
		if (i > 0 && !r_strbuf_append (sb, ", ")) {
			return false;
		}
		const char *s = r_bin_wasm_valuetype_to_string (vec->types[i]);
		if (!s || !r_strbuf_append (sb, s)) {
			return false;
		}
	}

	if (!r_strbuf_append (sb, ")")) {
		return false;
	}
	return true;
}

Project: radare2
File: wasm.c

RPVector *r_bin_wasm_get_exports(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_exports? bin->g_exports: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_EXPORT);
}

Project: radare2
File: wasm.c

static inline RBinWasmCustomNameLocalName *parse_local_name(RBuffer *b, ut64 bound) {
	RBinWasmCustomNameLocalName *local_name = R_NEW0 (RBinWasmCustomNameLocalName);
	if (local_name) {
		if (!consume_u32_r (b, bound, &local_name->index)) {
			goto beach;
		}

		local_name->names = r_id_storage_new (0, UT32_MAX);
		if (!local_name->names) {
			goto beach;
		}

		if (!parse_namemap (b, bound, local_name->names, &local_name->names_count)) {
			goto beach;
		}

		return local_name;
	}
beach:
	wasm_custom_name_local_free (local_name);
	return NULL;
}

Project: radare2
File: wasm.c

RPVector *r_bin_wasm_get_datas(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_datas? bin->g_datas: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_DATA);
}

Project: radare2
File: wasm.c

static RPVector *parse_sub_section_vec(RBinWasmObj *bin, RBinWasmSection *sec) {
	RPVector **cache = NULL;
	RPVectorFree pfree = (RPVectorFree)free;
	ParseEntryFcn parser;
	switch (sec->id) {
	case R_BIN_WASM_SECTION_TYPE:
		parser = (ParseEntryFcn)parse_type_entry;
		pfree = (RPVectorFree)free_type_entry;
		cache = &bin->g_types;
		break;
	case R_BIN_WASM_SECTION_IMPORT:
		parser = (ParseEntryFcn)parse_import_entry;
		pfree = (RPVectorFree)free_import_entry;
		cache = &bin->g_imports;
		break;
	case R_BIN_WASM_SECTION_FUNCTION:
		parser = (ParseEntryFcn)parse_function_entry;
		cache = &bin->g_funcs;
		break;
	case R_BIN_WASM_SECTION_TABLE:
		parser = (ParseEntryFcn)parse_table_entry;
		cache = &bin->g_tables;
		break;
	case R_BIN_WASM_SECTION_MEMORY:
		parser = (ParseEntryFcn)parse_memory_entry;
		cache = &bin->g_memories;
		break;
	case R_BIN_WASM_SECTION_GLOBAL:
		parser = (ParseEntryFcn)parse_global_entry;
		cache = &bin->g_globals;
		break;
	case R_BIN_WASM_SECTION_EXPORT:
		parser = (ParseEntryFcn)parse_export_entry;
		pfree = (RPVectorFree)free_export_entry;
		cache = &bin->g_exports;
		break;
	case R_BIN_WASM_SECTION_ELEMENT:
		parser = (ParseEntryFcn)parse_element_entry;
		cache = &bin->g_elements;
		break;
	case R_BIN_WASM_SECTION_CODE:
		parser = (ParseEntryFcn)parse_code_entry;
		pfree = (RPVectorFree)free_code_entry;
		cache = &bin->g_codes;
		break;
	case R_BIN_WASM_SECTION_DATA:
		parser = (ParseEntryFcn)parse_data_entry;
		cache = &bin->g_datas;
		break;
	default:
		return NULL;
	}

	RBuffer *buf = bin->buf;
	ut64 offset = sec->payload_data;
	ut64 len = sec->payload_len;
	ut64 bound = offset + len - 1;

	if (bound >= r_buf_size (buf)) {
		r_warn_if_reached (); // section parsing should prevent this
		eprintf ("[wasm] End of %s section data is beyond file end\n", sec->name);
		return NULL;
	}
	if (r_buf_seek (buf, offset, R_BUF_SET) != offset) {
		return NULL;
	}

	*cache = parse_vec (bin, bound, parser, pfree);
	return *cache;
}

Project: radare2
File: wasm.c

static bool parse_namemap(RBuffer *b, ut64 bound, RIDStorage *map, ut32 *count) {
	size_t i;
	if (!consume_u32_r (b, bound, count)) {
		return false;
	}

	for (i = 0; i < *count; i++) {
		ut32 idx;
		if (!consume_u32_r (b, bound, &idx)) {
			return false;
		}

		char *name = NULL;
		if (!consume_encoded_name_new (b, bound, NULL, &name)) {
			R_FREE (name);
			return false;
		}

		if (!r_id_storage_add (map, name, &idx)) {
			R_FREE (name);
			return false;
		};
	}

	return true;
}

Project: radare2
File: wasm.c

static void free_type_vec(RBinWasmTypeVec *vec) {
	if (vec) {
		free (vec->types);
		free (vec);
	}
}

Project: radare2
File: wasm.c

static void wasm_custom_name_free(RBinWasmCustomNameEntry *cust) {
	if (cust) {
		switch (cust->type) {
		case R_BIN_WASM_NAMETYPE_Module:
			R_FREE (cust->mod_name);
			break;
		case R_BIN_WASM_NAMETYPE_Function:
			if (cust->func) {
				r_id_storage_free (cust->func->names);
				R_FREE (cust->func);
			}
			break;
		case R_BIN_WASM_NAMETYPE_Local:
			wasm_custom_local_names_free (cust->local);
			break;
		case R_BIN_WASM_NAMETYPE_None:
			break;
		default:
			eprintf ("Unkown type: 0x%x\n", cust->type);
			r_warn_if_reached ();
		}
		R_FREE (cust);
	}
}

Project: radare2
File: wasm.c

static RBinWasmCustomNameEntry *parse_custom_name_entry(RBinWasmObj *bin, ut64 bound) {
	RBuffer *b = bin->buf;
	RBinWasmCustomNameEntry *cust = R_NEW0 (RBinWasmCustomNameEntry);
	if (!cust) {
		return NULL;
	}
	cust->type = R_BIN_WASM_NAMETYPE_None;

	size_t start = r_buf_tell (b);
	if (!consume_u7_r (b, bound, &cust->type)) {
		goto beach;
	};

	if (!consume_u32_r (b, bound, &cust->size)) {
		goto beach;
	};

	switch (cust->type) {
	case R_BIN_WASM_NAMETYPE_Module:
		if (!consume_encoded_name_new (b, bound, NULL, &cust->mod_name)) {
			goto beach;
		}
		break;
	case R_BIN_WASM_NAMETYPE_Function:
		cust->func = R_NEW0 (RBinWasmCustomNameFunctionNames);
		if (!cust->func) {
			goto beach;
		}
		cust->func->names = r_id_storage_new (0, UT32_MAX);
		if (!cust->func->names) {
			goto beach;
		}

		if (!parse_namemap (b, bound, cust->func->names, &cust->func->count)) {
			goto beach;
		}
		break;
	case R_BIN_WASM_NAMETYPE_Local:
		cust->local = parse_custom_names_local (b, bound);
		if (!cust->local) {
			goto beach;
		}
		break;
	default:
		R_LOG_WARN ("[wasm] Halting custom name section parsing at unknown type 0x%x offset 0x%" PFMTSZx "\n", cust->type, start);
		cust->type = R_BIN_WASM_NAMETYPE_None;
		goto beach;
	}

	return cust;
beach:
	wasm_custom_name_free (cust);
	return NULL;
}

Project: radare2
File: wasm.c

ut32 r_bin_wasm_get_entrypoint(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, 0);

	if (bin->entrypoint) {
		return bin->entrypoint;
	}
	ut32 start = r_bin_wasm_get_start (bin);
	RPVector *code = r_bin_wasm_get_codes (bin);
	// FIX: entrypoint can be also an import
	if (code && start != UT32_MAX) {
		RBinWasmCodeEntry *func = r_pvector_at (code, start);
		return func? func->code: 0;
	}
	return 0;
}

Project: radare2
File: wasm.c

RPVector *r_bin_wasm_get_elements(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_elements? bin->g_elements: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_ELEMENT);
}

Project: radare2
File: wasm.c

static inline RPVector *parse_vec(RBinWasmObj *bin, ut64 bound, ParseEntryFcn parse_entry, RPVectorFree free_entry) {
	RBuffer *buf = bin->buf;

	ut32 count;
	if (!consume_u32_r (buf, bound, &count)) {
		return NULL;
	}

	RPVector *vec = r_pvector_new (free_entry);
	if (vec) {
		r_pvector_reserve (vec, count);
		ut32 i;
		for (i = 0; i < count; i++) {
			ut64 start = r_buf_tell (buf);
			void *e = parse_entry (bin, bound, i);
			if (!e || !r_pvector_push (vec, e)) {
				eprintf ("[wasm] Failed to parse entry %u/%u of vec at 0x%" PFMT64x "\n", i, count, start);
				free_entry (e);
				break;
			}
		}
	}
	return vec;
}

Project: radare2
File: wasm.c

static void free_export_entry(RBinWasmExportEntry *entry) {
	if (entry) {
		free (entry->field_str);
		free (entry);
	}
}

Project: radare2
File: wasm.c

static size_t consume_init_expr_r(RBuffer *b, ut64 bound, ut8 eoc, void *out) {
	if (!b || bound >= r_buf_size (b) || r_buf_tell (b) > bound) {
		return 0;
	}
	size_t res = 0;
	ut8 cur = r_buf_read8 (b);
	while (r_buf_tell (b) <= bound && cur != eoc) {
		cur = r_buf_read8 (b);
		res++;
	}
	if (cur != eoc) {
		return 0;
	}
	return res + 1;
}

Project: radare2
File: wasm.c

const char *r_bin_wasm_valuetype_to_string (r_bin_wasm_value_type_t type) {
	switch (type) {
	case R_BIN_WASM_VALUETYPE_i32:
		return "i32";
	case R_BIN_WASM_VALUETYPE_i64:
		return "i62";
	case R_BIN_WASM_VALUETYPE_f32:
		return "f32";
	case R_BIN_WASM_VALUETYPE_f64:
		return "f64";
	case R_BIN_WASM_VALUETYPE_REFTYPE:
		return "ANYFUNC";
	case R_BIN_WASM_VALUETYPE_FUNC:
		return "FUNC";
	default:
		return "<?>";
	}
}

Project: radare2
File: wasm.c

static RBinWasmFunctionEntry *parse_function_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmFunctionEntry *func = R_NEW0 (RBinWasmFunctionEntry);
	if (func && consume_u32_r (b, bound, &func->typeindex)) {
		func->sec_i = index;
		func->file_offset = r_buf_tell (b);
		return func;
	}
	free (func);
	return NULL;
}

Project: radare2
File: wasm.c

static bool consume_encoded_name_new(RBuffer *b, ut64 bound, ut32 *len_out, char **str_out) {
	ut32 len;
	char *orig = NULL;
	if (!consume_str_new (b, bound, &len, &orig)) {
		return false;
	}

	// room for even every character getting encoded
	size_t maxsize = (len * 4) + 2;
	char *sout = malloc (maxsize);
	if (!sout) {
		free (orig);
		return false;
	}

	size_t i, oi = 0;
	for (i = 0; i < len && oi + 6 < maxsize; i++) {
		if (WASM_IS_OK (orig, i, len)) {
			sout[oi++] = orig[i];
		} else {
			int res = snprintf (sout + oi, maxsize - oi, "_%02x_", orig[i]);
			oi += res;
		}
	}
	if (oi >= maxsize) {
		sout[maxsize - 1] = '\0';
	} else {
		sout[oi++] = '\0';
	}
	free (orig);

	char *tmp = realloc (sout, oi);
	if (!tmp) {
		free (sout);
		free (tmp);
		return false;
	}
	*str_out = tmp;
	if (len_out) {
		*len_out = len;
	}
	return true;
}

Project: radare2
File: wasm.c

static RBinWasmDataEntry *parse_data_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmDataEntry *ptr = R_NEW0 (RBinWasmDataEntry);
	if (!ptr) {
		return NULL;
	}
	ptr->sec_i = index;
	ptr->file_offset = r_buf_tell (b);
	if (!consume_u32_r (b, bound, &ptr->index)) {
		goto beach;
	}
	if (!(ptr->offset.len = consume_init_expr_r (b, bound, R_BIN_WASM_END_OF_CODE, NULL))) {
		goto beach;
	}
	if (!consume_u32_r (b, bound, &ptr->size)) {
		goto beach;
	}
	ptr->data = r_buf_tell (b);
	r_buf_seek (b, ptr->size, R_BUF_CUR);
	return ptr;

beach:
	free (ptr);
	return NULL;
}

Project: radare2
File: wasm.c

static size_t consume_s7_r(RBuffer *b, ut64 bound, st8 *out) {
	size_t n = 0;
	ut32 tmp = consume_r (b, bound, &n, (ConsumeFcn)read_i32_leb128);
	if (out) {
		*out = (st8) (((tmp & 0x10000000) << 7) | (tmp & 0x7f));
	}
	return n;
}

Project: radare2
File: wasm.c

static RBinWasmCodeEntry *parse_code_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmCodeEntry *ptr = R_NEW0 (RBinWasmCodeEntry);
	if (!ptr) {
		return NULL;
	}
	ptr->sec_i = index;
	ptr->file_offset = r_buf_tell (b);
	if (!consume_u32_r (b, bound, &ptr->body_size)) {
		goto beach;
	}
	ut32 j = r_buf_tell (b);
	if (r_buf_tell (b) + ptr->body_size - 1 > bound) {
		goto beach;
	}
	if (!consume_u32_r (b, bound, &ptr->local_count)) {
		goto beach;
	}
	if (consume_locals_r (b, bound, ptr) < ptr->local_count) {
		goto beach;
	}
	ptr->code = r_buf_tell (b);
	ptr->len = ptr->body_size - ptr->code + j;
	r_buf_seek (b, ptr->len - 1, R_BUF_CUR); // consume bytecode
	ut8 end;
	r_buf_read (b, &end, 1);
	if (end != R_BIN_WASM_END_OF_CODE) {
		ut32 where = r_buf_tell (b) - 1;
		eprintf ("[wasm] Code entry at starting at 0x%x has ending byte 0x%x at 0x%x, should be 0x%x\n",
			(ut32)ptr->file_offset, end, where, R_BIN_WASM_END_OF_CODE);
		goto beach;
	}
	return ptr;

beach:
	free_code_entry (ptr);
	return NULL;
}

Project: radare2
File: wasm.c

RBinWasmObj *r_bin_wasm_init(RBinFile *bf, RBuffer *buf) {
	RBinWasmObj *bin = R_NEW0 (RBinWasmObj);
	if (bin) {
		bin->g_start = UT32_MAX;
		bin->buf = r_buf_ref (buf);
		bin->size = (ut32)r_buf_size (bf->buf);
		bin->g_sections = r_bin_wasm_get_sections (bin);
		// TODO: recursive invocation more natural with streamed parsing
		// but dependency problems when sections are disordered (against spec)

		bin->g_types = r_bin_wasm_get_types (bin);
		bin->g_imports = r_bin_wasm_get_imports (bin);
		bin->g_funcs = r_bin_wasm_get_functions (bin);
		bin->g_tables = r_bin_wasm_get_tables (bin);
		bin->g_memories = r_bin_wasm_get_memories (bin);
		bin->g_globals = r_bin_wasm_get_globals (bin);
		bin->g_exports = r_bin_wasm_get_exports (bin);
		bin->g_codes = r_bin_wasm_get_codes (bin);
		bin->g_datas = r_bin_wasm_get_datas (bin);

		bin->g_names = r_bin_wasm_get_custom_names (bin);

		// entrypoint from Start section
		bin->entrypoint = r_bin_wasm_get_entrypoint (bin);
	}
	return bin;
}

Project: radare2
File: wasm.c

static void wasm_custom_name_local_free(RBinWasmCustomNameLocalName *name) {
	if (name) {
		r_id_storage_free (name->names);
		R_FREE (name);
	}
}

Project: radare2
File: wasm.c

RPVector *r_bin_wasm_get_tables(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_tables? bin->g_tables: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_TABLE);
}

Project: radare2
File: wasm.c

const char *r_bin_wasm_get_function_name(RBinWasmObj *bin, ut32 idx) {
	if (!(bin && bin->g_names)) {
		return NULL;
	};

	RListIter *iter;
	RBinWasmCustomNameEntry *nam;
	r_list_foreach (bin->g_names, iter, nam) {
		if (nam->type == R_BIN_WASM_NAMETYPE_Function) {
			const char *n = r_id_storage_get (nam->func->names, idx);
			if (n) {
				return n;
			}
		}
	}

	return NULL;
}

Project: radare2
File: wasm.c

static void free_type_entry(RBinWasmTypeEntry *ptr) {
	if (ptr) {
		free_type_vec (ptr->args);
		free_type_vec (ptr->rets);
		free (ptr->to_str);
		free (ptr);
	}
}

Project: radare2
File: wasm.c

static void wasm_sec_free(RBinWasmSection *sec) {
	if (sec) {
		free (sec->name);
		free (sec);
	}
}

Project: radare2
File: wasm.c

static ut32 consume_r(RBuffer *b, ut64 bound, size_t *n_out, ConsumeFcn consume_fcn) {
	r_return_val_if_fail (b && n_out && consume_fcn, 0);

	ut32 tmp;
	ut64 cur = r_buf_tell (b);
	if (bound >= r_buf_size (b) || cur > bound) {
		return 0;
	}
	// 16 bytes are enough to store 128bits values
	ut8 *buf = R_NEWS (ut8, 16);
	if (!buf) {
		return 0;
	}
	r_buf_read (b, buf, 16);
	size_t n = consume_fcn (buf, buf + bound + 1, &tmp);
	if (!n) {
		free (buf);
		return 0;
	}
	r_buf_seek (b, cur + n, R_BUF_SET);
	*n_out = n;
	free (buf);
	return tmp;
}

Project: radare2
File: wasm.c

static RBinWasmElementEntry *parse_element_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmElementEntry *elem = R_NEW0 (RBinWasmElementEntry);
	if (elem) {
		elem->sec_i = index;
		elem->file_offset = r_buf_tell (b);
		if (!consume_u32_r (b, bound, &elem->index)) {
			goto beach;
		}
		if (!consume_init_expr_r (b, bound, R_BIN_WASM_END_OF_CODE, NULL)) {
			goto beach;
		}
		if (!consume_u32_r (b, bound, &elem->num_elem)) {
			goto beach;
		}
		ut32 j = 0;
		while (r_buf_tell (b) <= bound && j < elem->num_elem) {
			// TODO: allocate space and fill entry
			if (!consume_u32_r (b, bound, NULL)) {
				goto beach;
			}
		}
	}
	return elem;

beach:
	free (elem);
	return NULL;
}

Project: radare2
File: wasm.c

static void *parse_import_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmImportEntry *ptr = R_NEW0 (RBinWasmImportEntry);
	if (!ptr) {
		return NULL;
	}
	ptr->sec_i = index;
	ptr->file_offset = r_buf_tell (b);

	if (!consume_encoded_name_new (b, bound, &ptr->module_len, &ptr->module_str)) {
		goto beach;
	}

	if (!consume_encoded_name_new (b, bound, &ptr->field_len, &ptr->field_str)) {
		goto beach;
	}

	if (!consume_u7_r (b, bound, &ptr->kind)) {
		goto beach;
	}
	switch (ptr->kind) {
	case 0: // Function
		if (!consume_u32_r (b, bound, &ptr->type_f)) {
			goto beach;
		}
		break;
	case 1: // Table
		if (!consume_s7_r (b, bound, (st8 *)&ptr->type_t.elem_type)) {
			goto beach;
		}
		if (!consume_limits_r (b, bound, &ptr->type_t.limits)) {
			goto beach;
		}
		break;
	case 2: // Memory
		if (!consume_limits_r (b, bound, &ptr->type_m.limits)) {
			goto beach;
		}
		break;
	case 3: // Global
		if (!consume_s7_r (b, bound, (st8 *)&ptr->type_g.content_type)) {
			goto beach;
		}
		if (!consume_u1_r (b, bound, (ut8 *)&ptr->type_g.mutability)) {
			goto beach;
		}
		break;
	default:
		goto beach;
	}
	return ptr;

beach:
	free (ptr);
	return NULL;
}

Project: radare2
File: wasm.c

static RList *r_bin_wasm_get_sections_by_id(RList *sections, ut8 id) {
	RList *ret = r_list_newf (NULL);
	if (ret) {
		RBinWasmSection *sec;
		RListIter *iter;
		r_list_foreach (sections, iter, sec) {
			if (sec->id == id) {
				r_list_append (ret, sec);
			}
		}
	}
	return ret;
}

Project: radare2
File: wasm.c

RPVector *r_bin_wasm_get_types(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_types? bin->g_types: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_TYPE);
}

Project: radare2
File: wasm.c

static RList *r_bin_wasm_get_custom_name_entries(RBinWasmObj *bin, RBinWasmSection *sec) {
	RList *ret = r_list_newf ((RListFree)wasm_custom_name_free);

	RBuffer *buf = bin->buf;

	r_buf_seek (buf, sec->payload_data, R_BUF_SET);
	ut64 bound = sec->payload_data + sec->payload_len - 1;

	if (bound > r_buf_size (buf)) {
		goto beach;
	}

	while (r_buf_tell (buf) < bound) {
		RBinWasmCustomNameEntry *nam = parse_custom_name_entry (bin, bound);

		if (!nam) {
			break; // allow partial parsing of section
		}

		if (!r_list_append (ret, nam)) {
			goto beach;
		}
	}

	return ret;
beach:
	r_list_free (ret);
	return NULL;
}

Project: radare2
File: wasm.c

static inline void wasm_custom_local_names_free(RBinWasmCustomNameLocalNames *local) {
	if (local) {
		r_list_free (local->locals);
		R_FREE (local);
	}
}

Project: radare2
File: wasm.c

RPVector *r_bin_wasm_get_functions(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_funcs? bin->g_funcs: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_FUNCTION);
}

Project: radare2
File: wasm.c

static ut32 r_bin_wasm_get_start(RBinWasmObj *bin) {
	if (bin->g_start == UT32_MAX) {
		RBinWasmSection *sec = section_by_id_unique (bin->g_sections, R_BIN_WASM_SECTION_START);
		if (sec) {
			RBuffer *b = bin->buf;
			r_buf_seek (b, sec->payload_data, R_BUF_SET);
			ut64 bound = r_buf_tell (b) + sec->payload_len - 1;
			if (!consume_u32_r (b, bound, &bin->g_start)) {
				bin->g_start = UT32_MAX;
			}
		}
	}
	return bin->g_start;
}

Project: radare2
File: wasm.c

static RBinWasmExportEntry *parse_export_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmExportEntry *export = R_NEW0 (RBinWasmExportEntry);
	if (export) {
		export->sec_i = index;
		export->file_offset = r_buf_tell (b);
		if (!consume_encoded_name_new (b, bound, &export->field_len, &export->field_str)) {
			goto beach;
		}
		if (!consume_u7_r (b, bound, &export->kind)) {
			goto beach;
		}
		if (!consume_u32_r (b, bound, &export->index)) {
			goto beach;
		}
	}
	return export;
beach:
	free_export_entry (export);
	return NULL;
}

Project: radare2
File: wasm.c

static size_t consume_u7_r(RBuffer *b, ut64 bound, ut8 *out) {
	size_t n = 0;
	ut32 tmp = consume_r (b, bound, &n, read_u32_leb128);
	if (out) {
		*out = (ut8) (tmp & 0x7f);
	}
	return n;
}

Project: radare2
File: wasm.c

static inline RBinWasmCustomNameLocalNames *parse_custom_names_local(RBuffer *b, ut64 bound) {
	RBinWasmCustomNameLocalNames *local = R_NEW0 (RBinWasmCustomNameLocalNames);
	if (!local) {
		return NULL;
	}
	if (!consume_u32_r (b, bound, &local->count)) {
		goto beach;
	}

	local->locals = r_list_newf ((RListFree)wasm_custom_name_local_free);
	if (local->locals) {
		size_t i;
		for (i = 0; i < local->count; i++) {
			RBinWasmCustomNameLocalName *local_name = parse_local_name (b, bound);
			if (!local_name || !r_list_append (local->locals, local_name)) {
				wasm_custom_name_local_free (local_name);
				goto beach;
			}
		}
		return local;
	}

beach:
	wasm_custom_local_names_free (local);
	return NULL;
}

Project: radare2
File: wasm.c

static size_t consume_limits_r(RBuffer *b, ut64 bound, struct r_bin_wasm_resizable_limits_t *out) {
	r_return_val_if_fail (b && out, 0);
	if (bound >= r_buf_size (b) || r_buf_tell (b) > bound || !out) {
		return 0;
	}
	ut32 i = r_buf_tell (b);
	if (!consume_u7_r (b, bound, &out->flags)) {
		return 0;
	}
	if (!consume_u32_r (b, bound, &out->initial)) {
		return 0;
	}
	if (out->flags && !consume_u32_r (b, bound, &out->maximum)) {
		return 0;
	}
	int delta = r_buf_tell (b) - i;
	return (delta > 0)? delta: 0;
}

Project: radare2
File: wasm.c

static bool consume_str_r(RBuffer *b, ut64 bound, size_t len, char *out) {
	r_return_val_if_fail (b && bound > 0 && bound < r_buf_size (b), 0);
	*out = 0;

	if (r_buf_tell (b) + len <= bound + 1) {
		if (r_buf_read (b, (ut8 *)out, len) == len) {
			out[len] = 0;
			return true;
		}
	}
	return false;
}

Project: radare2
File: wasm.c

static size_t consume_u1_r(RBuffer *b, ut64 bound, ut8 *out) {
	size_t n = 0;
	ut32 tmp = consume_r (b, bound, &n, read_u32_leb128);
	if (out) {
		*out = (ut8) (tmp & 0x1);
	}
	return n;
}

Project: radare2
File: wasm.c

static bool inline consume_str_new(RBuffer *b, ut64 bound, ut32 *len_out, char **str_out) {
	r_return_val_if_fail (str_out, false);
	*str_out = NULL;
	if (len_out) {
		*len_out = 0;
	}

	ut32 len = 0;
	// module_str
	if (consume_u32_r (b, bound, &len)) {
		char *str = (char *)malloc (len + 1);
		if (str && consume_str_r (b, bound, len, str)) {
			if (len_out) {
				*len_out = len;
			}
			*str_out = str;
			return true;
		}
		free (str);
	}
	return false;
}

Project: radare2
File: wasm.c

static RBinWasmGlobalEntry *parse_global_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmGlobalEntry *ptr = R_NEW0 (RBinWasmGlobalEntry);
	if (ptr) {
		ptr->sec_i = index;
		ptr->file_offset = r_buf_tell (b);
		if (!consume_u7_r (b, bound, (ut8 *)&ptr->content_type)) {
			goto beach;
		}
		if (!consume_u1_r (b, bound, &ptr->mutability)) {
			goto beach;
		}
		if (!consume_init_expr_r (b, bound, R_BIN_WASM_END_OF_CODE, NULL)) {
			goto beach;
		}
	}
	return ptr;

beach:
	free (ptr);
	return NULL;
}

Project: radare2
File: wasm.c

void r_bin_wasm_destroy(RBinFile *bf) {
	if (bf && bf->o) {
		wasm_obj_free (bf->o->bin_obj);
		bf->o->bin_obj = NULL;
	}
}

Project: radare2
File: wasm.c

static inline RBinWasmTypeVec *parse_type_vector(RBuffer *b, ut64 bound) {
	RBinWasmTypeVec *vec = R_NEW0 (RBinWasmTypeVec);
	// types are all ut8, so leb128 shouldn't be needed, we can reuse consume_str_new
	if (vec && !consume_str_new (b, bound, &vec->count, (char **)&vec->types)) {
		free_type_vec (vec);
		return NULL;
	}
	return vec;
}

Project: radare2
File: wasm.c

RPVector *r_bin_wasm_get_codes(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_codes? bin->g_codes: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_CODE);
}

Project: radare2
File: wasm.c

static const char *r_bin_wasm_type_entry_to_string(RBinWasmTypeEntry *type) {
	r_return_val_if_fail (type, NULL);
	if (type->to_str) {
		return type->to_str;
	}

	RStrBuf *sb = r_strbuf_new ("");
	if (!sb) {
		return NULL;
	}
	r_strbuf_reserve (sb, (type->args->count + type->rets->count) * 8);

	bool appended = strbuf_append_type_vec (sb, type->args);
	appended &= r_strbuf_append (sb, " -> ");
	appended &= append_rets (sb, type->rets);
	if (appended) {
		type->to_str = r_strbuf_drain (sb);
	} else {
		r_strbuf_free (sb);
	}
	return type->to_str;
}

Project: radare2
File: wasm.c

static RBinWasmSection *section_by_id_unique(RList *sections, ut8 id) {
	RList *l = r_bin_wasm_get_sections_by_id (sections, id);
	RBinWasmSection *sec = NULL;
	if (l) {
		int len = r_list_length (l);
		if (len) {
			sec = r_list_first (l);
			if (sec && len > 1) {
				eprintf ("[wasm] Using first %s section of %d\n", sec->name, len);
			}
		}
	}
	r_list_free (l);
	return sec;
}

Project: radare2
File: wasm.c

static RBinWasmTypeEntry *parse_type_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmTypeEntry *type = R_NEW0 (RBinWasmTypeEntry);
	if (!type) {
		return NULL;
	}
	type->sec_i = index;
	type->file_offset = r_buf_tell (b);
	if (!consume_u7_r (b, bound, &type->form)) {
		goto beach;
	}
	if (type->form != R_BIN_WASM_VALUETYPE_FUNC) {
		R_LOG_WARN ("Halting types section parsing at invalid type 0x%02x at offset: 0x%" PFMTSZx "\n", type->form, type->file_offset);
		goto beach;
	}

	type->args = parse_type_vector (b, bound);
	if (!type->args) {
		goto beach;
	}

	type->rets = parse_type_vector (b, bound);
	if (!type->rets) {
		goto beach;
	}
	r_bin_wasm_type_entry_to_string (type);

	return type;

beach:
	free_type_entry (type);
	return NULL;
}

Project: radare2
File: wasm.c

static RBinWasmTableEntry *parse_table_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmTableEntry *table = R_NEW0 (RBinWasmTableEntry);
	if (table) {
		table->sec_i = index;
		table->file_offset = r_buf_tell (b);
		if (!consume_s7_r (b, bound, (st8 *)&table->element_type)) {
			goto beach;
		}
		if (!consume_limits_r (b, bound, &table->limits)) {
			goto beach;
		}
	}
	return table;

beach:
	free (table);
	return NULL;
}

Project: radare2
File: wasm.c

RList *r_bin_wasm_get_custom_names(RBinWasmObj *bin) {
	RList *customs = NULL;

	r_return_val_if_fail (bin && bin->g_sections, NULL);

	if (bin->g_names) {
		return bin->g_names;
	}
	if (!(customs = r_bin_wasm_get_sections_by_id (bin->g_sections, R_BIN_WASM_SECTION_CUSTOM))) {
		return r_list_new ();
	}
	// support for multiple "name" sections against spec
	RBinWasmSection *cust = (RBinWasmSection *)r_list_first (customs);
	if (!cust || !cust->name) {
		r_list_free (customs);
		return r_list_new ();
	}
	if (strcmp (cust->name, "name")) {
		r_list_free (customs);
		return r_list_new ();
	}
	bin->g_names = r_bin_wasm_get_custom_name_entries (bin, cust);
	r_list_free (customs);
	return bin->g_names;
}

Project: radare2
File: wasm.c

RList *r_bin_wasm_get_sections(RBinWasmObj *bin) {
	RList *ret = NULL;
	RBinWasmSection *ptr = NULL;

	if (!bin) {
		return NULL;
	}
	if (bin->g_sections) {
		return bin->g_sections;
	}
	if (!(ret = r_list_newf ((RListFree)wasm_sec_free))) {
		return NULL;
	}
	RBuffer *b = bin->buf;
	ut64 bound = r_buf_size (b) - 1;
	r_buf_seek (b, 8, R_BUF_SET);
	while (r_buf_tell (b) <= bound) {
		if (!(ptr = R_NEW0 (RBinWasmSection))) {
			return ret;
		}
		if (!consume_u7_r (b, bound, &ptr->id)) {
			goto beach;
		}
		if (!consume_u32_r (b, bound, &ptr->size)) {
			goto beach;
		}
		// against spec. TODO: choose criteria for parsing
		if (ptr->size < 1) {
			goto beach;
			// free (ptr);
			// continue;
		}
		ptr->offset = r_buf_tell (b);
		switch (ptr->id) {
		case R_BIN_WASM_SECTION_CUSTOM:
			// eprintf("custom section: 0x%x, ", (ut32)b->cur);
			if (!consume_encoded_name_new (b, bound, &ptr->name_len, &ptr->name)) {
				goto beach;
			}
			break;
		case R_BIN_WASM_SECTION_TYPE:
			// eprintf("section type: 0x%x, ", (ut32)b->cur);
			ptr->name = strdup ("type");
			ptr->name_len = 4;
			break;
		case R_BIN_WASM_SECTION_IMPORT:
			// eprintf("section import: 0x%x, ", (ut32)b->cur);
			ptr->name = strdup ("import");
			ptr->name_len = 6;
			break;
		case R_BIN_WASM_SECTION_FUNCTION:
			// eprintf("section function: 0x%x, ", (ut32)b->cur);
			ptr->name = strdup ("function");
			ptr->name_len = 8;
			break;
		case R_BIN_WASM_SECTION_TABLE:
			// eprintf("section table: 0x%x, ", (ut32)b->cur);
			ptr->name = strdup ("table");
			ptr->name_len = 5;
			break;
		case R_BIN_WASM_SECTION_MEMORY:
			// eprintf("section memory: 0x%x, ", (ut32)b->cur);
			ptr->name = strdup ("memory");
			ptr->name_len = 6;
			break;
		case R_BIN_WASM_SECTION_GLOBAL:
			// eprintf("section global: 0x%x, ", (ut32)b->cur);
			ptr->name = strdup ("global");
			ptr->name_len = 6;
			break;
		case R_BIN_WASM_SECTION_EXPORT:
			// eprintf("section export: 0x%x, ", (ut32)b->cur);
			ptr->name = strdup ("export");
			ptr->name_len = 6;
			break;
		case R_BIN_WASM_SECTION_START:
			// eprintf("section start: 0x%x\n", (ut32)b->cur);
			ptr->name = strdup ("start");
			ptr->name_len = 5;
			break;
		case R_BIN_WASM_SECTION_ELEMENT:
			// eprintf("section element: 0x%x, ", (ut32)b->cur);
			ptr->name = strdup ("element");
			ptr->name_len = 7;
			break;
		case R_BIN_WASM_SECTION_CODE:
			// eprintf("section code: 0x%x, ", (ut32)b->cur);
			ptr->name = strdup ("code");
			ptr->name_len = 4;
			break;
		case R_BIN_WASM_SECTION_DATA:
			// eprintf("section data: 0x%x, ", (ut32)b->cur);
			ptr->name = strdup ("data");
			ptr->name_len = 4;
			break;
		default:
			eprintf ("[wasm] error: unkown section id: %d\n", ptr->id);
			r_buf_seek (b, ptr->size - 1, R_BUF_CUR);
			continue;
		}
		if (ptr->offset + (ut64)ptr->size - 1 > bound) {
			// TODO: Better error handling here
			ut32 diff = ptr->size - (bound + 1 - ptr->offset);
			eprintf ("[wasm] Artificially reducing size of section %s by 0x%x bytes so it fits in the file\n", ptr->name, diff);
			ptr->size -= diff;
		}
		ptr->payload_data = r_buf_tell (b);
		ptr->payload_len = ptr->size - (ptr->payload_data - ptr->offset);
		if (ptr->payload_len > ptr->size) {
			goto beach;
		}
		r_buf_seek (b, ptr->payload_len, R_BUF_CUR);
		if (!r_list_append (ret, ptr)) {
			free (ptr);
			// should it jump to beach?
		}
		ptr = NULL;
	}
	bin->g_sections = ret;
	return ret;
beach:
	eprintf ("[wasm] error: beach sections\n");
	free (ptr);
	return ret;
}

Project: radare2
File: wasm.c

static void free_code_entry(RBinWasmCodeEntry *ptr) {
	if (ptr) {
		free (ptr->locals);
		free (ptr);
	}
}

Project: radare2
File: wasm.c

static bool append_rets(RStrBuf *sb, RBinWasmTypeVec *rets) {
	bool ret = true;
	if (!rets->count) {
		ret &= r_strbuf_append (sb, "nil");
	} else if (rets->count == 1) {
		ret &= r_strbuf_append (sb, r_bin_wasm_valuetype_to_string (rets->types[0]));
	} else {
		ret &= strbuf_append_type_vec (sb, rets);
	}
	return ret;
}