diff --git a/ext/spl/spl_dllist.c b/ext/spl/spl_dllist.c index ebbcb90cf53..666dca0de77 100644 --- a/ext/spl/spl_dllist.c +++ b/ext/spl/spl_dllist.c @@ -796,14 +796,14 @@ PHP_METHOD(SplDoublyLinkedList, offsetUnset) /* finally, delete the element */ llist->count--; - zval_ptr_dtor(&element->data); - ZVAL_UNDEF(&element->data); - if (intern->traverse_pointer == element) { SPL_LLIST_DELREF(element); intern->traverse_pointer = NULL; } + zval_ptr_dtor(&element->data); + ZVAL_UNDEF(&element->data); + SPL_LLIST_DELREF(element); } else { zend_argument_error(spl_ce_OutOfRangeException, 1, "is an invalid offset"); diff --git a/ext/spl/tests/bug80111.phpt b/ext/spl/tests/bug80111.phpt new file mode 100644 index 00000000000..7cfa2bab712 --- /dev/null +++ b/ext/spl/tests/bug80111.phpt @@ -0,0 +1,62 @@ +--TEST-- +Bug #80111: PHP SplDoublyLinkedList::offsetUnset UAF Sandbox Escape +--FILE-- +>= 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