From bd17511070fb39a67bfa19682affb765e706a974 Mon Sep 17 00:00:00 2001
From: Viktor Dukhovni <viktor@openssl.org>
Date: Wed, 29 Apr 2026 18:23:24 +1000
Subject: [PATCH] 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: Daniel Kubec <kubec@openssl.foundation>
MergeDate: Mon Jun 8 13:59:47 2026
(cherry picked from commit d32350ae8ef7426718f5aa9e383d4b51398ee255)
crypto/asn1/a_mbstr.c | 33 +++++++++++++++++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
@@ -169,18 +169,41 @@ 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;
case MBSTRING_UTF8:
outlen = 0;
- traverse_string(in, len, inform, out_utf8, &outlen);
+ ret = traverse_string(in, len, inform, out_utf8, &outlen);
+ if (ret < 0) { /* error already raised in out_utf8() */
+ if (free_out) {
+ ASN1_STRING_free(dest);
+ *out = NULL;
+ }
+ return -1;
+ }
cpyfunc = cpy_utf8;
break;
}
@@ -260,9 +283,15 @@ static int out_utf8(unsigned long value, void *arg)
int *outlen, len;
len = UTF8_putc(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;
}
--