Skip to content

Commit d32350a

Browse files
Viktor Dukhovnit8m
authored andcommitted
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
1 parent 786cebf commit d32350a

1 file changed

Lines changed: 28 additions & 3 deletions

File tree

‎crypto/asn1/a_mbstr.c‎

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,20 +185,39 @@ int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len,
185185
break;
186186

187187
case MBSTRING_BMP:
188+
if (nchar > INT_MAX / 2) {
189+
ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG);
190+
if (free_out) {
191+
ASN1_STRING_free(dest);
192+
*out = NULL;
193+
}
194+
return -1;
195+
}
188196
outlen = nchar << 1;
189197
cpyfunc = cpy_bmp;
190198
break;
191199

192200
case MBSTRING_UNIV:
201+
if (nchar > INT_MAX / 4) {
202+
ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG);
203+
if (free_out) {
204+
ASN1_STRING_free(dest);
205+
*out = NULL;
206+
}
207+
return -1;
208+
}
193209
outlen = nchar << 2;
194210
cpyfunc = cpy_univ;
195211
break;
196212

197213
case MBSTRING_UTF8:
198214
outlen = 0;
199215
ret = traverse_string(in, len, inform, out_utf8, &outlen);
200-
if (ret < 0) {
201-
ERR_raise(ERR_LIB_ASN1, ASN1_R_INVALID_UTF8STRING);
216+
if (ret < 0) { /* error already raised in out_utf8() */
217+
if (free_out) {
218+
ASN1_STRING_free(dest);
219+
*out = NULL;
220+
}
202221
return -1;
203222
}
204223
cpyfunc = cpy_utf8;
@@ -281,9 +300,15 @@ static int out_utf8(unsigned long value, void *arg)
281300
int *outlen, len;
282301

283302
len = UTF8_putc(NULL, -1, value);
284-
if (len <= 0)
303+
if (len <= 0) {
304+
ERR_raise(ERR_LIB_ASN1, ASN1_R_INVALID_UTF8STRING);
285305
return len;
306+
}
286307
outlen = arg;
308+
if (*outlen > INT_MAX - len) {
309+
ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG);
310+
return -1;
311+
}
287312
*outlen += len;
288313
return 1;
289314
}

0 commit comments

Comments
 (0)