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.
This commit is contained in:
lkn 2026-06-23 22:11:24 +02:00
parent 01904fdb39
commit a6f43cdbe1
1 changed files with 26 additions and 5 deletions

View File

@ -28,6 +28,12 @@ extern void *W_GetViewBacking(unsigned long id);
void frame_buf_destroy(struct wlr_buffer *wlr_buf) void frame_buf_destroy(struct wlr_buffer *wlr_buf)
{ {
struct wl_frame_buf *fb = wl_container_of(wlr_buf, fb, base); 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) if (fb->bg_pixmap)
pixman_image_unref(fb->bg_pixmap); pixman_image_unref(fb->bg_pixmap);
if (fb->image) if (fb->image)
@ -472,11 +478,26 @@ wl_frame_destroy(WScreen *scr, WNativeWindow win)
} }
} }
W_UnregisterBacking(fb->id); /* Before destroying the tree, detach any other frame_bufs whose
/* Destroy the scene_buf and (if any) the tree. Without destroying * trees are descendants of ours (e.g. reparented fake_leaders) so
* the tree, leaked tree nodes accumulate in the scene graph and * their tree_destroy_handler doesn't fire on freed memory. */
* can cause stale rendering or hit-testing issues (e.g. ghost if (fb->tree) {
* titlebars after a menu is closed). */ 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) if (fb->scene_buf)
wlr_scene_node_destroy(&fb->scene_buf->node); wlr_scene_node_destroy(&fb->scene_buf->node);
if (fb->tree) if (fb->tree)