Commit message: Fix #10296 - Heap out of bounds read in java_switch_op()
CVE: CVE-2018-12321
CWE: CWE-125
Vulnerability description: There is a heap out of bounds read in radare2 2.6.0 in java_switch_op() in libr/anal/p/anal_java.c via a crafted Java binary file.

Project: radare2

static int analyze_from_code_attr (RAnal *anal, RAnalFunction *fcn, RBinJavaField *method, ut64 loadaddr) {
	RBinJavaAttrInfo* code_attr = method ? r_bin_java_get_method_code_attribute(method) : NULL;
	ut8 * code_buf = NULL;
	int result = false;
	ut64 code_length = 0;
	ut64 code_addr = -1;

	if (!code_attr) {
		fcn->name = strdup ("sym.UNKNOWN");
		fcn->dsc = strdup ("unknown");
		r_anal_fcn_set_size (NULL, fcn, code_length);
		fcn->type = R_ANAL_FCN_TYPE_FCN;
		fcn->addr = 0;
		return R_ANAL_RET_ERROR;
	}

	code_length = code_attr->info.code_attr.code_length;
	code_addr = code_attr->info.code_attr.code_offset;
	code_buf = calloc (1, code_length);

	anal->iob.read_at (anal->iob.io, code_addr + loadaddr, code_buf, code_length);
	result = analyze_from_code_buffer (anal, fcn, code_addr + loadaddr, code_buf, code_length);
	free (code_buf);

	char *name = strdup (method->name);
	if (name) {
		r_name_filter (name, 80);
		free (fcn->name);
		if (method->class_name) {
			char *cname = strdup (method->class_name);
			r_name_filter (cname, 50);
			fcn->name = r_str_newf ("sym.%s.%s", cname, name);
			free (cname);
		} else {
			fcn->name = r_str_newf ("sym.%s", name);
		}
		free (name);
	}

	free (fcn->dsc);
	fcn->dsc = strdup (method->descriptor);
	IFDBG eprintf ("Completed analysing code from attr, name: %s, desc: %s", fcn->name, fcn->dsc);

	return result;
}

Project: radare2

static int analyze_from_code_buffer(RAnal *anal, RAnalFunction *fcn, ut64 addr, const ut8 *code_buf, ut64 code_length) {
	char gen_name[1025];
	RListIter *bb_iter;
	RAnalBlock *bb;
	ut64 actual_size = 0;
	RAnalState *state = NULL;
	int result = R_ANAL_RET_ERROR;
	RAnalJavaLinearSweep *nodes;

	free (fcn->name);
	free (fcn->dsc);
	snprintf (gen_name, 1024, "sym.%08"PFMT64x"", addr);

	fcn->name = strdup (gen_name);
	fcn->dsc = strdup ("unknown");
	r_anal_fcn_set_size (NULL, fcn, code_length);
	fcn->type = R_ANAL_FCN_TYPE_FCN;
	fcn->addr = addr;
	state = r_anal_state_new (addr, (ut8*) code_buf, code_length);
	nodes = R_NEW0 (RAnalJavaLinearSweep);
	nodes->cfg_node_addrs = r_list_new ();
	nodes->cfg_node_addrs->free = free;
	state->user_state = nodes;
	result = analyze_method (anal, fcn, state);
	r_list_foreach (fcn->bbs, bb_iter, bb) {
		actual_size += bb->size;
	}
	r_anal_fcn_set_size (NULL, fcn, state->bytes_consumed);
	result = state->anal_ret_val;
	r_list_free (nodes->cfg_node_addrs);
	free (nodes);
	if (r_anal_fcn_size (fcn) != code_length) {
		return R_ANAL_RET_ERROR;
#if 0
		eprintf ("WARNING Analysis of %s Incorrect: Code Length: 0x%"PFMT64x", Function size reported 0x%x\n", fcn->name, code_length, r_anal_fcn_size(fcn));
		eprintf ("Deadcode detected, setting code length to: 0x%"PFMT64x"\n", code_length);
		r_anal_fcn_set_size (fcn, code_length);
#endif
	}
	return result;
}

