Commit message: Fix crash in fuzzed wasm r2_hoobr_consume_init_expr
CVE: CVE-2017-7854
CWE: CWE-125
Vulnerability description: The consume_init_expr function in wasm.c in radare2 1.3.0 allows remote attackers to cause a denial of service (heap-based buffer over-read and application crash) via a crafted Web Assembly file.

Project: radare2
File: wasm.c

static size_t consume_limits (ut8 *buf, ut8 *max, struct r_bin_wasm_resizable_limits_t *out, ut32 *offset) {
	ut32 i = 0;
	if (!(consume_u8 (buf + i, max, &out->flags, &i))) return 0;
	if (!(consume_u32 (buf + i, max, &out->initial, &i))) return 0;
	if (out->flags && (!(consume_u32 (buf + i, max, &out->maximum, &i)))) return 0;
	if (offset) *offset += i;
	return i;
}

Project: radare2

static size_t consume_locals (ut8 *buf, ut8 *max, ut32 count, RBinWasmCodeEntry *out, ut32 *offset) {
	ut32 i = 0, j = 0;
	if (count < 1) return 0;
	if (!(out->locals = (struct r_bin_wasm_local_entry_t*) malloc (sizeof(struct r_bin_wasm_local_entry_t) * count))) {
		return 0;
	}
	while (buf + i < max && j < count) {
		if (!(consume_u32 (buf + i, max, &out->locals[j].count, &i))) {
			free (out->locals);
			return 0;	
		}

		if (!(consume_s8 (buf + i, max, (st8*)&out->locals[j].type, &i))) {
			free (out->locals);
			return 0;
		}
		j += 1;
	}
	if (offset) *offset += i;
	return j;
}

Project: radare2
File: wasm.c

static size_t consume_s32 (ut8 *buf, ut8 *max, st32 *out, ut32 *offset) {
	size_t n;
	if (!buf || !max || !out) {
		return 0;
	}
	if (!(n = read_i32_leb128 (buf, max, out)) || n > 5) {
		return 0;
	}
	if (offset) {
		*offset += n;
	}
	return n;
}

Project: radare2
File: wasm.c

static size_t consume_s8 (ut8 *buf, ut8 *max, st8 *out, ut32 *offset) {
	size_t n;
	ut32 tmp;
	if (!(n = consume_u32 (buf, max, &tmp, offset)) || n > 1) {
		return 0;
	}
	*out = (st8)(tmp & 0x7f);
	return 1;	
}

Project: radare2
File: wasm.c

static size_t consume_str (ut8 *buf, ut8 *max, size_t sz, char *out, ut32 *offset) {
	if (!buf || !max || !out || !sz) {
		return 0;
	}
	if (!(buf + sz < max)) {
		return 0;
	}
	strncpy ((char*)out, (char*)buf, R_MIN (R_BIN_WASM_STRING_LENGTH-1, sz));
 	if (offset) *offset += sz;
 	return sz;
 }

Project: radare2
File: wasm.c

static size_t consume_u32 (ut8 *buf, ut8 *max, ut32 *out, ut32 *offset) {
	size_t n;
	if (!buf || !max || !out) {
		return 0;
	}
	if (!(n = read_u32_leb128 (buf, max, out)) || n > 5) {
		return 0;
	}
	if (offset) {
		*offset += n;
	}
	return n;
}

Project: radare2
File: wasm.c

void r_bin_wasm_destroy (RBinFile *arch) {
	RBinWasmObj *bin;

	if (!arch || !arch->o || !arch->o->bin_obj) {
		return;
	}

	bin = arch->o->bin_obj;
	r_buf_free (bin->buf);

	r_list_free (bin->g_sections);
	r_list_free (bin->g_types);

	r_list_free (bin->g_imports);
	r_list_free (bin->g_exports);
	r_list_free (bin->g_tables);
	r_list_free (bin->g_memories);
	r_list_free (bin->g_globals);
	r_list_free (bin->g_codes);
	r_list_free (bin->g_datas);

	free (bin->g_start);
	free (bin);
	arch->o->bin_obj = NULL;
}

