Commit message: Fixed out of bounds access in ASN.1 Octet string

Credit to OSS-Fuzz
CVE: CVE-2019-15946
CWE: CWE-119
Vulnerability description: OpenSC before 0.20.0-rc1 has an out-of-bounds access of an ASN.1 Octet string in asn1_decode_entry in libopensc/asn1.c.

Project: OpenSC
File: asn1.c

static int asn1_encode_se_info(sc_context_t *ctx,
		struct sc_pkcs15_sec_env_info **se, size_t se_num,
		unsigned char **buf, size_t *bufsize, int depth)
{
	unsigned char *ptr = NULL, *out = NULL, *p;
	size_t ptrlen = 0, outlen = 0, idx;
	int ret;

	for (idx=0; idx < se_num; idx++)   {
		struct sc_asn1_entry asn1_se[2];
		struct sc_asn1_entry asn1_se_info[4];

		sc_copy_asn1_entry(c_asn1_se, asn1_se);
		sc_copy_asn1_entry(c_asn1_se_info, asn1_se_info);

		sc_format_asn1_entry(asn1_se_info + 0, &se[idx]->se, NULL, 1);
		if (sc_valid_oid(&se[idx]->owner))
			sc_format_asn1_entry(asn1_se_info + 1, &se[idx]->owner, NULL, 1);
		if (se[idx]->aid.len)
			sc_format_asn1_entry(asn1_se_info + 2, &se[idx]->aid.value, &se[idx]->aid.len, 1);
		sc_format_asn1_entry(asn1_se + 0, asn1_se_info, NULL, 1);

		ret = sc_asn1_encode(ctx, asn1_se, &ptr, &ptrlen);
		if (ret != SC_SUCCESS)
			goto err;

		if (!ptrlen)
			continue;
		p = (unsigned char *) realloc(out, outlen + ptrlen);
		if (!p)   {
			ret = SC_ERROR_OUT_OF_MEMORY;
			goto err;
		}
		out = p;
		memcpy(out + outlen, ptr, ptrlen);
		outlen += ptrlen;
		free(ptr);
		ptr = NULL;
		ptrlen = 0;
	}

	*buf = out;
	*bufsize = outlen;
	ret = SC_SUCCESS;
err:
	if (ret != SC_SUCCESS && out != NULL)
		free(out);
	return ret;
}

Project: OpenSC
File: asn1.c

static int decode_bit_string(const u8 * inbuf, size_t inlen, void *outbuf,
			     size_t outlen, int invert)
{
	const u8 *in = inbuf;
	u8 *out = (u8 *) outbuf;
	int i, count = 0;
	int zero_bits;
	size_t octets_left;

	if (outlen < octets_left)
		return SC_ERROR_BUFFER_TOO_SMALL;
	if (inlen < 1)
		return SC_ERROR_INVALID_ASN1_OBJECT;

	zero_bits = *in & 0x07;
	octets_left = inlen - 1;
	in++;
	memset(outbuf, 0, outlen);

	while (octets_left) {
		/* 1st octet of input:  ABCDEFGH, where A is the MSB */
		/* 1st octet of output: HGFEDCBA, where A is the LSB */
		/* first bit in bit string is the LSB in first resulting octet */
		int bits_to_go;

		*out = 0;
		if (octets_left == 1)
			bits_to_go = 8 - zero_bits;
		else
			bits_to_go = 8;
		if (invert)
			for (i = 0; i < bits_to_go; i++) {
				*out |= ((*in >> (7 - i)) & 1) << i;
			}
		else {
			*out = *in;
		}
		out++;
		in++;
		octets_left--;
		count++;
	}
	return (count * 8) - zero_bits;
}

Project: OpenSC
File: asn1.c

