From 4dcbd24badf5505224bf110107dd9fdc9b2baf9e Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 16 May 2025 20:54:56 +0100 Subject: [PATCH] GH-18572: infinite stack recursion in fallback object comparison. With nested objects and recursive comparisons, it is for now unavoidable to have a stack overflow we do some early damage control attempt early on with zend.max_allowed_stack_size check but ultimately more a band-aid than a definitive solution. close GH-18577 --- NEWS | 2 ++ Zend/tests/gh18572.phpt | 39 +++++++++++++++++++++++++++++++++++++ Zend/zend_object_handlers.c | 14 +++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 Zend/tests/gh18572.phpt diff --git a/NEWS b/NEWS index 6532b30c1ec..25651e5ca77 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ PHP NEWS - Core: . Fixed GH-18480 (array_splice with large values for offset/length arguments). (nielsdos/David Carlier) + . Partially fixed GH-18572 (nested object comparisons leading to stack overflow). + (David Carlier) - Curl: . Fixed GH-18460 (curl_easy_setopt with CURLOPT_USERPWD/CURLOPT_USERNAME/ diff --git a/Zend/tests/gh18572.phpt b/Zend/tests/gh18572.phpt new file mode 100644 index 00000000000..46361abe966 --- /dev/null +++ b/Zend/tests/gh18572.phpt @@ -0,0 +1,39 @@ +--TEST-- +GH-18572: Nested object comparison leading to stack overflow +--SKIPIF-- + +--FILE-- +previous = $first; +$first->next = $first; + +$cur = $first; + +for ($i = 0; $i < 50000; $i++) { + $new = new Node(); + $new->previous = $cur; + $cur->next = $new; + $new->next = $first; + $first->previous = $new; + $cur = $new; +} + +try { + // Force comparison manually to trigger zend_hash_compare + $first == $cur; +} catch(Error $e) { + echo $e->getMessage(). PHP_EOL; +} +?> +--EXPECTREGEX-- +(Maximum call stack size reached during object comparison|Fatal error: Nesting level too deep - recursive dependency?.+) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index d688e4b63ed..180364b248d 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -42,6 +42,15 @@ #define IN_UNSET ZEND_GUARD_PROPERTY_UNSET #define IN_ISSET ZEND_GUARD_PROPERTY_ISSET +static zend_always_inline bool zend_objects_check_stack_limit(void) +{ +#ifdef ZEND_CHECK_STACK_LIMIT + return zend_call_stack_overflowed(EG(stack_limit)); +#else + return false; +#endif +} + /* __X accessors explanation: @@ -1714,6 +1723,11 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ { zend_object *zobj1, *zobj2; + if (zend_objects_check_stack_limit()) { + zend_throw_error(NULL, "Maximum call stack size reached during object comparison"); + return ZEND_UNCOMPARABLE; + } + if (Z_TYPE_P(o1) != Z_TYPE_P(o2)) { /* Object and non-object */ zval *object;