From 3e788ffe5d2a8891dc2ce3d0128d2a4aee2d52eb Mon Sep 17 00:00:00 2001 From: PuH4ck3rX Date: Sat, 11 Jul 2026 07:31:57 +0800 Subject: [PATCH] Fix callable invalidation during autoloading --- NEWS | 3 ++ ...callable_resolution_autoload_mutation.phpt | 41 +++++++++++++++++++ .../dynamic_call_array_autoload_mutation.phpt | 18 ++++++++ ...dynamic_call_string_autoload_mutation.phpt | 18 ++++++++ Zend/zend_API.c | 14 +++++-- Zend/zend_execute.c | 18 +++++--- 6 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 Zend/tests/callable_resolution_autoload_mutation.phpt create mode 100644 Zend/tests/dynamic_call_array_autoload_mutation.phpt create mode 100644 Zend/tests/dynamic_call_string_autoload_mutation.phpt diff --git a/NEWS b/NEWS index 7324e5c591aa..b14e9163a92e 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.24 +- Core: + . Fixed use-after-free when an autoloader modifies a callable. (PuH4ck3rX) + - Calendar: . Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with INT_MAX year). (arshidkv12) diff --git a/Zend/tests/callable_resolution_autoload_mutation.phpt b/Zend/tests/callable_resolution_autoload_mutation.phpt new file mode 100644 index 000000000000..4b2a29cf1b45 --- /dev/null +++ b/Zend/tests/callable_resolution_autoload_mutation.phpt @@ -0,0 +1,41 @@ +--TEST-- +Autoloading must not invalidate callables during callable resolution +--FILE-- + +--EXPECT-- +bool(true) +original +mOne diff --git a/Zend/tests/dynamic_call_array_autoload_mutation.phpt b/Zend/tests/dynamic_call_array_autoload_mutation.phpt new file mode 100644 index 000000000000..872a82dd1968 --- /dev/null +++ b/Zend/tests/dynamic_call_array_autoload_mutation.phpt @@ -0,0 +1,18 @@ +--TEST-- +Autoloading must not invalidate an array callable during a dynamic call +--FILE-- + +--EXPECT-- +original diff --git a/Zend/tests/dynamic_call_string_autoload_mutation.phpt b/Zend/tests/dynamic_call_string_autoload_mutation.phpt new file mode 100644 index 000000000000..8862f5a9006b --- /dev/null +++ b/Zend/tests/dynamic_call_string_autoload_mutation.phpt @@ -0,0 +1,18 @@ +--TEST-- +Autoloading must not invalidate a string callable during a dynamic call +--FILE-- + +--EXPECT-- +mOne diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 3c9891a00e92..d01eca577344 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -4179,6 +4179,7 @@ ZEND_API bool zend_is_callable_at_frame( bool ret; zend_fcall_info_cache fcc_local; bool strict_class = 0; + zval callable_copy; if (fcc == NULL) { fcc = &fcc_local; @@ -4205,9 +4206,11 @@ ZEND_API bool zend_is_callable_at_frame( fcc->called_scope = fcc->calling_scope; return 1; } + ZVAL_STR_COPY(&callable_copy, Z_STR_P(callable)); check_func: - ret = zend_is_callable_check_func(callable, frame, fcc, strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS); + ret = zend_is_callable_check_func(&callable_copy, frame, fcc, strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS); + zval_ptr_dtor(&callable_copy); if (fcc == &fcc_local) { zend_release_fcall_info_cache(fcc); } @@ -4244,7 +4247,12 @@ ZEND_API bool zend_is_callable_at_frame( return 1; } - if (!zend_is_callable_check_class(Z_STR_P(obj), get_scope(frame), frame, fcc, &strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS)) { + ZVAL_STR_COPY(&callable_copy, Z_STR_P(method)); + zend_string *class_name = zend_string_copy(Z_STR_P(obj)); + ret = zend_is_callable_check_class(class_name, get_scope(frame), frame, fcc, &strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS); + zend_string_release(class_name); + if (!ret) { + zval_ptr_dtor(&callable_copy); return 0; } } else { @@ -4256,9 +4264,9 @@ ZEND_API bool zend_is_callable_at_frame( fcc->called_scope = fcc->calling_scope; return 1; } + ZVAL_STR_COPY(&callable_copy, Z_STR_P(method)); } - callable = method; goto check_func; } return 0; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index ea5f55cf6ef7..f2303250c87c 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4871,15 +4871,15 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s size_t mname_length = ZSTR_LEN(function) - cname_length - (sizeof("::") - 1); lcname = zend_string_init(ZSTR_VAL(function), cname_length, 0); + mname = zend_string_init(ZSTR_VAL(function) + (cname_length + sizeof("::") - 1), mname_length, 0); called_scope = zend_fetch_class_by_name(lcname, NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION); if (UNEXPECTED(called_scope == NULL)) { zend_string_release_ex(lcname, 0); + zend_string_release_ex(mname, 0); return NULL; } - mname = zend_string_init(ZSTR_VAL(function) + (cname_length + sizeof("::") - 1), mname_length, 0); - if (called_scope->get_static_method) { fbc = called_scope->get_static_method(called_scope, mname); } else { @@ -5008,23 +5008,29 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_array(zend_ar } if (Z_TYPE_P(obj) == IS_STRING) { - zend_class_entry *called_scope = zend_fetch_class_by_name(Z_STR_P(obj), NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION); + zend_string *class_name = zend_string_copy(Z_STR_P(obj)); + zend_string *method_name = zend_string_copy(Z_STR_P(method)); + zend_class_entry *called_scope = zend_fetch_class_by_name(class_name, NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION); + zend_string_release(class_name); if (UNEXPECTED(called_scope == NULL)) { + zend_string_release(method_name); return NULL; } if (called_scope->get_static_method) { - fbc = called_scope->get_static_method(called_scope, Z_STR_P(method)); + fbc = called_scope->get_static_method(called_scope, method_name); } else { - fbc = zend_std_get_static_method(called_scope, Z_STR_P(method), NULL); + fbc = zend_std_get_static_method(called_scope, method_name, NULL); } if (UNEXPECTED(fbc == NULL)) { if (EXPECTED(!EG(exception))) { - zend_undefined_method(called_scope, Z_STR_P(method)); + zend_undefined_method(called_scope, method_name); } + zend_string_release(method_name); return NULL; } + zend_string_release(method_name); if (!(fbc->common.fn_flags & ZEND_ACC_STATIC)) { zend_non_static_method_call(fbc); if (fbc->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {