[php-src] PHP-8.4: GH-18572: infinite stack recursion in fallback object comparison.
Author: David Carlier (devnexen)
Date: 2025-05-17T11:20:16+01:00
Commit: https://github.com/php/php-src/commit/4dcbd24badf5505224bf110107dd9fdc9b2baf9e
Raw diff: https://github.com/php/php-src/commit/4dcbd24badf5505224bf110107dd9fdc9b2baf9e.diff
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
Changed paths:
A Zend/tests/gh18572.phpt
M NEWS
M Zend/zend_object_handlers.c
Diff:
diff --git a/NEWS b/NEWS
index 6532b30c1ecf6..25651e5ca7709 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 0000000000000..46361abe96641
--- /dev/null
+++ b/Zend/tests/gh18572.phpt
@@ -0,0 +1,39 @@
+--TEST--
+GH-18572: Nested object comparison leading to stack overflow
+--SKIPIF--
+<?php
+if (getenv('SKIP_ASAN')) die('skip as it fatally crash');
+?>
+--FILE--
+<?php
+
+#[AllowDynamicProperties]
+class Node {
+ public $next;
+ // forcing dynamic property creation is key
+}
+
+$first = new Node();
+$first->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 d688e4b63ed69..180364b248d71 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;
Thread (1 message)
- David Carlier