From 462fcad4198c1bedef67184bf6c0bc90facb3a29 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 15 Jan 2026 23:46:41 +0100 Subject: [PATCH 1/6] Avoid huge output in gh20840.phpt This can trigger the memory limit in run-tests.php, which buffers the tests output. Instead, only output "nesting level too deep" and discard the rest. Closes GH-20946 --- ext/standard/tests/general_functions/gh20840.phpt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ext/standard/tests/general_functions/gh20840.phpt b/ext/standard/tests/general_functions/gh20840.phpt index 839b4728be18..ac0440a3bcdf 100644 --- a/ext/standard/tests/general_functions/gh20840.phpt +++ b/ext/standard/tests/general_functions/gh20840.phpt @@ -28,11 +28,20 @@ for ($i = 0; $i < 50000; $i++) { $node = $newNode; } +$buffer = ''; +ob_start(function ($chunk) use (&$buffer) { + $buffer .= $chunk; + $buffer = preg_replace('(\s*object\(Node\)#\d+ \(\d+\) \{\s*)', '', $buffer); + $buffer = preg_replace('(\s*\["next"\]=>\s*)', '', $buffer); + $buffer = preg_replace('(\s*\}\s*)', '', $buffer); +}); var_dump($firstNode); +ob_end_flush(); +echo $buffer; while ($next = $firstNode->next) { $firstNode->next = $next->next; } ?> ---EXPECTREGEX-- -^object\(Node\)#\d+ \(\d+\).*(nesting level too deep|["\s}]*)$ +--EXPECT-- +nesting level too deep From 6e6a850cb99d90e9979de1381641d20236ce11ba Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:40:31 +0100 Subject: [PATCH 2/6] Followup GH-19022 * Fix zend_call_trampoline_arginfo arg name Name is "arguments" in documentation: https://www.php.net/__call#language.oop5.overloading.methods * Use zend_call_trampoline_arginfo in zend_get_call_trampoline_func() * Copy the original arg_info in zend_closure_from_frame None of these changes are observable, but this is cleaner, and this becomes observable in GH-20848. Closes GH-20951 --- Zend/zend.c | 1 - Zend/zend_closures.c | 14 +------------- Zend/zend_closures.h | 1 - Zend/zend_object_handlers.c | 6 +++--- Zend/zend_string.h | 1 + 5 files changed, 5 insertions(+), 18 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index c46c8e9ada86..6b0ffb73ad76 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1082,7 +1082,6 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ #endif zend_enum_startup(); - zend_closure_startup(); } /* }}} */ diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 05b686204481..43eefbeff9da 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -878,8 +878,6 @@ ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_clas } /* }}} */ -static zend_arg_info trampoline_arg_info[1]; - void zend_closure_from_frame(zval *return_value, const zend_execute_data *call) { /* {{{ */ zval instance; zend_internal_function trampoline; @@ -904,9 +902,7 @@ void zend_closure_from_frame(zval *return_value, const zend_execute_data *call) trampoline.function_name = mptr->common.function_name; trampoline.scope = mptr->common.scope; trampoline.doc_comment = NULL; - if (trampoline.fn_flags & ZEND_ACC_VARIADIC) { - trampoline.arg_info = trampoline_arg_info; - } + trampoline.arg_info = mptr->common.arg_info; trampoline.attributes = mptr->common.attributes; zend_free_trampoline(mptr); @@ -943,11 +939,3 @@ void zend_closure_bind_var_ex(zval *closure_zv, uint32_t offset, zval *val) /* { ZVAL_COPY_VALUE(var, val); } /* }}} */ - -void zend_closure_startup(void) -{ - /* __call and __callStatic name the arguments "$arguments" in the docs. */ - trampoline_arg_info[0].name = zend_string_init_interned("arguments", strlen("arguments"), true); - trampoline_arg_info[0].type = (zend_type)ZEND_TYPE_INIT_CODE(IS_MIXED, false, _ZEND_ARG_INFO_FLAGS(false, 1, 0)); - trampoline_arg_info[0].default_value = NULL; -} diff --git a/Zend/zend_closures.h b/Zend/zend_closures.h index a118044c6e24..8bea4ffb051e 100644 --- a/Zend/zend_closures.h +++ b/Zend/zend_closures.h @@ -28,7 +28,6 @@ BEGIN_EXTERN_C() #define ZEND_CLOSURE_OBJECT(op_array) \ ((zend_object*)((char*)(op_array) - sizeof(zend_object))) -void zend_closure_startup(void); void zend_register_closure_ce(void); void zend_closure_bind_var(zval *closure_zv, zend_string *var_name, zval *var); void zend_closure_bind_var_ex(zval *closure_zv, uint32_t offset, zval *val); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 88b7b1112d7b..baa27fd9e420 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1685,7 +1685,6 @@ ZEND_API ZEND_ATTRIBUTE_NONNULL zend_function *zend_get_call_trampoline_func( * The low bit must be zero, to not be interpreted as a MAP_PTR offset. */ static const void *dummy = (void*)(intptr_t)2; - static const zend_arg_info arg_info[1] = {{0}}; if (EXPECTED(EG(trampoline).common.function_name == NULL)) { func = &EG(trampoline).op_array; @@ -1732,7 +1731,7 @@ ZEND_API ZEND_ATTRIBUTE_NONNULL zend_function *zend_get_call_trampoline_func( func->prop_info = NULL; func->num_args = 0; func->required_num_args = 0; - func->arg_info = (zend_arg_info *) arg_info; + func->arg_info = zend_call_trampoline_arginfo; return (zend_function*)func; } @@ -2576,6 +2575,7 @@ ZEND_API const zend_object_handlers std_object_handlers = { }; void zend_object_handlers_startup(void) { - zend_call_trampoline_arginfo[0].name = ZSTR_KNOWN(ZEND_STR_ARGS); + zend_call_trampoline_arginfo[0].name = ZSTR_KNOWN(ZEND_STR_ARGUMENTS); + zend_call_trampoline_arginfo[0].type = (zend_type)ZEND_TYPE_INIT_CODE(IS_MIXED, false, _ZEND_ARG_INFO_FLAGS(false, 1, 0)); zend_property_hook_arginfo[0].name = ZSTR_KNOWN(ZEND_STR_VALUE); } diff --git a/Zend/zend_string.h b/Zend/zend_string.h index fc7705ff7865..97386ea6bad6 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -563,6 +563,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_OBJECT_OPERATOR, "->") \ _(ZEND_STR_PAAMAYIM_NEKUDOTAYIM, "::") \ _(ZEND_STR_ARGS, "args") \ + _(ZEND_STR_ARGUMENTS, "arguments") \ _(ZEND_STR_UNKNOWN, "unknown") \ _(ZEND_STR_UNKNOWN_CAPITALIZED, "Unknown") \ _(ZEND_STR_EXIT, "exit") \ From 0efecbc432538a86dde4714b5d5cd7dbf212bc1f Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 15 Jan 2026 14:53:57 +0100 Subject: [PATCH 3/6] Fix by-ref assignment to uninitialized hooked backing value Within hooks, the backing value can directly be accessed as if no hooks were present. This was previously handled only in read_property(). zend_fetch_property_address(), which is used for by-ref assignment, will first call get_property_ptr_ptr() and then try read_property(). However, when called on uninitialized backing values, read_property() will return &EG(uninitialized_zval) with an uninitialized property warning. This is problematic for zend_fetch_property_address() because it write to the result of read_property() unless there's an exception. For untyped properties, this can result in writes to &EG(uninitialized_zval) (see oss-fuzz-471486164-001.phpt). For types properties, it will result in an unexpected "Typed property C::$prop must not be accessed before initialization" exception. Fixes OSS-Fuzz #471486164 Closes GH-20943 --- NEWS | 2 ++ Zend/tests/oss-fuzz-471486164-001.phpt | 22 ++++++++++++++++++++++ Zend/tests/oss-fuzz-471486164-002.phpt | 26 ++++++++++++++++++++++++++ Zend/zend_object_handlers.c | 11 ++++++++++- 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/oss-fuzz-471486164-001.phpt create mode 100644 Zend/tests/oss-fuzz-471486164-002.phpt diff --git a/NEWS b/NEWS index 0c4c5da48b43..19f900235d81 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,8 @@ PHP NEWS . Fixed bug GH-GH-20914 (Internal enums can be cloned and compared). (Arnaud) . Fix OSS-Fuzz #474613951 (Leaked parent property default value). (ilutov) . Fixed bug GH-20766 (Use-after-free in FE_FREE with GC interaction). (Bob) + . Fix OSS-Fuzz #471486164 (Broken by-ref assignment to uninitialized hooked + backing value). (ilutov) - Date: . Update timelib to 2022.16. (Derick) diff --git a/Zend/tests/oss-fuzz-471486164-001.phpt b/Zend/tests/oss-fuzz-471486164-001.phpt new file mode 100644 index 000000000000..a48a56398c1e --- /dev/null +++ b/Zend/tests/oss-fuzz-471486164-001.phpt @@ -0,0 +1,22 @@ +--TEST-- +OSS-Fuzz #471486164: get_property_ptr_ptr() on uninitialized hooked property +--FILE-- + $this->a; + set { $this->a = &$value; } + } + public $x = 1; +} + +$proxy = (new ReflectionClass(C::class))->newLazyProxy(function ($proxy) { + $proxy->a = 1; + return new C; +}); +var_dump($proxy->x); + +?> +--EXPECT-- +int(1) diff --git a/Zend/tests/oss-fuzz-471486164-002.phpt b/Zend/tests/oss-fuzz-471486164-002.phpt new file mode 100644 index 000000000000..688dd7612201 --- /dev/null +++ b/Zend/tests/oss-fuzz-471486164-002.phpt @@ -0,0 +1,26 @@ +--TEST-- +OSS-Fuzz #471486164: get_property_ptr_ptr() on uninitialized hooked property +--FILE-- + $this->a; + set { + global $ref; + $this->a = &$ref; + } + } +} + +$ref = 1; +$proxy = new C; +$proxy->a = 1; +var_dump($proxy->a); +$ref++; +var_dump($proxy->a); + +?> +--EXPECT-- +int(1) +int(2) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index c113f65a7a89..ccedb79acc08 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1392,6 +1392,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__get != NULL), cache_slot, &prop_info); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { +try_again: retval = OBJ_PROP(zobj, property_offset); if (UNEXPECTED(Z_TYPE_P(retval) == IS_UNDEF)) { if (EXPECTED(!zobj->ce->__get) || @@ -1471,7 +1472,15 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam } retval = zend_hash_add(zobj->properties, name, &EG(uninitialized_zval)); } - } else if (!IS_HOOKED_PROPERTY_OFFSET(property_offset) && zobj->ce->__get == NULL) { + } else if (IS_HOOKED_PROPERTY_OFFSET(property_offset)) { + if (!(prop_info->flags & ZEND_ACC_VIRTUAL) && !zend_should_call_hook(prop_info, zobj)) { + property_offset = prop_info->offset; + if (!ZEND_TYPE_IS_SET(prop_info->type)) { + prop_info = NULL; + } + goto try_again; + } + } else if (zobj->ce->__get == NULL) { retval = &EG(error_zval); } From 19b30032c9d7592e5b390de07d47de7abff90db1 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Fri, 15 Aug 2025 15:59:15 +0200 Subject: [PATCH 4/6] Fix uaf for nested finally with repeated return type check Fixes OSS-Fuzz #438780145 Closes GH-19488 --- NEWS | 2 ++ Zend/tests/oss_fuzz_438780145.phpt | 27 +++++++++++++++++++++++++++ Zend/zend_vm_def.h | 4 ++++ Zend/zend_vm_execute.h | 4 ++++ 4 files changed, 37 insertions(+) create mode 100644 Zend/tests/oss_fuzz_438780145.phpt diff --git a/NEWS b/NEWS index 19f900235d81..39f1d4e7db3a 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,8 @@ PHP NEWS . Fixed bug GH-20766 (Use-after-free in FE_FREE with GC interaction). (Bob) . Fix OSS-Fuzz #471486164 (Broken by-ref assignment to uninitialized hooked backing value). (ilutov) + . Fix OSS-Fuzz #438780145 (Nested finally with repeated return type check may + uaf). (ilutov) - Date: . Update timelib to 2022.16. (Derick) diff --git a/Zend/tests/oss_fuzz_438780145.phpt b/Zend/tests/oss_fuzz_438780145.phpt new file mode 100644 index 000000000000..4c6936a69a01 --- /dev/null +++ b/Zend/tests/oss_fuzz_438780145.phpt @@ -0,0 +1,27 @@ +--TEST-- +OSS-Fuzz #438780145: Nested finally with repeated return type check may uaf +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: test(): Return value must be of type int, string returned in %s:%d +Stack trace: +#0 %s(%d): test() +#1 {main} + thrown in %s on line %d diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 1f06eab120d3..3e1026ef60e8 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8537,6 +8537,10 @@ ZEND_VM_HANDLER(159, ZEND_DISCARD_EXCEPTION, ANY, ANY) zval *return_value = EX_VAR(EX(func)->op_array.opcodes[Z_OPLINE_NUM_P(fast_call)].op2.var); zval_ptr_dtor(return_value); + /* Clear return value in case we hit both DISCARD_EXCEPTION and + * zend_dispatch_try_catch_finally_helper, which will free the return + * value again. See OSS-Fuzz #438780145. */ + ZVAL_NULL(return_value); } /* cleanup delayed exception */ diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index fdef3e3a1b74..46c04bdd6d28 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3365,6 +3365,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DISCARD_EXCEPTION_SPEC_HANDLER zval *return_value = EX_VAR(EX(func)->op_array.opcodes[Z_OPLINE_NUM_P(fast_call)].op2.var); zval_ptr_dtor(return_value); + /* Clear return value in case we hit both DISCARD_EXCEPTION and + * zend_dispatch_try_catch_finally_helper, which will free the return + * value again. See OSS-Fuzz #438780145. */ + ZVAL_NULL(return_value); } /* cleanup delayed exception */ From 8b4ef3a09f078a69a39ccf4429ed36382f559d4f Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 17 Jul 2025 19:07:15 +0200 Subject: [PATCH 5/6] Fix FETCH_OBJ_UNSET IS_UNDEF result UNSET_OBJ et al. do not expect to find IS_UNDEF results for IS_INDIRECT vars. To solve this, return IS_NULL from FETCH_OBJ_UNSET when properties are uninitialized. Do the same for FETCH_STATIC_PROP_IS, as we're otherwise copying IS_UNDEF into the VAR result, which is not a valid value for VAR. Fixes OSS-Fuzz #429429090 Closes GH-19160 --- NEWS | 2 ++ Zend/Optimizer/zend_inference.c | 2 +- Zend/tests/oss_fuzz_429429090.phpt | 20 ++++++++++++++++++++ Zend/zend_execute.c | 8 ++++++++ Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 4 ++-- 6 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 Zend/tests/oss_fuzz_429429090.phpt diff --git a/NEWS b/NEWS index 062265576af7..f42cfc4e5ee3 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,8 @@ PHP NEWS . It is now possible to use reference assign on WeakMap without the key needing to be present beforehand. (ndossche) . Added `clamp()`. (kylekatarnls, thinkverse) + . Fix OSS-Fuzz #429429090 (Failed assertion on unset() with uninitialized + container). (ilutov) - Date: . Update timelib to 2022.16. (Derick) diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 54670c804d00..9b5561e7fbe2 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -3840,7 +3840,7 @@ static zend_always_inline zend_result _zend_update_type_info( tmp &= ~MAY_BE_RC1; } if (opline->opcode == ZEND_FETCH_STATIC_PROP_IS) { - tmp |= MAY_BE_UNDEF; + tmp |= MAY_BE_NULL; } } UPDATE_SSA_TYPE(tmp, ssa_op->result_def); diff --git a/Zend/tests/oss_fuzz_429429090.phpt b/Zend/tests/oss_fuzz_429429090.phpt new file mode 100644 index 000000000000..d5279c2806ac --- /dev/null +++ b/Zend/tests/oss_fuzz_429429090.phpt @@ -0,0 +1,20 @@ +--TEST-- +OSS-Fuzz #429429090: FETCH_OBJ_UNSET IS_UNDEF result +--FILE-- +x[0]->prop); +unset($c->x[0]->prop); +isset(C::$y[0]->prop); +unset(C::$y[0]->prop); + +?> +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 570aac4a8dbf..a4460286bf64 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -3626,6 +3626,9 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c } else if (UNEXPECTED(Z_ISERROR_P(ptr))) { ZVAL_ERROR(result); goto end; + } else if (type == BP_VAR_UNSET && UNEXPECTED(Z_TYPE_P(ptr) == IS_UNDEF)) { + ZVAL_NULL(result); + goto end; } ZVAL_INDIRECT(result, ptr); @@ -3777,6 +3780,11 @@ static zend_never_inline zval* zend_fetch_static_property_address_ex(zend_proper return NULL; } + if (UNEXPECTED(Z_TYPE_P(result) == IS_UNDEF) + && (fetch_type == BP_VAR_IS || fetch_type == BP_VAR_UNSET)) { + return NULL; + } + *prop_info = property_info; if (EXPECTED(op1_type == IS_CONST) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 9afca6015c53..86de5992a8f8 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -1866,7 +1866,7 @@ ZEND_VM_INLINE_HELPER(zend_fetch_static_prop_helper, ANY, ANY, int type) &prop_info, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS, type, type == BP_VAR_W ? opline->extended_value : 0 OPLINE_CC EXECUTE_DATA_CC); if (UNEXPECTED(!prop)) { - ZEND_ASSERT(EG(exception) || (type == BP_VAR_IS)); + ZEND_ASSERT(EG(exception) || (type == BP_VAR_IS) || (type == BP_VAR_UNSET)); prop = &EG(uninitialized_zval); } else if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK) && (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index e0c380deedd3..62d645c15109 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -895,7 +895,7 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET zend_fetch_static_prop_helper_ &prop_info, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS, type, type == BP_VAR_W ? opline->extended_value : 0 OPLINE_CC EXECUTE_DATA_CC); if (UNEXPECTED(!prop)) { - ZEND_ASSERT(EG(exception) || (type == BP_VAR_IS)); + ZEND_ASSERT(EG(exception) || (type == BP_VAR_IS) || (type == BP_VAR_UNSET)); prop = &EG(uninitialized_zval); } else if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK) && (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) @@ -112163,7 +112163,7 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET zend_fetch_static_prop_helper_ &prop_info, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS, type, type == BP_VAR_W ? opline->extended_value : 0 OPLINE_CC EXECUTE_DATA_CC); if (UNEXPECTED(!prop)) { - ZEND_ASSERT(EG(exception) || (type == BP_VAR_IS)); + ZEND_ASSERT(EG(exception) || (type == BP_VAR_IS) || (type == BP_VAR_UNSET)); prop = &EG(uninitialized_zval); } else if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK) && (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) From 084e409694202af1562d3c5f91caeb88534afe2c Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 22 Oct 2025 01:03:43 +0200 Subject: [PATCH 6/6] Remove zend_exception_save() and zend_exception_restore() These are leftovers from the pre-PHP-7.0 era. This also implicitly solves GH-20564 by not clearing exceptions before entering the autoloader. Closes GH-20256 Fixes GH-20564 --- NEWS | 1 + UPGRADING.INTERNALS | 2 ++ Zend/tests/gh20564.phpt | 24 ++++++++++++++++++++++++ Zend/zend.c | 1 - Zend/zend_exceptions.c | 29 ----------------------------- Zend/zend_exceptions.h | 2 -- Zend/zend_execute.c | 1 + Zend/zend_execute_API.c | 3 --- Zend/zend_globals.h | 2 +- Zend/zend_language_scanner.l | 5 +++-- Zend/zend_vm_def.h | 3 --- Zend/zend_vm_execute.h | 14 -------------- ext/opcache/ZendAccelerator.c | 1 - sapi/fuzzer/fuzzer-execute-common.h | 2 -- sapi/phpdbg/phpdbg_prompt.c | 5 ----- 15 files changed, 32 insertions(+), 63 deletions(-) create mode 100644 Zend/tests/gh20564.phpt diff --git a/NEWS b/NEWS index f42cfc4e5ee3..74816c94a290 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ PHP NEWS . Added `clamp()`. (kylekatarnls, thinkverse) . Fix OSS-Fuzz #429429090 (Failed assertion on unset() with uninitialized container). (ilutov) + . Fixed GH-20564 (Don't call autoloaders with pending exception). (ilutov) - Date: . Update timelib to 2022.16. (Derick) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 748549e085b8..2132006540c4 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -54,6 +54,8 @@ PHP 8.6 INTERNALS UPGRADE NOTES ZEND_ACC_USER_ARG_INFO flag was set. . Added zend_ast_call_get_args() to fetch the argument node from any call node. + . The zend_exception_save() and zend_exception_restore() functions were + removed. ======================== 2. Build system changes diff --git a/Zend/tests/gh20564.phpt b/Zend/tests/gh20564.phpt new file mode 100644 index 000000000000..53311d952de7 --- /dev/null +++ b/Zend/tests/gh20564.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-20564: Don't call autoloaders with pending exception +--CREDITS-- +Viet Hoang Luu (@vi3tL0u1s) +--FILE-- +test(); +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +array_map(): Argument #1 ($callback) must be a valid callback or null, class "B" not found diff --git a/Zend/zend.c b/Zend/zend.c index 6b0ffb73ad76..d2be69a75769 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1978,7 +1978,6 @@ ZEND_API zend_result zend_execute_script(int type, zval *retval, zend_file_handl zend_result ret = SUCCESS; if (op_array) { zend_execute(op_array, retval); - zend_exception_restore(); if (UNEXPECTED(EG(exception))) { if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) { zend_user_exception_handler(); diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index 191d8f7fe6ae..52e3ab009254 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -145,31 +145,6 @@ void zend_exception_set_previous(zend_object *exception, zend_object *add_previo } /* }}} */ -void zend_exception_save(void) /* {{{ */ -{ - if (EG(prev_exception)) { - zend_exception_set_previous(EG(exception), EG(prev_exception)); - } - if (EG(exception)) { - EG(prev_exception) = EG(exception); - } - EG(exception) = NULL; -} -/* }}} */ - -void zend_exception_restore(void) /* {{{ */ -{ - if (EG(prev_exception)) { - if (EG(exception)) { - zend_exception_set_previous(EG(exception), EG(prev_exception)); - } else { - EG(exception) = EG(prev_exception); - } - EG(prev_exception) = NULL; - } -} -/* }}} */ - static zend_always_inline bool is_handle_exception_set(void) { zend_execute_data *execute_data = EG(current_execute_data); return !execute_data @@ -241,10 +216,6 @@ ZEND_API ZEND_COLD void zend_throw_exception_internal(zend_object *exception) /* ZEND_API void zend_clear_exception(void) /* {{{ */ { zend_object *exception; - if (EG(prev_exception)) { - OBJ_RELEASE(EG(prev_exception)); - EG(prev_exception) = NULL; - } if (!EG(exception)) { return; } diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index 24d9f4efd80a..e5a6be2f32fe 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -41,8 +41,6 @@ extern ZEND_API zend_class_entry *zend_ce_unhandled_match_error; extern ZEND_API zend_class_entry *zend_ce_request_parse_body_exception; ZEND_API void zend_exception_set_previous(zend_object *exception, zend_object *add_previous); -ZEND_API void zend_exception_save(void); -ZEND_API void zend_exception_restore(void); ZEND_API ZEND_COLD void zend_throw_exception_internal(zend_object *exception); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index a4460286bf64..e95931276ef5 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1315,6 +1315,7 @@ ZEND_API bool zend_internal_call_should_throw(const zend_function *fbc, zend_exe if ((fbc->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) && !zend_verify_internal_arg_types(fbc, call)) { + zend_clear_exception(); return 1; } diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index e134d3d496b6..69337e27fd53 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -176,7 +176,6 @@ void init_executor(void) /* {{{ */ ZEND_ATOMIC_BOOL_INIT(&EG(timed_out), false); EG(exception) = NULL; - EG(prev_exception) = NULL; EG(fake_scope) = NULL; EG(trampoline).common.function_name = NULL; @@ -1268,9 +1267,7 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * zend_long previous_lineno = EG(lineno_override); EG(filename_override) = NULL; EG(lineno_override) = -1; - zend_exception_save(); ce = zend_autoload(autoload_name, lc_name); - zend_exception_restore(); EG(filename_override) = previous_filename; EG(lineno_override) = previous_lineno; diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index ef81ae5faaf2..f09b81acb315 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -255,7 +255,7 @@ struct _zend_executor_globals { zend_objects_store objects_store; zend_lazy_objects_store lazy_objects_store; - zend_object *exception, *prev_exception; + zend_object *exception; const zend_op *opline_before_exception; zend_op exception_op[3]; diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 1e26ddbd9919..5a8a78cc3bd7 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -2765,7 +2765,8 @@ skip_escape_conversion: zend_ptr_stack_reverse_apply(¤t_state.heredoc_label_stack, copy_heredoc_label_stack); - zend_exception_save(); + zend_object *prev_exception = EG(exception); + EG(exception) = NULL; while (heredoc_nesting_level) { zval zv; int retval; @@ -2794,7 +2795,7 @@ skip_escape_conversion: heredoc_nesting_level = 0; } } - zend_exception_restore(); + EG(exception) = prev_exception; if ( (first_token == T_VARIABLE diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 86de5992a8f8..9840bf280407 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4797,10 +4797,8 @@ ZEND_VM_COLD_CONST_HANDLER(108, ZEND_THROW, CONST|TMPVAR|CV, ANY) } } while (0); - zend_exception_save(); Z_TRY_ADDREF_P(value); zend_throw_exception_object(value); - zend_exception_restore(); FREE_OP1(); HANDLE_EXCEPTION(); } @@ -4813,7 +4811,6 @@ ZEND_VM_HANDLER(107, ZEND_CATCH, CONST, JMP_ADDR, LAST_CATCH|CACHE_SLOT) SAVE_OPLINE(); /* Check whether an exception has been thrown, if not, jump over code */ - zend_exception_restore(); if (EG(exception) == NULL) { ZEND_VM_JMP_EX(OP_JMP_ADDR(opline, opline->op2), 0); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 62d645c15109..7aa0296aa426 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5125,10 +5125,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } } while (0); - zend_exception_save(); Z_TRY_ADDREF_P(value); zend_throw_exception_object(value); - zend_exception_restore(); HANDLE_EXCEPTION(); @@ -5142,7 +5140,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CATCH_SPEC_CO SAVE_OPLINE(); /* Check whether an exception has been thrown, if not, jump over code */ - zend_exception_restore(); if (EG(exception) == NULL) { ZEND_VM_JMP_EX(OP_JMP_ADDR(opline, opline->op2), 0); } @@ -15720,10 +15717,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_THROW_SPEC_TM } } while (0); - zend_exception_save(); Z_TRY_ADDREF_P(value); zend_throw_exception_object(value); - zend_exception_restore(); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); HANDLE_EXCEPTION(); } @@ -41901,10 +41896,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_THROW_SPEC_CV } } while (0); - zend_exception_save(); Z_TRY_ADDREF_P(value); zend_throw_exception_object(value); - zend_exception_restore(); HANDLE_EXCEPTION(); @@ -60784,10 +60777,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_THROW } } while (0); - zend_exception_save(); Z_TRY_ADDREF_P(value); zend_throw_exception_object(value); - zend_exception_restore(); HANDLE_EXCEPTION(); @@ -60801,7 +60792,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CATCH_SPEC_CONST_T SAVE_OPLINE(); /* Check whether an exception has been thrown, if not, jump over code */ - zend_exception_restore(); if (EG(exception) == NULL) { ZEND_VM_JMP_EX(OP_JMP_ADDR(opline, opline->op2), 0); } @@ -71277,10 +71267,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_THROW_SPEC_TMPVAR_ } } while (0); - zend_exception_save(); Z_TRY_ADDREF_P(value); zend_throw_exception_object(value); - zend_exception_restore(); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); HANDLE_EXCEPTION(); } @@ -97358,10 +97346,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_THROW_SPEC_CV_TAIL } } while (0); - zend_exception_save(); Z_TRY_ADDREF_P(value); zend_throw_exception_object(value); - zend_exception_restore(); HANDLE_EXCEPTION(); diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 7acb14b778f8..f4134212bc4e 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4663,7 +4663,6 @@ static zend_result accel_preload(const char *config, bool in_child) zend_destroy_file_handle(&file_handle); if (op_array) { zend_execute(op_array, NULL); - zend_exception_restore(); if (UNEXPECTED(EG(exception))) { if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) { zend_user_exception_handler(); diff --git a/sapi/fuzzer/fuzzer-execute-common.h b/sapi/fuzzer/fuzzer-execute-common.h index 338c771e551a..ef2ff4ee79ba 100644 --- a/sapi/fuzzer/fuzzer-execute-common.h +++ b/sapi/fuzzer/fuzzer-execute-common.h @@ -134,7 +134,6 @@ ZEND_ATTRIBUTE_UNUSED static void create_file(void) { ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) { steps_left = MAX_STEPS; - zend_exception_save(); zval retval, args[2]; zend_function *fn = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("opcache_invalidate")); ZEND_ASSERT(fn != NULL); @@ -145,5 +144,4 @@ ZEND_ATTRIBUTE_UNUSED static void opcache_invalidate(void) { ZEND_ASSERT(Z_TYPE(retval) == IS_TRUE); zval_ptr_dtor(&args[0]); zval_ptr_dtor(&retval); - zend_exception_restore(); } diff --git a/sapi/phpdbg/phpdbg_prompt.c b/sapi/phpdbg/phpdbg_prompt.c index 7215888cb25e..9566c1abd4ee 100644 --- a/sapi/phpdbg/phpdbg_prompt.c +++ b/sapi/phpdbg/phpdbg_prompt.c @@ -716,10 +716,6 @@ static inline void phpdbg_handle_exception(void) /* {{{ */ phpdbg_writeln("%s", ZSTR_VAL(msg)); zend_string_release(msg); - if (EG(prev_exception)) { - OBJ_RELEASE(EG(prev_exception)); - EG(prev_exception) = 0; - } OBJ_RELEASE(ex); EG(opline_before_exception) = NULL; @@ -876,7 +872,6 @@ PHPDBG_COMMAND(run) /* {{{ */ } zend_end_try(); if (restore) { - zend_exception_restore(); zend_try { zend_try_exception_handler(); PHPDBG_G(in_execution) = 1;