CVE-2021-43814: Out-of-bounds Write (CWE-787)
value: Rizin is a UNIX-like reverse engineering framework and command-line toolset. In versions up to and including 0.3.1 there is a heap-based out of bounds write in parse_die() when reversing an AMD64 ELF binary with DWARF debug info. When a malicious AMD64 ELF binary is opened by a victim user, Rizin may crash or execute unintended actions. No workaround are known and users are advised to upgrade.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.8
Published: 2021-12-13T20:15Z
Repository: https://github.com/rizinorg/rizin
Language: Other
Files changed: librz/bin/dwarf.c, test/db/formats/elf/crash
Commit date: 2021-12-10 15:43:12 +0100
Commit message: Fix oob write for dwarf with abbrev with count 0 (Fix #2083) (#2086)


Diff: diff --git a/librz/bin/dwarf.c b/librz/bin/dwarf.c
index 1ed1d3517..23dd1f9f0 100644
--- a/librz/bin/dwarf.c
+++ b/librz/bin/dwarf.c
@@ -1218,13 +1218,17 @@ wurzelbert_comp_units:
 
 static int init_die(RzBinDwarfDie *die, ut64 abbr_code, ut64 attr_count) {
 	if (!die) {
 		return -1;
 	}
-	die->attr_values = calloc(sizeof(RzBinDwarfAttrValue), attr_count);
-	if (!die->attr_values) {
-		return -1;
+	if (attr_count) {
+		die->attr_values = calloc(sizeof(RzBinDwarfAttrValue), attr_count);
+		if (!die->attr_values) {
+			return -1;
+		}
+	} else {
+		die->attr_values = NULL;
 	}
 	die->abbrev_code = abbr_code;
 	die->capacity = attr_count;
 	die->count = 0;
 	return 0;
@@ -1724,29 +1728,31 @@ static const ut8 *parse_attr_value(const ut8 *obuf, int obuf_len,
 static const ut8 *parse_die(const ut8 *buf, const ut8 *buf_end, RzBinDwarfDebugInfo *info, RzBinDwarfAbbrevDecl *abbrev,
 	RzBinDwarfCompUnitHdr *hdr, RzBinDwarfDie *die, const ut8 *debug_str, size_t debug_str_len, bool big_endian) {
 	size_t i;
 	const char *comp_dir = NULL;
 	ut64 line_info_offset = UT64_MAX;
-	for (i = 0; i < abbrev->count - 1; i++) {
-		memset(&die->attr_values[i], 0, sizeof(die->attr_values[i]));
+	if (abbrev->count) {
+		for (i = 0; i < abbrev->count - 1; i++) {
+			memset(&die->attr_values[i], 0, sizeof(die->attr_values[i]));
 
-		buf = parse_attr_value(buf, buf_end - buf, &abbrev->defs[i],
-			&die->attr_values[i], hdr, debug_str, debug_str_len, big_endian);
+			buf = parse_attr_value(buf, buf_end - buf, &abbrev->defs[i],
+				&die->attr_values[i], hdr, debug_str, debug_str_len, big_endian);
 
-		RzBinDwarfAttrValue *attribute = &die->attr_values[i];
+			RzBinDwarfAttrValue *attribute = &die->attr_values[i];
 
-		if (attribute->attr_name == DW_AT_comp_dir && (attribute->attr_form == DW_FORM_strp || attribute->attr_form == DW_FORM_string) && attribute->string.content) {
-			comp_dir = attribute->string.content;
-		}
-		if (attribute->attr_name == DW_AT_stmt_list) {
-			if (attribute->kind == DW_AT_KIND_CONSTANT) {
-				line_info_offset = attribute->uconstant;
-			} else if (attribute->kind == DW_AT_KIND_REFERENCE) {
-				line_info_offset = attribute->reference;
+			if (attribute->attr_name == DW_AT_comp_dir && (attribute->attr_form == DW_FORM_strp || attribute->attr_form == DW_FORM_string) && attribute->string.content) {
+				comp_dir = attribute->string.content;
+			}
+			if (attribute->attr_name == DW_AT_stmt_list) {
+				if (attribute->kind == DW_AT_KIND_CONSTANT) {
+					line_info_offset = attribute->uconstant;
+				} else if (attribute->kind == DW_AT_KIND_REFERENCE) {
+					line_info_offset = attribute->reference;
+				}
 			}
+			die->count++;
 		}
-		die->count++;
 	}
 
 	// If this is a compilation unit dir attribute, we want to cache it so the line info parsing
 	// which will need this info can quickly look it up.
 	if (comp_dir && line_info_offset != UT64_MAX) {
diff --git a/test/db/formats/elf/crash b/test/db/formats/elf/crash
index ea6c2c214..fb8a572bd 100644
--- a/test/db/formats/elf/crash
+++ b/test/db/formats/elf/crash
@@ -23,5 +23,13 @@ EXPECT=<<EOF
 nth vaddr bind type lib name 
 -----------------------------
 []
 EOF
 RUN
+
+NAME=ELF/Dwarf: abbrev empty
+FILE=bins/elf/dwarf_fuzzed_abbrev_empty
+CMDS=<<EOF
+aaa
+EOF
+EXPECT=
+RUN

Vulnerable code: 	die->attr_values = calloc(sizeof(RzBinDwarfAttrValue), attr_count);
	if (!die->attr_values) {
		return -1;
	for (i = 0; i < abbrev->count - 1; i++) {
		memset(&die->attr_values[i], 0, sizeof(die->attr_values[i]));
		buf = parse_attr_value(buf, buf_end - buf, &abbrev->defs[i],
			&die->attr_values[i], hdr, debug_str, debug_str_len, big_endian);
		RzBinDwarfAttrValue *attribute = &die->attr_values[i];
		if (attribute->attr_name == DW_AT_comp_dir && (attribute->attr_form == DW_FORM_strp || attribute->attr_form == DW_FORM_string) && attribute->string.content) {
			comp_dir = attribute->string.content;
		}
		if (attribute->attr_name == DW_AT_stmt_list) {
			if (attribute->kind == DW_AT_KIND_CONSTANT) {
				line_info_offset = attribute->uconstant;
			} else if (attribute->kind == DW_AT_KIND_REFERENCE) {
				line_info_offset = attribute->reference;
		die->count++;
Fixed code: 	if (attr_count) {
		die->attr_values = calloc(sizeof(RzBinDwarfAttrValue), attr_count);
		if (!die->attr_values) {
			return -1;
		}
	} else {
		die->attr_values = NULL;
	if (abbrev->count) {
		for (i = 0; i < abbrev->count - 1; i++) {
			memset(&die->attr_values[i], 0, sizeof(die->attr_values[i]));
			buf = parse_attr_value(buf, buf_end - buf, &abbrev->defs[i],
				&die->attr_values[i], hdr, debug_str, debug_str_len, big_endian);
			RzBinDwarfAttrValue *attribute = &die->attr_values[i];
			if (attribute->attr_name == DW_AT_comp_dir && (attribute->attr_form == DW_FORM_strp || attribute->attr_form == DW_FORM_string) && attribute->string.content) {
				comp_dir = attribute->string.content;
			}
			if (attribute->attr_name == DW_AT_stmt_list) {
				if (attribute->kind == DW_AT_KIND_CONSTANT) {
					line_info_offset = attribute->uconstant;
				} else if (attribute->kind == DW_AT_KIND_REFERENCE) {
					line_info_offset = attribute->reference;
				}
			die->count++;

NAME=ELF/Dwarf: abbrev empty
FILE=bins/elf/dwarf_fuzzed_abbrev_empty
CMDS=<<EOF
aaa
EOF
EXPECT=
RUN