From d7fca5d9daa0f33d6b6b9149e372bca528bef4a7 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 9 Jun 2023 17:06:24 +0200 Subject: [PATCH] Fix GH-11406: segfault with unpacking and magic method closure The magic method trampoline closure may be variadic. However, the arg_info for the variadic argument was not set, resulting in a crash both in reflection and in the VM. Fix it by creating an arg_info containing a single element in case of the variadic case. The variadic argument is the last one (and in this case only one) in the arg_info array. We make sure the argument info is equivalent to the argument info of `$closure` of the following code snippet: ``` function foo(...$arguments) {} $closure = foo(...); ``` --- .../trampoline_closure_named_arguments.phpt | 35 +++++++++++++++++++ Zend/zend_closures.c | 6 ++++ 2 files changed, 41 insertions(+) diff --git a/Zend/tests/trampoline_closure_named_arguments.phpt b/Zend/tests/trampoline_closure_named_arguments.phpt index e209853e509cb..e4ccaf16e63a6 100644 --- a/Zend/tests/trampoline_closure_named_arguments.phpt +++ b/Zend/tests/trampoline_closure_named_arguments.phpt @@ -14,12 +14,15 @@ class Test { $test = new Test; +$array = ["unpacked"]; + echo "-- Non-static cases --\n"; $test->test(1, 2, a: 123); $test->test(...)(1, 2); $test->test(...)(1, 2, a: 123, b: $test); $test->test(...)(a: 123, b: $test); $test->test(...)(); +$test->test(...)(...$array); echo "-- Static cases --\n"; Test::testStatic(1, 2, a: 123); @@ -27,6 +30,16 @@ Test::testStatic(...)(1, 2); Test::testStatic(...)(1, 2, a: 123, b: $test); Test::testStatic(...)(a: 123, b: $test); Test::testStatic(...)(); +Test::testStatic(...)(...$array); + +echo "-- Reflection tests --\n"; +$reflectionFunction = new ReflectionFunction(Test::fail(...)); +var_dump($reflectionFunction->getParameters()); +$argument = $reflectionFunction->getParameters()[0]; +var_dump($argument->isVariadic()); +$type = $argument->getType(); +var_dump($type); +var_dump($type->getName()); ?> --EXPECT-- @@ -70,6 +83,11 @@ array(2) { string(4) "test" array(0) { } +string(4) "test" +array(1) { + [0]=> + string(8) "unpacked" +} -- Static cases -- string(10) "testStatic" array(3) { @@ -110,3 +128,20 @@ array(2) { string(10) "testStatic" array(0) { } +string(10) "testStatic" +array(1) { + [0]=> + string(8) "unpacked" +} +-- Reflection tests -- +array(1) { + [0]=> + object(ReflectionParameter)#4 (1) { + ["name"]=> + string(9) "arguments" + } +} +bool(true) +object(ReflectionNamedType)#5 (0) { +} +string(5) "mixed" diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 69eeb3cf1ceef..2072eac72d712 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -833,6 +833,9 @@ ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_clas } /* }}} */ +/* __call and __callStatic name the arguments "$arguments" in the docs. */ +static zend_internal_arg_info trampoline_arg_info[] = {ZEND_ARG_VARIADIC_TYPE_INFO(false, arguments, IS_MIXED, false)}; + void zend_closure_from_frame(zval *return_value, zend_execute_data *call) { /* {{{ */ zval instance; zend_internal_function trampoline; @@ -856,6 +859,9 @@ void zend_closure_from_frame(zval *return_value, zend_execute_data *call) { /* { trampoline.handler = zend_closure_call_magic; trampoline.function_name = mptr->common.function_name; trampoline.scope = mptr->common.scope; + if (trampoline.fn_flags & ZEND_ACC_VARIADIC) { + trampoline.arg_info = trampoline_arg_info; + } zend_free_trampoline(mptr); mptr = (zend_function *) &trampoline;