Reject oversized inputs in ASN1_mbstring_ncopy()

In ASN1_mbstring_ncopy() the destination size for BMPSTRING and
UNIVERSALSTRING output was computed by a signed left shift on an
int:

    outlen = nchar << 1;        /* MBSTRING_BMP  */
    outlen = nchar << 2;        /* MBSTRING_UNIV */

For nchar large enough the result is not representable in int.  In
the worst case (nchar == 0x40000000) nchar << 2 wraps to zero,
OPENSSL_malloc(1) is called, and traverse_string() then writes
4*nchar bytes into the one-byte allocation: a heap buffer
overflow.  The MBSTRING_UTF8 path computes outlen by summing
per-character byte counts in out_utf8(), and that sum can overflow
the same int under similarly large inputs.

Neither path is reachable from code that processes X.509
certificates through the DIRSTRING_TYPE mask used by
ASN1_STRING_set_by_NID(): UNIVERSALSTRING is absent from that
mask, and the UTF-8 sum requires inputs on the order of half a
gigabyte.  Reaching them needs an application that calls
ASN1_mbstring_copy()/ASN1_mbstring_ncopy() directly, or registers
a custom NID via ASN1_STRING_TABLE_add(), with an oversized
attacker-controlled input.

Add range checks before each shift and in out_utf8(), raising
ASN1_R_STRING_TOO_LONG at the point of detection.  Move the
existing ASN1_R_INVALID_UTF8STRING raise into out_utf8() too so
the two failure modes report distinct codes; the MBSTRING_UTF8
caller is left with cleanup only and now frees dest on error,
matching the BMP/UNIV branches.

Fixes CVE-2026-7383

Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
Reviewed-by: Norbert Pocs <norbertp@openssl.org>
Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
MergeDate: Mon Jun  8 14:02:18 2026
This commit is contained in:
Viktor Dukhovni 2026-04-29 18:23:24 +10:00 committed by Tomas Mraz
parent cb005ceaaf
commit 97f6b621f7

View file

@ -187,11 +187,27 @@ int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len,
break;
case MBSTRING_BMP:
if (nchar > INT_MAX / 2) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG);
if (free_out) {
ASN1_STRING_free(dest);
*out = NULL;
}
return -1;
}
outlen = nchar << 1;
cpyfunc = cpy_bmp;
break;
case MBSTRING_UNIV:
if (nchar > INT_MAX / 4) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG);
if (free_out) {
ASN1_STRING_free(dest);
*out = NULL;
}
return -1;
}
outlen = nchar << 2;
cpyfunc = cpy_univ;
break;
@ -199,8 +215,11 @@ int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len,
case MBSTRING_UTF8:
outlen = 0;
ret = traverse_string(in, len, inform, out_utf8, &outlen);
if (ret < 0) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_INVALID_UTF8STRING);
if (ret < 0) { /* error already raised in out_utf8() */
if (free_out) {
ASN1_STRING_free(dest);
*out = NULL;
}
return -1;
}
cpyfunc = cpy_utf8;
@ -281,9 +300,15 @@ static int out_utf8(uint32_t value, void *arg)
int *outlen, len;
len = ossl_utf8_putc_internal(NULL, -1, value);
if (len <= 0)
if (len <= 0) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_INVALID_UTF8STRING);
return len;
}
outlen = arg;
if (*outlen > INT_MAX - len) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG);
return -1;
}
*outlen += len;
return 1;
}