static void print_tags_recursive(const u8 * buf0, const u8 * buf,
				 size_t buflen, size_t depth)
{
	int r;
	size_t i;
	size_t bytesleft = buflen;
	const char *classes[4] = {
		"Universal",
		"Application",
		"Context",
		"Private"
	};
	const u8 *p = buf;

	while (bytesleft >= 2) {
		unsigned int cla = 0, tag = 0, hlen;
		const u8 *tagp = p;
		size_t len;

		r = sc_asn1_read_tag(&tagp, bytesleft, &cla, &tag, &len);
		if (r != SC_SUCCESS || tagp == NULL) {
			printf("Error in decoding.\n");
			return;
		}
		hlen = tagp - p;
		if (cla == 0 && tag == 0) {
			printf("Zero tag, finishing\n");
			break;
		}
		print_indent(depth);
		/* let i be the length of the tag in bytes */
		for (i = 1; i < sizeof tag - 1; i++) {
			if (!(tag >> 8*i))
				break;
		}
		printf("%02X", cla<<(i-1)*8 | tag);

		if ((cla & SC_ASN1_TAG_CLASS) == SC_ASN1_TAG_UNIVERSAL) {
			printf(" %s", tag2str(tag));
		} else {
			printf(" %s %-2u",
					classes[cla >> 6],
					i == 1 ? tag & SC_ASN1_TAG_PRIMITIVE : tag & (((unsigned int) ~0) >> (i-1)*8));
		}
		if (!((cla & SC_ASN1_TAG_CLASS) == SC_ASN1_TAG_UNIVERSAL
					&& tag == SC_ASN1_TAG_NULL && len == 0)) {
			printf(" (%"SC_FORMAT_LEN_SIZE_T"u byte%s)",
					len,
					len != 1 ? "s" : "");
		}

		if (len + hlen > bytesleft) {
			printf(" Illegal length!\n");
			return;
		}
		p += hlen + len;
		bytesleft -= hlen + len;

		if (cla & SC_ASN1_TAG_CONSTRUCTED) {
			putchar('\n');
			print_tags_recursive(buf0, tagp, len, depth + 2*i + 1);
			continue;
		}

		switch (tag) {
			case SC_ASN1_TAG_BIT_STRING:
				printf(": ");
				sc_asn1_print_bit_string(tagp, len, depth + 2*i + 1);
				break;
			case SC_ASN1_TAG_OCTET_STRING:
				sc_asn1_print_octet_string(tagp, len, depth + 2*i + 1);
				break;
			case SC_ASN1_TAG_OBJECT:
				printf(": ");
				sc_asn1_print_object_id(tagp, len);
				break;
			case SC_ASN1_TAG_INTEGER:
			case SC_ASN1_TAG_ENUMERATED:
				printf(": ");
				sc_asn1_print_integer(tagp, len);
				break;
			case SC_ASN1_TAG_IA5STRING:
			case SC_ASN1_TAG_PRINTABLESTRING:
			case SC_ASN1_TAG_T61STRING:
			case SC_ASN1_TAG_UTF8STRING:
				printf(": ");
				sc_asn1_print_utf8string(tagp, len);
				break;
			case SC_ASN1_TAG_BOOLEAN:
				printf(": ");
				sc_asn1_print_boolean(tagp, len);
				break;
			case SC_ASN1_GENERALIZEDTIME:
				printf(": ");
				sc_asn1_print_generalizedtime(tagp, len);
				break;
			case SC_ASN1_UTCTIME:
				printf(": ");
				sc_asn1_print_utctime(tagp, len);
				break;
		}

		if ((cla & SC_ASN1_TAG_CLASS) == SC_ASN1_TAG_APPLICATION) {
			print_hex(tagp, len, depth + 2*i + 1);
		}

		if ((cla & SC_ASN1_TAG_CLASS) == SC_ASN1_TAG_CONTEXT) {
			print_hex(tagp, len, depth + 2*i + 1);
		}

		putchar('\n');
	}
}

Project: OpenSC
File: asn1.c

