Vulnerability: In radare2 prior to 3.1.1, r_bin_dyldcache_extract in libr/bin/format/mach0/dyldcache.c may allow attackers to cause a denial-of-service (application crash caused by out-of-bounds read) by crafting an input file.
Vulnerability ID: CVE-2018-20458
CWE: CWE-125
Severity score: 2.9
Project: https://github.com/radareorg/radare2
Published: 2018-12-25
File: libr/bin/format/mach0/dyldcache.c
Commit message: Fix #12374 - oobread crash in truncated dyldcache ##bin (#12381)
Commit author: radare

Vulnerable version (before fix):
 /* radare - LGPL - Copyright 2010-2016 - nibble, pancake  */  #include <stdio.h> #include <r_types.h> static int r_bin_dyldcache_apply_patch (struct r_buf_t* buf, ut32 data, ut64 off  #define NZ_OFFSET(x) if((x) > 0) r_bin_dyldcache_apply_patch (dbuf, (x) - linkedit_offset, (ut64)((size_t)&(x) - (size_t)data))  /* TODO: Needs more testing and ERROR HANDLING */ struct r_bin_dyldcache_lib_t *r_bin_dyldcache_extract(struct r_bin_dyldcache_obj_t* bin, int idx, int *nlib) { 	ut64 liboff, linkedit_offset; struct r_bin_dyldcache_lib_t *r_bin_dyldcache_extract(struct r_bin_dyldcache_obj 	*nlib = bin->nlibs; 	ret = R_NEW0 (struct r_bin_dyldcache_lib_t); 	if (!ret) { 		perror ("malloc (ret)"); 		return NULL; 	} 	if (bin->hdr.startaddr > bin->size) { 	    	eprintf ("corrupted dyldcache"); 		free (ret); 		return NULL; 	} 	if (bin->hdr.startaddr > bin->size || bin->hdr.baseaddroff > bin->size) { 		eprintf ("corrupted dyldcache"); 		free (ret); 		return NULL; 	} 	image_infos = (struct dyld_cache_image_info*) (bin->b->buf + bin->hdr.startaddr); 	dyld_vmbase = *(ut64 *)(bin->b->buf + bin->hdr.baseaddroff); 	liboff = image_infos[idx].address - dyld_vmbase; 	if (liboff > bin->size) { 		eprintf ("Corrupted file\n"); 		free (ret); 		return NULL; 	} 	ret->offset = liboff; 	if (image_infos[idx].pathFileOffset > bin->size) { 	    eprintf ("corrupted file\n"); 		free (ret); 		return NULL; 	} 	libname = (char *)(bin->b->buf + image_infos[idx].pathFileOffset); 	/* Locate lib hdr in cache */ 	data = bin->b->buf + liboff; 	mh = (struct mach_header *)data; struct r_bin_dyldcache_obj_t* r_bin_dyldcache_new(const char* file) { }  struct r_bin_dyldcache_obj_t* r_bin_dyldcache_from_bytes_new(const ut8* buf, ut64 size) { 	struct r_bin_dyldcache_obj_t *bin; 	if (!(bin = malloc (sizeof (struct r_bin_dyldcache_obj_t)))) { 		return NULL; 	} 	memset (bin, 0, sizeof (struct r_bin_dyldcache_obj_t)); 	if (!buf) { 		return r_bin_dyldcache_free (bin); 	} 	bin->b = r_buf_new(); 	if (!r_buf_set_bytes (bin->b, buf, size)) { 		return r_bin_dyldcache_free (bin); 	} 	if (!r_bin_dyldcache_init (bin)) {

