mirror of
https://github.com/php/php-src.git
synced 2024-11-27 11:53:33 +08:00
Fix #81992: SplFixedArray::setSize() causes use-after-free
Upon resizing, the elements are destroyed from lower index to higher index. When an element refers to an object with a destructor, it can refer to a lower (i.e. already destroyed) element, causing a uaf. Set refcounted zvals to NULL after destroying them to avoid a uaf. Closes GH-11959.
This commit is contained in:
parent
2012fd3f04
commit
b71c6b2c6c
4
NEWS
4
NEWS
@ -55,6 +55,10 @@ PHP NEWS
|
||||
. Revert behaviour of receiving SIGCHLD signals back to the behaviour
|
||||
before 8.1.22. (nielsdos)
|
||||
|
||||
- SPL:
|
||||
. Fixed bug #81992 (SplFixedArray::setSize() causes use-after-free).
|
||||
(nielsdos)
|
||||
|
||||
- Standard:
|
||||
. Prevent int overflow on $decimals in number_format. (Marc Bennewitz)
|
||||
. Fixed bug GH-11870 (Fix off-by-one bug when truncating tempnam prefix)
|
||||
|
@ -46,6 +46,8 @@ typedef struct _spl_fixedarray {
|
||||
zval *elements;
|
||||
/* True if this was modified after the last call to get_properties or the hash table wasn't rebuilt. */
|
||||
bool should_rebuild_properties;
|
||||
/* If positive, it's a resize within a resize and the value gives the desired size. If -1, it's not. */
|
||||
zend_long cached_resize;
|
||||
} spl_fixedarray;
|
||||
|
||||
typedef struct _spl_fixedarray_methods {
|
||||
@ -117,6 +119,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size)
|
||||
} else {
|
||||
spl_fixedarray_default_ctor(array);
|
||||
}
|
||||
array->cached_resize = -1;
|
||||
}
|
||||
|
||||
/* Copies the range [begin, end) into the fixedarray, beginning at `offset`.
|
||||
@ -148,6 +151,7 @@ static void spl_fixedarray_copy_ctor(spl_fixedarray *to, spl_fixedarray *from)
|
||||
*/
|
||||
static void spl_fixedarray_dtor_range(spl_fixedarray *array, zend_long from, zend_long to)
|
||||
{
|
||||
array->size = from;
|
||||
zval *begin = array->elements + from, *end = array->elements + to;
|
||||
while (begin != end) {
|
||||
zval_ptr_dtor(begin++);
|
||||
@ -184,19 +188,35 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size)
|
||||
return;
|
||||
}
|
||||
|
||||
if (UNEXPECTED(array->cached_resize >= 0)) {
|
||||
/* We're already resizing, so just remember the desired size.
|
||||
* The resize will happen later. */
|
||||
array->cached_resize = size;
|
||||
return;
|
||||
}
|
||||
array->cached_resize = size;
|
||||
|
||||
/* clearing the array */
|
||||
if (size == 0) {
|
||||
spl_fixedarray_dtor(array);
|
||||
array->elements = NULL;
|
||||
array->size = 0;
|
||||
} else if (size > array->size) {
|
||||
array->elements = safe_erealloc(array->elements, size, sizeof(zval), 0);
|
||||
spl_fixedarray_init_elems(array, array->size, size);
|
||||
array->size = size;
|
||||
} else { /* size < array->size */
|
||||
/* Size set in spl_fixedarray_dtor_range() */
|
||||
spl_fixedarray_dtor_range(array, size, array->size);
|
||||
array->elements = erealloc(array->elements, sizeof(zval) * size);
|
||||
}
|
||||
|
||||
array->size = size;
|
||||
/* If resized within the destructor, take the last resize command and perform it */
|
||||
zend_long cached_resize = array->cached_resize;
|
||||
array->cached_resize = -1;
|
||||
if (cached_resize != size) {
|
||||
spl_fixedarray_resize(array, cached_resize);
|
||||
}
|
||||
}
|
||||
|
||||
static HashTable* spl_fixedarray_object_get_gc(zend_object *obj, zval **table, int *n)
|
||||
|
32
ext/spl/tests/bug81992.phpt
Normal file
32
ext/spl/tests/bug81992.phpt
Normal file
@ -0,0 +1,32 @@
|
||||
--TEST--
|
||||
Bug #81992 (SplFixedArray::setSize() causes use-after-free)
|
||||
--FILE--
|
||||
<?php
|
||||
class InvalidDestructor {
|
||||
public function __destruct() {
|
||||
global $obj;
|
||||
var_dump($obj[0]);
|
||||
try {
|
||||
var_dump($obj[2]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
var_dump($obj[4]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$obj = new SplFixedArray(5);
|
||||
$obj[0] = str_repeat("A", 10);
|
||||
$obj[2] = str_repeat('B', 10);
|
||||
$obj[3] = new InvalidDestructor();
|
||||
$obj[4] = str_repeat('C', 10);
|
||||
$obj->setSize(2);
|
||||
?>
|
||||
--EXPECT--
|
||||
string(10) "AAAAAAAAAA"
|
||||
Index invalid or out of range
|
||||
Index invalid or out of range
|
66
ext/spl/tests/bug81992b.phpt
Normal file
66
ext/spl/tests/bug81992b.phpt
Normal file
@ -0,0 +1,66 @@
|
||||
--TEST--
|
||||
Bug #81992 (SplFixedArray::setSize() causes use-after-free) - setSize variation
|
||||
--FILE--
|
||||
<?php
|
||||
class InvalidDestructor {
|
||||
public function __construct(
|
||||
private int $desiredSize,
|
||||
private SplFixedArray $obj,
|
||||
) {}
|
||||
|
||||
public function __destruct() {
|
||||
echo "In destructor\n";
|
||||
$this->obj->setSize($this->desiredSize);
|
||||
echo "Destroyed, size is now still ", $this->obj->getSize(), "\n";
|
||||
}
|
||||
}
|
||||
|
||||
class DestructorLogger {
|
||||
public function __construct(private int $id) {}
|
||||
|
||||
public function __destruct() {
|
||||
echo "Destroyed the logger with id ", $this->id, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
function test(int $desiredSize) {
|
||||
$obj = new SplFixedArray(5);
|
||||
$obj[0] = str_repeat("A", 10);
|
||||
$obj[1] = new DestructorLogger(1);
|
||||
$obj[2] = str_repeat('B', 10);
|
||||
$obj[3] = new InvalidDestructor($desiredSize, $obj);
|
||||
$obj[4] = new DestructorLogger(4);
|
||||
$obj->setSize(2);
|
||||
echo "Size is now ", $obj->getSize(), "\n";
|
||||
echo "Done\n";
|
||||
}
|
||||
|
||||
echo "--- Smaller size test ---\n";
|
||||
test(1);
|
||||
echo "--- Equal size test ---\n";
|
||||
test(2);
|
||||
echo "--- Larger size test ---\n";
|
||||
test(10);
|
||||
?>
|
||||
--EXPECT--
|
||||
--- Smaller size test ---
|
||||
In destructor
|
||||
Destroyed, size is now still 2
|
||||
Destroyed the logger with id 4
|
||||
Destroyed the logger with id 1
|
||||
Size is now 1
|
||||
Done
|
||||
--- Equal size test ---
|
||||
In destructor
|
||||
Destroyed, size is now still 2
|
||||
Destroyed the logger with id 4
|
||||
Size is now 2
|
||||
Done
|
||||
Destroyed the logger with id 1
|
||||
--- Larger size test ---
|
||||
In destructor
|
||||
Destroyed, size is now still 2
|
||||
Destroyed the logger with id 4
|
||||
Size is now 10
|
||||
Done
|
||||
Destroyed the logger with id 1
|
Loading…
Reference in New Issue
Block a user