Commit fa8a9d1ca99 for php

commit fa8a9d1ca996c6954a4220e8c3b4e84234bbc2ad
Author: Eyüp Can Akman <eyupcanakman@gmail.com>
Date:   Thu Nov 12 16:11:38 2026 +0800

    Fix stack overflow in filter_var() and filter_var_array() (#25349)

    Deeply nested arrays could overflow the C stack while being filtered.

    Check the stack limit before descending, and stop processing array elements and
    keys once an exception is pending. This prevents repeated stack errors from a
    second deep entry.

    Closes #25349

diff --git a/NEWS b/NEWS
index 18b784ca064..e8051495b75 100644
--- a/NEWS
+++ b/NEWS
@@ -45,6 +45,10 @@ PHP                                                                        NEWS
   . Fixed the argument blamed by the imagepolygon(), imageopenpolygon() and
     imagefilledpolygon() too few points error. (lacatoire)

+- Filter:
+  . Fixed stack overflow in filter_var() and filter_var_array() with deeply
+    nested arrays. (Eyüp Can Akman)
+
 - MBString:
   . Fixed stack overflow in mb_check_encoding() and mb_convert_encoding() with
     deeply nested arrays. (Eyüp Can Akman)
diff --git a/ext/filter/filter.c b/ext/filter/filter.c
index 7e1a0311099..a98fc56df24 100644
--- a/ext/filter/filter.c
+++ b/ext/filter/filter.c
@@ -393,6 +393,13 @@ static void php_zval_filter_recursive(zval *value, zend_long filter, zend_long f
 	if (Z_TYPE_P(value) == IS_ARRAY) {
 		zval *element;

+#ifdef ZEND_CHECK_STACK_LIMIT
+		if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
+			zend_call_stack_size_error();
+			return;
+		}
+#endif
+
 		if (Z_IS_RECURSIVE_P(value)) {
 			return;
 		}
@@ -403,6 +410,10 @@ static void php_zval_filter_recursive(zval *value, zend_long filter, zend_long f
 			if (Z_TYPE_P(element) == IS_ARRAY) {
 				SEPARATE_ARRAY(element);
 				php_zval_filter_recursive(element, filter, flags, options, charset, copy);
+				if (EG(exception)) {
+					Z_UNPROTECT_RECURSION_P(value);
+					return;
+				}
 			} else {
 				php_zval_filter(element, filter, flags, options, charset, copy);
 			}
@@ -590,6 +601,10 @@ static void php_filter_array_handler(zval *input, HashTable *op_ht, zend_long op
 					Z_TYPE_P(arg_elm) == IS_ARRAY ? 0 : zval_get_long(arg_elm),
 					0, FILTER_REQUIRE_SCALAR
 				);
+				if (EG(exception)) {
+					zval_ptr_dtor(&nval);
+					RETURN_THROWS();
+				}
 				zend_hash_update(Z_ARRVAL_P(return_value), arg_key, &nval);
 			}
 		} ZEND_HASH_FOREACH_END();
diff --git a/ext/filter/tests/filter_recursive_stack_limit.phpt b/ext/filter/tests/filter_recursive_stack_limit.phpt
new file mode 100644
index 00000000000..fb8d4c6f385
--- /dev/null
+++ b/ext/filter/tests/filter_recursive_stack_limit.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Stack overflow in filter_var() and filter_var_array() with deeply nested arrays
+--EXTENSIONS--
+filter
+--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
+$deep = 'x';
+for ($i = 0; $i < 30000; $i++) {
+    $deep = [$deep];
+}
+
+/* Two top-level entries: the second must not be visited once the
+ * stack limit error has been thrown, so only one Error is thrown. */
+try {
+    filter_var([$deep, $deep], FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
+} catch (Error $e) {
+    echo $e->getMessage(), "\n";
+    var_dump($e->getPrevious());
+}
+try {
+    filter_var_array(['a' => $deep, 'b' => $deep], ['a' => ['flags' => FILTER_REQUIRE_ARRAY], 'b' => ['flags' => FILTER_REQUIRE_ARRAY]]);
+} catch (Error $e) {
+    echo $e->getMessage(), "\n";
+    var_dump($e->getPrevious());
+}
+
+unset($e);
+while (is_array($deep)) {
+    $deep = $deep[0];
+}
+?>
+--EXPECTF--
+Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
+NULL
+Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
+NULL