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

From: Date: Mon, 10 Aug 2026 08:07:47 +0000
Subject: [php-src] PHP-8.4: Add a stack limit check in zend_hash_compare() (#23090)
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-140570@lists.php.net to get a copy of this message
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.

« previous php.cvs (#140570) next »