Project: radare2

static RList *r_bin_wasm_get_code_entries (RBinWasmObj *bin, RBinWasmSection *sec) {
	RList *ret = NULL;
	RBinWasmCodeEntry *ptr = NULL;

	if (!(ret = r_list_newf ((RListFree)free))) {
		return NULL;
	}

	ut8* buf = bin->buf->buf + (ut32)sec->payload_data;
	ut32 len =  sec->payload_len;
	ut32 count = sec->count;
	ut32 i = 0, j = 0, r = 0;
	size_t n = 0;

	while (i < len && r < count) {

		if (!(ptr = R_NEW0 (RBinWasmCodeEntry))) {
			return ret;
		}

		if (!(n = consume_u32 (buf + i, buf + len, &ptr->body_size, &i))) {
			free (ptr);
			return ret;
		}

		if (!(i + ptr->body_size - 1 < len)) {
			free (ptr);
			return ret;
		}

		j = i;

		if (!(n = consume_u32 (buf + i, buf + len, &ptr->local_count, &i))) {
			free (ptr);
			return ret;
		}

		if ((n = consume_locals (buf + i, buf + len, ptr->local_count,ptr, &i)) < ptr->local_count) {
			free (ptr);
			return ret;
		}

		ptr->code = sec->payload_data + i;
		ptr->len = ptr->body_size - (i - j);

		i += ptr->len - 1; // consume bytecode

		if (!(consume_u8 (buf + i, buf + len, &ptr->byte, &i))) {
			free (ptr);
			return ret;
		}

		if (ptr->byte != R_BIN_WASM_END_OF_CODE) {
			free (ptr);
			return ret;
		}


		r_list_append (ret, ptr);

		r += 1;

	}

	return ret;
 }

Project: radare2

RList *r_bin_wasm_get_codes (RBinWasmObj *bin) {
	RBinWasmSection *code = NULL;;
	RList *codes = NULL;

	if (!bin || !bin->g_sections) {
		return NULL;
	}

	if (bin->g_codes) {
		return bin->g_codes;
	}

	if (!(codes = r_bin_wasm_get_sections_by_id (bin->g_sections,
						R_BIN_WASM_SECTION_CODE))) {
		return r_list_new();
	}

	if (!(code = (RBinWasmSection*) r_list_first (codes))) {
		return r_list_new();
	}

	bin->g_codes = r_bin_wasm_get_code_entries (bin, code);

	return bin->g_codes;
}

Project: radare2

RList *r_bin_wasm_get_datas (RBinWasmObj *bin) {
	RBinWasmSection *data = NULL;
	RList *datas = NULL;

	if (!bin || !bin->g_sections) {
		return NULL;
	}

	if (bin->g_datas) {
		return bin->g_datas;
	}

	if (!(datas = r_bin_wasm_get_sections_by_id (bin->g_sections,
						R_BIN_WASM_SECTION_DATA))) {
		return r_list_new();
	}

	if (!(data = (RBinWasmSection*) r_list_first (datas))) {
		return r_list_new();
	}

	bin->g_datas = r_bin_wasm_get_data_entries (bin, data);

	return bin->g_datas;
}

Project: radare2

ut32 r_bin_wasm_get_entrypoint (RBinWasmObj *bin) {
	RList *secs = NULL;
	RBinWasmStartEntry *start = NULL;
	RBinWasmSection *sec = NULL;
	RBinWasmCodeEntry *func = NULL;

	if (!bin || !bin->g_sections) {
		return 0;
	}
	if (bin->entrypoint) {
		return bin->entrypoint;
	}
	if (bin->g_start) {
		start = bin->g_start;
	} else if (!(secs = r_bin_wasm_get_sections_by_id (bin->g_sections, R_BIN_WASM_SECTION_START))) {
		return 0;
	} else if (!(sec = (RBinWasmSection*) r_list_first (secs))) {
		return 0;
	} else {
		start = r_bin_wasm_get_start (bin, sec);
		bin->g_start = start;
	}

	if (!start) {
		return 0;
	}

	func = r_list_get_n (r_bin_wasm_get_codes (bin), start->index);
	return (ut32)func? func->code: 0;

}

