Commit 3b1acea33c for openssl.org

commit 3b1acea33c054ae2bebc5689b39e15d37f437539
Author: Bob Beck <beck@openssl.org>
Date:   Mon Aug 31 09:57:26 2026 -0600

    Don't update the certificate in cache_extensions if we lost the race.

    ossl_x509v3_cache_extensions() checks for an unprocessed cert under
    a read lock, computes the cached values, then takes a write lock to
    install them. Re-check EXFLAG_SET after acquiring the write lock in
    case another thread has updated the certificate in the meantime, and
    if so, do not install our values over theirs.

    Since losing the race requires freeing the temporary objects, convert
    the function to a single exit that frees whatever was not installed.
    This also fixes a pre-existing leak of the temporaries when acquiring
    the write lock failed.

    Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Merge-date: Sat Sep 26 12:31:24 2026
    Merged-from: https://github.com/openssl/openssl/pull/32614

diff --git a/crypto/x509/v3_purp.c b/crypto/x509/v3_purp.c
index f13d785a38..1d3918d1a9 100644
--- a/crypto/x509/v3_purp.c
+++ b/crypto/x509/v3_purp.c
@@ -543,20 +543,27 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)
     uint32_t tmp_ex_kusage;
     uint32_t tmp_ex_xkusage;
     uint32_t tmp_ex_nscert;
-    ASN1_OCTET_STRING *tmp_skid;
-    AUTHORITY_KEYID *tmp_akid;
-    STACK_OF(GENERAL_NAME) *tmp_altname;
-    NAME_CONSTRAINTS *tmp_nc;
+    ASN1_OCTET_STRING *tmp_skid = NULL;
+    AUTHORITY_KEYID *tmp_akid = NULL;
+    STACK_OF(GENERAL_NAME) *tmp_altname = NULL;
+    NAME_CONSTRAINTS *tmp_nc = NULL;
     STACK_OF(DIST_POINT) *tmp_crldp = NULL;
+#ifndef OPENSSL_NO_RFC3779
+    STACK_OF(IPAddressFamily) *tmp_rfc3779_addr = NULL;
+    struct ASIdentifiers_st *tmp_rfc3779_asid = NULL;
+#endif
+    int ret = 0;

 #ifdef tsan_ld_acq
     /* Fast lock-free check, see end of the function for details. */
-    if (tsan_ld_acq((TSAN_QUALIFIER int *)&const_x->ex_cached))
-        return (const_x->ex_flags & EXFLAG_INVALID) == 0;
+    if (tsan_ld_acq((TSAN_QUALIFIER int *)&const_x->ex_cached)) {
+        ret = (const_x->ex_flags & EXFLAG_INVALID) == 0;
+        goto done;
+    }
 #endif

     if (!CRYPTO_THREAD_read_lock(const_x->lock))
-        return 0;
+        goto done;
     tmp_ex_flags = const_x->ex_flags;
     tmp_ex_pcpathlen = const_x->ex_pcpathlen;
     tmp_ex_kusage = const_x->ex_kusage;
@@ -564,7 +571,8 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)

     if ((tmp_ex_flags & EXFLAG_SET) != 0) { /* Cert has already been processed */
         CRYPTO_THREAD_unlock(const_x->lock);
-        return (tmp_ex_flags & EXFLAG_INVALID) == 0;
+        ret = (tmp_ex_flags & EXFLAG_INVALID) == 0;
+        goto done;
     }

     ERR_set_mark();
@@ -744,13 +752,11 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)
         tmp_ex_flags |= EXFLAG_INVALID;

 #ifndef OPENSSL_NO_RFC3779
-    STACK_OF(IPAddressFamily) *tmp_rfc3779_addr
-        = X509_get_ext_d2i(const_x, NID_sbgp_ipAddrBlock, &i, NULL);
+    tmp_rfc3779_addr = X509_get_ext_d2i(const_x, NID_sbgp_ipAddrBlock, &i, NULL);
     if (tmp_rfc3779_addr == NULL && i != -1)
         tmp_ex_flags |= EXFLAG_INVALID;

