Commit message: fix #2963 - oob write (1 byte) in pyc/marshal.c
CVE: CVE-2022-36040
CWE: CWE-787
Vulnerability description: 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 PYC(python) files. A user opening a malicious PYC file could be affected by this vulnerability, allowing an attacker to execute code on the user's machine. Commit number 68948017423a12786704e54227b8b2f918c2fd27 contains a patch.

Project: rizin
File: marshal.c

static pyc_object *get_array_object_generic(RzBinPycObj *pyc, RzBuffer *buffer, ut32 size) {
	pyc_object *tmp = NULL;
	pyc_object *ret = NULL;
	ut32 i = 0;

	ret = RZ_NEW0(pyc_object);
	if (!ret) {
		return NULL;
	}
	ret->data = rz_list_newf((RzListFree)free_object);
	if (!ret->data) {
		free(ret);
		return NULL;
	}
	for (i = 0; i < size; i++) {
		tmp = get_object(pyc, buffer);
		if (!tmp) {
			rz_list_free(ret->data);
			RZ_FREE(ret);
			return NULL;
		}
		if (!rz_list_append(ret->data, tmp)) {
			free_object(tmp);
			rz_list_free(ret->data);
			free(ret);
			return NULL;
		}
	}
	return ret;
}

Project: rizin
File: marshal.c

static pyc_object *get_tuple_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	pyc_object *ret = NULL;
	bool error = false;
	ut32 n = 0;

	n = get_ut32(buffer, &error);
	if (n > ST32_MAX) {
		RZ_LOG_ERROR("bad marshal data (tuple size out of range)\n");
		return NULL;
	}
	if (error) {
		return NULL;
	}
	ret = get_array_object_generic(pyc, buffer, n);
	if (ret) {
		ret->type = TYPE_TUPLE;
		return ret;
	}
	return NULL;
}

Project: rizin
File: marshal.c

