Commit c6de4e63c84 for php
commit c6de4e63c84a4a1832f37d92497c98b4c50ab917
Merge: a47a2be280b 055e11e0c7e
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Sat Oct 24 18:19:43 2026 -0400
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
phpdbg: Backport watchpoint fixes to PHP-8.4
diff --cc NEWS
index 605d0ac8b8c,4a3383c25a4..1ee7d6fd2fb
--- a/NEWS
+++ b/NEWS
@@@ -111,11 -128,11 +111,15 @@@ PH
- Phpdbg:
. Fix wrong command names. (ndossche, Weilin Du)
+ . Fixed bug GH-22480 (Use-after-free when re-watching an already-watched
+ variable). (Ilia Alshanetsky)
+ . Fixed over-read when watching a packed array element. (Ilia Alshanetsky)
+ . Fixed double free of watch element chains. (Arnaud)
+- POSIX:
+ . Fixed posix_setrlimit() rejecting POSIX_RLIMIT_INFINITY on Solaris.
+ (Jorg Sowa)
+
- Readline:
. Fixed a crash in readline_info() when completion_append_character
contains a non-ASCII byte. (Weilin Du)
diff --cc sapi/phpdbg/phpdbg_watch.c
index 5657649efdb,1c63f26ea70..6daf4060222
--- a/sapi/phpdbg/phpdbg_watch.c
+++ b/sapi/phpdbg/phpdbg_watch.c
@@@ -138,13 -148,13 +148,13 @@@ static bool phpdbg_check_zval_watch_dif
bool phpdbg_check_watch_diff(phpdbg_watchtype type, void *oldPtr, void *newPtr) {
switch (type) {
case WATCH_ON_BUCKET:
- if (memcmp(&((Bucket *) oldPtr)->h, &((Bucket *) newPtr)->h, sizeof(Bucket) - sizeof(zval) /* key/val comparison */) != 0) {
- return 2;
+ if (memcmp(&((Bucket *) oldPtr)->h, &((Bucket *) newPtr)->h, sizeof(Bucket) - sizeof(zval) /* hash+key comparison */) != 0) {
+ return true;
}
- /* TODO: Is this intentional? */
+ /* Fall through to also compare the value from the bucket. */
ZEND_FALLTHROUGH;
case WATCH_ON_ZVAL:
- return memcmp(oldPtr, newPtr, sizeof(zend_value) + sizeof(uint32_t) /* value + typeinfo */) != 0;
+ return phpdbg_check_zval_watch_diff((zval *) oldPtr, (zval *) newPtr);
case WATCH_ON_HASHTABLE:
return zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_PTR_HT(newPtr));
case WATCH_ON_REFCOUNTED:
@@@ -748,18 -837,48 +837,48 @@@ bool phpdbg_try_re_adding_watch_element
phpdbg_print_watch_diff(WATCH_ON_ZVAL, element->str, &element->backup.zv, zv);
}
+ if ((element->flags & PHPDBG_WATCH_RECURSIVE) && phpdbg_bucket_watch_element_collides(zv, element)) {
+ return false;
+ }
element->parent_container = ht;
- phpdbg_add_bucket_watch_element((Bucket *) zv, element);
- phpdbg_watch_parent_ht(element);
+ element = phpdbg_add_bucket_watch_element(zv, element, NULL);
} else {
- return 0;
+ return false;
}
- return 1;
+ return true;
}
+ /* watch_recreation is keyed by element->str, so a parent and its child are stored
+ * under distinct keys and the deduplication in phpdbg_queue_element_for_recreation()
+ * cannot notice that they belong to the same chain. As phpdbg_free_watch_element_tree()
+ * frees the whole chain, any other entry referencing a member of it must be dropped
+ * first, or the chain gets freed twice. The buckets are only nulled out as the hash is
+ * being iterated over by the callers; they clean it right after. */
+ static void phpdbg_forget_queued_watch_element(phpdbg_watch_element *element) {
+ zval *zv = zend_hash_find(&PHPDBG_G(watch_recreation), element->str);
+ if (zv && Z_PTR_P(zv) == element) {
+ Z_PTR_P(zv) = NULL;
+ }
+ }
+
+ static void phpdbg_dequeue_watch_element_tree(phpdbg_watch_element *element) {
+ phpdbg_watch_element *cur;
+
+ for (cur = element->parent; cur; cur = cur->parent) {
+ phpdbg_forget_queued_watch_element(cur);
+ }
+ for (cur = element->child; cur; cur = cur->child) {
+ phpdbg_forget_queued_watch_element(cur);
+ }
+ phpdbg_forget_queued_watch_element(element);
+ }
+
void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element) {
phpdbg_watch_element *child = element;
+
+ phpdbg_dequeue_watch_element_tree(element);
+
while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
child = child->child;
}