int sc_asn1_read_tag(const u8 ** buf, size_t buflen, unsigned int *cla_out,
		     unsigned int *tag_out, size_t *taglen)
{
	const u8 *p = *buf;
	size_t left = buflen, len;
	unsigned int cla, tag, i;

	*buf = NULL;

	if (left == 0)
		return SC_ERROR_INVALID_ASN1_OBJECT;
	if (*p == 0xff || *p == 0) {
		/* end of data reached */
		*taglen = 0;
		*tag_out = SC_ASN1_TAG_EOC;
		return SC_SUCCESS;
	}

	/* parse tag byte(s)
	 * Resulted tag is presented by integer that has not to be
	 * confused with the 'tag number' part of ASN.1 tag.
	 */
	cla = (*p & SC_ASN1_TAG_CLASS) | (*p & SC_ASN1_TAG_CONSTRUCTED);
	tag = *p & SC_ASN1_TAG_PRIMITIVE;
	p++;
	left--;
	if (tag == SC_ASN1_TAG_PRIMITIVE) {
		/* high tag number */
		size_t n = SC_ASN1_TAGNUM_SIZE - 1;
		/* search the last tag octet */
		do {
			if (left == 0 || n == 0)
				/* either an invalid tag or it doesn't fit in
				 * unsigned int */
				return SC_ERROR_INVALID_ASN1_OBJECT;
			tag <<= 8;
			tag |= *p;
			p++;
			left--;
			n--;
		} while (tag & 0x80);
	}

	/* parse length byte(s) */
	if (left == 0)
		return SC_ERROR_INVALID_ASN1_OBJECT;
	len = *p;
	p++;
	left--;
	if (len & 0x80) {
		len &= 0x7f;
		unsigned int a = 0;
		if (len > sizeof a || len > left)
			return SC_ERROR_INVALID_ASN1_OBJECT;
		for (i = 0; i < len; i++) {
			a <<= 8;
			a |= *p;
			p++;
			left--;
		}
		len = a;
	}

	*cla_out = cla;
	*tag_out = tag;
	*taglen = len;
	*buf = p;

	if (len > left)
		return SC_ERROR_ASN1_END_OF_CONTENTS;

	return SC_SUCCESS;
}

Project: OpenSC
File: asn1.c

sc_asn1_sig_value_sequence_to_rs(struct sc_context *ctx, const unsigned char *in, size_t inlen,
		unsigned char *buf, size_t buflen)
{
	struct sc_asn1_entry asn1_sig_value[C_ASN1_SIG_VALUE_SIZE];
	struct sc_asn1_entry asn1_sig_value_coefficients[C_ASN1_SIG_VALUE_COEFFICIENTS_SIZE];
	unsigned char *r = NULL, *s = NULL;
	size_t r_len, s_len, halflen = buflen/2;
	int rv;

	LOG_FUNC_CALLED(ctx);
	if (!buf || !buflen)
		LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS);

	sc_copy_asn1_entry(c_asn1_sig_value, asn1_sig_value);
	sc_format_asn1_entry(asn1_sig_value + 0, asn1_sig_value_coefficients, NULL, 0);

	sc_copy_asn1_entry(c_asn1_sig_value_coefficients, asn1_sig_value_coefficients);
	sc_format_asn1_entry(asn1_sig_value_coefficients + 0, &r, &r_len, 0);
	sc_format_asn1_entry(asn1_sig_value_coefficients + 1, &s, &s_len, 0);

	rv = sc_asn1_decode(ctx, asn1_sig_value, in, inlen, NULL, NULL);
	LOG_TEST_GOTO_ERR(ctx, rv, "ASN.1 decoding ECDSA-Sig-Value failed");

	if (halflen < r_len || halflen < s_len)   {
		rv = SC_ERROR_BUFFER_TOO_SMALL;
		goto err;
	}

	memset(buf, 0, buflen);
	memcpy(buf + (halflen - r_len), r, r_len);
	memcpy(buf + (buflen - s_len), s, s_len);

	sc_log(ctx, "r(%"SC_FORMAT_LEN_SIZE_T"u): %s", halflen,
	       sc_dump_hex(buf, halflen));
	sc_log(ctx, "s(%"SC_FORMAT_LEN_SIZE_T"u): %s", halflen,
	       sc_dump_hex(buf + halflen, halflen));

	rv = SC_SUCCESS;
err:
	free(r);
	free(s);

	LOG_FUNC_RETURN(ctx, rv);
}