static pyc_object *get_code_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	bool error = false;

	pyc_object *ret = RZ_NEW0(pyc_object);
	pyc_code_object *cobj = RZ_NEW0(pyc_code_object);
	if (!ret || !cobj) {
		free(ret);
		free(cobj);
		return NULL;
	}

	// ret->type = TYPE_CODE_v1;
	//  support start from v1.0
	ret->data = cobj;

	bool v10_to_12 = magic_int_within(pyc->magic_int, 39170, 16679, &error); // 1.0.1 - 1.2
	bool v13_to_22 = magic_int_within(pyc->magic_int, 11913, 60718, &error); // 1.3b1 - 2.2a1
	bool v11_to_14 = magic_int_within(pyc->magic_int, 39170, 20117, &error); // 1.0.1 - 1.4
	bool v15_to_22 = magic_int_within(pyc->magic_int, 20121, 60718, &error); // 1.5a1 - 2.2a1
	bool v13_to_20 = magic_int_within(pyc->magic_int, 11913, 50824, &error); // 1.3b1 - 2.0b1
	// bool v21_to_27 = (!v13_to_20) && magic_int_within (magic_int, 60124, 62212, &error);
	bool has_posonlyargcount = magic_int_within(pyc->magic_int, 3410, 3424, &error); // v3.8.0a4 - latest
	if (error) {
		free(ret);
		free(cobj);
		return NULL;
	}

	if (v13_to_22) {
		cobj->argcount = get_ut16(buffer, &error);
	} else if (v10_to_12) {
		cobj->argcount = 0;
	} else {
		cobj->argcount = get_ut32(buffer, &error);
	}

	if (has_posonlyargcount) {
		cobj->posonlyargcount = get_ut32(buffer, &error); // Included in argcount
	} else {
		cobj->posonlyargcount = 0; // None
	}

	if (((3020 < (pyc->magic_int & 0xffff)) && ((pyc->magic_int & 0xffff) < 20121)) && (!v11_to_14)) {
		cobj->kwonlyargcount = get_ut32(buffer, &error); // Not included in argcount
	} else {
		cobj->kwonlyargcount = 0;
	}

	if (v13_to_22) {
		cobj->nlocals = get_ut16(buffer, &error);
	} else if (v10_to_12) {
		cobj->nlocals = 0;
	} else {
		cobj->nlocals = get_ut32(buffer, &error);
	}

	if (v15_to_22) {
		cobj->stacksize = get_ut16(buffer, &error);
	} else if (v11_to_14 || v10_to_12) {
		cobj->stacksize = 0;
	} else {
		cobj->stacksize = get_ut32(buffer, &error);
	}

	if (v13_to_22) {
		cobj->flags = get_ut16(buffer, &error);
	} else if (v10_to_12) {
		cobj->flags = 0;
	} else {
		cobj->flags = get_ut32(buffer, &error);
	}

	// to help disassemble the code
	cobj->start_offset = rz_buf_tell(buffer) + 5; // 1 from get_object() and 4 from get_string_object()
	if (!pyc->refs) {
		return ret; // return for entried part to get the root object of this file
	}
	cobj->code = get_object(pyc, buffer);
	cobj->end_offset = rz_buf_tell(buffer);

	cobj->consts = get_object(pyc, buffer);
	cobj->names = get_object(pyc, buffer);

	if (v10_to_12) {
		cobj->varnames = NULL;
	} else {
		cobj->varnames = get_object(pyc, buffer);
	}

	if (!(v10_to_12 || v13_to_20)) {
		cobj->freevars = get_object(pyc, buffer);
		cobj->cellvars = get_object(pyc, buffer);
	} else {
		cobj->freevars = NULL;
		cobj->cellvars = NULL;
	}

	cobj->filename = get_object(pyc, buffer);
	cobj->name = get_object(pyc, buffer);

	if (v15_to_22) {
		cobj->firstlineno = get_ut16(buffer, &error);
	} else if (v11_to_14) {
		cobj->firstlineno = 0;
	} else {
		cobj->firstlineno = get_ut32(buffer, &error);
	}

	if (v11_to_14) {
		cobj->lnotab = NULL;
	} else {
		cobj->lnotab = get_object(pyc, buffer);
	}

	if (error) {
		free_object(cobj->code);
		free_object(cobj->consts);
		free_object(cobj->names);
		free_object(cobj->varnames);
		free_object(cobj->freevars);
		free_object(cobj->cellvars);
		free_object(cobj->filename);
		free_object(cobj->name);
		free_object(cobj->lnotab);
		free(cobj);
		RZ_FREE(ret);
		return NULL;
	}
	return ret;
}

Project: rizin
File: marshal.c

static pyc_object *get_ref_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	bool error = false;
	pyc_object *ret;
	pyc_object *obj;
	ut32 index;

	index = get_ut32(buffer, &error);
	if (error) {
		return NULL;
	}
	if (index >= rz_list_length(pyc->refs)) {
		return NULL;
	}
	obj = rz_list_get_n(pyc->refs, index);
	if (!obj) {
		return NULL;
	}
	ret = copy_object(obj);
	return ret;
}

Project: rizin
File: marshal.c

static pyc_object *get_small_tuple_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	pyc_object *ret = NULL;
	bool error = false;
	ut8 n = 0;

	n = get_ut8(buffer, &error);
	if (error) {
		return NULL;
	}
	ret = get_array_object_generic(pyc, buffer, n);
	if (ret) {
		ret->type = TYPE_SMALL_TUPLE;
		return ret;
	}
	return NULL;
}

Project: rizin
File: marshal.c

bool get_sections_symbols_from_code_objects(RzBinPycObj *pyc, RzBuffer *buffer, RzList *sections, RzList *symbols, RzList *cobjs, ut32 magic) {
	bool ret;
	pyc->magic_int = magic;
	pyc->refs = rz_list_newf((RzListFree)free_object);
	if (!pyc->refs) {
		return false;
	}
	ret = extract_sections_symbols(pyc, get_object(pyc, buffer), sections, symbols, cobjs, NULL);
	rz_list_free(pyc->refs);
	return ret;
}

Project: rizin
File: marshal.c