Project: radare2

static int analyze_method(RAnal *anal, RAnalFunction *fcn, RAnalState *state) {
	r_list_free (fcn->bbs);
	fcn->bbs = r_anal_bb_list_new ();
	java_new_method (fcn->addr);
	state->current_fcn = fcn;
	r_anal_ex_perform_analysis (anal, state, fcn->addr);
	return state->anal_ret_val;
}

Project: radare2

static int check_addr_in_code (RBinJavaField *method, ut64 addr) {
	return !check_addr_less_start (method, addr) && \
		check_addr_less_end ( method, addr);
}

Project: radare2

static int check_addr_less_end (RBinJavaField *method, ut64 addr) {
	ut64 end = r_bin_java_get_method_code_size (method);
	return (addr < end);
}

Project: radare2

static RBinJavaObj * get_java_bin_obj(RAnal *anal) {
	RBin *b = anal->binb.bin;
	RBinPlugin *plugin = b->cur && b->cur->o ? b->cur->o->plugin : NULL;
	ut8 is_java = (plugin && strcmp (plugin->name, "java") == 0) ? 1 : 0;
	return is_java ? b->cur->o->bin_obj : NULL;
}

Project: radare2

static int handle_bb_cf_linear_sweep (RAnal *anal, RAnalState *state) {
	ut64 * paddr64;
	RAnalBlock *bb = state->current_bb;
	RAnalJavaLinearSweep *nodes = state->user_state;

	if (!nodes || !nodes->cfg_node_addrs) {
		state->done = 1;
		return R_ANAL_RET_ERROR;
	}

	ut64 addr = 0;
	int result = 0;
	if (!bb) {
		eprintf ("Error: unable to handle basic block @ 0x%08"PFMT64x"\n", addr);
		return R_ANAL_RET_ERROR;
	} else if (state->max_depth <= state->current_depth) {
		return R_ANAL_RET_ERROR;
	}

	state->current_depth++;
	addr = bb->addr;
	IFDBG eprintf ("Handling a control flow change @ 0x%04"PFMT64x".\n", addr);
	ut32 control_type = r_anal_ex_map_anal_ex_to_anal_op_type (bb->type2);

	switch (control_type) {
		case R_ANAL_OP_TYPE_CALL:
			IFDBG eprintf (" - Handling a call @ 0x%04"PFMT64x"\n", addr);
			r_anal_xrefs_set (anal, bb->addr, bb->jump, R_ANAL_REF_TYPE_CALL);
			result = R_ANAL_RET_ERROR;
			break;
		case R_ANAL_OP_TYPE_JMP:
			paddr64 = malloc (sizeof(ut64));
			*paddr64 = bb->jump;
			IFDBG eprintf (" - Handling a jmp @ 0x%04"PFMT64x", adding for future visit\n", addr);
			r_list_append (nodes->cfg_node_addrs, paddr64);
			result = R_ANAL_RET_END;
			break;
		case R_ANAL_OP_TYPE_CJMP:
			paddr64 = malloc (sizeof(ut64));
			*paddr64 = bb->jump;
			IFDBG eprintf (" - Handling a bb->jump @ 0x%04"PFMT64x", adding 0x%04"PFMT64x" for future visit\n", addr, *paddr64);
			r_list_append (nodes->cfg_node_addrs, paddr64);
			paddr64 = malloc (sizeof(ut64));
			if (paddr64) {
				*paddr64 = bb->fail;
				IFDBG eprintf (" - Handling a bb->fail @ 0x%04"PFMT64x", adding 0x%04"PFMT64x" for future visit\n", addr, *paddr64);
				r_list_append (nodes->cfg_node_addrs, paddr64);
			}
			result = R_ANAL_RET_END;
			break;
		case R_ANAL_OP_TYPE_SWITCH:
			if (bb->switch_op) {
				RAnalCaseOp *caseop;
				RListIter *iter;
				IFDBG eprintf (" - Handling a switch_op @ 0x%04"PFMT64x":\n", addr);
				r_list_foreach (bb->switch_op->cases, iter, caseop) {
					ut64 * paddr64;
					if (caseop) {
						paddr64 = malloc (sizeof(ut64));
						*paddr64 = caseop->jump;
						IFDBG eprintf ("Adding 0x%04"PFMT64x" for future visit\n", *paddr64);
						r_list_append (nodes->cfg_node_addrs, paddr64);
					}
				}
			}
			result = R_ANAL_RET_END;
			break;
		case R_ANAL_OP_TYPE_TRAP:
		case R_ANAL_OP_TYPE_UJMP:
		case R_ANAL_OP_TYPE_RJMP:
		case R_ANAL_OP_TYPE_IJMP:
		case R_ANAL_OP_TYPE_IRJMP:
		case R_ANAL_OP_TYPE_RET:
			IFDBG eprintf (" - Handling an ret @ 0x%04"PFMT64x".\n", addr);
			state->done = 1;
			result = R_ANAL_RET_END;
			break;
		default: break;
	}

	state->current_depth--;
	return result;
}