-    struct ASIdentifiers_st *tmp_rfc3779_asid
-        = X509_get_ext_d2i(const_x, NID_sbgp_autonomousSysNum, &i, NULL);
+    tmp_rfc3779_asid = X509_get_ext_d2i(const_x, NID_sbgp_autonomousSysNum, &i, NULL);
     if (tmp_rfc3779_asid == NULL && i != -1)
         tmp_ex_flags |= EXFLAG_INVALID;
 #endif
@@ -766,7 +772,16 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)
      * do all the updating under a write lock
      */
     if (!CRYPTO_THREAD_write_lock(const_x->lock))
-        return 0;
+        goto done;
+
+    /* See if another thread updated this certificate before we got the write lock. */
+    if ((const_x->ex_flags & EXFLAG_SET) != 0) { /* Cert has already been processed */
+        CRYPTO_THREAD_unlock(const_x->lock);
+        ret = (const_x->ex_flags & EXFLAG_INVALID) == 0;
+        goto done;
+    }
+
+    /* Otherwise, we have the lock, set the cached fields in the cert. */
     ((X509 *)const_x)->ex_flags = tmp_ex_flags;
     ((X509 *)const_x)->ex_pathlen = tmp_ex_pathlen;
     ((X509 *)const_x)->ex_pcpathlen = tmp_ex_pcpathlen;
@@ -780,19 +795,26 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)
         ((X509 *)const_x)->ex_nscert = tmp_ex_nscert;
     ASN1_OCTET_STRING_free(((X509 *)const_x)->skid);
     ((X509 *)const_x)->skid = tmp_skid;
+    tmp_skid = NULL;
     AUTHORITY_KEYID_free(((X509 *)const_x)->akid);
     ((X509 *)const_x)->akid = tmp_akid;
+    tmp_akid = NULL;
     sk_GENERAL_NAME_pop_free(((X509 *)const_x)->altname, GENERAL_NAME_free);
     ((X509 *)const_x)->altname = tmp_altname;
+    tmp_altname = NULL;
     NAME_CONSTRAINTS_free(((X509 *)const_x)->nc);
     ((X509 *)const_x)->nc = tmp_nc;
+    tmp_nc = NULL;
     sk_DIST_POINT_pop_free(((X509 *)const_x)->crldp, DIST_POINT_free);
     ((X509 *)const_x)->crldp = tmp_crldp;
+    tmp_crldp = NULL;
 #ifndef OPENSSL_NO_RFC3779
     sk_IPAddressFamily_pop_free(((X509 *)const_x)->rfc3779_addr, IPAddressFamily_free);
     ((X509 *)const_x)->rfc3779_addr = tmp_rfc3779_addr;
+    tmp_rfc3779_addr = NULL;
     ASIdentifiers_free(((X509 *)const_x)->rfc3779_asid);
     ((X509 *)const_x)->rfc3779_asid = tmp_rfc3779_asid;
+    tmp_rfc3779_asid = NULL;
 #endif

 #ifdef tsan_st_rel
@@ -806,9 +828,22 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)
     CRYPTO_THREAD_unlock(const_x->lock);
     if (tmp_ex_flags & EXFLAG_INVALID) {
         ERR_raise(ERR_LIB_X509V3, X509V3_R_INVALID_CERTIFICATE);
-        return 0;
+        goto done;
     }
-    return 1;
+
+    ret = 1;
+
+done:
+    ASN1_OCTET_STRING_free(tmp_skid);
+    AUTHORITY_KEYID_free(tmp_akid);
+    sk_GENERAL_NAME_pop_free(tmp_altname, GENERAL_NAME_free);
+    NAME_CONSTRAINTS_free(tmp_nc);
+    sk_DIST_POINT_pop_free(tmp_crldp, DIST_POINT_free);
+#ifndef OPENSSL_NO_RFC3779
+    sk_IPAddressFamily_pop_free(tmp_rfc3779_addr, IPAddressFamily_free);
+    ASIdentifiers_free(tmp_rfc3779_asid);
+#endif
+    return ret;
 }

 /*-