Skip to content

Commit c818fa7

Browse files
committed
Fix GH-21639: Protect frameless call args
1 parent f7eb5ef commit c818fa7

16 files changed

+368
-72
lines changed

Zend/tests/gh21639.phpt

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
--TEST--
2+
GH-21639: Frameless calls keep volatile arguments alive
3+
--FILE--
4+
<?php
5+
class ImplodeElement {
6+
public function __toString(): string {
7+
global $separator, $pieces;
8+
9+
$separator = null;
10+
$pieces = null;
11+
12+
return "C";
13+
}
14+
}
15+
16+
$separator = str_repeat(",", 1) . " ";
17+
$pieces = [new ImplodeElement(), 42];
18+
19+
var_dump(implode($separator, $pieces));
20+
var_dump($separator, $pieces);
21+
22+
class MutatingSeparator {
23+
public function __toString(): string {
24+
global $piecesFromSeparator;
25+
26+
$piecesFromSeparator = null;
27+
28+
return ", ";
29+
}
30+
}
31+
32+
$piecesFromSeparator = ["A", "B"];
33+
34+
var_dump(implode(new MutatingSeparator(), $piecesFromSeparator));
35+
var_dump($piecesFromSeparator);
36+
37+
class ImplodeElementWithoutSeparator {
38+
public function __toString(): string {
39+
global $oneArgPieces;
40+
41+
$oneArgPieces = null;
42+
43+
return "D";
44+
}
45+
}
46+
47+
$oneArgPieces = [new ImplodeElementWithoutSeparator(), 42];
48+
49+
var_dump(implode($oneArgPieces));
50+
var_dump($oneArgPieces);
51+
52+
class InArrayNeedle {
53+
public function __toString(): string {
54+
global $inArrayHaystack;
55+
56+
$inArrayHaystack = null;
57+
58+
return "needle";
59+
}
60+
}
61+
62+
$inArrayHaystack = [new InArrayNeedle()];
63+
64+
var_dump(in_array("needle", $inArrayHaystack));
65+
var_dump($inArrayHaystack);
66+
67+
class StrtrReplacement {
68+
public function __toString(): string {
69+
global $strtrReplacements;
70+
71+
$strtrReplacements = null;
72+
73+
return "b";
74+
}
75+
}
76+
77+
$strtrReplacements = ["a" => new StrtrReplacement()];
78+
79+
var_dump(strtr("a", $strtrReplacements));
80+
var_dump($strtrReplacements);
81+
82+
class StrReplaceSubject {
83+
public function __toString(): string {
84+
global $strReplaceSubject;
85+
86+
$strReplaceSubject = null;
87+
88+
return "a";
89+
}
90+
}
91+
92+
$strReplaceSubject = [new StrReplaceSubject(), "aa"];
93+
94+
var_dump(str_replace("a", "b", $strReplaceSubject));
95+
var_dump($strReplaceSubject);
96+
97+
class MinArg {
98+
public function __toString(): string {
99+
global $minArg;
100+
101+
$minArg = null;
102+
103+
return "a";
104+
}
105+
}
106+
107+
$minArg = new MinArg();
108+
$minResult = min($minArg, "b");
109+
110+
var_dump($minResult instanceof MinArg);
111+
var_dump($minArg);
112+
113+
class MaxArg {
114+
public function __toString(): string {
115+
global $maxArg;
116+
117+
$maxArg = null;
118+
119+
return "a";
120+
}
121+
}
122+
123+
$maxArg = new MaxArg();
124+
$maxResult = max("0", $maxArg);
125+
126+
var_dump($maxResult instanceof MaxArg);
127+
var_dump($maxArg);
128+
?>
129+
--EXPECT--
130+
string(5) "C, 42"
131+
NULL
132+
NULL
133+
string(4) "A, B"
134+
NULL
135+
string(3) "D42"
136+
NULL
137+
bool(true)
138+
NULL
139+
string(1) "b"
140+
NULL
141+
array(2) {
142+
[0]=>
143+
string(1) "b"
144+
[1]=>
145+
string(2) "bb"
146+
}
147+
NULL
148+
bool(true)
149+
NULL
150+
bool(true)
151+
NULL

Zend/zend_builtin_functions.stub.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ function method_exists($object_or_class, string $method): bool {}
7575

