From a6f43cdbe135bf4361e7a6dea1f472ed1d3a117a Mon Sep 17 00:00:00 2001 From: lkn Date: Tue, 23 Jun 2026 22:11:24 +0200 Subject: [PATCH] fix use-after-free: detach reparented children before tree destroy When destroying a frame_buf tree, any other frame_bufs whose trees were reparented into it (e.g. fake_leaders) would have their tree_destroy_handler fire on already-freed memory during cascade. Detach their listeners and NULL their tree pointers before destroying the parent tree. --- src/backend/wayland/wl_framebuf.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/backend/wayland/wl_framebuf.c b/src/backend/wayland/wl_framebuf.c index 823cf5a..44001d1 100644 --- a/src/backend/wayland/wl_framebuf.c +++ b/src/backend/wayland/wl_framebuf.c @@ -28,6 +28,12 @@ extern void *W_GetViewBacking(unsigned long id); void frame_buf_destroy(struct wlr_buffer *wlr_buf) { struct wl_frame_buf *fb = wl_container_of(wlr_buf, fb, base); + /* Unlink destroy listeners before freeing so cascade signals + * from parent tree destruction can't reach this freed struct. */ + if (fb->tree_destroy.link.next) + wl_list_remove(&fb->tree_destroy.link); + if (fb->scene_buf_destroy.link.next) + wl_list_remove(&fb->scene_buf_destroy.link); if (fb->bg_pixmap) pixman_image_unref(fb->bg_pixmap); if (fb->image) @@ -472,11 +478,26 @@ wl_frame_destroy(WScreen *scr, WNativeWindow win) } } - W_UnregisterBacking(fb->id); - /* Destroy the scene_buf and (if any) the tree. Without destroying - * the tree, leaked tree nodes accumulate in the scene graph and - * can cause stale rendering or hit-testing issues (e.g. ghost - * titlebars after a menu is closed). */ + /* Before destroying the tree, detach any other frame_bufs whose + * trees are descendants of ours (e.g. reparented fake_leaders) so + * their tree_destroy_handler doesn't fire on freed memory. */ + if (fb->tree) { + struct wl_frame_buf *other, *otmp; + wl_list_for_each_safe(other, otmp, &wl_state.frame_list, link) { + if (other == fb || !other->tree) continue; + struct wlr_scene_tree *p = other->tree->node.parent; + while (p) { + if (p == fb->tree) { + wl_list_remove(&other->tree_destroy.link); + wl_list_init(&other->tree_destroy.link); + other->tree = NULL; + break; + } + p = p->node.parent; + } + } + } + if (fb->scene_buf) wlr_scene_node_destroy(&fb->scene_buf->node); if (fb->tree)