Merge "Fix bug in dlmalloc's version of calloc."
diff --git a/libc/upstream-dlmalloc/malloc.c b/libc/upstream-dlmalloc/malloc.c
index 3c9d36b..a61c0da 100644
--- a/libc/upstream-dlmalloc/malloc.c
+++ b/libc/upstream-dlmalloc/malloc.c
@@ -4822,8 +4822,13 @@
       req = MAX_SIZE_T; /* force downstream failure on overflow */
   }
   mem = dlmalloc(req);
-  if (mem != 0 && calloc_must_clear(mem2chunk(mem)))
-    memset(mem, 0, req);
+  if (mem != 0) {
+    mchunkptr p = mem2chunk(mem);
+    if (calloc_must_clear(p)) {
+      /* Make sure to clear all of the buffer, not just the requested size. */
+      memset(mem, 0, chunksize(p) - overhead_for(p));
+    }
+  }
   return mem;
 }
 
diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp
index b76625a..5af5a6f 100644
--- a/tests/malloc_test.cpp
+++ b/tests/malloc_test.cpp
@@ -372,3 +372,22 @@
   }
 #endif
 }
+
+TEST(malloc, calloc_usable_size) {
+  for (size_t size = 1; size <= 2048; size++) {
+    void* pointer = malloc(size);
+    ASSERT_TRUE(pointer != nullptr);
+    memset(pointer, 0xeb, malloc_usable_size(pointer));
+    free(pointer);
+
+    // We should get a previous pointer that has been set to non-zero.
+    // If calloc does not zero out all of the data, this will fail.
+    uint8_t* zero_mem = reinterpret_cast<uint8_t*>(calloc(1, size));
+    ASSERT_TRUE(pointer != nullptr);
+    size_t usable_size = malloc_usable_size(zero_mem);
+    for (size_t i = 0; i < usable_size; i++) {
+      ASSERT_EQ(0, zero_mem[i]) << "Failed at allocation size " << size << " at byte " << i;
+    }
+    free(zero_mem);
+  }
+}