7676
/**
7777
* @param object|string $object_or_class
78-
* @frameless-function {"arity": 2}
78+
* @frameless-function {"arity": 2, "needs_arg_copy": true}
7979
*/
8080
function property_exists($object_or_class, string $property): bool {}
8181

8282
/**
83-
* @frameless-function {"arity": 1}
84-
* @frameless-function {"arity": 2}
83+
* @frameless-function {"arity": 1, "needs_arg_copy": true}
84+
* @frameless-function {"arity": 2, "needs_arg_copy": true}
8585
*/
8686
function class_exists(string $class, bool $autoload = true): bool {}
8787

Zend/zend_builtin_functions_arginfo.h

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Zend/zend_compile.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4668,11 +4668,11 @@ static const zend_frameless_function_info *find_frameless_function_info(zend_ast
46684668
}
46694669

46704670
while (frameless_function_info->handler) {
4671-
if (frameless_function_info->num_args >= args->children
4671+
uint32_t num_args = ZEND_FLF_INFO_NUM_ARGS(frameless_function_info);
4672+
if (num_args >= args->children
46724673
&& fbc->common.required_num_args <= args->children
46734674
&& (!(fbc->common.fn_flags & ZEND_ACC_VARIADIC)
4674-
|| frameless_function_info->num_args == args->children)) {
4675-
uint32_t num_args = frameless_function_info->num_args;
4675+
|| num_args == args->children)) {
46764676
uint32_t offset = find_frameless_function_offset(num_args, frameless_function_info->handler);
46774677
if (offset == (uint32_t)-1) {
46784678
continue;
@@ -4688,7 +4688,7 @@ static const zend_frameless_function_info *find_frameless_function_info(zend_ast
46884688
static uint32_t zend_compile_frameless_icall_ex(znode *result, zend_ast_list *args, zend_function *fbc, const zend_frameless_function_info *frameless_function_info, uint32_t type)
46894689
{
46904690
int lineno = CG(zend_lineno);
4691-
uint32_t num_args = frameless_function_info->num_args;
4691+
uint32_t num_args = ZEND_FLF_INFO_NUM_ARGS(frameless_function_info);
46924692
uint32_t offset = find_frameless_function_offset(num_args, frameless_function_info->handler);
46934693
znode arg_zvs[3];
46944694
for (uint32_t i = 0; i < num_args; i++) {
@@ -4705,7 +4705,7 @@ static uint32_t zend_compile_frameless_icall_ex(znode *result, zend_ast_list *ar
47054705
uint8_t opcode = ZEND_FRAMELESS_ICALL_0 + num_args;
47064706
uint32_t opnum = get_next_op_number();
47074707
zend_op *opline = zend_emit_op_tmp(result, opcode, NULL, NULL);
4708-
opline->extended_value = offset;
4708+
opline->extended_value = offset | ZEND_FLF_INFO_FLAGS(frameless_function_info);
47094709
opline->lineno = lineno;
47104710
if (num_args >= 1) {
47114711
SET_NODE(opline->op1, &arg_zvs[0]);

Zend/zend_execute.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,15 +1582,20 @@ static zend_never_inline void zend_assign_to_object_dim(zend_object *obj, zval *
15821582
}
15831583
}
15841584

1585-
static void frameless_observed_call_copy(zend_execute_data *call, uint32_t arg, zval *zv)
1585+
static zend_always_inline void zend_frameless_copy_arg(zval *dst, zval *src)
15861586
{
1587-
if (Z_ISUNDEF_P(zv)) {
1588-
ZVAL_NULL(ZEND_CALL_VAR_NUM(call, arg));
1587+
if (Z_ISUNDEF_P(src)) {
1588+
ZVAL_NULL(dst);
15891589
} else {
1590-
ZVAL_COPY_DEREF(ZEND_CALL_VAR_NUM(call, arg), zv);
1590+
ZVAL_COPY_DEREF(dst, src);
15911591
}
15921592
}
15931593

1594+
static void frameless_observed_call_copy(zend_execute_data *call, uint32_t arg, zval *zv)
1595+
{
1596+
zend_frameless_copy_arg(ZEND_CALL_VAR_NUM(call, arg), zv);
1597+
}
1598+
15941599
ZEND_API void zend_frameless_observed_call(zend_execute_data *execute_data)
15951600
{
15961601
const zend_op *opline = EX(opline);

Zend/zend_frameless_function.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,14 @@
3636
#define ZEND_FRAMELESS_FUNCTION_NAME(name, arity) zflf_##name##_##arity
3737
#define ZEND_OP_IS_FRAMELESS_ICALL(opcode) ((opcode) >= ZEND_FRAMELESS_ICALL_0 && (opcode) <= ZEND_FRAMELESS_ICALL_3)
3838
#define ZEND_FLF_NUM_ARGS(opcode) ((opcode) - ZEND_FRAMELESS_ICALL_0)
39-
#define ZEND_FLF_FUNC(opline) (zend_flf_functions[(opline)->extended_value])
40-
#define ZEND_FLF_HANDLER(opline) (zend_flf_handlers[(opline)->extended_value])
39+
#define ZEND_FLF_ARG_COPY (1u << 31)
40+
#define ZEND_FLF_NUM_ARGS_MASK (~ZEND_FLF_ARG_COPY)
41+
#define ZEND_FLF_INFO_NUM_ARGS(info) ((info)->num_args & ZEND_FLF_NUM_ARGS_MASK)
42+
#define ZEND_FLF_INFO_FLAGS(info) ((info)->num_args & ZEND_FLF_ARG_COPY)
43+
#define ZEND_FLF_OFFSET(opline) ((opline)->extended_value & ZEND_FLF_NUM_ARGS_MASK)
44+
#define ZEND_FLF_USES_ARG_COPY(opline) ((opline)->extended_value & ZEND_FLF_ARG_COPY)
45+
#define ZEND_FLF_FUNC(opline) (zend_flf_functions[ZEND_FLF_OFFSET(opline)])
46+
#define ZEND_FLF_HANDLER(opline) (zend_flf_handlers[ZEND_FLF_OFFSET(opline)])
4147

4248
#define ZEND_FRAMELESS_FUNCTION(name, arity) \
4349
void ZEND_FRAMELESS_FUNCTION_NAME(name, arity)(ZEND_FRAMELESS_FUNCTION_PARAMETERS_##arity)

Zend/zend_vm_def.h

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9713,7 +9713,14 @@ ZEND_VM_HANDLER(205, ZEND_FRAMELESS_ICALL_1, ANY, UNUSED, SPEC(OBSERVER))
97139713
#endif
97149714
{
97159715
zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline);
9716-
function(result, arg1);
9716+
if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) {
9717+
zval arg1_copy;
9718+
zend_frameless_copy_arg(&arg1_copy, arg1);
9719+
function(result, &arg1_copy);
9720+
zval_ptr_dtor_nogc(&arg1_copy);
9721+
} else {
9722+
function(result, arg1);
9723+
}
97179724
}
97189725
FREE_OP1();
97199726
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
@@ -9741,7 +9748,16 @@ ZEND_VM_HANDLER(206, ZEND_FRAMELESS_ICALL_2, ANY, ANY, SPEC(OBSERVER))
97419748
#endif
97429749
{
97439750
zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline);
9744-
function(result, arg1, arg2);
9751+
if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) {
9752+
zval arg1_copy, arg2_copy;
9753+
zend_frameless_copy_arg(&arg1_copy, arg1);
9754+
zend_frameless_copy_arg(&arg2_copy, arg2);
9755+
function(result, &arg1_copy, &arg2_copy);
9756+
zval_ptr_dtor_nogc(&arg1_copy);
9757+
zval_ptr_dtor_nogc(&arg2_copy);
9758+
} else {
9759+
function(result, arg1, arg2);
9760+
}
97459761
}
97469762

97479763
FREE_OP1();
@@ -9777,7 +9793,18 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER))
97779793
#endif
97789794
{
97799795
zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline);
9780-
function(result, arg1, arg2, arg3);
9796+
if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) {
9797+
zval arg1_copy, arg2_copy, arg3_copy;
9798+
zend_frameless_copy_arg(&arg1_copy, arg1);
9799+
zend_frameless_copy_arg(&arg2_copy, arg2);
9800+
zend_frameless_copy_arg(&arg3_copy, arg3);
9801+
function(result, &arg1_copy, &arg2_copy, &arg3_copy);
9802+
zval_ptr_dtor_nogc(&arg1_copy);
9803+
zval_ptr_dtor_nogc(&arg2_copy);
9804+
zval_ptr_dtor_nogc(&arg3_copy);
9805+
} else {
9806+
function(result, arg1, arg2, arg3);
9807+
}
97819808
}
97829809

97839810
FREE_OP1();

0 commit comments

Comments
 (0)