Commit 1e9b70735e for openssl.org

commit 1e9b70735ec4d616219f9c0173d277edd6889db6
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date:   Tue Jul 21 13:47:30 2026 +0200

    rand: factor out seed source creation and fix store race

    rand_get0_primary() created the seed source ahead of the primary DRBG
    and stored both under the write lock at the end.  When two threads
    raced to create the primary DRBG and one of them failed after having
    created and stored the seed source, the other thread overwrote the
    stored seed source with its own instance, leaking the previous one.

    Factor the get-or-create logic out into rand_get0_seed() which
    rechecks the global under the write lock, so exactly one instance is
    kept and returned to every racing thread, and the seed source is
    stored independently of the primary DRBG creation outcome.

    Add a regression test checking that creating the primary DRBG creates
    and stores the seed source and that later use keeps returning the same
    instance.

    Assisted-by: Claude:claude-fable-5

    Reviewed-by: Paul Dale <paul.dale@oracle.com>
    Reviewed-by: Matt Caswell <matt@openssl.foundation>
    Reviewed-by: Ryan Hooper <ryanh@openssl.foundation>
    MergeDate: Mon Jul 27 09:13:12 2026
    (Merged from https://github.com/openssl/openssl/pull/32029)

diff --git a/crypto/rand/rand_lib.c b/crypto/rand/rand_lib.c
index 63a3d1bca8..8d4977a1de 100644
--- a/crypto/rand/rand_lib.c
+++ b/crypto/rand/rand_lib.c
@@ -569,6 +569,41 @@ err:
     EVP_RAND_CTX_free(ctx);
     return NULL;
 }
+
+/*
+ * Get the global seed source, creating and storing it if it does not
+ * exist yet.  If several threads race here, exactly one instance is
+ * kept and returned to all of them.
+ */
+static EVP_RAND_CTX *rand_get0_seed(OSSL_LIB_CTX *ctx, RAND_GLOBAL *dgbl)
+{
+    EVP_RAND_CTX *ret, *seed;
+
+    if (!CRYPTO_THREAD_read_lock(dgbl->lock))
+        return NULL;
+    ret = dgbl->seed;
+    CRYPTO_THREAD_unlock(dgbl->lock);
+    if (ret != NULL)
+        return ret;
+
+    seed = rand_new_seed(ctx);
+    if (seed == NULL)
+        return NULL;
+
+    if (!CRYPTO_THREAD_write_lock(dgbl->lock)) {
+        EVP_RAND_CTX_free(seed);
+        return NULL;
+    }
+    if (dgbl->seed == NULL) {
+        dgbl->seed = seed;
+        seed = NULL;
+    }
+    ret = dgbl->seed;
+    CRYPTO_THREAD_unlock(dgbl->lock);
+    /* Free the instance that lost a creation race */
+    EVP_RAND_CTX_free(seed);
+    return ret;
+}
 #endif /* !FIPS_MODULE || !OPENSSL_NO_FIPS_JITTER */

 #ifndef FIPS_MODULE