Project: radare2

RList *r_bin_wasm_get_exports (RBinWasmObj *bin) {

	RBinWasmSection *export = NULL;
	RList *exports = NULL;

	if (!bin || !bin->g_sections) {
		return NULL;
	}

	if (bin->g_exports) {
		return bin->g_exports;
	}

	if (!(exports= r_bin_wasm_get_sections_by_id (bin->g_sections,
						R_BIN_WASM_SECTION_EXPORT))) {
		return r_list_new();
	}

	if (!(export = (RBinWasmSection*) r_list_first (exports))) {
		return r_list_new();
	}

	bin->g_exports = r_bin_wasm_get_export_entries (bin, export);

	return bin->g_exports;
}

Project: radare2
File: wasm.c

static RList *r_bin_wasm_get_import_entries (RBinWasmObj *bin, RBinWasmSection *sec) {
	RList *ret = NULL;
	RBinWasmImportEntry *ptr = NULL;

	if (!(ret = r_list_newf ((RListFree)free))) {
		return NULL;
	}

	ut8* buf = bin->buf->buf + (ut32)sec->payload_data;
	ut32 len =  sec->payload_len;
	ut32 count = sec->count;
	ut32 i = 0, r = 0;

	while (i < len && r < count) {
		if (!(ptr = R_NEW0 (RBinWasmImportEntry))) {
			return ret;
		}
		if (!(consume_u32 (buf + i, buf + len, &ptr->module_len, &i))) {
			goto culvert;
		}
		if (!(consume_str (buf + i, buf + len, ptr->module_len, ptr->module_str, &i))) {
			goto culvert;
		}
		if (!(consume_u32 (buf + i, buf + len, &ptr->field_len, &i))) {
			goto culvert;
		}
		if (!(consume_str (buf + i, buf + len, ptr->field_len, ptr->field_str, &i))) {
			goto culvert;
		} 
		if (!(consume_u8 (buf + i, buf + len, &ptr->kind, &i))) {
			goto culvert;
		}
		switch (ptr->kind) {
		case 0: // Function
			if (!(consume_u32 (buf + i, buf + len, &ptr->type_f, &i))) {
				goto sewer;
			}
			break;
		case 1: // Table
			if (!(consume_u8 (buf + i, buf + len, (ut8*)&ptr->type_t.elem_type, &i))) {
				goto sewer; // varint7
			}
			if (!(consume_limits (buf + i, buf + len, &ptr->type_t.limits, &i))) {
				goto sewer;
			}
			break;
		case 2: // Memory
			if (!(consume_limits (buf + i, buf + len, &ptr->type_m.limits, &i))) {
				goto sewer;
			}
			break;
		case 3: // Global
			if (!(consume_u8 (buf + i, buf + len, (ut8*)&ptr->type_g.content_type, &i))) {
				goto sewer; // varint7
			}
			if (!(consume_u8 (buf + i, buf + len, (ut8*)&ptr->type_g.mutability, &i))) {
				goto sewer; // varuint1
			}
			break;
		default:
			goto sewer;
		}
		r_list_append (ret, ptr);
		r++;
	}
	return ret;
sewer:
	ret = NULL;
culvert:
	free (ptr);
	return ret;
}

Project: radare2

RList *r_bin_wasm_get_imports (RBinWasmObj *bin) {
	RBinWasmSection *import = NULL;
	RList *imports = NULL;

	if (!bin || !bin->g_sections) {
		return NULL;
	}
	if (bin->g_imports) {
		return bin->g_imports;
	}
	if (!(imports = r_bin_wasm_get_sections_by_id (bin->g_sections,
						R_BIN_WASM_SECTION_IMPORT))) {
		return r_list_new();
	}
	if (!(import = (RBinWasmSection*) r_list_first (imports))) {
		return r_list_new();
	}
	return bin->g_imports = r_bin_wasm_get_import_entries (bin, import);
}

Project: radare2

