[php-src] PHP-8.4: Add a stack limit check in zend_hash_compare() (#23090)

Lazizbek Ergashev via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Lazizbek Ergashev (lazerg)
Committer: GitHub (web-flow)
Pusher: arnaud-lb
Date: 2026-08-10T10:07:44+02:00

Commit: https://github.com/php/php-src/commit/77170ee6ee26e4fc8078629e6e8720202769ca45
Raw diff: https://github.com/php/php-src/commit/77170ee6ee26e4fc8078629e6e8720202769ca45.diff

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

Changed paths:
  A  Zend/tests/gh23088.phpt
  M  NEWS
  M  Zend/tests/gh18572.phpt
  M  Zend/zend_hash.c


Diff:

diff --git a/NEWS b/NEWS
index 05e3a23118d2..7a93e72de06f 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 ff178ebef24f..cf45d2afaaba 100644
--- a/Zend/tests/gh18572.phpt
+++ b/Zend/tests/gh18572.phpt
@@ -36,4 +36,4 @@ try {
 }
 ?>
 --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 000000000000..59153a1f2ba3
--- /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 23637b94bceb..82d0318428fa 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.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.