Skip to content

Commit 1a54819

Browse files
committed
Handle concurrent mutations during reference assignment
Typed references may be modified while assigning to them, during coercion: * The reference may be freed, resulting in UAF * The type source list maybe freed or reallocated, resulting in UAF * Some newly added types may skipped, resulting in incorrect typing Here we fix these issues. Freeing is avoided by increasing the refcount during assignment. Source list issues are fixed by updating the iteration code, and modifying the list in append-only mode during assignment.
1 parent 5f062de commit 1a54819

File tree

7 files changed

+553
-41
lines changed

7 files changed

+553
-41
lines changed

Zend/tests/gh20318-001.phpt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
GH-20318 001: Assign to ref: Ref may be freed by __toString()
3+
--CREDITS--
4+
iluuu1994
5+
--FILE--
6+
<?php
7+
8+
class C {
9+
public mixed $prop1;
10+
public ?string $prop2;
11+
12+
public function __toString() {
13+
unset($this->prop1);
14+
unset($this->prop2);
15+
return 'bar';
16+
}
17+
}
18+
19+
function test() {
20+
$c = new C();
21+
$c->prop1 = 'foo';
22+
$c->prop1 = &$c->prop2;
23+
$c->prop1 = $c;
24+
var_dump($c);
25+
}
26+
27+
test();
28+
29+
?>
30+
==DONE==
31+
--EXPECTF--
32+
object(C)#%d (0) {
33+
["prop1"]=>
34+
uninitialized(mixed)
35+
["prop2"]=>
36+
uninitialized(?string)
37+
}
38+
==DONE==

