From 34894738de8cda20ba2abbb8e5d3e37b435fc694 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Tue, 16 Jun 2026 18:17:08 -0400 Subject: [PATCH] Fix leak of preserved input string with FILTER_THROW_ON_FAILURE php_zval_filter() copies the filtered value so it can be quoted in the FilterFailedException message, then released the copy with zend_string_delref(), which only decrements the refcount. When the input is a non-string scalar that convert_to_string() turns into a fresh heap string, the copy was the sole owner and leaked one string per call on both the failure and the success path. Use zend_string_release() so it is freed at refcount zero. Closes GH-22339 --- ext/filter/filter.c | 4 +- .../tests/filter_throw_on_failure_leak.phpt | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 ext/filter/tests/filter_throw_on_failure_leak.phpt diff --git a/ext/filter/filter.c b/ext/filter/filter.c index 4a928379877b..a169ecf987d0 100644 --- a/ext/filter/filter.c +++ b/ext/filter/filter.c @@ -298,10 +298,10 @@ static void php_zval_filter(zval *value, zend_long filter, zend_long flags, zval filter_func.name, ZSTR_VAL(copy_for_throwing) ); - zend_string_delref(copy_for_throwing); + zend_string_release(copy_for_throwing); return; } - zend_string_delref(copy_for_throwing); + zend_string_release(copy_for_throwing); copy_for_throwing = NULL; } diff --git a/ext/filter/tests/filter_throw_on_failure_leak.phpt b/ext/filter/tests/filter_throw_on_failure_leak.phpt new file mode 100644 index 000000000000..d42896a94e8f --- /dev/null +++ b/ext/filter/tests/filter_throw_on_failure_leak.phpt @@ -0,0 +1,37 @@ +--TEST-- +filter: FILTER_THROW_ON_FAILURE does not leak the preserved input string +--EXTENSIONS-- +filter +--FILE-- + exception thrown. +var_dump(leakcheck(function () { + try { + filter_var(1.5, FILTER_VALIDATE_INT, ['flags' => FILTER_THROW_ON_FAILURE]); + } catch (\Filter\FilterFailedException $e) { + } +})); + +// Validation succeeds. +var_dump(leakcheck(function () { + filter_var(15, FILTER_VALIDATE_INT, ['flags' => FILTER_THROW_ON_FAILURE]); +})); +?> +--EXPECT-- +bool(true) +bool(true)