Fixed version (after fix):
 /* radare - LGPL - Copyright 2010-2018 - nibble, pancake  */  #include <stdio.h> #include <r_types.h> static int r_bin_dyldcache_apply_patch (struct r_buf_t* buf, ut32 data, ut64 off  #define NZ_OFFSET(x) if((x) > 0) r_bin_dyldcache_apply_patch (dbuf, (x) - linkedit_offset, (ut64)((size_t)&(x) - (size_t)data))  // make it public in util/buf.c ? static ut64 r_buf_read64le (RBuffer *buf, ut64 off) { 	ut8 data[8] = {0}; 	r_buf_read_at (buf, off, data, 8); 	return r_read_le64 (data); }  static char *r_buf_read_string (RBuffer *buf, ut64 addr, int len) { 	ut8 *data = malloc (len); 	if (data) { 		r_buf_read_at (buf, addr, data, len); 		data[len-1] = 0; 		return data; 	} 	return NULL; }  /* TODO: Needs more testing and ERROR HANDLING */ struct r_bin_dyldcache_lib_t *r_bin_dyldcache_extract(struct r_bin_dyldcache_obj_t* bin, int idx, int *nlib) { 	ut64 liboff, linkedit_offset; struct r_bin_dyldcache_lib_t *r_bin_dyldcache_extract(struct r_bin_dyldcache_obj 	*nlib = bin->nlibs; 	ret = R_NEW0 (struct r_bin_dyldcache_lib_t); 	if (!ret) { 		return NULL; 	} 	if (bin->hdr.startaddr > bin->size) { 	    	eprintf ("corrupted dyldcache"); 		free (ret); 		return NULL; 	}  	if (bin->hdr.startaddr > bin->size || bin->hdr.baseaddroff > bin->size) { 		eprintf ("corrupted dyldcache"); 		free (ret); 		return NULL; 	} 	int sz = bin->nlibs * sizeof (struct dyld_cache_image_info); 	image_infos = malloc (sz); //(struct dyld_cache_image_info*) (bin->b->buf + bin->hdr.startaddr); 	if (!image_infos) { 		free (ret); 		return NULL; 	} 	r_buf_read_at (bin->b, bin->hdr.startaddr, (ut8*)image_infos, sz); 	dyld_vmbase = r_buf_read64le (bin->b, bin->hdr.baseaddroff); 	liboff = image_infos[idx].address - dyld_vmbase; 	if (liboff > bin->size) { 		eprintf ("Corrupted file\n"); 		free (ret); 		return NULL; 	} 	ret->offset = liboff; 	int pfo = image_infos[idx].pathFileOffset; 	if (pfo < 0 || pfo > bin->size) { 		eprintf ("corrupted file: pathFileOffset > bin->size (%d)\n", pfo); 		free (ret); 		return NULL; 	} 	libname = r_buf_read_string (bin->b, pfo, 64); 	/* Locate lib hdr in cache */ 	data = bin->b->buf + liboff; 	mh = (struct mach_header *)data; struct r_bin_dyldcache_obj_t* r_bin_dyldcache_new(const char* file) { }  struct r_bin_dyldcache_obj_t* r_bin_dyldcache_from_bytes_new(const ut8* buf, ut64 size) { 	struct r_bin_dyldcache_obj_t *bin = R_NEW0 (struct r_bin_dyldcache_obj_t); 	if (!bin) { 		return NULL; 	} 	if (!buf) { 		return r_bin_dyldcache_free (bin); 	} 	bin->b = r_buf_new (); 	if (!bin->b || !r_buf_set_bytes (bin->b, buf, size)) { 		return r_bin_dyldcache_free (bin); 	} 	if (!r_bin_dyldcache_init (bin)) {

Patch:
patch: @@ -1,4 +1,4 @@
-/* radare - LGPL - Copyright 2010-2016 - nibble, pancake  */
+/* radare - LGPL - Copyright 2010-2018 - nibble, pancake  */
 
 #include <stdio.h>
 #include <r_types.h>