static pyc_object *get_dict_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	pyc_object *ret = NULL,
		   *key = NULL,
		   *val = NULL;

	ret = RZ_NEW0(pyc_object);
	if (!ret) {
		return NULL;
	}
	ret->data = rz_list_newf((RzListFree)free_object);
	if (!ret->data) {
		RZ_FREE(ret);
		return NULL;
	}
	for (;;) {
		key = get_object(pyc, buffer);
		if (!key) {
			break;
		}
		if (!rz_list_append(ret->data, key)) {
			rz_list_free(ret->data);
			RZ_FREE(ret);
			free_object(key);
			return NULL;
		}
		val = get_object(pyc, buffer);
		if (!val) {
			break;
		}
		if (!rz_list_append(ret->data, val)) {
			rz_list_free(ret->data);
			RZ_FREE(ret);
			free_object(val);
			return NULL;
		}
	}
	ret->type = TYPE_DICT;
	return ret;
}

Project: rizin
File: marshal.c

static bool extract_sections_symbols(RzBinPycObj *pyc, pyc_object *obj, RzList *sections, RzList *symbols, RzList *cobjs, char *prefix) {
	pyc_code_object *cobj = NULL;
	RzBinSection *section = NULL;
	RzBinSymbol *symbol = NULL;
	RzListIter *i = NULL;

	// each code object is a section
	if_true_return(!obj || (obj->type != TYPE_CODE_v1 && obj->type != TYPE_CODE_v0), false);

	cobj = obj->data;

	if_true_return(!cobj || !cobj->name, false);
	if_true_return(cobj->name->type != TYPE_ASCII && cobj->name->type != TYPE_STRING && cobj->name->type != TYPE_INTERNED, false);
	if_true_return(!cobj->name->data, false);
	if_true_return(!cobj->consts, false);

	// add the cobj to objs list
	if (!rz_list_append(cobjs, cobj)) {
		goto fail;
	}
	section = RZ_NEW0(RzBinSection);
	symbol = RZ_NEW0(RzBinSymbol);
	prefix = rz_str_newf("%s%s%s", prefix ? prefix : "",
		prefix ? "." : "", (const char *)cobj->name->data);
	if (!prefix || !section || !symbol) {
		goto fail;
	}
	section->name = strdup(prefix);
	if (!section->name) {
		goto fail;
	}
	section->paddr = cobj->start_offset;
	section->vaddr = cobj->start_offset;
	section->size = cobj->end_offset - cobj->start_offset;
	section->vsize = cobj->end_offset - cobj->start_offset;
	if (!rz_list_append(sections, section)) {
		goto fail;
	}
	section = NULL;
	// start building symbol
	symbol->name = strdup(prefix);
	// symbol->bind;
	symbol->type = RZ_BIN_TYPE_FUNC_STR;
	symbol->size = cobj->end_offset - cobj->start_offset;
	symbol->vaddr = cobj->start_offset;
	symbol->paddr = cobj->start_offset;
	symbol->ordinal = pyc->symbols_ordinal++;
	if (cobj->consts->type != TYPE_TUPLE && cobj->consts->type != TYPE_SMALL_TUPLE) {
		goto fail;
	}
	if (!rz_list_append(symbols, symbol)) {
		goto fail;
	}
	rz_list_foreach (((RzList *)(cobj->consts->data)), i, obj)
		extract_sections_symbols(pyc, obj, sections, symbols, cobjs, prefix);
	free(prefix);
	return true;
fail:

	free(section);
	free(prefix);
	free(symbol);
	return false;
}

Project: rizin
File: marshal.c