Project: radare2

static int handle_bb_cf_recursive_descent (RAnal *anal, RAnalState *state) {

	RAnalBlock *bb = state->current_bb;

	ut64 addr = 0;
	int result = 0;
	if (!bb) {
		eprintf ("Error: unable to handle basic block @ 0x%08"PFMT64x"\n", addr);
		return R_ANAL_RET_ERROR;
	} else if (state->max_depth <= state->current_depth) {
		return R_ANAL_RET_ERROR;
	}

	state->current_depth++;
	addr = bb->addr;
	IFDBG eprintf ("Handling a control flow change @ 0x%04"PFMT64x".\n", addr);
	ut64 control_type = r_anal_ex_map_anal_ex_to_anal_op_type (bb->type2);

	switch (control_type) {
		case R_ANAL_OP_TYPE_CALL:
			IFDBG eprintf (" - Handling a call @ 0x%04"PFMT64x".\n", addr);
			r_anal_xrefs_set (anal, bb->addr, bb->jump, R_ANAL_REF_TYPE_CALL);
			result = R_ANAL_RET_ERROR;
			break;
		case R_ANAL_OP_TYPE_JMP:
			{
				RList * jmp_list;
				IFDBG eprintf (" - Handling a jmp @ 0x%04"PFMT64x" to 0x%04"PFMT64x".\n", addr, bb->jump);

				if (!r_anal_state_search_bb (state, bb->jump)) {
					jmp_list = r_anal_ex_perform_analysis ( anal, state, bb->jump );
					if (jmp_list)
						bb->jumpbb = (RAnalBlock *) r_list_get_n (jmp_list, 0);
					if (bb->jumpbb)
						bb->jump = bb->jumpbb->addr;
				} else {
					bb->jumpbb = r_anal_state_search_bb (state, bb->jump);
					if (bb->jumpbb)
						bb->jump = bb->jumpbb->addr;
				}

				if (state->done == 1) {
					IFDBG eprintf (" Looks like this jmp (bb @ 0x%04"PFMT64x") found a return.\n", addr);
				}
				result = R_ANAL_RET_END;
			}
			break;
		case R_ANAL_OP_TYPE_CJMP:
			{
				RList *jmp_list;
				ut8 encountered_stop = 0;
				IFDBG eprintf (" - Handling a cjmp @ 0x%04"PFMT64x" jmp to 0x%04"PFMT64x" and fail to 0x%04"PFMT64x".\n", addr, bb->jump, bb->fail);
				IFDBG eprintf (" - Handling jmp to 0x%04"PFMT64x".\n", bb->jump);
				if (!r_anal_state_search_bb (state, bb->jump)) {
					jmp_list = r_anal_ex_perform_analysis ( anal, state, bb->jump );
					if (jmp_list)
						bb->jumpbb = (RAnalBlock *) r_list_get_n (jmp_list, 0);
					if (bb->jumpbb) {
						bb->jump = bb->jumpbb->addr;
					}
				} else {
					bb->jumpbb = r_anal_state_search_bb (state, bb->jump);
					bb->jump = bb->jumpbb->addr;
				}

				if (state->done == 1) {
					IFDBG eprintf (" Looks like this jmp (bb @ 0x%04"PFMT64x") found a return.\n", addr);
					state->done = 0;
					encountered_stop = 1;
				}

				if (!r_anal_state_search_bb (state, bb->fail)) {
					jmp_list = r_anal_ex_perform_analysis ( anal, state, bb->fail );
					if (jmp_list)
						bb->failbb = (RAnalBlock *) r_list_get_n (jmp_list, 0);
					if (bb->failbb) {
						bb->fail = bb->failbb->addr;
					}
				} else {
					bb->failbb = r_anal_state_search_bb (state, bb->fail);
					if (bb->failbb) {
						bb->fail = bb->failbb->addr;
					}
				}

				IFDBG eprintf (" - Handling an cjmp @ 0x%04"PFMT64x" jmp to 0x%04"PFMT64x" and fail to 0x%04"PFMT64x".\n", addr, bb->jump, bb->fail);
				IFDBG eprintf (" - Handling fail to 0x%04"PFMT64x".\n", bb->fail);
				if (state->done == 1) {
					IFDBG eprintf (" Looks like this fail (bb @ 0x%04"PFMT64x") found a return.\n", addr);
				}

				result = R_ANAL_RET_END;
				if (encountered_stop) state->done = 1;
			}
			break;

		case R_ANAL_OP_TYPE_SWITCH:
		{
			IFDBG eprintf (" - Handling an switch @ 0x%04"PFMT64x".\n", addr);
			if (bb->switch_op) {
				RAnalCaseOp *caseop;
				RListIter *iter;
				RList *jmp_list = NULL;
				ut8 encountered_stop = 0;
				r_list_foreach (bb->switch_op->cases, iter, caseop) {
					if (caseop) {
						if (r_anal_state_addr_is_valid (state, caseop->jump) ) {
							jmp_list = r_anal_ex_perform_analysis ( anal, state, caseop->jump );
							if (jmp_list)
								caseop->jumpbb = (RAnalBlock *) r_list_get_n (jmp_list, 0);
							if (state->done == 1) {
								IFDBG eprintf (" Looks like this jmp (bb @ 0x%04"PFMT64x") found a return.\n", addr);
								state->done = 0;
								encountered_stop = 1;
							}
						}
					}
				}
				r_list_free (jmp_list);
				if (encountered_stop) state->done = 1;
			}

			result = R_ANAL_RET_END;
		}
			break;
		case R_ANAL_OP_TYPE_TRAP:
		case R_ANAL_OP_TYPE_UJMP:
		case R_ANAL_OP_TYPE_IJMP:
		case R_ANAL_OP_TYPE_RJMP:
		case R_ANAL_OP_TYPE_IRJMP:
		case R_ANAL_OP_TYPE_RET:
		case R_ANAL_OP_TYPE_ILL:
			IFDBG eprintf (" - Handling an ret @ 0x%04"PFMT64x".\n", addr);
			state->done = 1;
			result = R_ANAL_RET_END;
			break;
		default: break;
	}

	state->current_depth--;
	return result;
}

