Verify that allocated memory is always zeroed.

If this is a low ram device, then do not run these tests. Otherwise,
verify that memory returned from the allocator is zero.

Bug: 171429763
Bug: 264539505
Bug: 265431478

Test: New unit tests pass on scudo.
Test: New unit tests fail on original jemalloc.
Test: New unit tests pass on jemalloc that always zeroes memory.
Test: New unit tests are skipped on low ram device.
Test: Ran unit tests atest CtsBionicTestCases in above scenarios.
Change-Id: Id62ab74f649c0f7c14a951235fe79972926ecec0
diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp
index e90a824..63ad99d 100644
--- a/tests/malloc_test.cpp
+++ b/tests/malloc_test.cpp
@@ -35,6 +35,7 @@
 
 #include <algorithm>
 #include <atomic>
+#include <functional>
 #include <thread>
 #include <vector>
 
@@ -1528,3 +1529,157 @@
     }
   }
 }
+
+void VerifyAllocationsAreZero(std::function<void*(size_t)> alloc_func, std::string function_name,
+                              std::vector<size_t>& test_sizes, size_t max_allocations) {
+  // Vector of zero'd data used for comparisons. Make it twice the largest size.
+  std::vector<char> zero(test_sizes.back() * 2, 0);
+
+  SCOPED_TRACE(testing::Message() << function_name << " failed to zero memory");
+
+  for (size_t test_size : test_sizes) {
+    std::vector<void*> ptrs(max_allocations);
+    for (size_t i = 0; i < ptrs.size(); i++) {
+      SCOPED_TRACE(testing::Message() << "size " << test_size << " at iteration " << i);
+      ptrs[i] = alloc_func(test_size);
+      ASSERT_TRUE(ptrs[i] != nullptr);
+      size_t alloc_size = malloc_usable_size(ptrs[i]);
+      ASSERT_LE(alloc_size, zero.size());
+      ASSERT_EQ(0, memcmp(ptrs[i], zero.data(), alloc_size));
+
+      // Set the memory to non-zero to make sure if the pointer
+      // is reused it's still zero.
+      memset(ptrs[i], 0xab, alloc_size);
+    }
+    // Free the pointers.
+    for (size_t i = 0; i < ptrs.size(); i++) {
+      free(ptrs[i]);
+    }
+    for (size_t i = 0; i < ptrs.size(); i++) {
+      SCOPED_TRACE(testing::Message() << "size " << test_size << " at iteration " << i);
+      ptrs[i] = malloc(test_size);
+      ASSERT_TRUE(ptrs[i] != nullptr);
+      size_t alloc_size = malloc_usable_size(ptrs[i]);
+      ASSERT_LE(alloc_size, zero.size());
+      ASSERT_EQ(0, memcmp(ptrs[i], zero.data(), alloc_size));
+    }
+    // Free all of the pointers later to maximize the chance of reusing from
+    // the first loop.
+    for (size_t i = 0; i < ptrs.size(); i++) {
+      free(ptrs[i]);
+    }
+  }
+}
+
+// Verify that small and medium allocations are always zero.
+TEST(malloc, zeroed_allocations_small_medium_sizes) {
+#if !defined(__BIONIC__)
+  GTEST_SKIP() << "Only valid on bionic";
+#endif
+
+  if (IsLowRamDevice()) {
+    GTEST_SKIP() << "Skipped on low memory devices.";
+  }
+
+  constexpr size_t kMaxAllocations = 1024;
+  std::vector<size_t> test_sizes = {16, 48, 128, 1024, 4096, 65536};
+  VerifyAllocationsAreZero([](size_t size) -> void* { return malloc(size); }, "malloc", test_sizes,
+                           kMaxAllocations);
+
+  VerifyAllocationsAreZero([](size_t size) -> void* { return memalign(64, size); }, "memalign",
+                           test_sizes, kMaxAllocations);
+
+  VerifyAllocationsAreZero(
+      [](size_t size) -> void* {
+        void* ptr;
+        if (posix_memalign(&ptr, 64, size) == 0) {
+          return ptr;
+        }
+        return nullptr;
+      },
+      "posix_memalign", test_sizes, kMaxAllocations);
+}
+
+// Verify that large allocations are always zero.
+TEST(malloc, zeroed_allocations_large_sizes) {
+#if !defined(__BIONIC__)
+  GTEST_SKIP() << "Only valid on bionic";
+#endif
+
+  if (IsLowRamDevice()) {
+    GTEST_SKIP() << "Skipped on low memory devices.";
+  }
+
+  constexpr size_t kMaxAllocations = 20;
+  std::vector<size_t> test_sizes = {1000000, 2000000, 3000000, 4000000};
+  VerifyAllocationsAreZero([](size_t size) -> void* { return malloc(size); }, "malloc", test_sizes,
+                           kMaxAllocations);
+
+  VerifyAllocationsAreZero([](size_t size) -> void* { return memalign(64, size); }, "memalign",
+                           test_sizes, kMaxAllocations);
+
+  VerifyAllocationsAreZero(
+      [](size_t size) -> void* {
+        void* ptr;
+        if (posix_memalign(&ptr, 64, size) == 0) {
+          return ptr;
+        }
+        return nullptr;
+      },
+      "posix_memalign", test_sizes, kMaxAllocations);
+}
+
+TEST(malloc, zeroed_allocations_realloc) {
+#if !defined(__BIONIC__)
+  GTEST_SKIP() << "Only valid on bionic";
+#endif
+
+  if (IsLowRamDevice()) {
+    GTEST_SKIP() << "Skipped on low memory devices.";
+  }
+
+  // Vector of zero'd data used for comparisons.
+  constexpr size_t kMaxMemorySize = 131072;
+  std::vector<char> zero(kMaxMemorySize, 0);
+
+  constexpr size_t kMaxAllocations = 1024;
+  std::vector<size_t> test_sizes = {16, 48, 128, 1024, 4096, 65536};
+  // Do a number of allocations and set them to non-zero.
+  for (size_t test_size : test_sizes) {
+    std::vector<void*> ptrs(kMaxAllocations);
+    for (size_t i = 0; i < kMaxAllocations; i++) {
+      ptrs[i] = malloc(test_size);
+      ASSERT_TRUE(ptrs[i] != nullptr);
+
+      // Set the memory to non-zero to make sure if the pointer
+      // is reused it's still zero.
+      memset(ptrs[i], 0xab, malloc_usable_size(ptrs[i]));
+    }
+    // Free the pointers.
+    for (size_t i = 0; i < kMaxAllocations; i++) {
+      free(ptrs[i]);
+    }
+  }
+
+  // Do the reallocs to a larger size and verify the rest of the allocation
+  // is zero.
+  constexpr size_t kInitialSize = 8;
+  for (size_t test_size : test_sizes) {
+    std::vector<void*> ptrs(kMaxAllocations);
+    for (size_t i = 0; i < kMaxAllocations; i++) {
+      ptrs[i] = malloc(kInitialSize);
+      ASSERT_TRUE(ptrs[i] != nullptr);
+      size_t orig_alloc_size = malloc_usable_size(ptrs[i]);
+
+      ptrs[i] = realloc(ptrs[i], test_size);
+      ASSERT_TRUE(ptrs[i] != nullptr);
+      size_t new_alloc_size = malloc_usable_size(ptrs[i]);
+      char* ptr = reinterpret_cast<char*>(ptrs[i]);
+      ASSERT_EQ(0, memcmp(&ptr[orig_alloc_size], zero.data(), new_alloc_size - orig_alloc_size))
+          << "realloc from " << kInitialSize << " to size " << test_size << " at iteration " << i;
+    }
+    for (size_t i = 0; i < kMaxAllocations; i++) {
+      free(ptrs[i]);
+    }
+  }
+}