Remove traverse_pointer before destroying the element contents.
This commit is contained in:
Nikita Popov 2021-04-19 14:41:19 +02:00
parent 044de83635
commit 71cbef78ba
2 changed files with 65 additions and 3 deletions

View file

@ -796,14 +796,14 @@ PHP_METHOD(SplDoublyLinkedList, offsetUnset)
/* finally, delete the element */ /* finally, delete the element */
llist->count--; llist->count--;
zval_ptr_dtor(&element->data);
ZVAL_UNDEF(&element->data);
if (intern->traverse_pointer == element) { if (intern->traverse_pointer == element) {
SPL_LLIST_DELREF(element); SPL_LLIST_DELREF(element);
intern->traverse_pointer = NULL; intern->traverse_pointer = NULL;
} }
zval_ptr_dtor(&element->data);
ZVAL_UNDEF(&element->data);
SPL_LLIST_DELREF(element); SPL_LLIST_DELREF(element);
} else { } else {
zend_argument_error(spl_ce_OutOfRangeException, 1, "is an invalid offset"); zend_argument_error(spl_ce_OutOfRangeException, 1, "is an invalid offset");

View file

@ -0,0 +1,62 @@
--TEST--
Bug #80111: PHP SplDoublyLinkedList::offsetUnset UAF Sandbox Escape
--FILE--
<?php
function i2s(&$s, $p, $i, $x=8)
{
for($j=0;$j<$x;$j++)
{
$s[$p+$j] = chr($i & 0xff);
$i >>= 8;
}
}
class Trigger
{
function __destruct()
{
global $s, $b;
# Add a reference afterwards
//$v = new SplDoublyLinkedList();
//$v->setIteratorMode(SplDoublyLinkedList::IT_MODE_DELETE);
# Remove element #2 from the list: this has no effect on
# intern->traverse_pointer, since it is removed from the list already
# The element, along with the zval, is freed
unset($s[0]);
$a = str_shuffle(str_repeat('A', 40-24-1));
# Build a fake zval (long, value: 12345678)
i2s($a, 0x00, 12345678); # ptr
i2s($a, 0x08, 4, 7); # type: long
var_dump($s->current());
$s->next();
# The value is our fake zval
var_dump($s->current());
print_r('DONE'."\n");
}
}
# Create a 3-item dllist
$s = new SplDoublyLinkedList();
# This is the UAF trigger
$s->push(new Trigger());
#$b = &$a;
$s->push(3);
# Points intern->traverse_pointer to our object element
$s->rewind();
#$s->next();
# calls SplDoublyLinkedList::offsetUnset, which will remove the element from the
# dllist, and then destruct the object, before clearing traverse_pointer
unset($s[0]);
?>
--EXPECT--
NULL
NULL
DONE