Commit b5526aa2fe0 for php
commit b5526aa2fe0db6fbff55270e38aff245bff4a9ca
Author: ndossche <7771979+ndossche@users.noreply.github.com>
Date: Thu Sep 24 20:27:27 2026 +0200
Fix OSS-Fuzz #565486253: coerced arg with '...' on non-variadic function
The forwarding call is built 2x, and if the first time coerces an
argument, you have a failed assertion the second time. So the type check
(which does the coercion) has to happen on a value copy.
Closes GH-23895.
diff --git a/NEWS b/NEWS
index a23e22ab27f..5e233c1b818 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,8 @@ PHP NEWS
proxy objects instead of forwarding to the real instance). (lisachenko)
. Fixed bug GH-23882 (array_map() optimization is incorrect for
strict_types=1). (timwolla)
+ . Fixed OSS-Fuzz #565486253 (coerced arg with '...' on non-variadic
+ function). (ndossche)
- Opcache:
. Fix zend_analyze_calls() call_stack buffer overrun. (Mrmaxmeier)
diff --git a/Zend/tests/partial_application/const_arg_opt_004.phpt b/Zend/tests/partial_application/const_arg_opt_004.phpt
new file mode 100644
index 00000000000..f80c14dc6ab
--- /dev/null
+++ b/Zend/tests/partial_application/const_arg_opt_004.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Constant argument optimization - coerced arg with '...' on non-variadic function
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.file_update_protection=0
+--FILE--
+<?php
+function g(string $s, $x = 1) {
+ var_dump($s, func_get_args());
+}
+
+$f = g(15, ...);
+$f();
+$f(2, 3);
+
+?>
+--EXPECT--
+string(2) "15"
+array(2) {
+ [0]=>
+ string(2) "15"
+ [1]=>
+ int(1)
+}
+string(2) "15"
+array(3) {
+ [0]=>
+ string(2) "15"
+ [1]=>
+ int(2)
+ [2]=>
+ int(3)
+}
diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c
index 497e9b80d57..83bd67bccb8 100644
--- a/Zend/zend_partial.c
+++ b/Zend/zend_partial.c
@@ -566,7 +566,10 @@ static zend_ast *zp_compile_forwarding_call(
ZEND_ASSERT(!Z_REFCOUNTED(argv[offset]));
/* This argument never changes, so we can burn it into the op_array
- * and check its type ahead of time. */
+ * and check its type ahead of time.
+ * Work with a value copy because a scalar type check may coerce this value. */
+ zval value;
+ ZVAL_COPY_VALUE(&value, &argv[offset]);
zend_arg_info *arg_info;
if (offset < function->common.num_args) {
@@ -577,18 +580,18 @@ static zend_ast *zp_compile_forwarding_call(
arg_info = NULL;
}
if (arg_info && ZEND_TYPE_IS_SET(arg_info->type)
- && UNEXPECTED(!zend_check_type_ex(&arg_info->type, &argv[offset],
+ && UNEXPECTED(!zend_check_type_ex(&arg_info->type, &value,
/* current_frame */ true, /* is_internal */ false))) {
zend_string *need_msg = zend_type_to_string_resolved(arg_info->type,
function->common.scope);
zend_argument_type_error_ex(function, offset + 1,
"must be of type %s, %s given",
- ZSTR_VAL(need_msg), zend_zval_value_name(&argv[offset]));
+ ZSTR_VAL(need_msg), zend_zval_value_name(&value));
zend_string_release(need_msg);
goto error;
}
- args_ast = zend_ast_list_add(args_ast, zend_ast_create_zval(&argv[offset]));
+ args_ast = zend_ast_list_add(args_ast, zend_ast_create_zval(&value));
} else {
args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_VAR,
zend_ast_create_zval_from_str(zend_string_copy(var_names->params[offset]))));