CVE-2022-1052: Out-of-bounds Write (CWE-787)
value: Heap Buffer Overflow in iterate_chained_fixups in GitHub repository radareorg/radare2 prior to 5.6.6.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 5.5
Published: 2022-03-24T13:15Z
Repository: https://github.com/radareorg/radare2
Language: C
Files changed: libr/bin/format/mach0/mach0.c, libr/bin/format/mach0/mach0.h, libr/core/cmd_api.c
Commit date: 2022-03-22 16:56:27 +0100
Commit message: Fix heap OOB read in macho.iterate_chained_fixups ##crash

* Reported by peacock-doris via huntr.dev
* Reproducer 'tests_65305'

mrmacete:
* Return early if segs_count is 0
* Initialize segs_count also for reconstructed fixups

Co-authored-by: pancake <pancake@nopcode.org>
Co-authored-by: Francesco Tamagni <mrmacete@protonmail.ch>
Diff: diff --git a/libr/bin/format/mach0/mach0.c b/libr/bin/format/mach0/mach0.c
index 679198ed620..033f2e83b0c 100644
--- a/libr/bin/format/mach0/mach0.c
+++ b/libr/bin/format/mach0/mach0.c
@@ -1508,14 +1508,15 @@ static bool parse_chained_fixups(struct MACH0_(obj_t) *bin, ut32 offset, ut32 si
 	}
 	ut64 starts_at = offset + header.starts_offset;
 	if (header.starts_offset > size) {
 		return false;
 	}
-	ut32 segs_count;
-	if ((segs_count = r_buf_read_le32_at (bin->b, starts_at)) == UT32_MAX) {
+	ut32 segs_count = r_buf_read_le32_at (bin->b, starts_at);
+	if (segs_count == UT32_MAX || segs_count == 0) {
 		return false;
 	}
+	bin->segs_count = segs_count;
 	bin->chained_starts = R_NEWS0 (struct r_dyld_chained_starts_in_segment *, segs_count);
 	if (!bin->chained_starts) {
 		return false;
 	}
 	bin->fixups_header = header;
@@ -1697,10 +1698,11 @@ static bool reconstruct_chained_fixup(struct MACH0_(obj_t) *bin) {
 			return false;
 		}
 	}
 	R_FREE (opcodes);
 
+	bin->segs_count = bin->nsegs;
 	return true;
 }
 
 static int init_items(struct MACH0_(obj_t) *bin) {
 	struct load_command lc = {0, 0};
@@ -2122,11 +2124,11 @@ void *MACH0_(mach0_free)(struct MACH0_(obj_t) *mo) {
 	free (mo->func_start);
 	free (mo->signature);
 	free (mo->intrp);
 	free (mo->compiler);
 	if (mo->chained_starts) {
-		for (i = 0; i < mo->nsegs; i++) {
+		for (i = 0; i < mo->nsegs && i < mo->segs_count; i++) {
 			if (mo->chained_starts[i]) {
 				free (mo->chained_starts[i]->page_start);
 				free (mo->chained_starts[i]);
 			}
 		}
@@ -4556,11 +4558,11 @@ struct MACH0_(mach_header) *MACH0_(get_hdr)(RBuffer *buf) {
 	return macho_hdr;
 }
 
 void MACH0_(iterate_chained_fixups)(struct MACH0_(obj_t) *bin, ut64 limit_start, ut64 limit_end, ut32 event_mask, RFixupCallback callback, void * context) {
 	int i = 0;
-	for (; i < bin->nsegs; i++) {
+	for (; i < bin->nsegs && i < bin->segs_count; i++) {
 		if (!bin->chained_starts[i]) {
 			continue;
 		}
 		int page_size = bin->chained_starts[i]->page_size;
 		if (page_size < 1) {
diff --git a/libr/bin/format/mach0/mach0.h b/libr/bin/format/mach0/mach0.h
index 442b4f1a3f0..0ad79a794e7 100644
--- a/libr/bin/format/mach0/mach0.h
+++ b/libr/bin/format/mach0/mach0.h
@@ -128,10 +128,11 @@ struct MACH0_(obj_t) {
 	struct MACH0_(mach_header) hdr;
 	struct MACH0_(segment_command) *segs;
 	char *intrp;
 	char *compiler;
 	int nsegs;
+	int segs_count;
 	struct r_dyld_chained_starts_in_segment **chained_starts;
 	struct dyld_chained_fixups_header fixups_header;
 	ut64 fixups_offset;
 	ut64 fixups_size;
 	struct MACH0_(section) *sects;
diff --git a/libr/core/cmd_api.c b/libr/core/cmd_api.c
index 14cd7fa97b5..71c4136405c 100644
--- a/libr/core/cmd_api.c
+++ b/libr/core/cmd_api.c
@@ -389,11 +389,11 @@ R_API int r_cmd_alias_set_raw(RCmd *cmd, const char *k, const ut8 *v, int sz) {
 	return ret;
 }
 
 R_API RCmdAliasVal *r_cmd_alias_get(RCmd *cmd, const char *k) {
 	r_return_val_if_fail (cmd && cmd->aliases && k, NULL);
-	return ht_pp_find(cmd->aliases, k, NULL);
+	return ht_pp_find (cmd->aliases, k, NULL);
 }
 
 static ut8 *alias_append_internal(int *out_szp, const RCmdAliasVal *first, const ut8 *second, int second_sz) {
 	ut8* out;
 	int out_sz;

Vulnerable code: 	ut32 segs_count;
	if ((segs_count = r_buf_read_le32_at (bin->b, starts_at)) == UT32_MAX) {
		for (i = 0; i < mo->nsegs; i++) {
	for (; i < bin->nsegs; i++) {
	return ht_pp_find(cmd->aliases, k, NULL);
Fixed code: 	ut32 segs_count = r_buf_read_le32_at (bin->b, starts_at);
	if (segs_count == UT32_MAX || segs_count == 0) {
	bin->segs_count = segs_count;
	bin->segs_count = bin->nsegs;
		for (i = 0; i < mo->nsegs && i < mo->segs_count; i++) {
	for (; i < bin->nsegs && i < bin->segs_count; i++) {
	int segs_count;
	return ht_pp_find (cmd->aliases, k, NULL);