RList *r_bin_wasm_get_memories (RBinWasmObj *bin) {
	RBinWasmSection *memory;
	RList *memories;

	if (!bin || !bin->g_sections) {
		return NULL;
	}

	if (bin->g_memories) {
		return bin->g_memories;
	}

	if (!(memories = r_bin_wasm_get_sections_by_id (bin->g_sections,
						R_BIN_WASM_SECTION_MEMORY))) {
		return r_list_new();
	}

	if (!(memory = (RBinWasmSection*) r_list_first (memories))) {
		return r_list_new();
	}

	bin->g_memories = r_bin_wasm_get_memory_entries (bin, memory);

	return bin->g_memories;
}

Project: radare2
File: wasm.c

static RList *r_bin_wasm_get_memory_entries (RBinWasmObj *bin, RBinWasmSection *sec) {

	RList *ret = NULL;
	RBinWasmMemoryEntry *ptr = NULL;

	if (!(ret = r_list_newf ((RListFree)free))) {
		return NULL;
	}

	ut8* buf = bin->buf->buf + (ut32)sec->payload_data;
	ut32 len =  sec->payload_len;
	ut32 count = sec->count;
	ut32 i = 0, r = 0;

	while (i < len && r < count) {

		if (!(ptr = R_NEW0 (RBinWasmMemoryEntry))) {
			return ret;
		}

		if (!(consume_limits (buf + i, buf + len, &ptr->limits, &i))) {
			free (ptr);
			return ret;
		}

		r_list_append (ret, ptr);

		r += 1;

	}

	return ret;
}

Project: radare2

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)free))) {
		return NULL;
	}

	ut8* buf = bin->buf->buf;
	ut32 len = bin->size, i = 8; // skip magic bytes + version

	while (i < len) {

		if (!(ptr = R_NEW0 (RBinWasmSection))) {
			return ret;
		}

		if (!(consume_u8 (buf + i, buf + len, &ptr->id, &i))) {
			return ret;
		}

		if (!(consume_u32 (buf + i, buf + len, &ptr->size, &i))) {
			free(ptr);
			return NULL;
		}	

		ptr->count = 0;
		ptr->offset = i;

		switch (ptr->id) {

		case R_BIN_WASM_SECTION_CUSTOM:
			if (!(consume_u32 (buf + i, buf + len, &ptr->name_len, &i))) {
				free(ptr);
				return ret;
			}
			if (!(consume_str (buf + i, buf + len, ptr->name_len,
					ptr->name, &i))) {
				free(ptr);
				return ret;
			}
			break;

		case R_BIN_WASM_SECTION_TYPE:
			strcpy (ptr->name, "type");
			ptr->name_len = 4;
			break;

		case R_BIN_WASM_SECTION_IMPORT:
			strcpy (ptr->name, "import");
			ptr->name_len = 6;
			break;

		case R_BIN_WASM_SECTION_FUNCTION:
			strcpy (ptr->name, "function");
			ptr->name_len = 8;
			break;

		case R_BIN_WASM_SECTION_TABLE:
			strcpy (ptr->name, "table");
			ptr->name_len = 5;
			break;

		case R_BIN_WASM_SECTION_MEMORY:
			strcpy (ptr->name, "memory");
			ptr->name_len = 6;
			break;

		case R_BIN_WASM_SECTION_GLOBAL:
			strcpy (ptr->name, "global");
			ptr->name_len = 6;
			break;

		case R_BIN_WASM_SECTION_EXPORT:
			strcpy (ptr->name, "export");
			ptr->name_len = 6;
			break;

		case R_BIN_WASM_SECTION_START:
			strcpy (ptr->name, "start");
			ptr->name_len = 5;
			break;

		case R_BIN_WASM_SECTION_ELEMENT:
			strncpy (ptr->name, "element", R_BIN_WASM_STRING_LENGTH);
			ptr->name_len = 7;
			break;

		case R_BIN_WASM_SECTION_CODE:
			strncpy (ptr->name, "code", R_BIN_WASM_STRING_LENGTH);
			ptr->name_len = 4;
			break;

		case R_BIN_WASM_SECTION_DATA:
			strncpy (ptr->name, "data", R_BIN_WASM_STRING_LENGTH);
			ptr->name_len = 4;
			break;

		default:
			eprintf("unkown section id: %d\n", ptr->id);
			i += ptr->size - 1; // next
			continue;

		}

		if (ptr->id != R_BIN_WASM_SECTION_START
				&& ptr->id != R_BIN_WASM_SECTION_CUSTOM) {
			if (!(consume_u32 (buf + i, buf + len, &ptr->count, &i))) {
				free (ptr);
				return ret;
			}
		}

		ptr->payload_data = i;
		ptr->payload_len = ptr->size - (i - ptr->offset);

		r_list_append (ret, ptr);

		i += ptr->payload_len; // next

	}

	bin->g_sections = ret;

	return ret;

}