Project: radare2

static int java_analyze_fns( RAnal *anal, ut64 start, ut64 end, int reftype, int depth) {
	RBinJavaObj *bin = NULL;// = get_java_bin_obj (anal);
	RBinJavaField *method = NULL;
	RListIter *methods_iter, *bin_obs_iter;
	RList * bin_objs_list = get_java_bin_obj_list (anal);

	ut8 analyze_all = 0;
	int result = R_ANAL_RET_ERROR;

	if (end == UT64_MAX) {
		analyze_all = 1;
	}
	if (!bin_objs_list || r_list_empty (bin_objs_list)) {
		r_list_free (bin_objs_list);
		return java_analyze_fns_from_buffer (anal, start, end, reftype, depth);
	}
	r_list_foreach (bin_objs_list, bin_obs_iter, bin) {
		java_update_anal_types (anal, bin);
		RList *methods_list = (RList *) r_bin_java_get_methods_list (bin);
		ut64 loadaddr = bin->loadaddr;
		r_list_foreach (methods_list, methods_iter, method) {
			if ((method && analyze_all) ||
			    (check_addr_less_start (method, end) ||
			     check_addr_in_code (method, end))) {
				RAnalFunction *fcn = r_anal_fcn_new ();
				fcn->cc = r_str_const (r_anal_cc_default (anal));
				java_set_function_prototype (anal, fcn, method);
				result = analyze_from_code_attr (anal, fcn, method, loadaddr);
				if (result == R_ANAL_RET_ERROR) {
					return result;
				}
				r_anal_fcn_update_tinyrange_bbs (fcn);
				r_anal_fcn_tree_insert (&anal->fcn_tree, fcn);
				r_list_append (anal->fcns, fcn);
			}
		} // End of methods loop
	}// end of bin_objs list loop
	return result;
}

