Skip to content

Commit 8393de4

Browse files
committed
Fix the name constraints code to not assume NUL terminated strings
ASN.1 strings may not be NUL terminated. Don't assume they are. CVE-2021-3712 Reviewed-by: Viktor Dukhovni <viktor@openssl.org> Reviewed-by: Paul Dale <pauli@openssl.org>
1 parent 2344695 commit 8393de4

File tree

1 file changed

+52
-25
lines changed

1 file changed

+52
-25
lines changed

crypto/x509v3/v3_ncons.c

+52-25
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,31 @@ ASN1_SEQUENCE(NAME_CONSTRAINTS) = {
6363
IMPLEMENT_ASN1_ALLOC_FUNCTIONS(GENERAL_SUBTREE)
6464
IMPLEMENT_ASN1_ALLOC_FUNCTIONS(NAME_CONSTRAINTS)
6565

66+
67+
#define IA5_OFFSET_LEN(ia5base, offset) \
68+
((ia5base)->length - ((unsigned char *)(offset) - (ia5base)->data))
69+
70+
/* Like memchr but for ASN1_IA5STRING. Additionally you can specify the
71+
* starting point to search from
72+
*/
73+
# define ia5memchr(str, start, c) memchr(start, c, IA5_OFFSET_LEN(str, start))
74+
75+
/* Like memrrchr but for ASN1_IA5STRING */
76+
static char *ia5memrchr(ASN1_IA5STRING *str, int c)
77+
{
78+
int i;
79+
80+
for (i = str->length; i > 0 && str->data[i - 1] != c; i--);
81+
82+
if (i == 0)
83+
return NULL;
84+
85+
return (char *)&str->data[i - 1];
86+
}
87+
6688
/*
67-
* We cannot use strncasecmp here because that applies locale specific rules.
89+
* We cannot use strncasecmp here because that applies locale specific rules. It
90+
* also doesn't work with ASN1_STRINGs that may have embedded NUL characters.
6891
* For example in Turkish 'I' is not the uppercase character for 'i'. We need to
6992
* do a simple ASCII case comparison ignoring the locale (that is why we use
7093
* numeric constants below).
@@ -89,20 +112,12 @@ static int ia5ncasecmp(const char *s1, const char *s2, size_t n)
89112

90113
/* c1 > c2 */
91114
return 1;
92-
} else if (*s1 == 0) {
93-
/* If we get here we know that *s2 == 0 too */
94-
return 0;
95115
}
96116
}
97117

98118
return 0;
99119
}
100120