Project: radare2

static RList *r_bin_wasm_get_sections_by_id (RList *sections, ut8 id) {
	RBinWasmSection *sec = NULL;
	RList *ret = NULL;	
	RListIter *iter = NULL;

	if (!(ret = r_list_new ())) {
		return NULL;
	}
	r_list_foreach (sections, iter, sec) {
		if (sec->id == id) {
			r_list_append(ret, sec);
		}
	}
	return ret;
}

Project: radare2
File: wasm.c

static RBinWasmStartEntry *r_bin_wasm_get_start (RBinWasmObj *bin, RBinWasmSection *sec) {

	RBinWasmStartEntry *ptr;	

	if (!(ptr = R_NEW0 (RBinWasmStartEntry))) {
		return NULL;
	}

	ut8* buf = bin->buf->buf + (ut32)sec->payload_data;
	ut32 len =  sec->payload_len;
	ut32 i = 0;

	if (!(consume_u32 (buf + i, buf + len, &ptr->index, &i))) {
		free (ptr);
		return NULL;
	}

	return ptr;

}

Project: radare2
File: wasm.c

static RList *r_bin_wasm_get_table_entries (RBinWasmObj *bin, RBinWasmSection *sec) {

	RList *ret = NULL;
	RBinWasmTableEntry *ptr = NULL;

	if (!(ret = r_list_newf ((RListFree)free))) {
		return NULL;
	}

	ut8* buf = bin->buf->buf + (ut32)sec->payload_data;
	ut32 len =  sec->payload_len;
	ut32 count = sec->count;
	ut32 i = 0, r = 0;

	while (i < len && r < count) {

		if (!(ptr = R_NEW0 (RBinWasmTableEntry))) {
			return ret;
		}

		if (!(consume_u8 (buf + i, buf + len, &ptr->element_type, &i))) {
			free (ptr);
			return ret;
		}

		if (!(consume_limits (buf + i, buf + len, &ptr->limits, &i))) {
			free (ptr);
			return ret;
		}

		r_list_append (ret, ptr);

		r += 1;

	}

	return ret;
}

Project: radare2

RList *r_bin_wasm_get_tables (RBinWasmObj *bin) {
	RBinWasmSection *table = NULL;
	RList *tables = NULL;

	if (!bin || !bin->g_sections) {
		return NULL;
	}

	if (bin->g_tables) {
		return bin->g_tables;
	}

	if (!(tables = r_bin_wasm_get_sections_by_id (bin->g_sections,
						R_BIN_WASM_SECTION_TABLE))) {
		return r_list_new();
	}

	if (!(table = (RBinWasmSection*) r_list_first (tables))) {
		r_list_free (tables);
		return r_list_new();
	}

	bin->g_tables = r_bin_wasm_get_table_entries (bin, table);

	r_list_free (tables);
	return bin->g_tables;
}

Project: radare2
File: wasm.c