static pyc_object *get_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	bool error = false;
	pyc_object *ret = NULL;
	ut8 code = get_ut8(buffer, &error);
	ut8 flag = code & FLAG_REF;
	RzListIter *ref_idx = NULL;
	ut8 type = code & ~FLAG_REF;

	if (error) {
		return NULL;
	}

	if (flag) {
		ret = get_none_object();
		if (!ret) {
			return NULL;
		}
		ref_idx = rz_list_append(pyc->refs, ret);
		if (!ref_idx) {
			free_object(ret);
			return NULL;
		}
	}

	switch (type) {
	case TYPE_NULL:
		return NULL;
	case TYPE_TRUE:
		return get_true_object();
	case TYPE_FALSE:
		return get_false_object();
	case TYPE_NONE:
		return get_none_object();
	case TYPE_REF:
		return get_ref_object(pyc, buffer);
	case TYPE_SMALL_TUPLE:
		ret = get_small_tuple_object(pyc, buffer);
		break;
	case TYPE_TUPLE:
		ret = get_tuple_object(pyc, buffer);
		break;
	case TYPE_STRING:
		ret = get_string_object(buffer);
		break;
	case TYPE_CODE_v0:
		ret = get_code_object(pyc, buffer);
		if (ret) {
			ret->type = TYPE_CODE_v0;
		}
		break;
	case TYPE_CODE_v1:
		ret = get_code_object(pyc, buffer);
		if (ret) {
			ret->type = TYPE_CODE_v1;
		}
		break;
	case TYPE_INT:
		ret = get_int_object(buffer);
		break;
	case TYPE_ASCII_INTERNED:
		ret = get_ascii_interned_object(buffer);
		break;
	case TYPE_SHORT_ASCII:
		ret = get_short_ascii_object(buffer);
		break;
	case TYPE_ASCII:
		ret = get_ascii_object(buffer);
		break;
	case TYPE_SHORT_ASCII_INTERNED:
		ret = get_short_ascii_interned_object(buffer);
		break;
	case TYPE_INT64:
		ret = get_int64_object(buffer);
		break;
	case TYPE_INTERNED:
		ret = get_interned_object(pyc, buffer);
		break;
	case TYPE_STRINGREF:
		ret = get_stringref_object(pyc, buffer);
		break;
	case TYPE_FLOAT:
		ret = get_float_object(buffer);
		break;
	case TYPE_BINARY_FLOAT:
		ret = get_binary_float_object(buffer);
		break;
	case TYPE_COMPLEX:
		ret = get_complex_object(pyc, buffer); // behaviour depends on Python version
		break;
	case TYPE_BINARY_COMPLEX:
		ret = get_binary_complex_object(buffer);
		break;
	case TYPE_LIST:
		ret = get_list_object(pyc, buffer);
		break;
	case TYPE_LONG:
		ret = get_long_object(buffer);
		break;
	case TYPE_UNICODE:
		ret = get_unicode_object(buffer);
		break;
	case TYPE_DICT:
		ret = get_dict_object(pyc, buffer);
		break;
	case TYPE_FROZENSET:
	case TYPE_SET:
		ret = get_set_object(pyc, buffer);
		break;
	case TYPE_STOPITER:
		ret = RZ_NEW0(pyc_object);
		break;
	case TYPE_ELLIPSIS:
		ret = RZ_NEW0(pyc_object);
		break;
	case TYPE_UNKNOWN:
		RZ_LOG_ERROR("Get not implemented for type 0x%x\n", type);
		return NULL;
	default:
		RZ_LOG_ERROR("Undefined type in get_object (0x%x)\n", type);
		return NULL;
	}

	if (flag && ref_idx) {
		free_object(ref_idx->data);
		ref_idx->data = copy_object(ret);
	}
	return ret;
}

Project: rizin
File: marshal.c

static pyc_object *get_set_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	pyc_object *ret = NULL;
	bool error = false;
	ut32 n = 0;

	n = get_ut32(buffer, &error);
	if (n > ST32_MAX) {
		RZ_LOG_ERROR("bad marshal data (set size out of range)\n");
		return NULL;
	}
	if (error) {
		return NULL;
	}
	ret = get_array_object_generic(pyc, buffer, n);
	if (!ret) {
		return NULL;
	}
	ret->type = TYPE_SET;
	return ret;
}

Project: rizin
File: marshal.c

