CVE-2022-36044: Out-of-bounds Write (CWE-787)
value: Rizin is a UNIX-like reverse engineering framework and command-line toolset. Versions 0.4.0 and prior are vulnerable to an out-of-bounds write when getting data from Luac files. A user opening a malicious Luac file could be affected by this vulnerability, allowing an attacker to execute code on the user's machine. Commits 07b43bc8aa1ffebd9b68d60624c9610cf7e460c7 and 05bbd147caccc60162d6fba9baaaf24befa281cd contain fixes for the issue.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.8
Published: 2022-09-06T20:15Z
Repository: https://github.com/rizinorg/rizin
Language: C
Files changed: librz/bin/format/luac/luac_bin.c, librz/bin/format/luac/luac_common.h, librz/bin/p/bin_luac.c
Commit date: 2022-08-17 21:20:37 +0200
Commit message: Fix oob read on _luac_build_info and luac memleaks

Diff: diff --git a/librz/bin/format/luac/luac_bin.c b/librz/bin/format/luac/luac_bin.c
index 7142a557a6..cd322cb870 100644
--- a/librz/bin/format/luac/luac_bin.c
+++ b/librz/bin/format/luac/luac_bin.c
@@ -68,16 +68,10 @@ void luac_add_string(RzList *string_list, char *string, ut64 offset, ut64 size)
 	bin_string->type = RZ_STRING_ENC_UTF8;
 
 	rz_list_append(string_list, bin_string);
 }
 
-static void try_free_empty_list(RzList *list) {
-	if (list != NULL) {
-		rz_list_free(list);
-	}
-}
-
 static void free_rz_section(RzBinSection *section) {
 	if (!section) {
 		return;
 	}
 
@@ -109,10 +103,21 @@ static void free_rz_addr(RzBinAddr *addr) {
 		return;
 	}
 	RZ_FREE(addr);
 }
 