Project: radare2

static int java_analyze_fns_from_buffer( RAnal *anal, ut64 start, ut64 end, int reftype, int depth) {
	int result = R_ANAL_RET_ERROR;
	ut64 addr = start;
	ut64 offset = 0;
	ut64 buf_len = end - start;
	ut8 analyze_all = 0,
		*buffer = NULL;

	if (end == UT64_MAX) {
		buf_len = anal->iob.desc_size (anal->iob.io->desc);

		if (buf_len == UT64_MAX) buf_len = 1024;

		end = start + buf_len;
	}

	buffer = malloc (buf_len);
	if (!buffer) {
		return R_ANAL_RET_ERROR;
	}

	anal->iob.read_at (anal->iob.io, addr, buffer, buf_len);

	while (offset < buf_len) {
		ut64 length = buf_len - offset;

		RAnalFunction *fcn = r_anal_fcn_new ();
		fcn->cc = r_str_const (r_anal_cc_default (anal));
		result = analyze_from_code_buffer ( anal, fcn, addr, buffer+offset, length );
		if (result == R_ANAL_RET_ERROR) {
			eprintf ("Failed to parse java fn: %s @ 0x%04"PFMT64x"\n", fcn->name, fcn->addr);
			break;
		}
		r_anal_fcn_tree_insert (&anal->fcn_tree, fcn);
		r_list_append (anal->fcns, fcn);
		offset += r_anal_fcn_size (fcn);
		if (!analyze_all) break;
	}
	free (buffer);
	return result;
}

Project: radare2

static int java_cmd_ext(RAnal *anal, const char* input) {
	RBinJavaObj *obj = (RBinJavaObj *) get_java_bin_obj (anal);

	if (!obj) {
		eprintf ("Execute \"af\" to set the current bin, and this will bind the current bin\n");
		return -1;
	}
	switch (*input) {
	case 'c':
		r_java_new_method ();
		break;
	case 'u':
		switch (*(input+1)) {
			case 't': {java_update_anal_types (anal, obj); return true;}
			default: break;
		}
		break;
	case 's':
		switch (*(input+1)) {
			default: break;
		}
		break;

	default: eprintf("Command not supported"); break;
	}
	return 0;
}

Project: radare2

static int java_linear_sweep(RAnal *anal, RAnalState *state, ut64 addr) {
	RAnalBlock *bb = state->current_bb;
	if (state->current_bb_head && state->current_bb->type & R_ANAL_BB_TYPE_TAIL) {
	}

	if (bb->type2 & R_ANAL_EX_CODE_OP)  handle_bb_cf_linear_sweep (anal, state);
	return 0;
}

Project: radare2

static int java_new_method (ut64 method_start) {
	METHOD_START = method_start;
	r_java_new_method ();
	return 0;
}

Project: radare2

