Commit 16ca82827f6 for php.net

commit 16ca82827f6f88e1422e73a8f5c732f1173c85b6
Author: Julien Voisin <jvoisin@users.noreply.github.com>
Date:   Thu Sep 17 16:44:05 2026 +0200

    Check the links before unlinking a huge block (#23568)

    The same primitive was already fixed for the chunk list in 519920b2839 and
    for stream buckets in 492ebb54c1a; the huge list was missed.

    Costs one pointer per huge block, growing zend_mm_huge_list from 24 to 32
    bytes on 64-bit, i.e. one small slot per allocation of at least 2MB.

diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c
index f6c0a1ad0e9..d6fc959e9c4 100644
--- a/Zend/zend_alloc.c
+++ b/Zend/zend_alloc.c
@@ -356,6 +356,7 @@ struct _zend_mm_huge_list {
 	void              *ptr;
 	size_t             size;
 	zend_mm_huge_list *next;
+	zend_mm_huge_list *prev;
 #if ZEND_DEBUG
 	zend_mm_debug_info dbg;
 #endif
@@ -1931,6 +1932,10 @@ static void zend_mm_add_huge_block(zend_mm_heap *heap, void *ptr, size_t size ZE
 	list->ptr = ptr;
 	list->size = size;
 	list->next = heap->huge_list;
+	list->prev = NULL;
+	if (heap->huge_list) {
+		heap->huge_list->prev = list;
+	}
 #if ZEND_DEBUG
 	list->dbg.size = dbg_size;
 	list->dbg.filename = __zend_filename;
@@ -1943,22 +1948,28 @@ static void zend_mm_add_huge_block(zend_mm_heap *heap, void *ptr, size_t size ZE

 static size_t zend_mm_del_huge_block(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
 {
-	zend_mm_huge_list *prev = NULL;
 	zend_mm_huge_list *list = heap->huge_list;
 	while (list != NULL) {
 		if (list->ptr == ptr) {
 			size_t size;

-			if (prev) {
-				prev->next = list->next;
+			/* Unlinking writes through these, so make sure they still point back
+			 * at this block before trusting them. */
+			ZEND_MM_CHECK(list->prev ? list->prev->next == list : heap->huge_list == list, "zend_mm_heap corrupted");
+			ZEND_MM_CHECK(!list->next || list->next->prev == list, "zend_mm_heap corrupted");
+
+			if (list->prev) {
+				list->prev->next = list->next;
 			} else {
 				heap->huge_list = list->next;
 			}
+			if (list->next) {
+				list->next->prev = list->prev;
+			}
 			size = list->size;
 			zend_mm_free_heap(heap, list ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
 			return size;
 		}
-		prev = list;
 		list = list->next;
 	}
 	ZEND_MM_CHECK(0, "zend_mm_heap corrupted");
@@ -2388,6 +2399,9 @@ static zend_long zend_mm_find_leaks_huge(zend_mm_heap *heap, zend_mm_huge_list *
 	while (p) {
 		if (p->dbg.filename == list->dbg.filename && p->dbg.lineno == list->dbg.lineno) {
 			prev->next = p->next;
+			if (p->next) {
+				p->next->prev = prev;
+			}
 			zend_mm_chunk_free(heap, p->ptr, p->size);
 			zend_mm_free_heap(heap, p, NULL, 0, NULL, 0);
 			count++;
@@ -2430,6 +2444,9 @@ static void zend_mm_check_leaks(zend_mm_heap *heap)
 		}

 		heap->huge_list = list = list->next;
+		if (list) {
+			list->prev = NULL;
+		}
 		zend_mm_chunk_free(heap, q->ptr, q->size);
 		zend_mm_free_heap(heap, q, NULL, 0, NULL, 0);
 	}