static RList *r_bin_wasm_get_type_entries (RBinWasmObj *bin, RBinWasmSection *sec) {

	RList *ret = NULL;
	RBinWasmTypeEntry *ptr = NULL;

	if (!(ret = r_list_newf ((RListFree)free))) {
		return NULL;
	}

	ut8* buf = bin->buf->buf + (ut32)sec->payload_data;
	ut32 len =  sec->payload_len;
	ut32 count = sec->count;
	ut32 i = 0, r = 0;

	while (i < len && r < count) {
		if (!(ptr = R_NEW0 (RBinWasmTypeEntry))) {
			return ret;
		}

		if (!(consume_u8 (buf + i, buf + len, &ptr->form, &i))) {
			free (ptr);
			return ret;
		}

		if (!(consume_u32 (buf + i, buf + len, &ptr->param_count, &i))) {
			free (ptr);
			return ret;
		}

		if (!(i + ptr->param_count < len)) {
			free (ptr);
			return ret;
		}

		int j;
		for (j = 0; j < ptr->param_count; j++) {
			if (!(consume_s8 (buf + i, buf + len, (st8*)&ptr->param_types[j], &i))) {
				free (ptr);
				return ret;
			}
		}

		if (!(consume_s8 (buf + i, buf + len, &ptr->return_count, &i))) {
			free (ptr);
			return ret;
		}

		if (ptr->return_count > 1) {
			free(ptr);
			return ret;
		}

		if (ptr->return_count == 1) {
			if (!(consume_s8 (buf + i, buf + len, (st8*)&ptr->return_type, &i))) {
				free(ptr);
				return ret;
			}
		}

		ptr->to_str = r_bin_wasm_type_entry_to_string (ptr);

		r_list_append (ret, ptr);

		r += 1;

	}

	return ret;

}

Project: radare2

RList *r_bin_wasm_get_types (RBinWasmObj *bin) {
	RBinWasmSection *type = NULL;
	RList *types = NULL;

	if (!bin || !bin->g_sections) {
		return NULL;
	}

	if (bin->g_types) {
		return bin->g_types;
	}

	if (!(types = r_bin_wasm_get_sections_by_id (bin->g_sections,
						R_BIN_WASM_SECTION_TYPE))) {
		return r_list_new();
	}

	if (!(type = (RBinWasmSection*) r_list_first (types))) {
		return r_list_new();
	}

	bin->g_types = r_bin_wasm_get_type_entries (bin, type);

	return bin->g_types;
}

Project: radare2

RBinWasmObj *r_bin_wasm_init (RBinFile *arch) {
	RBinWasmObj *bin = R_NEW0 (RBinWasmObj);
	if (!bin) {
		return NULL;
	}
	if (!(bin->buf = r_buf_new ())) {
		free (bin);
		return NULL;
	}
	bin->size = (ut32)arch->buf->length;
	if (!r_buf_set_bytes (bin->buf, arch->buf->buf, bin->size)) {
		r_bin_wasm_destroy (arch);
		free (bin);
		return NULL;
	}

	bin->g_sections = r_bin_wasm_get_sections (bin);

	bin->g_types = r_bin_wasm_get_types (bin);
	bin->g_imports = r_bin_wasm_get_imports (bin);	
	bin->g_exports = r_bin_wasm_get_exports (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_codes = r_bin_wasm_get_codes (bin);
	bin->g_datas = r_bin_wasm_get_datas (bin);

	bin->entrypoint = r_bin_wasm_get_entrypoint (bin);

	return bin;
}

Project: radare2

static char *r_bin_wasm_type_entry_to_string (RBinWasmTypeEntry *ptr) {
	if (!ptr || ptr->to_str) {
		return NULL;
	}

	char *ret;

	int p, i = 0, sz;

	sz = (ptr->param_count + ptr->return_count) * 5 + 9;

	if (!(ret = (char*) malloc (sz * sizeof(char)))) {
		return NULL;
	}

	strcpy (ret + i, "(");
	i++;

	for (p = 0; p < ptr->param_count; p++ ) {
		R_BIN_WASM_VALUETYPETOSTRING (ret+i, ptr->param_types[p], i); // i+=3
		if (p < ptr->param_count - 1) {
			strcpy (ret+i, ", ");
			i += 2;
		}
	}		

	strcpy (ret + i, ") -> (");
	i += 6;

	if (ptr->return_count == 1) {
		R_BIN_WASM_VALUETYPETOSTRING (ret + i, ptr->return_type, i);
	}

	strcpy (ret + i, ")");

	return ret;
}