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) ""
+}