CVE-2023-0302: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') (CWE-74)
value: Failure to Sanitize Special Elements into a Different Plane (Special Element Injection) in GitHub repository radareorg/radare2 prior to 5.8.2.
CWE description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
CVSS v3 base score: 7.8
Published: 2023-01-15T01:15Z
Repository: https://github.com/radareorg/radare2
Language: C
Files changed: libr/anal/meta.c, libr/bin/dwarf.c, libr/cons/hud.c, libr/core/cmd_meta.c, libr/core/cmd_print.c, libr/util/str.c, test/db/cmd/dwarf
Commit date: 2023-01-06 12:55:15 +0100
Commit message: Fix ANSI Escape Sequence Injection vulns via DWARF ##vuln

* Reported by @solid-snail via huntrdev
* BountyID: 583133af-7ae6-4a21-beef-a4b0182cf82e
* Reproducer: dwarf_test_func_patched
Diff: diff --git a/libr/anal/meta.c b/libr/anal/meta.c
index 5f2a3201390f..6afeeeef7e42 100644
--- a/libr/anal/meta.c
+++ b/libr/anal/meta.c
@@ -109,16 +109,17 @@ static bool meta_set(RAnal *a, RAnalMetaType type, int subtype, ut64 from, ut64
 	}
 	item->type = type;
 	item->subtype = subtype;
 	item->space = space;
 	free (item->str);