static int java_op(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *data, int len) {
	/* get opcode size */
	ut8 op_byte = data[0];
	int sz = JAVA_OPS[op_byte].size;
	if (!op) {
		return sz;
	}
	memset (op, '\0', sizeof (RAnalOp));
	IFDBG {
	}
	op->addr = addr;
	op->size = sz;
	op->id = data[0];
	op->type2 = JAVA_OPS[op_byte].op_type;
	op->type = r_anal_ex_map_anal_ex_to_anal_op_type (op->type2);
	if (op_byte == 0xaa || op_byte == 0xab) {
		java_switch_op (anal, op, addr, data, len);
	}
	/* TODO: 
	if (IN_SWITCH_OP) {
		NUM_CASES_SEEN++;
		if (NUM_CASES_SEEN == SWITCH_OP_CASES) IN_SWITCH_OP=0;
		op->addr = addr;
		op->size = 4;
		op->type2 = 0;
		op->type = R_ANAL_OP_TYPE_CASE
		op->eob = 0;
		return op->sizes;
	}
	*/

	op->eob = r_anal_ex_is_op_type_eop (op->type2);
	IFDBG {
		const char *ot_str = r_anal_optype_to_string (op->type);
		eprintf ("op_type2: %s @ 0x%04"PFMT64x" 0x%08"PFMT64x" op_type: (0x%02"PFMT64x") %s.\n",
			JAVA_OPS[op_byte].name, addr, (ut64)op->type2, (ut64)op->type,  ot_str);
	}

	if (len < 4) {
		return 0;
	}
	if (op->type == R_ANAL_OP_TYPE_CJMP) {
		op->jump = addr + (short)(USHORT (data, 1));
		op->fail = addr + sz;
		IFDBG eprintf ("%s jmpto 0x%04"PFMT64x"  failto 0x%04"PFMT64x".\n",
			JAVA_OPS[op_byte].name, op->jump, op->fail);
	} else if (op->type  == R_ANAL_OP_TYPE_JMP) {
		op->jump = addr + (short)(USHORT (data, 1));
		IFDBG eprintf ("%s jmpto 0x%04"PFMT64x".\n", JAVA_OPS[op_byte].name, op->jump);
	} else if ( (op->type & R_ANAL_OP_TYPE_CALL) == R_ANAL_OP_TYPE_CALL ) {
		op->jump = (int)(short)(USHORT (data, 1));
		op->fail = addr + sz;
	}

	return op->size;
}

Project: radare2

static int java_recursive_descent(RAnal *anal, RAnalState *state, ut64 addr) {
	RAnalBlock *bb;
	RAnalBlock *current_head;

	if (!anal || !state || !state->current_bb || state->current_bb_head)
		return 0;

	bb = state->current_bb;
	current_head = state->current_bb_head;

	if (current_head && state->current_bb->type & R_ANAL_BB_TYPE_TAIL) {
		r_anal_ex_update_bb_cfg_head_tail (current_head, current_head, state->current_bb);
	}

	if (bb->type2 & R_ANAL_EX_CODE_OP)  handle_bb_cf_recursive_descent (anal, state);
	return 0;
}

Project: radare2

static int java_reset_counter (RAnal *anal, ut64 start_addr ) {
	IFDBG eprintf ("Setting the new METHOD_START to 0x%08"PFMT64x" was 0x%08"PFMT64x"\n", start_addr, METHOD_START);
	METHOD_START = start_addr;
	r_java_new_method ();
	return true;
}

Project: radare2

static int java_revisit_bb_anal_recursive_descent(RAnal *anal, RAnalState *state, ut64 addr) {
	RAnalBlock *current_head = state && state->current_bb_head ? state->current_bb_head : NULL;
	if (current_head && state->current_bb &&
		state->current_bb->type & R_ANAL_BB_TYPE_TAIL) {
		r_anal_ex_update_bb_cfg_head_tail (current_head, current_head, state->current_bb);
		state->done = 1;
	}
	return R_ANAL_RET_END;
}

Project: radare2

static void java_update_anal_types (RAnal *anal, RBinJavaObj *bin_obj) {
	Sdb *D = anal->sdb_types;
	if (D && bin_obj) {
		RListIter *iter;
		char *str;
		RList * the_list = r_bin_java_extract_all_bin_type_values (bin_obj);
		if (the_list) {
			r_list_foreach (the_list, iter, str) {
				IFDBG eprintf ("Adding type: %s to known types.\n", str);
				if (str) sdb_set (D, str, "type", 0);
			}
		}
		r_list_free (the_list);
	}
}