Commit 1b6206d05d for openssl.org
commit 1b6206d05d63fcb42a3b902e3c2459ae5cf284ff
Author: Bob Beck <beck@openssl.org>
Date: Thu Sep 3 14:55:10 2026 -0600
Add a threads test for concurrent X509_self_signed() on one certificate
Reproduces the race from #32609 under TSAN: multiple threads perform the first use of a certificate's cached extension data at the same time.
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:25 2026
Merged-from: https://github.com/openssl/openssl/pull/32614
diff --git a/test/threadstest.c b/test/threadstest.c
index 1e7841ce2d..65c7afcae6 100644
--- a/test/threadstest.c
+++ b/test/threadstest.c
@@ -1395,6 +1395,83 @@ err:
return ret;
}
+/* Self-signed "Root CA" certificate, the same as in the test store. */
+static const char *kSelfSignedRootCA[] = {
+ "-----BEGIN CERTIFICATE-----\n",
+ "MIIDFjCCAf6gAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdSb290\n",
+ "IENBMCAXDTIwMTIxMjIwMTEzN1oYDzIxMjAxMjEzMjAxMTM3WjASMRAwDgYDVQQD\n",
+ "DAdSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4eYA9Qa8\n",
+ "oEY4eQ8/HnEZE20C3yubdmv8rLAh7daRCEI7pWM17FJboKJKxdYAlAOXWj25ZyjS\n",
+ "feMhXKTtxjyNjoTRnVTDPdl0opZ2Z3H5xhpQd7P9eO5b4OOMiSPCmiLsPtQ3ngfN\n",
+ "wCtVERc6NEIcaQ06GLDtFZRexv2eh8Yc55QaksBfBcFzQ+UD3gmRySTO2I6Lfi7g\n",
+ "MUjRhipqVSZ66As2Tpex4KTJ2lxpSwOACFaDox+yKrjBTP7FsU3UwAGq7b7OJb3u\n",
+ "aa32B81uK6GJVPVo65gJ7clgZsszYkoDsGjWDqtfwTVVfv1G7rrr3Laio+2Ff3ff\n",
+ "tWgiQ35mJCOvxQIDAQABo3UwczAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB\n",
+ "BjAdBgNVHQ4EFgQUjvUlrx6ba4Q9fICayVOcTXL3o1IwHwYDVR0jBBgwFoAUjvUl\n",
+ "rx6ba4Q9fICayVOcTXL3o1IwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcN\n",
+ "AQELBQADggEBABWUjaqtkdRDhVAJZTxkJVgohjRrBwp86Y0JZWdCDua/sErmEaGu\n",
+ "nQVxWWFWIgu6sb8tyQo3/7dBIQl3Rpij9bsgKhToO1OzoG3Oi3d0+zRDHfY6xNrj\n",
+ "TUE00FeLHGNWsgZSIvu99DrGApT/+uPdWfJgMu5szillqW+4hcCUPLjG9ekVNt1s\n",
+ "KhdEklo6PrP6eMbm6s22EIVUxqGE6xxAmrvyhlY1zJH9BJ23Ps+xabjG6OeMRZzT\n",
+ "0F/fU7XIFieSO7rqUcjgo1eYc3ghsDxNUJ6TPBgv5z4SPnstoOBj59rjpJ7Qkpyd\n",
+ "L17VfEadezat37Cpeha7vGDduCsyMfN4kiw=\n",
+ "-----END CERTIFICATE-----\n",
+ NULL
+};
+
+/*
+ * A certificate shared by the threads of test_x509_self_signed(), and a
+ * lock the main thread holds for writing until every worker has started,
+ * so that the workers all reach the certificate at the same time.
+ */
+static X509 *shared_cert = NULL;
+static CRYPTO_RWLOCK *shared_cert_start = NULL;
+
+static void test_x509_self_signed_worker(void)
+{
+ /* Block until the main thread releases the start lock. */
+ if (!TEST_true(CRYPTO_THREAD_read_lock(shared_cert_start))) {
+ multi_set_success(0);
+ return;
+ }
+ CRYPTO_THREAD_unlock(shared_cert_start);
+
+ if (!TEST_int_eq(X509_self_signed(shared_cert, 0), 1))
+ multi_set_success(0);
+}
+
+static void test_x509_self_signed_start(void)
+{
+ CRYPTO_THREAD_unlock(shared_cert_start);
+}
+
+/*
+ * Test that concurrent first use of a certificate's cached extension data
+ * is safe. Every thread calls X509_self_signed() on the same unprocessed
+ * certificate, so they race in ossl_x509v3_cache_extensions() to compute
+ * and install the cache. Under TSAN this detects one thread installing the
+ * cache while another reads it.
+ */
+static int test_x509_self_signed(void)
+{
+ int ret = 0;
+
+ if (!TEST_ptr(shared_cert = X509_from_strings(kSelfSignedRootCA))
+ || !TEST_ptr(shared_cert_start = CRYPTO_THREAD_lock_new())
+ || !TEST_true(CRYPTO_THREAD_write_lock(shared_cert_start)))
+ goto err;
+
+ ret = thread_run_test(&test_x509_self_signed_start, MAXIMUM_THREADS,
+ &test_x509_self_signed_worker, 0, NULL);
+
+err:
+ CRYPTO_THREAD_lock_free(shared_cert_start);
+ shared_cert_start = NULL;
+ X509_free(shared_cert);
+ shared_cert = NULL;
+ return ret;
+}
+
/* Test using OBJ_create in multiple threads */
static void test_obj_create_worker(void)
{
@@ -1521,6 +1598,7 @@ int setup_tests(void)
#endif
ADD_TEST(test_pem_read);
ADD_TEST(test_x509_store);
+ ADD_TEST(test_x509_self_signed);
ADD_TEST(test_obj_stress);
return 1;
}