Skip to content

Commit 0fc7b53

Browse files
committed
Fix GH-22062: SplDoublyLinkedList iterator UAF via destructor releasing next node.
Pin the new traverse target via SPL_LLIST_CHECK_ADDREF before the shift/pop destructor runs. Otherwise a destructor that unlinks the next node (e.g. offsetUnset) frees it, leaving the iterator with a dangling pointer.
1 parent 51911cc commit 0fc7b53

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

ext/spl/spl_dllist.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ static void spl_dllist_it_helper_move_forward(spl_ptr_llist_element **traverse_p
798798

799799
if (flags & SPL_DLLIST_IT_LIFO) {
800800
*traverse_pointer_ptr = old->prev;
801+
SPL_LLIST_CHECK_ADDREF(*traverse_pointer_ptr);
801802
(*traverse_position_ptr)--;
802803

803804
if (flags & SPL_DLLIST_IT_DELETE) {
@@ -808,6 +809,7 @@ static void spl_dllist_it_helper_move_forward(spl_ptr_llist_element **traverse_p
808809
}
809810
} else {
810811
*traverse_pointer_ptr = old->next;
812+
SPL_LLIST_CHECK_ADDREF(*traverse_pointer_ptr);
811813

812814
if (flags & SPL_DLLIST_IT_DELETE) {
813815
zval prev;
@@ -820,7 +822,6 @@ static void spl_dllist_it_helper_move_forward(spl_ptr_llist_element **traverse_p
820822
}
821823

822824
SPL_LLIST_DELREF(old);
823-
SPL_LLIST_CHECK_ADDREF(*traverse_pointer_ptr);
824825
}
825826
}
826827
/* }}} */

ext/spl/tests/gh22062.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
GH-22062 (SplDoublyLinkedList iterator UAF via destructor releasing next node)
3+
--FILE--
4+
<?php
5+
$list = new SplDoublyLinkedList();
6+
$list->setIteratorMode(
7+
SplDoublyLinkedList::IT_MODE_FIFO |
8+
SplDoublyLinkedList::IT_MODE_DELETE
9+
);
10+
11+
$list->push(new class($list) {
12+
public function __construct(private SplDoublyLinkedList $list) {}
13+
public function __destruct() {
14+
if ($this->list->count() > 0) {
15+
$this->list->offsetUnset(0);
16+
}
17+
}
18+
});
19+
20+
$list->push(new stdClass());
21+
22+
foreach ($list as $item) {
23+
unset($item);
24+
}
25+
26+
var_dump($list->count());
27+
?>
28+
--EXPECT--
29+
int(0)

0 commit comments

Comments
 (0)