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);