Commit 0549c8e0545 for php.net

commit 0549c8e05457148ec88eff9d481b37235142d50c
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Thu Jul 16 08:09:34 2026 -0400

    ext/filter: fix use-after-free in parse_str() with filter.default

    php_sapi_filter() re-exported PARSE_STRING results through Z_STRLEN/Z_STRVAL
    without checking the type php_zval_filter() left behind. A validation failure
    frees the string and stores IS_FALSE, so the macros read the freed
    zend_string; a successful FILTER_VALIDATE_INT stores IS_LONG, so they
    dereference the integer as a pointer. Convert with zval_get_tmp_string()
    before writing back.

    Closes GH-22765

diff --git a/ext/filter/filter.c b/ext/filter/filter.c
index c9d0f256b59..c65f9ac7994 100644
--- a/ext/filter/filter.c
+++ b/ext/filter/filter.c
@@ -349,15 +349,18 @@ static unsigned int php_sapi_filter(int arg, const char *var, char **val, size_t
 	}

 	if (retval) {
+		zend_string *tmp_str;
+		zend_string *str = zval_get_tmp_string(&new_var, &tmp_str);
 		if (new_val_len) {
-			*new_val_len = Z_STRLEN(new_var);
+			*new_val_len = ZSTR_LEN(str);
 		}
 		efree(*val);
-		if (Z_STRLEN(new_var)) {
-			*val = estrndup(Z_STRVAL(new_var), Z_STRLEN(new_var));
+		if (ZSTR_LEN(str)) {
+			*val = estrndup(ZSTR_VAL(str), ZSTR_LEN(str));
 		} else {
 			*val = estrdup("");
 		}
+		zend_tmp_string_release(tmp_str);
 		zval_ptr_dtor(&new_var);
 	}

diff --git a/ext/filter/tests/filter_default_parse_str.phpt b/ext/filter/tests/filter_default_parse_str.phpt
new file mode 100644
index 00000000000..71563060db3
--- /dev/null
+++ b/ext/filter/tests/filter_default_parse_str.phpt
@@ -0,0 +1,19 @@
+--TEST--
+filter.default with parse_str must not crash on non-string filter results
+--EXTENSIONS--
+filter
+--INI--
+filter.default=int
+--FILE--
+<?php
+parse_str('a=1&b=notint', $out);
+var_dump($out);
+?>
+--EXPECTF--
+Deprecated: The filter.default ini setting is deprecated in %s on line %d
+array(2) {
+  ["a"]=>
+  string(1) "1"
+  ["b"]=>
+  string(0) ""
+}