[php-src] master: ext/filter: fix use-after-free in parse_str() with filter.default

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-16T12:34:04-04:00

Commit: https://github.com/php/php-src/commit/0549c8e05457148ec88eff9d481b37235142d50c
Raw diff: https://github.com/php/php-src/commit/0549c8e05457148ec88eff9d481b37235142d50c.diff

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

Changed paths:
  A  ext/filter/tests/filter_default_parse_str.phpt
  M  ext/filter/filter.c


Diff:

diff --git a/ext/filter/filter.c b/ext/filter/filter.c
index c9d0f256b590..c65f9ac7994c 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 000000000000..71563060db36
--- /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) ""
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.