From 7c3b07a125bc853d3c1f34f1772d9e9efa59e755 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Fri, 18 Sep 2026 21:16:17 +0200 Subject: [PATCH] Fix OSS-Fuzz #552682112: assertion failure wrt zp_arg_must_be_sent_by_ref() Runtime rejects sending PFA args non-variable in a by-ref position. The const expression path needs a similar check. --- UPGRADING.INTERNALS | 1 + .../partial_application/constexpr_016.phpt | 43 +++++++++++++++++++ Zend/zend_ast.c | 9 +++- Zend/zend_execute.c | 13 ++++-- Zend/zend_execute.h | 1 + 5 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 Zend/tests/partial_application/constexpr_016.phpt diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 8bbdc5caabe5..255d4c2ca893 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -215,6 +215,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES . Added zend_argument_error_ex(), zend_argument_type_error_ex(), zend_argument_value_error_ex(). . Added zend_ast_dup(). + . Added zend_cannot_pass_by_reference_ex(). . Added zend_compile_ast(). . Added zend_check_type_ex(). . Added zend_create_partial_closure(). diff --git a/Zend/tests/partial_application/constexpr_016.phpt b/Zend/tests/partial_application/constexpr_016.phpt new file mode 100644 index 000000000000..035831e07ff6 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_016.phpt @@ -0,0 +1,43 @@ +--TEST-- +PFA in constexpr: binding a by-reference parameter +--FILE-- +getMessage(), "\n"; + } +} + +function f6($x = byRef(new stdClass, ?)) { + return $x; +} + +$partial = f6(); +$var = 1; +var_dump($partial instanceof Closure); +$partial($var); + +?> +--EXPECT-- +Error: byRef(): Argument #2 ($b) could not be passed by reference +Error: byRef(): Argument #2 ($b) could not be passed by reference +Error: byRefVariadic(): Argument #1 could not be passed by reference +Error: byRefVariadic(): Argument #1 could not be passed by reference +Error: C::staticByRef(): Argument #2 ($b) could not be passed by reference +bool(true) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 6a71fc5aeca3..b81296599d6d 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -714,7 +714,8 @@ static zend_execute_data *zend_ast_evaluate_arg_list( arg = ZEND_CALL_VAR_NUM(frame, ZEND_CALL_NUM_ARGS(frame)); } - if (arg_ast->kind == ZEND_AST_PLACEHOLDER_ARG) { + bool is_placeholder = arg_ast->kind == ZEND_AST_PLACEHOLDER_ARG; + if (is_placeholder) { if (arg_ast->attr == ZEND_PLACEHOLDER_VARIADIC) { if (uses_variadic_placeholder) { *uses_variadic_placeholder = true; @@ -732,6 +733,12 @@ static zend_execute_data *zend_ast_evaluate_arg_list( if (!arg_name) { ZEND_CALL_NUM_ARGS(frame)++; } + + /* A constant expression can't be bound to a reference because it ain't a CV. */ + if (!is_placeholder && UNEXPECTED(ARG_MUST_BE_SENT_BY_REF(func, arg_num))) { + zend_cannot_pass_by_reference_ex(func, arg_num); + goto fail; + } } return frame; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 14a340ffee37..93c440c9a71a 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -599,11 +599,10 @@ static zend_never_inline ZEND_COLD zval *zend_wrong_assign_to_variable_reference return zend_assign_to_variable_ex(variable_ptr, value_ptr, IS_TMP_VAR, EX_USES_STRICT_TYPES(), garbage_ptr); } -ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_cannot_pass_by_reference(uint32_t arg_num) +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_cannot_pass_by_reference_ex(const zend_function *func, uint32_t arg_num) { - const zend_execute_data *execute_data = EG(current_execute_data); - zend_string *func_name = get_function_or_method_name(EX(call)->func); - const char *param_name = get_function_arg_name(EX(call)->func, arg_num); + zend_string *func_name = get_function_or_method_name(func); + const char *param_name = get_function_arg_name(func, arg_num); zend_throw_error(NULL, "%s(): Argument #%d%s%s%s could not be passed by reference", ZSTR_VAL(func_name), arg_num, param_name ? " ($" : "", param_name ? param_name : "", param_name ? ")" : "" @@ -612,6 +611,12 @@ ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_cannot_pass_by_refe zend_string_release(func_name); } +ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_cannot_pass_by_reference(uint32_t arg_num) +{ + const zend_execute_data *execute_data = EG(current_execute_data); + zend_cannot_pass_by_reference_ex(EX(call)->func, arg_num); +} + static zend_never_inline ZEND_COLD void zend_throw_auto_init_in_prop_error(const zend_property_info *prop) { zend_string *type_str = zend_type_to_string(prop->type); zend_type_error( diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 017c8d208a14..007d6f260ef0 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -474,6 +474,7 @@ ZEND_API uint32_t zend_get_executed_lineno(void); ZEND_API zend_class_entry *zend_get_executed_scope(void); ZEND_API bool zend_is_executing(void); ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_cannot_pass_by_reference(uint32_t arg_num); +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_cannot_pass_by_reference_ex(const zend_function *func, uint32_t arg_num); ZEND_API void zend_set_timeout(zend_long seconds, bool reset_signals); ZEND_API void zend_unset_timeout(void);