@@ -21,6 +21,23 @@ static int r_bin_dyldcache_apply_patch (struct r_buf_t* buf, ut32 data, ut64 off
 
 #define NZ_OFFSET(x) if((x) > 0) r_bin_dyldcache_apply_patch (dbuf, (x) - linkedit_offset, (ut64)((size_t)&(x) - (size_t)data))
 
+// make it public in util/buf.c ?
+static ut64 r_buf_read64le (RBuffer *buf, ut64 off) {
+	ut8 data[8] = {0};
+	r_buf_read_at (buf, off, data, 8);
+	return r_read_le64 (data);
+}
+
+static char *r_buf_read_string (RBuffer *buf, ut64 addr, int len) {
+	ut8 *data = malloc (len);
+	if (data) {
+		r_buf_read_at (buf, addr, data, len);
+		data[len-1] = 0;
+		return data;
+	}
+	return NULL;
+}
+
 /* TODO: Needs more testing and ERROR HANDLING */
 struct r_bin_dyldcache_lib_t *r_bin_dyldcache_extract(struct r_bin_dyldcache_obj_t* bin, int idx, int *nlib) {
 	ut64 liboff, linkedit_offset;
@@ -47,34 +64,41 @@ struct r_bin_dyldcache_lib_t *r_bin_dyldcache_extract(struct r_bin_dyldcache_obj
 	*nlib = bin->nlibs;
 	ret = R_NEW0 (struct r_bin_dyldcache_lib_t);
 	if (!ret) {
-		perror ("malloc (ret)");
 		return NULL;
 	}
 	if (bin->hdr.startaddr > bin->size) {
 	    	eprintf ("corrupted dyldcache");
 		free (ret);
 		return NULL;
 	}
+
 	if (bin->hdr.startaddr > bin->size || bin->hdr.baseaddroff > bin->size) {
 		eprintf ("corrupted dyldcache");
 		free (ret);
 		return NULL;
 	}
-	image_infos = (struct dyld_cache_image_info*) (bin->b->buf + bin->hdr.startaddr);
-	dyld_vmbase = *(ut64 *)(bin->b->buf + bin->hdr.baseaddroff);
+	int sz = bin->nlibs * sizeof (struct dyld_cache_image_info);
+	image_infos = malloc (sz); //(struct dyld_cache_image_info*) (bin->b->buf + bin->hdr.startaddr);
+	if (!image_infos) {
+		free (ret);
+		return NULL;
+	}
+	r_buf_read_at (bin->b, bin->hdr.startaddr, (ut8*)image_infos, sz);
+	dyld_vmbase = r_buf_read64le (bin->b, bin->hdr.baseaddroff);
 	liboff = image_infos[idx].address - dyld_vmbase;
 	if (liboff > bin->size) {
 		eprintf ("Corrupted file\n");
 		free (ret);
 		return NULL;
 	}
 	ret->offset = liboff;
-	if (image_infos[idx].pathFileOffset > bin->size) {
-	    eprintf ("corrupted file\n");
+	int pfo = image_infos[idx].pathFileOffset;
+	if (pfo < 0 || pfo > bin->size) {
+		eprintf ("corrupted file: pathFileOffset > bin->size (%d)\n", pfo);
 		free (ret);
 		return NULL;
 	}
-	libname = (char *)(bin->b->buf + image_infos[idx].pathFileOffset);
+	libname = r_buf_read_string (bin->b, pfo, 64);
 	/* Locate lib hdr in cache */
 	data = bin->b->buf + liboff;
 	mh = (struct mach_header *)data;
@@ -224,16 +248,15 @@ struct r_bin_dyldcache_obj_t* r_bin_dyldcache_new(const char* file) {
 }
 
 struct r_bin_dyldcache_obj_t* r_bin_dyldcache_from_bytes_new(const ut8* buf, ut64 size) {
-	struct r_bin_dyldcache_obj_t *bin;
-	if (!(bin = malloc (sizeof (struct r_bin_dyldcache_obj_t)))) {
+	struct r_bin_dyldcache_obj_t *bin = R_NEW0 (struct r_bin_dyldcache_obj_t);
+	if (!bin) {
 		return NULL;
 	}
-	memset (bin, 0, sizeof (struct r_bin_dyldcache_obj_t));
 	if (!buf) {
 		return r_bin_dyldcache_free (bin);
 	}
-	bin->b = r_buf_new();
-	if (!r_buf_set_bytes (bin->b, buf, size)) {
+	bin->b = r_buf_new ();
+	if (!bin->b || !r_buf_set_bytes (bin->b, buf, size)) {
 		return r_bin_dyldcache_free (bin);
 	}
 	if (!r_bin_dyldcache_init (bin)) {
path: libr/bin/format/mach0/dyldcache.c