+void luac_build_info_free(LuacBinInfo *bin_info) {
+	if (!bin_info) {
+		return;
+	}
+	rz_list_free(bin_info->entry_list);
+	rz_list_free(bin_info->symbol_list);
+	rz_list_free(bin_info->section_list);
+	rz_list_free(bin_info->string_list);
+	free(bin_info);
+}
+
 LuacBinInfo *luac_build_info(LuaProto *proto) {
 	if (!proto) {
 		RZ_LOG_ERROR("Invalid luac file\n");
 		return NULL;
 	}
@@ -126,14 +131,14 @@ LuacBinInfo *luac_build_info(LuaProto *proto) {
 	ret->symbol_list = rz_list_newf((RzListFree)rz_bin_symbol_free);
 	ret->section_list = rz_list_newf((RzListFree)free_rz_section);
 	ret->string_list = rz_list_newf((RzListFree)free_rz_string);
 
 	if (!(ret->entry_list && ret->symbol_list && ret->section_list && ret->string_list)) {
-		try_free_empty_list(ret->entry_list);
-		try_free_empty_list(ret->symbol_list);
-		try_free_empty_list(ret->section_list);
-		try_free_empty_list(ret->string_list);
+		rz_list_free(ret->entry_list);
+		rz_list_free(ret->symbol_list);
+		rz_list_free(ret->section_list);
+		rz_list_free(ret->string_list);
 	}
 
 	_luac_build_info(proto, ret);
 
 	// add entry of main
@@ -225,17 +230,17 @@ static char *get_upvalue_symbol_name(char *proto_name, LuaUpvalueEntry *entry, c
 void _luac_build_info(LuaProto *proto, LuacBinInfo *info) {
 	/* process proto header info */
 	char *section_name;
 	char *symbol_name;
 	char *proto_name;
+	char **upvalue_names = NULL;
 	RzListIter *iter;
+	int i = 0; // iter
 
 	ut64 current_offset;
 	ut64 current_size;
 
-	int i = 0; // iter
-
 	// 0. check if stripped (proto name is lost)
 	if (proto->name_size == 0 || proto->proto_name == NULL) {
 		// replace name with current offset
 		proto_name = rz_str_newf("fcn.%08llx", proto->offset);
 	} else {
@@ -293,25 +298,29 @@ void _luac_build_info(LuaProto *proto, LuacBinInfo *info) {
 			local_var_entry->offset,
 			local_var_entry->varname_len);
 	}
 
 	// 2.2 parse debug_upvalues
-	char **upvalue_names;
-	int real_upvalue_cnt;
-	LuaDbgUpvalueEntry *debug_upv_entry;
-	real_upvalue_cnt = rz_list_length(proto->upvalue_entries);
-	upvalue_names = RZ_NEWS0(char *, real_upvalue_cnt);
-	if (!upvalue_names) {
-		return;
-	}
-	rz_list_foreach (proto->dbg_upvalue_entries, iter, debug_upv_entry) {
-		upvalue_names[i] = (char *)debug_upv_entry->upvalue_name;
-		luac_add_string(
-			info->string_list,
-			upvalue_names[i],
-			debug_upv_entry->offset,
-			debug_upv_entry->name_len);
+	size_t real_upvalue_cnt = rz_list_length(proto->upvalue_entries);
+	if (real_upvalue_cnt > 0) {
+		LuaDbgUpvalueEntry *debug_upv_entry;
+		upvalue_names = RZ_NEWS0(char *, real_upvalue_cnt);
+		if (!upvalue_names) {
+			free(proto_name);
+			return;
+		}
+
+		i = 0;
+		rz_list_foreach (proto->dbg_upvalue_entries, iter, debug_upv_entry) {
+			upvalue_names[i] = (char *)debug_upv_entry->upvalue_name;
+			luac_add_string(
+				info->string_list,
+				upvalue_names[i],
+				debug_upv_entry->offset,
+				debug_upv_entry->name_len);
+			i++;
+		}
 	}
 
 	// 3.1 construct constant symbols
 	LuaConstEntry *const_entry;
 	rz_list_foreach (proto->const_entries, iter, const_entry) {
@@ -350,7 +359,8 @@ void _luac_build_info(LuaProto *proto, LuacBinInfo *info) {
 	LuaProto *sub_proto;
 	rz_list_foreach (proto->proto_entries, iter, sub_proto) {
 		_luac_build_info(sub_proto, info);
 	}
 
-	RZ_FREE(proto_name);
+	free(upvalue_names);
+	free(proto_name);
 }
diff --git a/librz/bin/format/luac/luac_common.h b/librz/bin/format/luac/luac_common.h
index 31739be9cc..27c39651af 100644
--- a/librz/bin/format/luac/luac_common.h
+++ b/librz/bin/format/luac/luac_common.h
@@ -191,10 +191,11 @@ void luac_add_section(RzList *section_list, char *name, ut64 offset, ut32 size,
 void luac_add_symbol(RzList *symbol_list, char *name, ut64 offset, ut64 size, const char *type);
 void luac_add_entry(RzList *entry_list, ut64 offset, int entry_type);
 void luac_add_string(RzList *string_list, char *string, ut64 offset, ut64 size);
 
 LuacBinInfo *luac_build_info(LuaProto *proto);
+void luac_build_info_free(LuacBinInfo *bin_info);
 void _luac_build_info(LuaProto *proto, LuacBinInfo *info);
 
 /* ========================================================
  * Export version specified Api to bin_luac.c
  * Implemented in 'bin/format/luac/v[version]/bin_[version]
diff --git a/librz/bin/p/bin_luac.c b/librz/bin/p/bin_luac.c
index 749afb35e1..24b58d42e3 100644
--- a/librz/bin/p/bin_luac.c
+++ b/librz/bin/p/bin_luac.c
@@ -98,11 +98,11 @@ static RzList *symbols(RzBinFile *bf) {
 	LuacBinInfo *bin_info_obj = GET_INTERNAL_BIN_INFO_OBJ(bf);
 	if (!bin_info_obj) {
 		return NULL;
 	}
 
-	return bin_info_obj->symbol_list;
+	return rz_list_clone(bin_info_obj->symbol_list);
 }
 
 static RzList *entries(RzBinFile *bf) {
 	if (!bf) {
 		return NULL;
@@ -110,11 +110,11 @@ static RzList *entries(RzBinFile *bf) {
 	LuacBinInfo *bin_info_obj = GET_INTERNAL_BIN_INFO_OBJ(bf);
 	if (!bin_info_obj) {
 		return NULL;
 	}
 
-	return bin_info_obj->entry_list;
+	return rz_list_clone(bin_info_obj->entry_list);
 }
 
 static RzList *strings(RzBinFile *bf) {
 	if (!bf) {
 		return NULL;
@@ -122,19 +122,25 @@ static RzList *strings(RzBinFile *bf) {
 	LuacBinInfo *bin_info_obj = GET_INTERNAL_BIN_INFO_OBJ(bf);
 	if (!bin_info_obj) {
 		return NULL;
 	}
 
-	return bin_info_obj->string_list;
+	return rz_list_clone(bin_info_obj->string_list);
+}
+
+static void destroy(RzBinFile *bf) {
+	LuacBinInfo *bin_info_obj = GET_INTERNAL_BIN_INFO_OBJ(bf);
+	luac_build_info_free(bin_info_obj);
 }
 
 RzBinPlugin rz_bin_plugin_luac = {
 	.name = "luac",
 	.desc = "LUA Compiled File",
 	.license = "LGPL3",
 	.get_sdb = NULL,
 	.load_buffer = &load_buffer,
+	.destroy = &destroy,
 	.check_buffer = &check_buffer,
 	.baddr = NULL,
 	.entries = &entries,
 	.maps = &rz_bin_maps_of_file_sections,
 	.sections = &sections,

Vulnerable code: static void try_free_empty_list(RzList *list) {
	if (list != NULL) {
		rz_list_free(list);
	}
}

		try_free_empty_list(ret->entry_list);
		try_free_empty_list(ret->symbol_list);
		try_free_empty_list(ret->section_list);
		try_free_empty_list(ret->string_list);
	int i = 0; // iter

	char **upvalue_names;
	int real_upvalue_cnt;
	LuaDbgUpvalueEntry *debug_upv_entry;
	real_upvalue_cnt = rz_list_length(proto->upvalue_entries);
	upvalue_names = RZ_NEWS0(char *, real_upvalue_cnt);
	if (!upvalue_names) {
		return;
	}
	rz_list_foreach (proto->dbg_upvalue_entries, iter, debug_upv_entry) {
		upvalue_names[i] = (char *)debug_upv_entry->upvalue_name;
		luac_add_string(
			info->string_list,
			upvalue_names[i],
			debug_upv_entry->offset,
			debug_upv_entry->name_len);
	RZ_FREE(proto_name);
	return bin_info_obj->symbol_list;
	return bin_info_obj->entry_list;
	return bin_info_obj->string_list;
Fixed code: void luac_build_info_free(LuacBinInfo *bin_info) {
	if (!bin_info) {
		return;
	}
	rz_list_free(bin_info->entry_list);
	rz_list_free(bin_info->symbol_list);
	rz_list_free(bin_info->section_list);
	rz_list_free(bin_info->string_list);
	free(bin_info);
}

		rz_list_free(ret->entry_list);
		rz_list_free(ret->symbol_list);
		rz_list_free(ret->section_list);
		rz_list_free(ret->string_list);
	char **upvalue_names = NULL;
	int i = 0; // iter
	size_t real_upvalue_cnt = rz_list_length(proto->upvalue_entries);
	if (real_upvalue_cnt > 0) {
		LuaDbgUpvalueEntry *debug_upv_entry;
		upvalue_names = RZ_NEWS0(char *, real_upvalue_cnt);
		if (!upvalue_names) {
			free(proto_name);
			return;
		}

		i = 0;
		rz_list_foreach (proto->dbg_upvalue_entries, iter, debug_upv_entry) {
			upvalue_names[i] = (char *)debug_upv_entry->upvalue_name;
			luac_add_string(
				info->string_list,
				upvalue_names[i],
				debug_upv_entry->offset,
				debug_upv_entry->name_len);
			i++;
		}
	free(upvalue_names);
	free(proto_name);
void luac_build_info_free(LuacBinInfo *bin_info);
	return rz_list_clone(bin_info_obj->symbol_list);
	return rz_list_clone(bin_info_obj->entry_list);
	return rz_list_clone(bin_info_obj->string_list);
}

static void destroy(RzBinFile *bf) {
	LuacBinInfo *bin_info_obj = GET_INTERNAL_BIN_INFO_OBJ(bf);
	luac_build_info_free(bin_info_obj);
	.destroy = &destroy,