Commit 5d0c3576b4 for openssl.org
commit 5d0c3576b4df2d56bfa4e98d7eb68a3fe35858ae
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date: Mon Jul 27 11:44:48 2026 +0900
Fix provider fallback duplication in provider store
provider_activate_fallbacks() created a second provider object when a
fallback had already been loaded with OSSL_PROVIDER_try_load(..., 1).
Child-provider synchronization matches by name, so the duplicate could
unbalance references and cause a double free during store teardown.
Reuse the existing store entry and take the activation reference owned by
automatic fallback loading. Construct a new object only when no match
exists, and assert that the upcall-suppressed reuse path is not used for
child providers.
Add regression tests for the original sequence, a default-only variant,
and reactivation of an inactive in-store provider.
Fixes #32079
Assisted-by: OpenCode:GLM-5.2
Assisted-by: Grok:Grok-4.5
Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
Merge-date: Thu Oct 29 12:00:04 2026
Merged-from: https://github.com/openssl/openssl/pull/32088
diff --git a/crypto/provider_core.c b/crypto/provider_core.c
index c19f945ba2..bfc6d189a4 100644
--- a/crypto/provider_core.c
+++ b/crypto/provider_core.c
@@ -1306,6 +1306,10 @@ static int provider_activate(OSSL_PROVIDER *prov, int lock, int upcalls)
if (count == 1 && store != NULL) {
ret = create_provider_children(prov);
+ if (!ret
+ && CRYPTO_atomic_add(&prov->activatecnt, -1, &count,
+ prov->activatecnt_lock))
+ prov->flag_activated = 0;
}
}
if (lock) {
@@ -1453,6 +1457,9 @@ static int provider_activate_fallbacks(struct provider_store_st *store)
int activated_fallback_count = 0;
int ret = 0;
const OSSL_PROVIDER_INFO *p;
+ OSSL_PROVIDER tmpl = {
+ 0,
+ };
if (!CRYPTO_THREAD_read_lock(store->lock))
return 0;
@@ -1475,10 +1482,39 @@ static int provider_activate_fallbacks(struct provider_store_st *store)
OSSL_PROVIDER_INFO *info = store->provinfo;
STACK_OF(INFOPAIR) *params = NULL;
size_t i;
+ int idx;
if (!p->is_fallback)
continue;
+ /*
+ * Reuse an explicitly loaded fallback and take the activation
+ * reference normally owned by automatic fallback loading.
+ */
+ tmpl.name = (char *)p->name;
+ idx = sk_OSSL_PROVIDER_find(store->providers, &tmpl);
+ if (idx != -1) {
+ int activate_count;
+
+ prov = sk_OSSL_PROVIDER_value(store->providers, idx);
+ /*
+ * store->lock is held. Take flag_lock in order and suppress
+ * internal locking. Fallback entries cannot require parent upcalls.
+ */
+#ifndef FIPS_MODULE
+ if (!ossl_assert(!prov->ischild))
+ goto err;
+#endif
+ if (!CRYPTO_THREAD_write_lock(prov->flag_lock))
+ goto err;
+ activate_count = provider_activate(prov, 0, 0);
+ CRYPTO_THREAD_unlock(prov->flag_lock);
+ if (activate_count < 0)
+ goto err;
+ activated_fallback_count++;
+ continue;
+ }
+
for (i = 0; i < store->numprovinfo; info++, i++) {
if (strcmp(info->name, p->name) != 0)
continue;
diff --git a/test/provider_fallback_test.c b/test/provider_fallback_test.c
index db65af3ea6..dfdff1d783 100644
--- a/test/provider_fallback_test.c
+++ b/test/provider_fallback_test.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-2024 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2020-2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
@@ -8,6 +8,7 @@
*/
#include <stddef.h>
+#include <string.h>
#include <openssl/provider.h>
#include <openssl/evp.h>
#include "testutil.h"
@@ -53,9 +54,200 @@ static int test_explicit_provider(void)
return ok;
}
+/* Count active default providers. */
+static int count_default_provider(OSSL_PROVIDER *prov, void *cbdata)
+{
+ int *count = cbdata;
+
+ if (strcmp(OSSL_PROVIDER_get0_name(prov), "default") == 0)
+ (*count)++;
+ return 1;
+}
+
+/* Reproduce fallback duplication with child-provider synchronization. */
+static int test_fallback_duplication(void)
+{
+ OSSL_LIB_CTX *ctx = NULL;
+ OSSL_PROVIDER *legacy = NULL, *deflt = NULL;
+ EVP_MD *md = NULL;
+ int default_count;
+ int ok = 0;
+#ifdef OPENSSL_NO_MD4
+ const char *trigger_algo = "SHA256";
+#else
+ const char *trigger_algo = "MD4";
+#endif
+
+ if (!TEST_ptr(ctx = OSSL_LIB_CTX_new()))
+ return 0;
+
+ /* Load legacy first so the child-provider path is exercised. */
+ legacy = OSSL_PROVIDER_try_load(ctx, "legacy", 1);
+ if (legacy == NULL) {
+ int skip = TEST_skip("Legacy provider not available");
+
+ OSSL_LIB_CTX_free(ctx);
+ return skip;
+ }
+
+ /* Load default while retaining automatic fallback activation. */
+ if (!TEST_ptr(deflt = OSSL_PROVIDER_try_load(ctx, "default", 1)))
+ goto err;
+
+ /* Trigger fallback activation; MD4 also exercises legacy. */
+ if (!TEST_ptr(md = EVP_MD_fetch(ctx, trigger_algo, "-fips")))
+ goto err;
+ EVP_MD_free(md);
+ md = NULL;
+
+ default_count = 0;
+ if (!TEST_true(OSSL_PROVIDER_do_all(ctx, count_default_provider,
+ &default_count))
+ || !TEST_int_eq(default_count, 1))
+ goto err;
+
+ /* The fallback reference must keep default active. */
+ if (!TEST_true(OSSL_PROVIDER_unload(deflt)))
+ goto err;
+ deflt = NULL;
+ if (!TEST_true(OSSL_PROVIDER_unload(legacy)))
+ goto err;
+ legacy = NULL;
+
+ if (!TEST_ptr(md = EVP_MD_fetch(ctx, "SHA256", "-fips")))
+ goto err;
+ EVP_MD_free(md);
+ md = NULL;
+
+ /* Repeat the issue's load/fetch/unload cycle. */
+ if (!TEST_ptr(deflt = OSSL_PROVIDER_try_load(ctx, "default", 1)))
+ goto err;
+ if (!TEST_ptr(md = EVP_MD_fetch(ctx, "SHA256", "-fips")))
+ goto err;
+ EVP_MD_free(md);
+ md = NULL;
+ if (!TEST_true(OSSL_PROVIDER_unload(deflt)))
+ goto err;
+ deflt = NULL;
+
+ ok = 1;
+
+err:
+ EVP_MD_free(md);
+ OSSL_PROVIDER_unload(deflt);
+ OSSL_PROVIDER_unload(legacy);
+ /* This teardown previously double-freed the duplicate. */
+ OSSL_LIB_CTX_free(ctx);
+ return ok;
+}
+
+/* Reproduce the duplication without relying on the legacy module. */
+static int test_fallback_default_only(void)
+{
+ OSSL_LIB_CTX *ctx = NULL;
+ OSSL_PROVIDER *deflt = NULL;
+ EVP_MD *md = NULL;
+ int default_count;
+ int ok = 0;
+
+ if (!TEST_ptr(ctx = OSSL_LIB_CTX_new()))
+ return 0;
+
+ if (!TEST_ptr(deflt = OSSL_PROVIDER_try_load(ctx, "default", 1)))
+ goto err;
+
+ /* Fallback activation must reuse the explicit default. */
+ if (!TEST_ptr(md = EVP_MD_fetch(ctx, "SHA256", "-fips")))
+ goto err;
+ EVP_MD_free(md);
+ md = NULL;
+
+ default_count = 0;
+ if (!TEST_true(OSSL_PROVIDER_do_all(ctx, count_default_provider,
+ &default_count))
+ || !TEST_int_eq(default_count, 1))
+ goto err;
+
+ /* Unloading the explicit handle must not deactivate the fallback. */
+ if (!TEST_true(OSSL_PROVIDER_unload(deflt)))
+ goto err;
+ deflt = NULL;
+
+ if (!TEST_ptr(md = EVP_MD_fetch(ctx, "SHA256", "-fips")))
+ goto err;
+ EVP_MD_free(md);
+ md = NULL;
+
+ ok = 1;
+
+err:
+ EVP_MD_free(md);
+ OSSL_PROVIDER_unload(deflt);
+ OSSL_LIB_CTX_free(ctx);
+ return ok;
+}
+
+/* Exercise fallback reuse with an inactive in-store provider. */
+static int test_fallback_reactivate_inactive(void)
+{
+ OSSL_LIB_CTX *ctx = NULL;
+ OSSL_PROVIDER *legacy = NULL, *deflt = NULL;
+ EVP_MD *md = NULL;
+ int default_count;
+ int ok = 0;
+
+ if (!TEST_ptr(ctx = OSSL_LIB_CTX_new()))
+ return 0;
+
+ /* Register child-provider callbacks before loading default. */
+ legacy = OSSL_PROVIDER_try_load(ctx, "legacy", 1);
+ if (legacy == NULL) {
+ int skip = TEST_skip("Legacy provider not available");
+
+ OSSL_LIB_CTX_free(ctx);
+ return skip;
+ }
+
+ if (!TEST_ptr(deflt = OSSL_PROVIDER_try_load(ctx, "default", 1)))
+ goto err;
+
+ /* Leave the in-store object inactive with fallbacks enabled. */
+ if (!TEST_true(OSSL_PROVIDER_unload(deflt)))
+ goto err;
+ deflt = NULL;
+
+ /* Reactivate the object through fallback loading. */
+ if (!TEST_ptr(md = EVP_MD_fetch(ctx, "SHA256", "-fips")))
+ goto err;
+ EVP_MD_free(md);
+ md = NULL;
+
+ default_count = 0;
+ if (!TEST_true(OSSL_PROVIDER_do_all(ctx, count_default_provider,
+ &default_count))
+ || !TEST_int_eq(default_count, 1))
+ goto err;
+
+ if (!TEST_true(OSSL_PROVIDER_unload(legacy)))
+ goto err;
+ legacy = NULL;
+
+ ok = 1;
+
+err:
+ EVP_MD_free(md);
+ OSSL_PROVIDER_unload(deflt);
+ OSSL_PROVIDER_unload(legacy);
+ OSSL_LIB_CTX_free(ctx);
+ return ok;
+}
+
int setup_tests(void)
{
ADD_TEST(test_fallback_provider);
ADD_TEST(test_explicit_provider);
+ ADD_TEST(test_fallback_duplication);
+ ADD_TEST(test_fallback_default_only);
+ ADD_TEST(test_fallback_reactivate_inactive);
return 1;
}