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.