[PATCH v2] malloc: Improve calloc [BZ #34582]

Wilco Dijkstra Wilco.Dijkstra@arm.com
Tue Sep 15 16:52:18 GMT 2026


Hi Yury,

> Does it make sense to add reproducers from BZ 34582 as testcases to the
> tst-calloc test? With this patch some assertions in the reproducers still
> fail (though it seems like the reproducers rely on UB).

I'm not sure there is any point since corruption of the malloc state will result
in crashes - just not the original crash.

> I think these two macros should depend on each other: I'd suggest to
> define 'memsize(p)' as 'chunk2usable(chunksize(p))'.

Done.

> Also, the name of the 'chunk2usable' macro should probably be something
> like 'chunk_size_to_usable_size' since we have macros that begin with 'chunk2'
> and work with pointers rather than sizes.

Done.

> Here variable 'nb' is undeclared if USE_TCACHE is false (use of variable
> outside #if USE_TCACHE ... #endif).

That's why #if is a bad idea in code... So USE_TCACHE is always true for this
function, or it would give an error.

> Also, I think __libc_calloc2's argument name should be 'usable[_size]',
> same as __libc_calloc_small.

I've changed it to usable_size as that is clearer indeed.

Cheers,
Wilco


v2: Use chunksize2usable and usable_size

Add __libc_calloc_small for small calloc sizes to avoid the complex logic of 
__libc_calloc2 which is unnecessary overhead for small blocks.  Cleanup
__libc_calloc2 and only clear the size of the requested chunk (since we
typically get the exact size after splitting off any tail).  This fixes
BZ #34582.

---

diff --git a/malloc/malloc.c b/malloc/malloc.c
index 5fc35a14f4ea95c27096ea45ca70d73048021d5f..011ecd687002c294324e5761ec5128ca212c3320 100644
--- a/malloc/malloc.c
+++ b/malloc/malloc.c
@@ -1115,7 +1115,8 @@ checked_request2size (size_t req)
 
 /* This is the size of the real usable data in the chunk.  Not valid for
    dumped heap chunks.  */
-#define memsize(p) (chunksize (p) - CHUNK_HDR_SZ + SIZE_SZ)
+#define chunksize2usable(n) (n - CHUNK_HDR_SZ + SIZE_SZ)
+#define memsize(p) (chunksize2usable (chunksize (p)))
 
 /* Huge page used for an mmap chunk.  */
 #define MMAP_HP 0x1
@@ -3299,17 +3300,17 @@ __libc_pvalloc (size_t bytes)
 libc_hidden_def (__libc_pvalloc)
 
 static void * __attribute_noinline__
-__libc_calloc2 (size_t sz)
+__libc_calloc2 (size_t usable_size)
 {
   mstate av;
   mchunkptr oldtop, p;
-  INTERNAL_SIZE_T oldtopsize, csz;
+  INTERNAL_SIZE_T oldtopsize;
   void *mem;
 
   if (SINGLE_THREAD_P)
     av = &main_arena;
   else
-    arena_get (av, sz);
+    arena_get (av, usable_size);
 
   if (av)
     {
@@ -3338,18 +3339,18 @@ __libc_calloc2 (size_t sz)
       oldtop = NULL;
       oldtopsize = 0;
     }
-  mem = _int_malloc (av, sz);
+  mem = _int_malloc (av, usable_size);
 
   assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
-          av == arena_for_chunk (mem2chunk (mem)));
+	  av == arena_for_chunk (mem2chunk (mem)));
 
   if (!SINGLE_THREAD_P)
     {
       if (mem == NULL && av != NULL)
 	{
-	  LIBC_PROBE (memory_calloc_retry, 1, sz);
-	  av = arena_get_retry (av, sz);
-	  mem = _int_malloc (av, sz);
+	  LIBC_PROBE (memory_calloc_retry, 1, usable_size);
+	  av = arena_get_retry (av, usable_size);
+	  mem = _int_malloc (av, usable_size);
 	}
 
       if (av != NULL)
@@ -3362,26 +3363,30 @@ __libc_calloc2 (size_t sz)
 
   p = mem2chunk (mem);
 
-  csz = chunksize (p);
-
   /* Two optional cases in which clearing not necessary */
-  if (chunk_is_mmapped (p))
-    {
-      if (__glibc_unlikely (perturb_byte))
-        return memset (mem, 0, sz);
-
-      return mem;
-    }
+  if (__glibc_unlikely (chunk_is_mmapped (p)) && perturb_byte == 0)
+    return mem;
 
 #if MORECORE_CLEARS
-  if (perturb_byte == 0 && (p == oldtop && csz > oldtopsize))
-    {
-      /* clear only the bytes from non-freshly-sbrked memory */
-      csz = oldtopsize;
-    }
+  /* clear only the bytes from non-freshly-sbrked memory */
+  if (__glibc_unlikely (p == oldtop)
+      && usable_size >= oldtopsize && perturb_byte == 0)
+    usable_size = chunksize2usable (oldtopsize);
 #endif
 
-  return clear_memory (mem, csz - SIZE_SZ);
+  /* Clear requested size.  */
+  return memset (mem, 0, usable_size);
+}
+
+static void * __attribute_noinline__
+__libc_calloc_small (size_t usable_size)
+{
+  void *mem = __libc_malloc2 (usable_size);
+
+  if (mem == NULL)
+    return NULL;
+
+  return clear_memory (mem, usable_size);
 }
 
 void *
@@ -3395,9 +3400,10 @@ __libc_calloc (size_t n, size_t elem_size)
        return NULL;
     }
 
-#if USE_TCACHE
   size_t nb = checked_request2size (bytes);
 
+#if USE_TCACHE
+
   if (nb < mp_.tcache_max_bytes)
     {
       size_t tc_idx = csize2tidx (nb);
@@ -3406,17 +3412,19 @@ __libc_calloc (size_t n, size_t elem_size)
 	{
 	  if (tcache->entries[tc_idx] != NULL)
 	    return clear_memory (tcache_get (tc_idx), tidx2usize (tc_idx));
+
+	  return __libc_calloc_small (chunksize2usable (nb));
 	}
       else
 	{
 	  tc_idx = large_csize2tidx (nb);
 	  void *mem = tcache_get_large (tc_idx, nb);
 	  if (mem != NULL)
-	    return memset (mem, 0, memsize (mem2chunk (mem)));
+	    return memset (mem, 0, chunksize2usable (nb));
 	}
     }
 #endif
-  return __libc_calloc2 (bytes);
+  return __libc_calloc2 (chunksize2usable (nb));
 }
 libc_hidden_def (__libc_calloc)
 #endif /* IS_IN (libc) */



More information about the Libc-alpha mailing list