Commit d6b7bd0f778 for php.net
commit d6b7bd0f778cd45c0bae91d6e334fdf9f7049199
Author: David Carlier <devnexen@gmail.com>
Date: Sat May 16 21:05:15 2026 +0100
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.
close GH-22066
diff --git a/NEWS b/NEWS
index 239becd194f..1200d086067 100644
--- a/NEWS
+++ b/NEWS
@@ -181,6 +181,8 @@ PHP NEWS
with re-entrant getHash()). (Pratik Bhujel)
. Fix bugs GH-8561, GH-8562, GH-8563, and GH-8564 (Fixing various
SplFileObject iterator desync bugs). (iliaal)
+ . Fix bug GH-22062 (SplDoublyLinkedList iterator UAF
+ via destructor releasing next node). (David Carlier)
- Sqlite3:
. Fix NUL byte truncation in sqlite3 TEXT column handling. (ndossche)
diff --git a/ext/spl/spl_dllist.c b/ext/spl/spl_dllist.c
index eaadec03cf8..be4f13cfcfc 100644
--- a/ext/spl/spl_dllist.c
+++ b/ext/spl/spl_dllist.c
@@ -798,6 +798,7 @@ static void spl_dllist_it_helper_move_forward(spl_ptr_llist_element **traverse_p
if (flags & SPL_DLLIST_IT_LIFO) {
*traverse_pointer_ptr = old->prev;
+ SPL_LLIST_CHECK_ADDREF(*traverse_pointer_ptr);
(*traverse_position_ptr)--;
if (flags & SPL_DLLIST_IT_DELETE) {
@@ -808,6 +809,7 @@ static void spl_dllist_it_helper_move_forward(spl_ptr_llist_element **traverse_p
}
} else {
*traverse_pointer_ptr = old->next;
+ SPL_LLIST_CHECK_ADDREF(*traverse_pointer_ptr);
if (flags & SPL_DLLIST_IT_DELETE) {
zval prev;
@@ -820,7 +822,6 @@ static void spl_dllist_it_helper_move_forward(spl_ptr_llist_element **traverse_p
}
SPL_LLIST_DELREF(old);
- SPL_LLIST_CHECK_ADDREF(*traverse_pointer_ptr);
}
}
/* }}} */
diff --git a/ext/spl/tests/gh22062.phpt b/ext/spl/tests/gh22062.phpt
new file mode 100644
index 00000000000..ea67a9983a3
--- /dev/null
+++ b/ext/spl/tests/gh22062.phpt
@@ -0,0 +1,29 @@
+--TEST--
+GH-22062 (SplDoublyLinkedList iterator UAF via destructor releasing next node)
+--FILE--
+<?php
+$list = new SplDoublyLinkedList();
+$list->setIteratorMode(
+ SplDoublyLinkedList::IT_MODE_FIFO |
+ SplDoublyLinkedList::IT_MODE_DELETE
+);
+
+$list->push(new class($list) {
+ public function __construct(private SplDoublyLinkedList $list) {}
+ public function __destruct() {
+ if ($this->list->count() > 0) {
+ $this->list->offsetUnset(0);
+ }
+ }
+});
+
+$list->push(new stdClass());
+
+foreach ($list as $item) {
+ unset($item);
+}
+
+var_dump($list->count());
+?>
+--EXPECT--
+int(0)