static pyc_object *get_complex_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	pyc_object *ret = NULL;
	bool error = false;
	ut32 n1 = 0;
	ut32 n2 = 0;

	ret = RZ_NEW0(pyc_object);
	if (!ret) {
		return NULL;
	}

	if ((pyc->magic_int & 0xffff) <= 62061) {
		n1 = get_ut8(buffer, &error);
	} else {
		n1 = get_st32(buffer, &error);
	}
	if (error || UT32_ADD_OVFCHK(n1, 1)) {
		free(ret);
		return NULL;
	}
	ut8 *s1 = malloc(n1 + 1);
	if (!s1) {
		return NULL;
	}
	/* object contain string representation of the number */
	if (rz_buf_read(buffer, s1, n1) != n1) {
		RZ_FREE(s1);
		RZ_FREE(ret);
		return NULL;
	}
	s1[n1] = '\0';

	if ((pyc->magic_int & 0xffff) <= 62061) {
		n2 = get_ut8(buffer, &error);
	} else {
		n2 = get_st32(buffer, &error);
	}
	if (error || UT32_ADD_OVFCHK(n2, 1)) {
		return NULL;
	}
	ut8 *s2 = malloc(n2 + 1);
	if (!s2) {
		return NULL;
	}
	/* object contain string representation of the number */
	if (rz_buf_read(buffer, s2, n2) != n2) {
		RZ_FREE(s1);
		RZ_FREE(s2);
		RZ_FREE(ret);
		return NULL;
	}
	s2[n2] = '\0';

	ret->type = TYPE_COMPLEX;
	ret->data = rz_str_newf("%s+%sj", s1, s2);
	RZ_FREE(s1);
	RZ_FREE(s2);
	if (!ret->data) {
		RZ_FREE(ret);
		return NULL;
	}
	return ret;
}

Project: rizin
File: marshal.c

static pyc_object *get_interned_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	pyc_object *ret = NULL;
	bool error = false;
	ut32 n = 0;

	n = get_ut32(buffer, &error);
	if (n > ST32_MAX) {
		RZ_LOG_ERROR("bad marshal data (string size out of range)");
		return NULL;
	}
	if (error) {
		return NULL;
	}
	ret = RZ_NEW0(pyc_object);
	if (!ret) {
		return NULL;
	}
	ret->type = TYPE_INTERNED;
	ret->data = get_bytes(buffer, n);
	/* add data pointer to interned table */
	rz_list_append(pyc->interned_table, ret->data);
	if (!ret->data) {
		RZ_FREE(ret);
	}
	return ret;
}

Project: rizin
File: marshal.c

static pyc_object *get_list_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	pyc_object *ret = NULL;
	bool error = false;
	ut32 n = 0;

	n = get_ut32(buffer, &error);
	if (n > ST32_MAX) {
		RZ_LOG_ERROR("bad marshal data (list size out of range)\n");
		return NULL;
	}
	if (error) {
		return NULL;
	}
	ret = get_array_object_generic(pyc, buffer, n);
	if (ret) {
		ret->type = TYPE_LIST;
		return ret;
	}
	return NULL;
}

Project: rizin
File: marshal.c

ut64 get_code_object_addr(RzBinPycObj *pyc, RzBuffer *buffer, ut32 magic) {
	pyc->magic_int = magic;
	pyc_object *co = get_code_object(pyc, buffer);
	ut64 result = 0;
	if (!co) {
		return 0;
	}

	pyc_code_object *cobj = co->data;
	result = cobj->start_offset;
	free_object(co);

	return result;
}

Project: rizin
File: marshal.c

static pyc_object *get_stringref_object(RzBinPycObj *pyc, RzBuffer *buffer) {
	pyc_object *ret = NULL;
	bool error = false;
	ut32 n = 0;

	n = get_st32(buffer, &error);
	if (n >= rz_list_length(pyc->interned_table)) {
		RZ_LOG_ERROR("bad marshal data (string ref out of range)");
		return NULL;
	}
	if (error) {
		return NULL;
	}
	ret = RZ_NEW0(pyc_object);
	if (!ret) {
		return NULL;
	}
	ret->type = TYPE_STRINGREF;
	ret->data = rz_list_get_n(pyc->interned_table, n);
	if (!ret->data) {
		RZ_FREE(ret);
	}
	return ret;
}