Skip to content

Commit 15ec47b

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

5 files changed

Lines changed: 207 additions & 13 deletions

File tree

Zend/tests/gh21639.phpt

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
--EXPECT--
98+
string(5) "C, 42"
99+
NULL
100+
NULL
101+
string(4) "A, B"
102+
NULL
103+
string(3) "D42"
104+
NULL
105+
bool(true)
106+
NULL
107+
string(1) "b"
108+
NULL
109+
array(2) {
110+
[0]=>
111+
string(1) "b"
112+
[1]=>
113+
string(2) "bb"
114+
}
115+
NULL

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_vm_def.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9712,8 +9712,11 @@ ZEND_VM_HANDLER(205, ZEND_FRAMELESS_ICALL_1, ANY, UNUSED, SPEC(OBSERVER))
97129712
} else
97139713
#endif
97149714
{
9715+
zval arg1_copy;
97159716
zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline);
9716-
function(result, arg1);
9717+
zend_frameless_copy_arg(&arg1_copy, arg1);
9718+
function(result, &arg1_copy);
9719+
zval_ptr_dtor_nogc(&arg1_copy);
97179720
}
97189721
FREE_OP1();
97199722
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
@@ -9740,8 +9743,13 @@ ZEND_VM_HANDLER(206, ZEND_FRAMELESS_ICALL_2, ANY, ANY, SPEC(OBSERVER))
97409743
} else
97419744
#endif
97429745
{
9746+
zval arg1_copy, arg2_copy;
97439747
zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline);
9744-
function(result, arg1, arg2);
9748+
zend_frameless_copy_arg(&arg1_copy, arg1);
9749+
zend_frameless_copy_arg(&arg2_copy, arg2);
9750+
function(result, &arg1_copy, &arg2_copy);
9751+
zval_ptr_dtor_nogc(&arg1_copy);
9752+
zval_ptr_dtor_nogc(&arg2_copy);
97459753
}
97469754

97479755
FREE_OP1();
@@ -9776,8 +9784,15 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER))
97769784
} else
97779785
#endif
97789786
{
9787+
zval arg1_copy, arg2_copy, arg3_copy;
97799788
zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline);
9780-
function(result, arg1, arg2, arg3);
9789+
zend_frameless_copy_arg(&arg1_copy, arg1);
9790+
zend_frameless_copy_arg(&arg2_copy, arg2);
9791+
zend_frameless_copy_arg(&arg3_copy, arg3);
9792+
function(result, &arg1_copy, &arg2_copy, &arg3_copy);
9793+
zval_ptr_dtor_nogc(&arg1_copy);
9794+
zval_ptr_dtor_nogc(&arg2_copy);
9795+
zval_ptr_dtor_nogc(&arg3_copy);
97819796
}
97829797

97839798
FREE_OP1();

Zend/zend_vm_execute.h

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

ext/pcre/tests/gh21639.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
GH-21639: Frameless preg_replace keeps volatile arguments alive
3+
--EXTENSIONS--
4+
pcre
5+
--FILE--
6+
<?php
7+
class PregReplaceSubject {
8+
public function __toString(): string {
9+
global $pregReplaceSubject;
10+
11+
$pregReplaceSubject = null;
12+
13+
return "a";
14+
}
15+
}
16+
17+
$pregReplaceSubject = [new PregReplaceSubject(), "aa"];
18+
19+
var_dump(preg_replace("/a/", "b", $pregReplaceSubject));
20+
var_dump($pregReplaceSubject);
21+
?>
22+
--EXPECT--
23+
array(2) {
24+
[0]=>
25+
string(1) "b"
26+
[1]=>
27+
string(2) "bb"
28+
}
29+
NULL

0 commit comments

Comments
 (0)