101-
static int ia5casecmp(const char *s1, const char *s2)
102-
{
103-
return ia5ncasecmp(s1, s2, SIZE_MAX);
104-
}
105-
106121
static void *v2i_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method,
107122
X509V3_CTX *ctx, STACK_OF(CONF_VALUE) *nval)
108123
{
@@ -337,7 +352,7 @@ static int cn2dnsid(ASN1_STRING *cn, unsigned char **dnsid, size_t *idlen)
337352
--utf8_length;
338353

339354
/* Reject *embedded* NULs */
340-
if ((size_t)utf8_length != strlen((char *)utf8_value)) {
355+
if (memchr(utf8_value, 0, utf8_length) != NULL) {
341356
OPENSSL_free(utf8_value);
342357
return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
343358
}
@@ -536,9 +551,14 @@ static int nc_dns(ASN1_IA5STRING *dns, ASN1_IA5STRING *base)
536551
{
537552
char *baseptr = (char *)base->data;
538553
char *dnsptr = (char *)dns->data;
554+
539555
/* Empty matches everything */
540-
if (!*baseptr)
556+
if (base->length == 0)
541557
return X509_V_OK;
558+
559+
if (dns->length < base->length)
560+
return X509_V_ERR_PERMITTED_VIOLATION;
561+
542562
/*
543563
* Otherwise can add zero or more components on the left so compare RHS
544564
* and if dns is longer and expect '.' as preceding character.
@@ -549,7 +569,7 @@ static int nc_dns(ASN1_IA5STRING *dns, ASN1_IA5STRING *base)
549569
return X509_V_ERR_PERMITTED_VIOLATION;
550570
}
551571

552-
if (ia5casecmp(baseptr, dnsptr))
572+
if (ia5ncasecmp(baseptr, dnsptr, base->length))
553573
return X509_V_ERR_PERMITTED_VIOLATION;
554574

555575
return X509_V_OK;
@@ -560,16 +580,17 @@ static int nc_email(ASN1_IA5STRING *eml, ASN1_IA5STRING *base)
560580
{
561581
const char *baseptr = (char *)base->data;
562582
const char *emlptr = (char *)eml->data;
583+
const char *baseat = ia5memrchr(base, '@');
584+
const char *emlat = ia5memrchr(eml, '@');
585+
size_t basehostlen, emlhostlen;
563586

564-
const char *baseat = strchr(baseptr, '@');
565-
const char *emlat = strchr(emlptr, '@');
566587
if (!emlat)
567588
return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
568589
/* Special case: initial '.' is RHS match */
569-
if (!baseat && (*baseptr == '.')) {
590+
if (!baseat && base->length > 0 && (*baseptr == '.')) {
570591
if (eml->length > base->length) {
571592
emlptr += eml->length - base->length;
572-
if (ia5casecmp(baseptr, emlptr) == 0)
593+
if (ia5ncasecmp(baseptr, emlptr, base->length) == 0)
573594
return X509_V_OK;
574595
}
575596
return X509_V_ERR_PERMITTED_VIOLATION;
@@ -589,8 +610,10 @@ static int nc_email(ASN1_IA5STRING *eml, ASN1_IA5STRING *base)
589610
baseptr = baseat + 1;
590611
}
591612
emlptr = emlat + 1;
613+
basehostlen = IA5_OFFSET_LEN(base, baseptr);
614+
emlhostlen = IA5_OFFSET_LEN(eml, emlptr);
592615
/* Just have hostname left to match: case insensitive */
593-
if (ia5casecmp(baseptr, emlptr))
616+
if (basehostlen != emlhostlen || ia5ncasecmp(baseptr, emlptr, emlhostlen))
594617
return X509_V_ERR_PERMITTED_VIOLATION;
595618

596619
return X509_V_OK;
@@ -601,32 +624,36 @@ static int nc_uri(ASN1_IA5STRING *uri, ASN1_IA5STRING *base)
601624
{
602625
const char *baseptr = (char *)base->data;
603626
const char *hostptr = (char *)uri->data;
604-
const char *p = strchr(hostptr, ':');
627+
const char *p = ia5memchr(uri, (char *)uri->data, ':');
605628
int hostlen;
629+
606630
/* Check for foo:// and skip past it */
607-
if (!p || (p[1] != '/') || (p[2] != '/'))
631+
if (p == NULL
632+
|| IA5_OFFSET_LEN(uri, p) < 3
633+
|| p[1] != '/'
634+
|| p[2] != '/')
608635
return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
609636
hostptr = p + 3;
610637

611638
/* Determine length of hostname part of URI */
612639

613640
/* Look for a port indicator as end of hostname first */
614641

615-
p = strchr(hostptr, ':');
642+
p = ia5memchr(uri, hostptr, ':');
616643
/* Otherwise look for trailing slash */
617-
if (!p)
618-
p = strchr(hostptr, '/');
644+
if (p == NULL)
645+
p = ia5memchr(uri, hostptr, '/');
619646

620-
if (!p)
621-
hostlen = strlen(hostptr);
647+
if (p == NULL)
648+
hostlen = IA5_OFFSET_LEN(uri, hostptr);
622649
else
623650
hostlen = p - hostptr;
624651

625652
if (hostlen == 0)
626653
return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
627654

628655
/* Special case: initial '.' is RHS match */
629-
if (*baseptr == '.') {
656+
if (base->length > 0 && *baseptr == '.') {
630657
if (hostlen > base->length) {
631658
p = hostptr + hostlen - base->length;
632659
if (ia5ncasecmp(p, baseptr, base->length) == 0)

0 commit comments

Comments
 (0)