Commit 94b39d044a for openssl.org
commit 94b39d044a2a304adf8ab373248a00beec2d210a
Author: Viktor Dukhovni <viktor@openssl.org>
Date: Wed Sep 2 18:53:50 2026 +1000
Defer computation of relative CRLDP names
These are derived just-in-time, during any actual CRL processing.
Fixes CVE-2026-35189
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Bob Beck <beck@openssl.org>
Reviewed-by: Norbert Pocs <norbertp@openssl.org>
Merge-date: Tue Sep 29 11:11:18 2026
diff --git a/crypto/x509/v3_crld.c b/crypto/x509/v3_crld.c
index 56715439a4..0a5daae747 100644
--- a/crypto/x509/v3_crld.c
+++ b/crypto/x509/v3_crld.c
@@ -522,33 +522,43 @@ static int i2r_object(const X509V3_EXT_METHOD *method, void *oid, BIO *bp,
return 1;
}
-/* Append any nameRelativeToCRLIssuer in dpn to iname, set in dpn->dpname */
-int DIST_POINT_set_dpname(DIST_POINT_NAME *dpn, const X509_NAME *iname)
+/*
+ * Return a new X509_NAME consisting of iname with the nameRelativeToCRLIssuer
+ * fragment of dpn appended, with its DER encoding already cached.
+ * dpn must be a relative name (type 1). Returns NULL on error.
+ */
+X509_NAME *ossl_dist_point_name_full(const DIST_POINT_NAME *dpn,
+ const X509_NAME *iname)
{
int i;
- STACK_OF(X509_NAME_ENTRY) *frag;
+ STACK_OF(X509_NAME_ENTRY) *frag = dpn->name.relativename;
X509_NAME_ENTRY *ne;
+ X509_NAME *dpname = X509_NAME_dup(iname);
- if (dpn == NULL || dpn->type != 1)
- return 1;
- frag = dpn->name.relativename;
- X509_NAME_free(dpn->dpname); /* just in case it was already set */
- dpn->dpname = X509_NAME_dup(iname);
- if (dpn->dpname == NULL)
- return 0;
+ if (dpname == NULL)
+ return NULL;
for (i = 0; i < sk_X509_NAME_ENTRY_num(frag); i++) {
ne = sk_X509_NAME_ENTRY_value(frag, i);
- if (!X509_NAME_add_entry(dpn->dpname, ne, -1, i ? 0 : 1))
+ if (!X509_NAME_add_entry(dpname, ne, -1, i ? 0 : 1))
goto err;
}
/* generate cached encoding of name */
- if (i2d_X509_NAME(dpn->dpname, NULL) >= 0)
- return 1;
+ if (i2d_X509_NAME(dpname, NULL) >= 0)
+ return dpname;
err:
- X509_NAME_free(dpn->dpname);
- dpn->dpname = NULL;
- return 0;
+ X509_NAME_free(dpname);
+ return NULL;
+}
+
+/* Append any nameRelativeToCRLIssuer in dpn to iname, set in dpn->dpname */
+int DIST_POINT_set_dpname(DIST_POINT_NAME *dpn, const X509_NAME *iname)
+{
+ if (dpn == NULL || dpn->type != 1)
+ return 1;
+ X509_NAME_free(dpn->dpname); /* just in case it was already set */
+ dpn->dpname = ossl_dist_point_name_full(dpn, iname);
+ return dpn->dpname != NULL;
}
ASN1_SEQUENCE(OSSL_AA_DIST_POINT) = {
diff --git a/crypto/x509/v3_purp.c b/crypto/x509/v3_purp.c
index 1d3918d1a9..a312d0519d 100644
--- a/crypto/x509/v3_purp.c
+++ b/crypto/x509/v3_purp.c
@@ -346,12 +346,19 @@ int X509_supported_extension(const X509_EXTENSION *ex)
return 0;
}
-/* Returns 1 on success, 0 if x is invalid, -1 on (internal) error. */
-static int setup_dp(const X509 *x, DIST_POINT *dp)
+/*
+ * Returns 1 on success, 0 if x is invalid.
+ *
+ * The full name of a nameRelativeToCRLIssuer distribution point is not
+ * computed here. Doing so for every parsed certificate cost an
+ * X509_NAME_dup() of the issuer name per relative distribution point,
+ * which a certificate with many such entries could turn into hundreds of
+ * megabytes of heap on a plain TLS handshake, while the result is only
+ * needed when a CRL is actually being matched against the certificate.
+ * That name is now built on demand in the CRL checking code instead.
+ */
+static int setup_dp(DIST_POINT *dp)
{
- const X509_NAME *iname = NULL;
- int i;
-
if (dp->distpoint == NULL && sk_GENERAL_NAME_num(dp->CRLissuer) <= 0) {
ERR_raise(ERR_LIB_X509, X509_R_INVALID_DISTPOINT);
return 0;
@@ -365,30 +372,10 @@ static int setup_dp(const X509 *x, DIST_POINT *dp)
} else {
dp->dp_reasons = CRLDP_ALL_REASONS;
}
- if (dp->distpoint == NULL || dp->distpoint->type != 1)
- return 1;
-
- /* Handle name fragment given by nameRelativeToCRLIssuer */
- /*
- * Note that the below way of determining iname is not really compliant
- * with https://tools.ietf.org/html/rfc5280#section-4.2.1.13
- * According to it, sk_GENERAL_NAME_num(dp->CRLissuer) MUST be <= 1
- * and any CRLissuer could be of type different to GEN_DIRNAME.
- */
- for (i = 0; i < sk_GENERAL_NAME_num(dp->CRLissuer); i++) {
- GENERAL_NAME *gen = sk_GENERAL_NAME_value(dp->CRLissuer, i);
-
- if (gen->type == GEN_DIRNAME) {
- iname = gen->d.directoryName;
- break;
- }
- }
- if (iname == NULL)
- iname = X509_get_issuer_name(x);
- return DIST_POINT_set_dpname(dp->distpoint, iname) ? 1 : -1;
+ return 1;
}
-/* Return 1 on success, 0 if x is invalid, -1 on (internal) error. */
+/* Return 1 on success, 0 if x is invalid. */
static int setup_crldp(const X509 *x, STACK_OF(DIST_POINT) **tmp_crldp)
{
int i;
@@ -398,10 +385,8 @@ static int setup_crldp(const X509 *x, STACK_OF(DIST_POINT) **tmp_crldp)
return 0;
for (i = 0; i < sk_DIST_POINT_num(*tmp_crldp); i++) {
- int res = setup_dp(x, sk_DIST_POINT_value(*tmp_crldp, i));
-
- if (res < 1)
- return res;
+ if (!setup_dp(sk_DIST_POINT_value(*tmp_crldp, i)))
+ return 0;
}
return 1;
}
diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c
index 973b586dac..032a86d03a 100644
--- a/crypto/x509/x509_vfy.c
+++ b/crypto/x509/x509_vfy.c
@@ -1858,16 +1858,59 @@ static int check_crl_chain(X509_STORE_CTX *ctx,
return X509_cmp(cert_ta, crl_ta) == 0;
}
+/*
+ * Return the full name of the certificate CRL distribution point dp, whose
+ * distpoint is a nameRelativeToCRLIssuer fragment: the CRL issuer name with
+ * the fragment appended. The CRL issuer is the directoryName in
+ * dp->CRLissuer if there is one, else the issuer of the certificate.
+ *
+ * The result is a fresh X509_NAME owned by the caller. It is deliberately
+ * not stored in dp->distpoint->dpname: once its extension cache has been
+ * published a certificate is shared between threads without locking, and
+ * computing the name here rather than when the certificate is parsed keeps
+ * a certificate with many relative distribution points from costing a copy
+ * of the issuer name per entry on every parse. Returns NULL on error.
+ */
+static X509_NAME *crldp_full_name(const X509 *x, const DIST_POINT *dp)
+{
+ const X509_NAME *iname = NULL;
+ int i;
+
+ /*
+ * Note that the below way of determining iname is not really compliant
+ * with https://tools.ietf.org/html/rfc5280#section-4.2.1.13
+ * According to it, sk_GENERAL_NAME_num(dp->CRLissuer) MUST be <= 1
+ * and any CRLissuer could be of type different to GEN_DIRNAME.
+ */
+ for (i = 0; i < sk_GENERAL_NAME_num(dp->CRLissuer); i++) {
+ GENERAL_NAME *gen = sk_GENERAL_NAME_value(dp->CRLissuer, i);
+
+ if (gen->type == GEN_DIRNAME) {
+ iname = gen->d.directoryName;
+ break;
+ }
+ }
+ if (iname == NULL)
+ iname = X509_get_issuer_name(x);
+ return ossl_dist_point_name_full(dp->distpoint, iname);
+}
+
/*-
* Check for match between two dist point names: three separate cases.
* 1. Both are relative names and compare X509_NAME types.
* 2. One full, one relative. Compare X509_NAME to GENERAL_NAMES.
* 3. Both are full names and compare two GENERAL_NAMES.
* 4. One is NULL: automatic match.
+ *
+ * a is the certificate's distribution point name and b the CRL's issuing
+ * distribution point name. When a is a relative name, aname is its full
+ * name as built by crldp_full_name(); a->dpname itself is not consulted.
+ * For b the full name is the cached b->dpname set when the CRL was parsed.
*/
-static int idp_check_dp(DIST_POINT_NAME *a, DIST_POINT_NAME *b)
+static int idp_check_dp(DIST_POINT_NAME *a, const X509_NAME *aname,
+ DIST_POINT_NAME *b)
{
- X509_NAME *nm = NULL;
+ const X509_NAME *nm = NULL;
GENERAL_NAMES *gens = NULL;
GENERAL_NAME *gena, *genb;
int i, j;
@@ -1875,16 +1918,16 @@ static int idp_check_dp(DIST_POINT_NAME *a, DIST_POINT_NAME *b)
if (a == NULL || b == NULL)
return 1;
if (a->type == 1) {
- if (a->dpname == NULL)
+ if (aname == NULL)
return 0;
/* Case 1: two X509_NAME */
if (b->type == 1) {
if (b->dpname == NULL)
return 0;
- return X509_NAME_cmp(a->dpname, b->dpname) == 0;
+ return X509_NAME_cmp(aname, b->dpname) == 0;
}
/* Case 2: set name and GENERAL_NAMES appropriately */
- nm = a->dpname;
+ nm = aname;
gens = b->name.fullname;
} else if (b->type == 1) {
if (b->dpname == NULL)
@@ -1976,7 +2019,7 @@ static int idp_check_issuer(DIST_POINT_NAME *idpname, X509 *x)
dpname.type = 0; /* fullName */
dpname.name.fullname = gens;
dpname.dpname = NULL;
- ret = idp_check_dp(&dpname, idpname);
+ ret = idp_check_dp(&dpname, NULL, idpname);
end:
GENERAL_NAME_free(gen);
@@ -2003,13 +2046,28 @@ static int crl_crldp_check(X509 *x, X509_CRL *crl, int crl_score,
*preasons = crl->idp_reasons;
for (i = 0; i < sk_DIST_POINT_num(x->crldp); i++) {
DIST_POINT *dp = sk_DIST_POINT_value(x->crldp, i);
+ X509_NAME *dpname = NULL;
+ int match;
- if (crldp_check_crlissuer(dp, crl, crl_score)) {
- if (crl->idp == NULL
- || idp_check_dp(dp->distpoint, crl->idp->distpoint)) {
- *preasons &= dp->dp_reasons;
- return 1;
- }
+ if (!crldp_check_crlissuer(dp, crl, crl_score))
+ continue;
+ if (crl->idp == NULL) {
+ match = 1;
+ } else {
+ /*
+ * A relative distribution point name is only comparable in
+ * full form. Build it for this one comparison and discard it;
+ * if that fails the entry simply does not match.
+ */
+ if (dp->distpoint != NULL && dp->distpoint->type == 1
+ && (dpname = crldp_full_name(x, dp)) == NULL)
+ continue;
+ match = idp_check_dp(dp->distpoint, dpname, crl->idp->distpoint);
+ X509_NAME_free(dpname);
+ }
+ if (match) {
+ *preasons &= dp->dp_reasons;
+ return 1;
}
}
/*
diff --git a/include/crypto/x509.h b/include/crypto/x509.h
index e89f2fed50..7ab7e79b44 100644
--- a/include/crypto/x509.h
+++ b/include/crypto/x509.h
@@ -14,6 +14,7 @@
#include "internal/refcount.h"
#include <openssl/asn1.h>
#include <openssl/x509.h>
+#include <openssl/x509v3.h>
#include <openssl/x509_vfy.h>
#include <openssl/conf.h>
#include "crypto/types.h"
@@ -363,6 +364,9 @@ int ossl_x509v3_cache_extensions(const X509 *x);
int ossl_x509_internal_fingerprint(const ASN1_ITEM *it, const void *val,
unsigned char *hash);
+X509_NAME *ossl_dist_point_name_full(const struct DIST_POINT_NAME_st *dpn,
+ const X509_NAME *iname);
+
int ossl_x509_set0_libctx(X509 *x, OSSL_LIB_CTX *libctx, const char *propq);
int ossl_x509_crl_set0_libctx(X509_CRL *x, OSSL_LIB_CTX *libctx,
const char *propq);