-	item->str = str ? strdup (str) : NULL;
-	if (str && !item->str) {
-		if (!node) { // If we just created this
-			free (item);
-		}
-		return false;
+	if (R_STR_ISNOTEMPTY (str)) {
+		item->str = strdup (str);
+		// this breaks the `ecHw` command
+		// (highlights word in current instruction, which uses ansi
+		// r_str_ansi_strip (item->str);
+	} else {
+		item->str = NULL;
 	}
 	R_DIRTY (a);
 	if (!node) {
 		r_interval_tree_insert (&a->meta, from, to, item);
 	} else if (node->end != to) {
diff --git a/libr/bin/dwarf.c b/libr/bin/dwarf.c
index cb1517aa627e..561c0b6b601f 100644
--- a/libr/bin/dwarf.c
+++ b/libr/bin/dwarf.c
@@ -429,10 +429,11 @@ static void line_header_fini(RBinDwarfLineHeader *hdr) {
 static const ut8 *parse_line_header_source(RBinFile *bf, const ut8 *buf, const ut8 *buf_end,
 	RBinDwarfLineHeader *hdr, Sdb *sdb, int mode, PrintfCallback print) {
 	int i = 0;
 	size_t count;
 	const ut8 *tmp_buf = NULL;
+	char *fn = NULL;
 
 	if (mode == R_MODE_PRINT) {
 		print (" The Directory Table:\n");
 	}
 	while (buf + 1 < buf_end) {
@@ -462,14 +463,16 @@ static const ut8 *parse_line_header_source(RBinFile *bf, const ut8 *buf, const u
 	}
 	int entry_index = 1; // used for printing information
 
 	for (i = 0; i < 2; i++) {
 		while (buf + 1 < buf_end) {
-			const char *filename = (const char *)buf;
 			size_t maxlen = R_MIN ((size_t) (buf_end - buf - 1), 0xfff);
 			ut64 id_idx, mod_time, file_len;
-			size_t len = r_str_nlen (filename, maxlen);
+			free (fn);
+			fn = r_str_ndup ((const char *)buf, maxlen);
+			r_str_ansi_strip (fn);
+			size_t len = strlen (fn);
 
 			if (!len) {
 				buf++;
 				break;
 			}
@@ -510,11 +513,11 @@ static const ut8 *parse_line_header_source(RBinFile *bf, const ut8 *buf, const u
 						include_dir = "./";
 					}
 				}
 
 				if (hdr->file_names) {
-					hdr->file_names[count].name = r_str_newf("%s/%s", r_str_get (include_dir), filename);
+					hdr->file_names[count].name = r_str_newf("%s/%s", r_str_get (include_dir), fn);
 					hdr->file_names[count].id_idx = id_idx;
 					hdr->file_names[count].mod_time = mod_time;
 					hdr->file_names[count].file_len = file_len;
 				}
 				if (comp_dir) {
@@ -523,11 +526,12 @@ static const ut8 *parse_line_header_source(RBinFile *bf, const ut8 *buf, const u
 				}
 				R_FREE (pinclude_dir);
 			}
 			count++;
 			if (mode == R_MODE_PRINT && i) {
-				print ("  %d     %" PFMT64d "       %" PFMT64d "         %" PFMT64d "          %s\n", entry_index++, id_idx, mod_time, file_len, filename);
+				print ("  %d     %" PFMT64d "       %" PFMT64d "         %" PFMT64d "          %s\n",
+						entry_index++, id_idx, mod_time, file_len, fn);
 			}
 		}
 		if (i == 0) {
 			if (count > 0) {
 				hdr->file_names = calloc (sizeof (file_entry), count);
@@ -542,10 +546,11 @@ static const ut8 *parse_line_header_source(RBinFile *bf, const ut8 *buf, const u
 	if (mode == R_MODE_PRINT) {
 		print ("\n");
 	}
 
 beach:
+	free (fn);
 	sdb_free (sdb);
 
 	return buf;
 }
 
@@ -675,11 +680,10 @@ static const ut8 *parse_line_header(
 	return buf;
 }
 
 static inline void add_sdb_addrline(Sdb *s, ut64 addr, const char *file, ut64 line, int mode, PrintfCallback print) {
 	const char *p;
-	char *fileline;
 	char offset[SDB_NUM_BUFSZ];
 	char *offset_ptr;
 
 	if (!s || !file) {
 		return;
@@ -704,11 +708,14 @@ static inline void add_sdb_addrline(Sdb *s, ut64 addr, const char *file, ut64 li
 		p = file;
 	}
 #else
 	p = file;
 #endif
-	fileline = r_str_newf ("%s|%"PFMT64d, p, line);
+	char *fileline = r_str_newf ("%s|%"PFMT64d, p, line);
+	r_str_ansi_strip (fileline);
+	r_str_replace_ch (fileline, '\n', 0, true);
+	r_str_replace_ch (fileline, '\t', 0, true);
 	offset_ptr = sdb_itoa (addr, 16, offset, sizeof (offset));
 	sdb_add (s, offset_ptr, fileline, 0);
 	sdb_add (s, fileline, offset_ptr, 0);
 	free (fileline);
 }
@@ -1664,11 +1671,19 @@ static const ut8 *parse_attr_value(const ut8 *obuf, int obuf_len,
 		value->kind = DW_AT_KIND_CONSTANT;
 		buf = r_uleb128 (buf, buf_end - buf, &value->uconstant, NULL);
 		break;
 	case DW_FORM_string:
 		value->kind = DW_AT_KIND_STRING;
-		value->string.content = *buf ? r_str_ndup ((const char *)buf, buf_end - buf) : NULL;
+		if (*buf) {
+			char *name = r_str_ndup ((const char *)buf, buf_end - buf);
+			r_str_ansi_strip (name);
+			r_str_replace_ch (name, '\n', 0, true);
+			r_str_replace_ch (name, '\t', 0, true);
+			value->string.content = name;
+		} else {
+			value->string.content = NULL;
+		}
 		if (value->string.content) {
 			buf += strlen (value->string.content) + 1;
 		}
 		break;
 	case DW_FORM_block1:
@@ -1709,12 +1724,19 @@ static const ut8 *parse_attr_value(const ut8 *obuf, int obuf_len,
 	// offset in .debug_str
 	case DW_FORM_strp:
 		value->kind = DW_AT_KIND_STRING;
 		value->string.offset = dwarf_read_offset (hdr->is_64bit, &buf, buf_end);
 		if (debug_str && value->string.offset < debug_str_len) {
-			const char *ds = (const char *)(debug_str + value->string.offset);
-			value->string.content = strdup (ds); // r_str_ndup (ds, debug_str_len - value->string.offset);
+			char *ds = r_str_ndup ((const char *)(debug_str + value->string.offset), debug_str_len);
+			if (ds) {
+				r_str_ansi_strip (ds);
+				r_str_replace_ch (ds, '\n', 0, true);
+				r_str_replace_ch (ds, '\t', 0, true);
+				value->string.content = ds;
+			} else {
+				value->string.content = NULL;
+			}
 		} else {
 			value->string.content = NULL; // Means malformed DWARF, should we print error message?
 		}
 		break;
 	// offset in .debug_info
@@ -1901,12 +1923,15 @@ static const ut8 *parse_die(const ut8 *buf, const ut8 *buf_end, RBinDwarfAbbrevD
 		bool is_valid_string_form = is_string && attribute->string.content;
 		// TODO  does this have a purpose anymore?
 		// Or atleast it needs to rework becase there will be
 		// more comp units -> more comp dirs and only the last one will be kept
 		if (attribute->attr_name == DW_AT_comp_dir && is_valid_string_form) {
-			const char *name = attribute->string.content;
-			sdb_set (sdb, "DW_AT_comp_dir", name, 0);
+			char *name = strdup (attribute->string.content);
+			r_str_ansi_strip (name);
+			r_str_replace_ch (name, '\n', 0, true);
+			r_str_replace_ch (name, '\t', 0, true);
+			sdb_set_owned (sdb, "DW_AT_comp_dir", name, 0);
 		}
 		die->count++;
 	}
 	return buf;
 }
diff --git a/libr/cons/hud.c b/libr/cons/hud.c
index 4a97cbac7ad4..f172646354d8 100644
--- a/libr/cons/hud.c
+++ b/libr/cons/hud.c
@@ -1,16 +1,17 @@
-/* radare - LGPL - Copyright 2008-2021 - pancake */
+/* radare - LGPL - Copyright 2008-2023 - pancake */
 
 #include <r_cons.h>
 #include <ctype.h>
 
 #define I(x) r_cons_singleton ()->x
 
 // Display the content of a file in the hud
 R_API char *r_cons_hud_file(const char *f) {
 	char *s = r_file_slurp (f, NULL);
 	if (s) {
+		r_str_ansi_strip (s);
 		char *ret = r_cons_hud_string (s);
 		free (s);
 		return ret;
 	}
 	return NULL;
@@ -27,10 +28,11 @@ R_API char *r_cons_hud_line_string(const char *s) {
 	if (!o) {
 		return NULL;
 	}
 	r_str_replace_ch (o, '\r', 0, true);
 	r_str_replace_ch (o, '\t', 0, true);
+	r_str_ansi_strip (o);
 	RList *fl = r_list_new ();
 	int i;
 	if (!fl) {
 		free (o);
 		return NULL;
@@ -64,10 +66,11 @@ R_API char *r_cons_hud_string(const char *s) {
 	}
 	char *os, *track, *ret, *o = strdup (s);
 	if (!o) {
 		return NULL;
 	}
+	r_str_ansi_strip (o);
 	r_str_replace_ch (o, '\r', 0, true);
 	r_str_replace_ch (o, '\t', 0, true);
 	RList *fl = r_list_new ();
 	int i;
 	if (!fl) {
diff --git a/libr/core/cmd_meta.c b/libr/core/cmd_meta.c
index c517c0bf0274..6d8e6f7ecc9a 100644
--- a/libr/core/cmd_meta.c
+++ b/libr/core/cmd_meta.c
@@ -537,14 +537,14 @@ static int cmd_meta_comment(RCore *core, const char *input) {
 	case 'j': // "CCj"
 		r_meta_print_list_all (core->anal, R_META_TYPE_COMMENT, 'j', input + 2);
 		break;
 	case '!':
 		{
-			char *out;
 			const char *comment = r_meta_get_string (core->anal, R_META_TYPE_COMMENT, addr);
-			out = r_core_editor (core, NULL, comment);
+			char *out = r_core_editor (core, NULL, comment);
 			if (out) {
+				r_str_ansi_strip (out);
 				//r_meta_set (core->anal->meta, R_META_TYPE_COMMENT, addr, 0, out);
 				r_core_cmdf (core, "CC-@0x%08"PFMT64x, addr);
 				//r_meta_del (core->anal->meta, input[0], addr, addr+1);
 				r_meta_set_string (core->anal,
 						R_META_TYPE_COMMENT, addr, out);
@@ -558,10 +558,11 @@ static int cmd_meta_comment(RCore *core, const char *input) {
 		const char *newcomment = r_str_trim_head_ro (input + 2);
 		const char *comment = r_meta_get_string (core->anal, R_META_TYPE_COMMENT, addr);
 		char *text;
 		char *nc = strdup (newcomment);
 		r_str_unescape (nc);
+		r_str_ansi_strip (nc);
 		if (comment) {
 			text = malloc (strlen (comment) + strlen (newcomment) + 2);
 			if (text) {
 				strcpy (text, comment);
 				strcat (text, " ");
diff --git a/libr/core/cmd_print.c b/libr/core/cmd_print.c
index 68067cc3f52a..ae0295156251 100644
--- a/libr/core/cmd_print.c
+++ b/libr/core/cmd_print.c
@@ -1,6 +1,6 @@
-/* radare - LGPL - Copyright 2009-2022 - pancake */
+/* radare - LGPL - Copyright 2009-2023 - pancake */
 
 #include <r_core.h>
 #include <limits.h>
 
 #define R_CORE_MAX_DISASM (1024 * 1024 * 8)
diff --git a/libr/util/str.c b/libr/util/str.c
index 6cf6b3ebbd9f..742e264784ad 100644
--- a/libr/util/str.c
+++ b/libr/util/str.c
@@ -1955,18 +1955,20 @@ R_API size_t r_str_ansi_nlen(const char *str, size_t slen) {
 	}
 	return len; // len > 0 ? len: 1;
 }
 
 // remove ansi escape codes from string, decolorizing it
+// TODO : optimize by just using two counter variables instead of strcpy()
 R_API size_t r_str_ansi_strip(char *str) {
 	size_t i = 0;
 	while (str[i]) {
 		size_t chlen = __str_ansi_length (str + i);
 		if (chlen > 1) {
-			r_str_cpy (str + i + 1, str + i + chlen);
+			r_str_cpy (str + i, str + i + chlen);
+		} else {
+			i++;
 		}
-		i++;
 	}
 	return i;
 }
 
 // insert a string into another string, supports ansi control chars
diff --git a/test/db/cmd/dwarf b/test/db/cmd/dwarf
index 3edef10dcc1a..534b0043b59d 100644
--- a/test/db/cmd/dwarf
+++ b/test/db/cmd/dwarf
@@ -1,5 +1,57 @@
+NAME="ansi injection via dwarf"
+FILE=bins/elf/dwarf_test_func_patched
+ARGS=-AA
+CMDS=<<EOF
+CL
+xc
+EOF
+EXPECT=<<EOF
+file: /_test.c
+line: 5
+addr: 0x00001159
+file: /_test.c
+line: 11
+addr: 0x00001184
+file: /_test.c
+line: 6
+addr: 0x00001165
+file: /_test.c
+line: 11
+addr: 0x00001186
+file: /_test.c
+line: 9
+addr: 0x00001170
+file: /_test.c
+line: 8
+addr: 0x00001168
+file: /_test.c
+line: 4
+addr: 0x00001149
+file: /_test.c
+line: 10
+addr: 0x0000117f
+- offset -  6061 6263 6465 6667 6869 6A6B 6C6D 6E6F  0123456789ABCDEF  comment
+0x00001060  f30f 1efa 31ed 4989 d15e 4889 e248 83e4  ....1.I..^H..H..  ; rip ; [16] -r-x section size 294 named .text ; arg3
+0x00001070  f050 5445 31c0 31c9 488d 3de9 0000 00ff  .PTE1.1.H.=.....
+0x00001080  1553 2f00 00f4 662e 0f1f 8400 0000 0000  .S/...f.........
+0x00001090  488d 3d79 2f00 0048 8d05 722f 0000 4839  H.=y/..H..r/..H9  ; sym.deregister_tm_clones
+0x000010a0  f874 1548 8b05 362f 0000 4885 c074 09ff  .t.H..6/..H..t..
+0x000010b0  e00f 1f80 0000 0000 c30f 1f80 0000 0000  ................
+0x000010c0  488d 3d49 2f00 0048 8d35 422f 0000 4829  H.=I/..H.5B/..H)  ; sym.register_tm_clones
+0x000010d0  fe48 89f0 48c1 ee3f 48c1 f803 4801 c648  .H..H..?H...H..H
+0x000010e0  d1fe 7414 488b 0505 2f00 0048 85c0 7408  ..t.H.../..H..t.
+0x000010f0  ffe0 660f 1f44 0000 c30f 1f80 0000 0000  ..f..D..........
+0x00001100  f30f 1efa 803d 052f 0000 0075 2b55 4883  .....=./...u+UH.  ; sym.__do_global_dtors_aux
+0x00001110  3de2 2e00 0000 4889 e574 0c48 8b3d e62e  =.....H..t.H.=..
+0x00001120  0000 e819 ffff ffe8 64ff ffff c605 dd2e  ........d.......
+0x00001130  0000 015d c30f 1f00 c30f 1f80 0000 0000  ...]............
+0x00001140  f30f 1efa e977 ffff fff3 0f1e fa55 4889  .....w.......UH.  ; sym.frame_dummy  ; dbg._func ; void _func(char * msg);
+0x00001150  e548 83ec 1048 897d f848 8b45 f848 89c7  .H...H.}.H.E.H.. ; arg1 ; const char *s
+EOF
+RUN
+
 NAME="open companion file for macho dwarf"
 FILE=bins/mach0/dwarf/a.out
 CMDS=<<EOF
 CL
 EOF

Vulnerable code: 	item->str = str ? strdup (str) : NULL;
	if (str && !item->str) {
		if (!node) { // If we just created this
			free (item);
		}
		return false;
			const char *filename = (const char *)buf;
			size_t len = r_str_nlen (filename, maxlen);
					hdr->file_names[count].name = r_str_newf("%s/%s", r_str_get (include_dir), filename);
				print ("  %d     %" PFMT64d "       %" PFMT64d "         %" PFMT64d "          %s\n", entry_index++, id_idx, mod_time, file_len, filename);
	char *fileline;
	fileline = r_str_newf ("%s|%"PFMT64d, p, line);
		value->string.content = *buf ? r_str_ndup ((const char *)buf, buf_end - buf) : NULL;
			const char *ds = (const char *)(debug_str + value->string.offset);
			value->string.content = strdup (ds); // r_str_ndup (ds, debug_str_len - value->string.offset);
			const char *name = attribute->string.content;
			sdb_set (sdb, "DW_AT_comp_dir", name, 0);
/* radare - LGPL - Copyright 2008-2021 - pancake */
			char *out;
			out = r_core_editor (core, NULL, comment);
/* radare - LGPL - Copyright 2009-2022 - pancake */
			r_str_cpy (str + i + 1, str + i + chlen);
		i++;
Fixed code: 	if (R_STR_ISNOTEMPTY (str)) {
		item->str = strdup (str);
		// this breaks the `ecHw` command
		// (highlights word in current instruction, which uses ansi
		// r_str_ansi_strip (item->str);
	} else {
		item->str = NULL;
	char *fn = NULL;
			free (fn);
			fn = r_str_ndup ((const char *)buf, maxlen);
			r_str_ansi_strip (fn);
			size_t len = strlen (fn);
					hdr->file_names[count].name = r_str_newf("%s/%s", r_str_get (include_dir), fn);
				print ("  %d     %" PFMT64d "       %" PFMT64d "         %" PFMT64d "          %s\n",
						entry_index++, id_idx, mod_time, file_len, fn);
	free (fn);
	char *fileline = r_str_newf ("%s|%"PFMT64d, p, line);
	r_str_ansi_strip (fileline);
	r_str_replace_ch (fileline, '\n', 0, true);
	r_str_replace_ch (fileline, '\t', 0, true);
		if (*buf) {
			char *name = r_str_ndup ((const char *)buf, buf_end - buf);
			r_str_ansi_strip (name);
			r_str_replace_ch (name, '\n', 0, true);
			r_str_replace_ch (name, '\t', 0, true);
			value->string.content = name;
		} else {
			value->string.content = NULL;
		}
			char *ds = r_str_ndup ((const char *)(debug_str + value->string.offset), debug_str_len);
			if (ds) {
				r_str_ansi_strip (ds);
				r_str_replace_ch (ds, '\n', 0, true);
				r_str_replace_ch (ds, '\t', 0, true);
				value->string.content = ds;
			} else {
				value->string.content = NULL;
			}
			char *name = strdup (attribute->string.content);
			r_str_ansi_strip (name);
			r_str_replace_ch (name, '\n', 0, true);
			r_str_replace_ch (name, '\t', 0, true);
			sdb_set_owned (sdb, "DW_AT_comp_dir", name, 0);
/* radare - LGPL - Copyright 2008-2023 - pancake */
		r_str_ansi_strip (s);
	r_str_ansi_strip (o);
	r_str_ansi_strip (o);
			char *out = r_core_editor (core, NULL, comment);
				r_str_ansi_strip (out);
		r_str_ansi_strip (nc);
/* radare - LGPL - Copyright 2009-2023 - pancake */
// TODO : optimize by just using two counter variables instead of strcpy()
			r_str_cpy (str + i, str + i + chlen);
		} else {
			i++;
NAME="ansi injection via dwarf"
FILE=bins/elf/dwarf_test_func_patched
ARGS=-AA
CMDS=<<EOF
CL
xc
EOF
EXPECT=<<EOF
file: /_test.c
line: 5
addr: 0x00001159
file: /_test.c
line: 11
addr: 0x00001184
file: /_test.c
line: 6
addr: 0x00001165
file: /_test.c
line: 11
addr: 0x00001186
file: /_test.c
line: 9
addr: 0x00001170
file: /_test.c
line: 8
addr: 0x00001168
file: /_test.c
line: 4
addr: 0x00001149
file: /_test.c
line: 10
addr: 0x0000117f
- offset -  6061 6263 6465 6667 6869 6A6B 6C6D 6E6F  0123456789ABCDEF  comment
0x00001060  f30f 1efa 31ed 4989 d15e 4889 e248 83e4  ....1.I..^H..H..  ; rip ; [16] -r-x section size 294 named .text ; arg3
0x00001070  f050 5445 31c0 31c9 488d 3de9 0000 00ff  .PTE1.1.H.=.....
0x00001080  1553 2f00 00f4 662e 0f1f 8400 0000 0000  .S/...f.........
0x00001090  488d 3d79 2f00 0048 8d05 722f 0000 4839  H.=y/..H..r/..H9  ; sym.deregister_tm_clones
0x000010a0  f874 1548 8b05 362f 0000 4885 c074 09ff  .t.H..6/..H..t..
0x000010b0  e00f 1f80 0000 0000 c30f 1f80 0000 0000  ................
0x000010c0  488d 3d49 2f00 0048 8d35 422f 0000 4829  H.=I/..H.5B/..H)  ; sym.register_tm_clones
0x000010d0  fe48 89f0 48c1 ee3f 48c1 f803 4801 c648  .H..H..?H...H..H
0x000010e0  d1fe 7414 488b 0505 2f00 0048 85c0 7408  ..t.H.../..H..t.
0x000010f0  ffe0 660f 1f44 0000 c30f 1f80 0000 0000  ..f..D..........
0x00001100  f30f 1efa 803d 052f 0000 0075 2b55 4883  .....=./...u+UH.  ; sym.__do_global_dtors_aux
0x00001110  3de2 2e00 0000 4889 e574 0c48 8b3d e62e  =.....H..t.H.=..
0x00001120  0000 e819 ffff ffe8 64ff ffff c605 dd2e  ........d.......
0x00001130  0000 015d c30f 1f00 c30f 1f80 0000 0000  ...]............
0x00001140  f30f 1efa e977 ffff fff3 0f1e fa55 4889  .....w.......UH.  ; sym.frame_dummy  ; dbg._func ; void _func(char * msg);
0x00001150  e548 83ec 1048 897d f848 8b45 f848 89c7  .H...H.}.H.E.H.. ; arg1 ; const char *s
EOF
RUN