Commit 77170ee6ee2 for php.net

commit 77170ee6ee26e4fc8078629e6e8720202769ca45
Author: Lazizbek Ergashev <lazerg2@gmail.com>
Date:   Mon Aug 10 13:07:44 2026 +0500

    Add a stack limit check in zend_hash_compare() (#23090)

    Comparing two deeply nested arrays recurses through zend_compare_arrays -> zend_compare_symbol_tables -> zend_hash_compare once per nesting level, and nothing bounds that recursion. zend_hash_compare() only guards against cycles, so a non-cyclic array a few tens of thousands of levels deep runs the C stack out and the process dies with a segfault. === crashes the same way through zend_is_identical().

    Both now check the stack limit before descending and throw an Error instead, the same way zend_std_compare_objects() already handles the object case.

    Fixes GH-23088

diff --git a/NEWS b/NEWS
index 05e3a23118d..7a93e72de06 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP                                                                        NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? ????, PHP 8.4.25

+- Core:
+  . Fixed bug GH-23088 (Stack overflow when comparing deeply nested arrays).
+    (Lazizbek Ergashev)
+
 - Date:
   . Fixed leak on double DatePeriod::__construct() call. (ilutov)

diff --git a/Zend/tests/gh18572.phpt b/Zend/tests/gh18572.phpt
index ff178ebef24..cf45d2afaab 100644
--- a/Zend/tests/gh18572.phpt
+++ b/Zend/tests/gh18572.phpt
@@ -36,4 +36,4 @@ class Node {
 }
 ?>
 --EXPECTREGEX--
-(Maximum call stack size reached during object comparison|Nesting level too deep - recursive dependency\?)
+(Maximum call stack size reached during (object )?comparison|Nesting level too deep - recursive dependency\?)
diff --git a/Zend/tests/gh23088.phpt b/Zend/tests/gh23088.phpt
new file mode 100644
index 00000000000..59153a1f2ba
--- /dev/null
+++ b/Zend/tests/gh23088.phpt
@@ -0,0 +1,40 @@
+--TEST--
+GH-23088 (Stack overflow when comparing deeply nested arrays)
+--SKIPIF--
+<?php
+if (ini_get('zend.max_allowed_stack_size') === false) {
+    die('skip No stack limit support');
+}
+if (getenv('SKIP_ASAN')) {
+    die('skip ASAN needs different stack limit setting due to more stack space usage');
+}
+?>
+--INI--
+zend.max_allowed_stack_size=256K
+--FILE--
+<?php
+
+$a = [];
+$b = [];
+
+for ($i = 0; $i < 20000; $i++) {
+    $a = [$a];
+    $b = [$b];
+}
+
+try {
+    var_dump($a == $b);
+} catch (Error $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+try {
+    var_dump($a === $b);
+} catch (Error $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Maximum call stack size reached during comparison
+Maximum call stack size reached during comparison
diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c
index 23637b94bce..82d0318428f 100644
--- a/Zend/zend_hash.c
+++ b/Zend/zend_hash.c
@@ -3214,6 +3214,13 @@ ZEND_API int zend_hash_compare(HashTable *ht1, HashTable *ht2, compare_func_t co
 		return 0;
 	}

+#ifdef ZEND_CHECK_STACK_LIMIT
+	if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
+		zend_throw_error(NULL, "Maximum call stack size reached during comparison");
+		return ZEND_UNCOMPARABLE;
+	}
+#endif
+
 	/* It's enough to protect only one of the arrays.
 	 * The second one may be referenced from the first and this may cause
 	 * false recursion detection.