[php-src] PHP-8.4: GH-18572: infinite stack recursion in fallback object comparison.

From: Date: Sat, 17 May 2025 10:32:29 +0000
Subject: [php-src] PHP-8.4: GH-18572: infinite stack recursion in fallback object comparison.
Groups: php.cvs 
Request: Send a blank email to [email protected] to get a copy of this message
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
« previous php.cvs (#134135) next »