Skip to content

Commit 63a7f22

Browse files
committed
Merge branch 'PHP-8.2'
* PHP-8.2: Fix #81992: SplFixedArray::setSize() causes use-after-free
2 parents 0b88704 + 0b516ae commit 63a7f22

File tree

3 files changed

+119
-1
lines changed

3 files changed

+119
-1
lines changed

ext/spl/spl_fixedarray.c

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ typedef struct _spl_fixedarray {
4848
zend_long size;
4949
/* It is possible to resize this, so this can't be combined with the object */
5050
zval *elements;
51+
/* If positive, it's a resize within a resize and the value gives the desired size. If -1, it's not. */
52+
zend_long cached_resize;
5153
} spl_fixedarray;
5254

5355
typedef struct _spl_fixedarray_object {
@@ -115,6 +117,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size)
115117
} else {
116118
spl_fixedarray_default_ctor(array);
117119
}
120+
array->cached_resize = -1;
118121
}
119122

120123
/* Copies the range [begin, end) into the fixedarray, beginning at `offset`.
@@ -146,6 +149,7 @@ static void spl_fixedarray_copy_ctor(spl_fixedarray *to, spl_fixedarray *from)
146149
*/
147150
static void spl_fixedarray_dtor_range(spl_fixedarray *array, zend_long from, zend_long to)
148151
{
152+
array->size = from;
149153
zval *begin = array->elements + from, *end = array->elements + to;
150154
while (begin != end) {
151155
zval_ptr_dtor(begin++);
@@ -181,19 +185,35 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size)
181185
return;
182186
}
183187

188+
if (UNEXPECTED(array->cached_resize >= 0)) {
189+
/* We're already resizing, so just remember the desired size.
190+
* The resize will happen later. */
191+
array->cached_resize = size;
192+
return;
193+
}
194+
array->cached_resize = size;
195+
184196
/* clearing the array */
185197
if (size == 0) {
186198
spl_fixedarray_dtor(array);
187199
array->elements = NULL;
200+
array->size = 0;
188201
} else if (size > array->size) {
189202
array->elements = safe_erealloc(array->elements, size, sizeof(zval), 0);
190203
spl_fixedarray_init_elems(array, array->size, size);
204+
array->size = size;
191205
} else { /* size < array->size */
206+
/* Size set in spl_fixedarray_dtor_range() */
192207
spl_fixedarray_dtor_range(array, size, array->size);
193208
array->elements = erealloc(array->elements, sizeof(zval) * size);
194209
}
195210

196-
array->size = size;
211+
/* If resized within the destructor, take the last resize command and perform it */
212+
zend_long cached_resize = array->cached_resize;
213+
array->cached_resize = -1;
214+
if (cached_resize != size) {
215+
spl_fixedarray_resize(array, cached_resize);
216+
}
197217
}
198218

199219
static HashTable* spl_fixedarray_object_get_gc(zend_object *obj, zval **table, int *n)

ext/spl/tests/bug81992.phpt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
Bug #81992 (SplFixedArray::setSize() causes use-after-free)
3+
--FILE--
4+
<?php
5+
class InvalidDestructor {
6+
public function __destruct() {
7+
global $obj;
8+
var_dump($obj[0]);
9+
try {
10+
var_dump($obj[2]);
11+
} catch (Throwable $e) {
12+
echo $e->getMessage(), "\n";
13+
}
14+
try {
15+
var_dump($obj[4]);
16+
} catch (Throwable $e) {
17+
echo $e->getMessage(), "\n";
18+
}
19+
}
20+
}
21+
22+
$obj = new SplFixedArray(5);
23+
$obj[0] = str_repeat("A", 10);
24+
$obj[2] = str_repeat('B', 10);
25+
$obj[3] = new InvalidDestructor();
26+
$obj[4] = str_repeat('C', 10);
27+
$obj->setSize(2);
28+
?>
29+
--EXPECT--
30+
string(10) "AAAAAAAAAA"
31+
Index invalid or out of range
32+
Index invalid or out of range

ext/spl/tests/bug81992b.phpt

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--TEST--
2+
Bug #81992 (SplFixedArray::setSize() causes use-after-free) - setSize variation
3+
--FILE--
4+
<?php
5+
class InvalidDestructor {
6+
public function __construct(
7+
private int $desiredSize,
8+
private SplFixedArray $obj,
9+
) {}
10+
11+
public function __destruct() {
12+
echo "In destructor\n";
13+
$this->obj->setSize($this->desiredSize);
14+
echo "Destroyed, size is now still ", $this->obj->getSize(), "\n";
15+
}
16+
}
17+
18+
class DestructorLogger {
19+
public function __construct(private int $id) {}
20+
21+
public function __destruct() {
22+
echo "Destroyed the logger with id ", $this->id, "\n";
23+
}
24+
}
25+
26+
function test(int $desiredSize) {
27+
$obj = new SplFixedArray(5);
28+
$obj[0] = str_repeat("A", 10);
29+
$obj[1] = new DestructorLogger(1);
30+
$obj[2] = str_repeat('B', 10);
31+
$obj[3] = new InvalidDestructor($desiredSize, $obj);
32+
$obj[4] = new DestructorLogger(4);
33+
$obj->setSize(2);
34+
echo "Size is now ", $obj->getSize(), "\n";
35+
echo "Done\n";
36+
}
37+
38+
echo "--- Smaller size test ---\n";
39+
test(1);
40+
echo "--- Equal size test ---\n";
41+
test(2);
42+
echo "--- Larger size test ---\n";
43+
test(10);
44+
?>
45+
--EXPECT--
46+
--- Smaller size test ---
47+
In destructor
48+
Destroyed, size is now still 2
49+
Destroyed the logger with id 4
50+
Destroyed the logger with id 1
51+
Size is now 1
52+
Done
53+
--- Equal size test ---
54+
In destructor
55+
Destroyed, size is now still 2
56+
Destroyed the logger with id 4
57+
Size is now 2
58+
Done
59+
Destroyed the logger with id 1
60+
--- Larger size test ---
61+
In destructor
62+
Destroyed, size is now still 2
63+
Destroyed the logger with id 4
64+
Size is now 10
65+
Done
66+
Destroyed the logger with id 1

0 commit comments

Comments
 (0)