Description
Summary
Two Use-After-Free (UAF) bugs were identified in the phpdbg module (sapi/phpdbg/phpdbg_watch.c).
The issue stems from an unsafe API consumption pattern: the function phpdbg_add_bucket_watch_element() can internally free the element pointer passed to it (if a duplicate watch string already exists in the hash table) and return a new, valid pointer. However, in multiple outer functions, this return value is ignored. The caller's local pointer becomes a dangling pointer, leading to subsequent UAF reads and writes.
Bug Detail
Bug 1: UAF Read in phpdbg_try_re_adding_watch_element
When attempting to re-add a watch element, phpdbg_add_bucket_watch_element is called on line 750. If the underlying phpdbg_add_watch_element finds the element string in watch->elements, it frees the passed element via efree(element) and returns the existing old_element.
Because phpdbg_try_re_adding_watch_element ignores the returned pointer, it proceeds to use the freed memory on line 751.
// sapi/phpdbg/phpdbg_watch.c
749: element->parent_container = ht;
750: phpdbg_add_bucket_watch_element((Bucket *) zv, element); // [!] Return value ignored. `element` might be freed here.
751: phpdbg_watch_parent_ht(element); // [!] UAF Read: dereferencing the dangling `element`.
Note: The call to phpdbg_watch_parent_ht on line 751 is also redundant, as phpdbg_add_bucket_watch_element already invokes it internally before returning.
Bug 2: UAF Write in phpdbg_create_array_watchpoint
A nearly identical alias disconnect occurs here. The function passes element to phpdbg_add_bucket_watch_element on line 1273 and ignores the updated pointer. Immediately after, on line 1274, it writes to a field within the freed struct, causing a memory corruption/UAF write.
// sapi/phpdbg/phpdbg_watch.c
1272: element->flags = PHPDBG_WATCH_IMPLICIT;
1273: phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element); // [!] Return value ignored.
1274: element->child = new; // [!] UAF Write: memory corruption occurs here.
Proposed Fix
The fix requires capturing the returned pointer to ensure the local element variable remains valid. For Bug 1, we also remove the redundant call to phpdbg_watch_parent_ht.
--- a/sapi/phpdbg/phpdbg_watch.c
+++ b/sapi/phpdbg/phpdbg_watch.c
@@ -747,8 +747,7 @@ bool phpdbg_try_re_adding_watch_element(zval *parent, phpdbg_watch_element *elem
}
element->parent_container = ht;
- phpdbg_add_bucket_watch_element((Bucket *) zv, element);
- phpdbg_watch_parent_ht(element);
+ element = phpdbg_add_bucket_watch_element((Bucket *) zv, element);
} else {
return false;
}
@@ -1270,8 +1269,7 @@ static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *elemen
zend_string_release(element->str);
element->str = str;
element->flags = PHPDBG_WATCH_IMPLICIT;
- phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
+ element = phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
element->child = new;
new->flags = PHPDBG_WATCH_SIMPLE;
PHP Version
PHP 8.6.0-dev (cli) (built: Apr 28 2026 17:35:13) (NTS DEBUG)
Copyright © The PHP Group and Contributors
Zend Engine v4.6.0-dev, Copyright © Zend by Perforce
with Zend OPcache v8.6.0-dev, Copyright ©, by Zend by Perforce
Operating System
No response
Description
Summary
Two Use-After-Free (UAF) bugs were identified in the
phpdbgmodule (sapi/phpdbg/phpdbg_watch.c).The issue stems from an unsafe API consumption pattern: the function
phpdbg_add_bucket_watch_element()can internally free theelementpointer passed to it (if a duplicate watch string already exists in the hash table) and return a new, valid pointer. However, in multiple outer functions, this return value is ignored. The caller's local pointer becomes a dangling pointer, leading to subsequent UAF reads and writes.Bug Detail
Bug 1: UAF Read in
phpdbg_try_re_adding_watch_elementWhen attempting to re-add a watch element,
phpdbg_add_bucket_watch_elementis called on line 750. If the underlyingphpdbg_add_watch_elementfinds the element string inwatch->elements, it frees the passedelementviaefree(element)and returns the existingold_element.Because
phpdbg_try_re_adding_watch_elementignores the returned pointer, it proceeds to use the freed memory on line 751.Note: The call to
phpdbg_watch_parent_hton line 751 is also redundant, asphpdbg_add_bucket_watch_elementalready invokes it internally before returning.Bug 2: UAF Write in
phpdbg_create_array_watchpointA nearly identical alias disconnect occurs here. The function passes
elementtophpdbg_add_bucket_watch_elementon line 1273 and ignores the updated pointer. Immediately after, on line 1274, it writes to a field within the freed struct, causing a memory corruption/UAF write.Proposed Fix
The fix requires capturing the returned pointer to ensure the local
elementvariable remains valid. For Bug 1, we also remove the redundant call tophpdbg_watch_parent_ht.PHP Version
Operating System
No response