Commit 383b5bb7c57 for php.net
commit 383b5bb7c579709fbb635fbea992b89307ec47cf
Author: jvoisin <julien.voisin@dustri.org>
Date: Tue Aug 18 23:17:12 2026 +0200
Detect immediate double-frees of zend_mm small slots
Freeing the same small pointer twice in a row pushed it onto the freelist
twice, so the next two allocations of that bin returned the same address.
That's a nifty primitive to obtain two live pointers of different
types to the same object. The shadow-pointer check does not catch it,
as both links are consistent.
This commit adds a simple check for when the freed pointer already is the head
of the freelist. heap->free_slot[bin_num] is loaded by the very next line, so
the check costs a single comparison on an already-hot value.
This only catches consecutive double-frees, not a free after other activity on
the same bin, but it doesn't cost ~anything performance wise, and catches real
bugs like error/cleanup paths freeing the same value twice. A quick look at `git log
--grep='double.free'` shows that this is a popular bug pattern.
diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c
index 575b54b11a2..02de1a543da 100644
--- a/Zend/zend_alloc.c
+++ b/Zend/zend_alloc.c
@@ -1430,6 +1430,12 @@ static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr,
#endif
p = (zend_mm_free_slot*)ptr;
+#if ZEND_MM_HEAP_PROTECTION
+ /* Catch the most common double-free pattern for free. */
+ if (UNEXPECTED(p == heap->free_slot[bin_num])) {
+ zend_mm_panic("zend_mm_heap corrupted (double free)");
+ }
+#endif
zend_mm_set_next_free_slot(heap, bin_num, p, heap->free_slot[bin_num]);
heap->free_slot[bin_num] = p;
}