@@ -684,7 +719,7 @@ static EVP_RAND_CTX *rand_new_crngt(OSSL_LIB_CTX *libctx, EVP_RAND_CTX *parent)
  */
 static EVP_RAND_CTX *rand_get0_primary(OSSL_LIB_CTX *ctx, RAND_GLOBAL *dgbl)
 {
-    EVP_RAND_CTX *ret, *seed, *newseed = NULL, *primary;
+    EVP_RAND_CTX *ret, *seed = NULL, *primary;

     if (dgbl == NULL)
         return NULL;
@@ -693,7 +728,6 @@ static EVP_RAND_CTX *rand_get0_primary(OSSL_LIB_CTX *ctx, RAND_GLOBAL *dgbl)
         return NULL;

     ret = dgbl->primary;
-    seed = dgbl->seed;
     CRYPTO_THREAD_unlock(dgbl->lock);

     if (ret != NULL)
@@ -701,16 +735,13 @@ static EVP_RAND_CTX *rand_get0_primary(OSSL_LIB_CTX *ctx, RAND_GLOBAL *dgbl)

 #if !defined(FIPS_MODULE) || !defined(OPENSSL_NO_FIPS_JITTER)
     /* Create a seed source for libcrypto or jitter enabled FIPS provider */
-    if (seed == NULL) {
-        ERR_set_mark();
-        seed = newseed = rand_new_seed(ctx);
-        if (ERR_count_to_mark() > 0) {
-            EVP_RAND_CTX_free(newseed);
-            ERR_clear_last_mark();
-            return NULL;
-        }
-        ERR_pop_to_mark();
+    ERR_set_mark();
+    seed = rand_get0_seed(ctx, dgbl);
+    if (seed == NULL && ERR_count_to_mark() > 0) {
+        ERR_clear_last_mark();
+        return NULL;
     }
+    ERR_pop_to_mark();
 #endif /* !FIPS_MODULE || !OPENSSL_NO_FIPS_JITTER */

 #if defined(FIPS_MODULE)
@@ -721,33 +752,30 @@ static EVP_RAND_CTX *rand_get0_primary(OSSL_LIB_CTX *ctx, RAND_GLOBAL *dgbl)
         PRIMARY_RESEED_TIME_INTERVAL);
 #endif /* FIPS_MODULE */

+    if (ret == NULL)
+        return NULL;
+
     /*
      * The primary DRBG may be shared between multiple threads so we must
      * enable locking.
      */
-    if (ret == NULL || !EVP_RAND_enable_locking(ret)) {
-        if (ret != NULL) {
-            ERR_raise(ERR_LIB_EVP, EVP_R_UNABLE_TO_ENABLE_LOCKING);
-            EVP_RAND_CTX_free(ret);
-        }
-        if (newseed == NULL)
-            return NULL;
-        /* else carry on and store seed */
-        ret = NULL;
+    if (!EVP_RAND_enable_locking(ret)) {
+        ERR_raise(ERR_LIB_EVP, EVP_R_UNABLE_TO_ENABLE_LOCKING);
+        EVP_RAND_CTX_free(ret);
+        return NULL;
     }

-    if (!CRYPTO_THREAD_write_lock(dgbl->lock))
+    if (!CRYPTO_THREAD_write_lock(dgbl->lock)) {
+        EVP_RAND_CTX_free(ret);
         return NULL;
+    }

     primary = dgbl->primary;
     if (primary != NULL) {
         CRYPTO_THREAD_unlock(dgbl->lock);
         EVP_RAND_CTX_free(ret);
-        EVP_RAND_CTX_free(newseed);
         return primary;
     }
-    if (newseed != NULL)
-        dgbl->seed = newseed;
     dgbl->primary = ret;
     CRYPTO_THREAD_unlock(dgbl->lock);

diff --git a/test/rand_test.c b/test/rand_test.c
index 5b2270cb4e..afbd746fc3 100644
--- a/test/rand_test.c
+++ b/test/rand_test.c
@@ -104,6 +104,50 @@ err:
     return res;
 }

+/*
+ * Check that creating the primary DRBG creates the seed source and
+ * stores it in the library context: later users must keep getting the
+ * same instance and no replacement may be created.
+ */
+static int test_rand_primary_seed_stored(void)
+{
+    OSSL_LIB_CTX *ctx = NULL;
+    EVP_RAND_CTX *seed;
+    unsigned char buf[16];
+    int ok, res = 0;
+
+    if (!TEST_ptr(ctx = OSSL_LIB_CTX_new())
+        || !TEST_ptr_null(ossl_rand_get0_seed_noncreating(ctx)))
+        goto err;
+
+    /* The default seed source may be unavailable in some configurations */
+    ERR_set_mark();
+    ok = RAND_bytes_ex(ctx, buf, sizeof(buf), 0) > 0;
+    ERR_pop_to_mark();
+    if (!ok) {
+        TEST_info("skipped: cannot instantiate the primary DRBG");
+        res = 1;
+        goto err;
+    }
+
+    seed = ossl_rand_get0_seed_noncreating(ctx);
+    if (seed == NULL) {
+        /* The seed source silently fell back to operating system entropy */
+        TEST_info("skipped: no seed source was created");
+        res = 1;
+        goto err;
+    }
+
+    if (!TEST_int_gt(RAND_bytes_ex(ctx, buf, sizeof(buf), 0), 0)
+        || !TEST_ptr_eq(ossl_rand_get0_seed_noncreating(ctx), seed))
+        goto err;
+
+    res = 1;
+err:
+    OSSL_LIB_CTX_free(ctx);
+    return res;
+}
+
 /* Test the FIPS health tests */
 static int fips_health_test_one(const uint8_t *buf, size_t n, size_t gen)
 {
@@ -427,6 +471,7 @@ int setup_tests(void)

     ADD_TEST(test_rand);
     ADD_TEST(test_rand_uniform);
+    ADD_TEST(test_rand_primary_seed_stored);

     if (OSSL_PROVIDER_available(NULL, "fips")
         && fips_provider_version_ge(NULL, 3, 4, 0))