Add a thread test for ex_data This is intended to be run under TSan. ex_data currently works by taking a global read lock every time an RSA, SSL, SSL_CTX, etc., is freed. We should be able to fix that but, first, make sure we have test coverage for the threading requirements. Bug: 570 Cq-Include-Trybots: luci.boringssl.try:linux_clang_rel_tsan Change-Id: I0e12907e116481d88e45191a1f15f3a51833bf6d Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/59865 Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index ef43a9e..be00e7c 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc
@@ -5839,6 +5839,81 @@ thread.join(); } } + +static void SetValueOnFree(void *parent, void *ptr, CRYPTO_EX_DATA *ad, + int index, long argl, void *argp) { + if (ptr != nullptr) { + *static_cast<long *>(ptr) = argl; + } +} + +// Test that one thread can register ex_data while another thread is destroying +// an object that uses it. +TEST(SSLTest, ExDataThreads) { + static bool already_run = false; + if (already_run) { + GTEST_SKIP() << "This test consumes process-global resources and can only " + "be run once in a process. It is not compatible with " + "--gtest_repeat."; + } + already_run = true; + + bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method())); + ASSERT_TRUE(ctx); + + // Register an initial index, so the threads can exercise having any ex_data. + int first_index = + SSL_get_ex_new_index(-1, nullptr, nullptr, nullptr, SetValueOnFree); + ASSERT_GE(first_index, 0); + + // Callers may register indices concurrently with using other indices. This + // may happen if one part of an application is initializing while another part + // is already running. + static constexpr int kNumIndices = 3; + static constexpr int kNumSSLs = 10; + int index[kNumIndices]; + long values[kNumSSLs]; + std::fill(std::begin(values), std::end(values), -2); + std::vector<std::thread> threads; + for (size_t i = 0; i < kNumIndices; i++) { + threads.emplace_back([&, i] { + index[i] = SSL_get_ex_new_index(static_cast<long>(i), nullptr, nullptr, + nullptr, SetValueOnFree); + ASSERT_GE(index[i], 0); + }); + } + for (size_t i = 0; i < kNumSSLs; i++) { + threads.emplace_back([&, i] { + bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get())); + ASSERT_TRUE(ssl); + ASSERT_TRUE(SSL_set_ex_data(ssl.get(), first_index, &values[i])); + }); + } + for (auto &thread : threads) { + thread.join(); + } + + // Each of the SSL threads should have set their flag via ex_data. + for (size_t i = 0; i < kNumSSLs; i++) { + EXPECT_EQ(values[i], -1); + } + + // Each of the newly-registered indices should be distinct and work correctly. + static_assert(kNumIndices <= kNumSSLs, "values buffer too small"); + std::fill(std::begin(values), std::end(values), -2); + bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get())); + ASSERT_TRUE(ssl); + for (size_t i = 0; i < kNumIndices; i++) { + for (size_t j = 0; j < i; j++) { + EXPECT_NE(index[i], index[j]); + } + ASSERT_TRUE(SSL_set_ex_data(ssl.get(), index[i], &values[i])); + } + ssl = nullptr; + for (size_t i = 0; i < kNumIndices; i++) { + EXPECT_EQ(values[i], static_cast<long>(i)); + } +} #endif // OPENSSL_THREADS constexpr size_t kNumQUICLevels = 4;