Zend/tests/gh20318-002.phpt

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
--TEST--
2+
GH-20318 002: Assign to ref: Concurrent reference source list mutations variations
3+
--ENV--
4+
LEN=10
5+
--FILE--
6+
<?php
7+
8+
function remove_single_ptr_source() {
9+
$obj = new class {
10+
public string $a = '';
11+
function __toString() {
12+
unset($this->a);
13+
return str_repeat('a', getenv('LEN'));
14+
}
15+
};
16+
17+
$r = &$obj->a;
18+
$r = $obj;
19+
20+
var_dump($obj, $r);
21+
}
22+
23+
function replace_single_ptr_source_incompatible() {
24+
$obj = new class {
25+
public int|string $a = 0;
26+
public int $b = 0;
27+
public $c;
28+
function __toString() {
29+
unset($this->a);
30+
$this->b = &$this->c;
31+
return str_repeat('a', getenv('LEN'));
32+
}
33+
};
34+
35+
$r = &$obj->a;
36+
$obj->c = &$r;
37+
try {
38+
$r = $obj;
39+
} catch (Error $e) {
40+
echo $e::class, ": ", $e->getMessage(), "\n";
41+
}
42+
43+
var_dump($obj, $r);
44+
}
45+
46+
function remove_current_source_in_list() {
47+
$obj = new class {
48+
public string $a = '';
49+
public string $b = '';
50+
function __toString() {
51+
unset($this->a);
52+
return str_repeat('a', getenv('LEN'));
53+
}
54+
};
55+
56+
$r = &$obj->a;
57+
$obj->b = &$obj->a;
58+
$r = $obj;
59+
60+
var_dump($obj, $r);
61+
}
62+
63+
function remove_next_source_in_list() {
64+
$obj = new class {
65+
public string $a = '';
66+
public string $b = '';
67+
function __toString() {
68+
unset($this->b);
69+
return str_repeat('a', getenv('LEN'));
70+
}
71+
};
72+
73+
$r = &$obj->a;
74+
$obj->b = &$obj->a;
75+
$r = $obj;
76+
77+
var_dump($obj, $r);
78+
}
79+
80+
function remove_all_sources_in_list() {
81+
$obj = new class {
82+
public string $a = '';
83+
public string $b = '';
84+
function __toString() {
85+
unset($this->a);
86+
unset($this->b);
87+
return str_repeat('a', getenv('LEN'));
88+
}
89+
};
90+
91+
$r = &$obj->a;
92+
$obj->b = &$obj->a;
93+
$r = $obj;
94+
95+
var_dump($obj, $r);
96+
}
97+
98+
function add_sources() {
99+
$obj = new class {
100+
public string $a = '';
101+
public string $b = '';
102+
public string $c = '';
103+
public string $d = '';
104+
public string $e = '';
105+
public string $f = '';
106+
public string $g = '';
107+
public string $h = '';
108+
function __toString() {
109+
var_dump(__METHOD__);
110+
$this->b = &$this->a;
111+
$this->c = &$this->a;
112+
$this->d = &$this->a;
113+
$this->e = &$this->a;
114+
$this->f = &$this->a;
115+
$this->g = &$this->a;
116+
$this->h = &$this->a;
117+
return str_repeat('a', getenv('LEN'));
118+
}
119+
};
120+
121+
$r = &$obj->a;
122+
$r = $obj;
123+
124+
var_dump($obj, $r);
125+
}
126+
127+
function cleanup_shrink() {
128+
$obj = new class {
129+
public string $a = '';
130+
};
131+
132+
$r = &$obj->a;
133+
134+
$objs = [];
135+
for ($i = 0; $i < 100; $i++) {
136+
$objs[] = clone $obj;
137+
}
138+
139+
$r = new class($objs) {
140+
function __construct(public mixed &$objs) {}
141+
function __toString() {
142+
$this->objs = array_slice($this->objs, 0, 2);
143+
return str_repeat('a', getenv('LEN'));
144+
}
145+
};
146+
147+
var_dump($obj, $r);
148+
}
149+
150+
function add_incompatible() {
151+
$obj = new class {
152+
public int|string $a = 1;
153+
public int $b = 2;
154+
function __toString() {
155+
$this->b = &$this->a;
156+
return str_repeat('a', getenv('LEN'));
157+
}
158+
};
159+
160+
$r = &$obj->a;
161+
try {
162+
$r = $obj;
163+
} catch (Error $e) {
164+
echo $e::class, ": ", $e->getMessage(), "\n";
165+
}
166+
167+
var_dump($obj, $r);
168+
}
169+
170+
foreach ([
171+
'remove_single_ptr_source',
172+
'replace_single_ptr_source_incompatible',
173+
'remove_current_source_in_list',
174+
'remove_next_source_in_list',
175+
'remove_all_sources_in_list',
176+
'cleanup_shrink',
177+
'add_incompatible',
178+
] as $func) {
179+
echo "# ", $func, ":\n";
180+
$func();
181+
}
182+
183+
?>
184+
==DONE==
185+
--EXPECT--
186+
# remove_single_ptr_source:
187+
object(class@anonymous)#1 (0) {
188+
["a"]=>
189+
uninitialized(string)
190+
}
191+
string(10) "aaaaaaaaaa"
192+
# replace_single_ptr_source_incompatible:
193+
TypeError: Cannot assign class@anonymous to reference held by property class@anonymous::$b of type int
194+
object(class@anonymous)#1 (2) {
195+
["a"]=>
196+
uninitialized(string|int)
197+
["b"]=>
198+
&int(0)
199+
["c"]=>
200+
&int(0)
201+
}
202+
int(0)
203+
# remove_current_source_in_list:
204+
object(class@anonymous)#2 (1) {
205+
["a"]=>
206+
uninitialized(string)
207+
["b"]=>
208+
&string(10) "aaaaaaaaaa"
209+
}
210+
string(10) "aaaaaaaaaa"
211+
# remove_next_source_in_list:
212+
object(class@anonymous)#2 (1) {
213+
["a"]=>
214+
&string(10) "aaaaaaaaaa"
215+
["b"]=>
216+
uninitialized(string)
217+
}
218+
string(10) "aaaaaaaaaa"
219+
# remove_all_sources_in_list:
220+
object(class@anonymous)#2 (0) {
221+
["a"]=>
222+
uninitialized(string)
223+
["b"]=>
224+
uninitialized(string)
225+
}
226+
string(10) "aaaaaaaaaa"
227+
# cleanup_shrink:
228+
object(class@anonymous)#2 (1) {
229+
["a"]=>
230+
&string(10) "aaaaaaaaaa"
231+
}
232+
string(10) "aaaaaaaaaa"
233+
# add_incompatible:
234+
TypeError: Cannot assign class@anonymous to reference held by property class@anonymous::$b of type int
235+
object(class@anonymous)#3 (2) {
236+
["a"]=>
237+
&int(1)
238+
["b"]=>
239+
&int(1)
240+
}
241+
int(1)
242+
==DONE==

0 commit comments

Comments
 (0)