Partially mitigate quadratic-time malloc tests in unit tests

Malloc failure testing is quadratic in the number of allocations. To
test a failure at allocation N, we must first run the previous N-1
allocations. Now that we have combined GTest binaries, this does not
work very well.

Use the test listener to reset the counter across independent tests. We
assume failures in a previous test won't interfere in the next one and
run each test's counter in parallel.

The assumption isn't *quite* true because we have a lot of internal
init-once machinery that is reused across otherwise "independent" tests,
but it's close enough that I was able to find some bugs, fixed in the
next commit. That said, the tests still take too long to run to
completion.

Bug: 127
Change-Id: I6836793448fbdc740a8cc424361e6b3dd66fb8a6
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/56926
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/internal.h b/crypto/internal.h
index 46a4e70..576ad85 100644
--- a/crypto/internal.h
+++ b/crypto/internal.h
@@ -225,6 +225,16 @@
 #define OPENSSL_SSE2
 #endif
 
+#if defined(BORINGSSL_MALLOC_FAILURE_TESTING)
+// OPENSSL_reset_malloc_counter_for_testing, when malloc testing is enabled,
+// resets the internal malloc counter, to simulate further malloc failures. This
+// should be called in between independent tests, at a point where failure from
+// a previous test will not impact subsequent ones.
+OPENSSL_EXPORT void OPENSSL_reset_malloc_counter_for_testing(void);
+#else
+OPENSSL_INLINE void OPENSSL_reset_malloc_counter_for_testing(void) {}
+#endif
+
 
 // Pointer utility functions.
 
diff --git a/crypto/mem.c b/crypto/mem.c
index abba4a4..97a85e9 100644
--- a/crypto/mem.c
+++ b/crypto/mem.c
@@ -146,11 +146,14 @@
     CRYPTO_STATIC_MUTEX_INIT;
 static uint64_t current_malloc_count = 0;
 static uint64_t malloc_number_to_fail = 0;
-static int malloc_failure_enabled = 0, break_on_malloc_fail = 0;
+static int malloc_failure_enabled = 0, break_on_malloc_fail = 0,
+           any_malloc_failed = 0;
 
 static void malloc_exit_handler(void) {
   CRYPTO_STATIC_MUTEX_lock_read(&malloc_failure_lock);
-  if (malloc_failure_enabled && current_malloc_count > malloc_number_to_fail) {
+  if (any_malloc_failed) {
+    // Signal to the test driver that some allocation failed, so it knows to
+    // increment the counter and continue.
     _exit(88);
   }
   CRYPTO_STATIC_MUTEX_unlock_read(&malloc_failure_lock);
@@ -183,6 +186,7 @@
   CRYPTO_STATIC_MUTEX_lock_write(&malloc_failure_lock);
   int should_fail = current_malloc_count == malloc_number_to_fail;
   current_malloc_count++;
+  any_malloc_failed = any_malloc_failed || should_fail;
   CRYPTO_STATIC_MUTEX_unlock_write(&malloc_failure_lock);
 
   if (should_fail && break_on_malloc_fail) {
@@ -194,6 +198,12 @@
   return should_fail;
 }
 
+void OPENSSL_reset_malloc_counter_for_testing(void) {
+  CRYPTO_STATIC_MUTEX_lock_write(&malloc_failure_lock);
+  current_malloc_count = 0;
+  CRYPTO_STATIC_MUTEX_unlock_write(&malloc_failure_lock);
+}
+
 #else
 static int should_fail_allocation(void) { return 0; }
 #endif
diff --git a/crypto/test/gtest_main.h b/crypto/test/gtest_main.h
index 20ccf21..05d468e 100644
--- a/crypto/test/gtest_main.h
+++ b/crypto/test/gtest_main.h
@@ -31,13 +31,15 @@
 #include <signal.h>
 #endif
 
+#include "../internal.h"
+
 
 BSSL_NAMESPACE_BEGIN
 
-class ErrorTestEventListener : public testing::EmptyTestEventListener {
+class TestEventListener : public testing::EmptyTestEventListener {
  public:
-  ErrorTestEventListener() {}
-  ~ErrorTestEventListener() override {}
+  TestEventListener() {}
+  ~TestEventListener() override {}
 
   void OnTestEnd(const testing::TestInfo &test_info) override {
     if (test_info.result()->Failed()) {
@@ -48,6 +50,13 @@
       // error queue without printing.
       ERR_clear_error();
     }
+
+    // Malloc failure testing is quadratic in the number of mallocs. Running
+    // multiple tests sequentially thus scales badly. Reset the malloc counter
+    // between tests. This way we will test, each test with the first allocation
+    // failing, then the second, and so on, until the test with the most
+    // allocations runs out.
+    OPENSSL_reset_malloc_counter_for_testing();
   }
 };
 
@@ -75,8 +84,7 @@
   signal(SIGPIPE, SIG_IGN);
 #endif
 
-  testing::UnitTest::GetInstance()->listeners().Append(
-      new ErrorTestEventListener);
+  testing::UnitTest::GetInstance()->listeners().Append(new TestEventListener);
 }
 
 BSSL_NAMESPACE_END