Skip to content

Fix GH-23088: Stack overflow when comparing deeply nested arrays - #23090

Open
lazerg wants to merge 2 commits into
php:PHP-8.4from
lazerg:fix/gh-23088-array-compare-stack-limit
Open

Fix GH-23088: Stack overflow when comparing deeply nested arrays#23090
lazerg wants to merge 2 commits into
php:PHP-8.4from
lazerg:fix/gh-23088-array-compare-stack-limit

Conversation

@lazerg

@lazerg lazerg commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

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

@lazerg
lazerg force-pushed the fix/gh-23088-array-compare-stack-limit branch 3 times, most recently from aa11ff5 to 983de1f Compare August 6, 2026 16:01
@lazerg
lazerg force-pushed the fix/gh-23088-array-compare-stack-limit branch from 983de1f to e0c17b3 Compare August 6, 2026 16:19
@Girgias
Girgias requested a review from arnaud-lb August 7, 2026 10:35
Comment thread Zend/zend_operators.c Outdated
Comment on lines +2423 to +2428
#ifdef ZEND_CHECK_STACK_LIMIT
if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
zend_throw_error(NULL, "Maximum call stack size reached during array comparison");
return 0;
}
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this one required, if zend_compare_arrays() checks it too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was, yes. === goes zend_is_identical() -> zend_hash_compare() -> hash_zval_identical_function() -> zend_is_identical(), so it never passes through zend_compare_arrays(). Moot now anyway, both checks are gone and the one in zend_hash_compare() covers each path.

Comment thread Zend/zend_operators.c Outdated
Comment on lines +3434 to +3439
#ifdef ZEND_CHECK_STACK_LIMIT
if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
zend_throw_error(NULL, "Maximum call stack size reached during array comparison");
return ZEND_UNCOMPARABLE;
}
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since zend_hash_compare() does the nesting level check, I would prefer if the stack check was moved there as well. WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, it now sits right next to the recursion guard in zend_hash_compare() and both copies are gone. One side effect: that function also handles object property tables and SplObjectStorage, so the message had to become generic ("Maximum call stack size reached during comparison"), which meant adjusting the regex in gh18572.phpt.

@lazerg
lazerg requested a review from dstogov as a code owner August 7, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants