From ff936a3a7d0e848ed6fab63bd2f3957a829240d4 Mon Sep 17 00:00:00 2001 From: Raphael Paez Date: Fri, 23 Oct 2026 15:29:59 -0300 Subject: [PATCH] Fix GH-24579: Validate option_array type before accessing as hash table Calling filter_var() with FILTER_VALIDATE_INT crashed when the input object implemented __toString() and modified the options array referenced by the filter arguments during string conversion. The FETCH_*_OPTION macros checked if option_array was non-null but did not validate it was an array before using Z_ARRVAL_P(). When __toString() mutated the reference to a string, the code attempted to access it as a hash table, causing a crash (SIGSEGV in non-debug builds, assertion failure in debug builds). This commit adds a Z_TYPE_P(option_array) == IS_ARRAY check to FETCH_LONG_OPTION, FETCH_DOUBLE_OPTION, and FETCH_STRING_OPTION to ensure the type is an array before accessing as a hash table. Added regression test GH-24579. Fixes GH-24579. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ext/filter/logical_filters.c | 6 +++--- ext/filter/tests/gh24579.phpt | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 ext/filter/tests/gh24579.phpt diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index 45acf6d47541..1c46e3a9d141 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -35,7 +35,7 @@ #define FETCH_DOUBLE_OPTION(var_name, option_name) \ var_name = 0; \ var_name##_set = 0; \ - if (option_array) { \ + if (option_array && Z_TYPE_P(option_array) == IS_ARRAY) { \ if ((option_val = zend_hash_str_find(Z_ARRVAL_P(option_array), option_name, sizeof(option_name) - 1)) != NULL) { \ var_name = zval_get_double(option_val); \ var_name##_set = 1; \ @@ -47,7 +47,7 @@ #define FETCH_LONG_OPTION(var_name, option_name) \ var_name = 0; \ var_name##_set = 0; \ - if (option_array) { \ + if (option_array && Z_TYPE_P(option_array) == IS_ARRAY) { \ if ((option_val = zend_hash_str_find(Z_ARRVAL_P(option_array), option_name, sizeof(option_name) - 1)) != NULL) { \ var_name = zval_get_long(option_val); \ var_name##_set = 1; \ @@ -60,7 +60,7 @@ var_name = NULL; \ var_name##_set = 0; \ var_name##_len = 0; \ - if (option_array) { \ + if (option_array && Z_TYPE_P(option_array) == IS_ARRAY) { \ if ((option_val = zend_hash_str_find_deref(Z_ARRVAL_P(option_array), option_name, sizeof(option_name) - 1)) != NULL) { \ if (Z_TYPE_P(option_val) == IS_STRING) { \ var_name = Z_STRVAL_P(option_val); \ diff --git a/ext/filter/tests/gh24579.phpt b/ext/filter/tests/gh24579.phpt new file mode 100644 index 000000000000..fc4941084f53 --- /dev/null +++ b/ext/filter/tests/gh24579.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-24579: FILTER_VALIDATE_INT should not crash when __toString() mutates options array +--EXTENSIONS-- +filter +--FILE-- + 0]; +$args = ['options' => &$shared]; + +class Reenter +{ + public function __toString(): string + { + global $shared; + $shared = 'changed'; + return '1'; + } +} + +var_dump(filter_var(new Reenter(), FILTER_VALIDATE_INT, $args)); + +echo "No crash\n"; +?> +--EXPECT-- +int(1) +No crash