wayland: fix use-after-free of WDock during clip restore

ASan caught it: heap-use-after-free at wl_clip_icon_paint:8478 reading
workspace->clip->collapsed.  The flow is:

  wWorkspaceRestoreState()
    if (scr->workspaces[i]->clip)
        wDockDestroy(scr->workspaces[i]->clip);     // frees the WDock
    scr->workspaces[i]->clip = wDockRestoreState(); // creates new
        wDockCreate -> dockIconPaint -> wClipIconPaint
            -> wl_clip_icon_paint
                workspace->clip->collapsed   // ← reads freed pointer

scr->workspaces[i]->clip is dangling between the wDockDestroy and the
assignment from wDockRestoreState.  wl_clip_icon_paint runs inside
that gap (wDockCreate calls dockIconPaint via wClipIconPaint).

Fix: read collapsed state from aicon->dock instead of workspace->clip.
aicon->dock is the dock currently being constructed and is always
valid at this point.

This was masquerading as a 'double free' on right-click of titlebars
(the heap corruption from the early use-after-free corrupted later
allocations that pixman_image_unref then tripped over).  Removed
the diagnostic logs added while debugging.
This commit is contained in:
Window Maker 2026-05-24 09:59:15 +02:00
parent bf56ccb8eb
commit d6e10203fa
1 changed files with 8 additions and 14 deletions

View File

@ -3986,7 +3986,6 @@ wl_show_crash_dialog(int sig)
static void frame_buf_destroy(struct wlr_buffer *wlr_buf)
{
struct wl_frame_buf *fb = wl_container_of(wlr_buf, fb, base);
wwarning("FBDESTROY: id=0x%lx fb=%p fb->image=%p", fb->id, (void*)fb, fb->image);
if (fb->image)
pixman_image_unref(fb->image);
wfree(fb);
@ -4357,15 +4356,10 @@ wl_frame_configure(WNativeWindow win, int x, int y, int width, int height)
/* Resize: reallocate the pixman image if dimensions changed. */
if (width > 0 && height > 0 && (width != fb->width || height != fb->height)) {
wwarning("FCFG: id=0x%lx fb->image=%p old_size=%dx%d new_size=%dx%d "
"scene_buf=%p tree=%p parent_id=0x%lx",
fb->id, fb->image, fb->width, fb->height, width, height,
fb->scene_buf, fb->tree, fb->parent_id);
pixman_image_t *old_image = fb->image;
if (fb->image)
pixman_image_unref(fb->image);
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8,
width, height, NULL, 0);
/* Update fb metadata BEFORE notifying wlroots so it sees the
* new size when accessing the buffer. */
fb->width = width;
fb->height = height;
fb->base.width = width;
@ -4375,11 +4369,6 @@ wl_frame_configure(WNativeWindow win, int x, int y, int width, int height)
* the old size would miss this scene_buf. */
if (fb->scene_buf)
wlr_scene_buffer_set_dest_size(fb->scene_buf, width, height);
/* Now drop the old image — wlroots will not access it again
* because we already pointed it to the new one above (via
* fb->image which is what begin_data_ptr_access reads). */
if (old_image)
pixman_image_unref(old_image);
}
/* Update absolute position tracking — recursively for all descendants. */
@ -8481,8 +8470,13 @@ static void wl_clip_icon_paint(WAppIcon *aicon)
return;
WWorkspace *workspace = scr->workspaces[scr->current_workspace];
/* Use aicon->dock to read collapsed state — workspace->clip may be a
* stale freed pointer during wWorkspaceRestoreState (the old clip is
* destroyed before scr->workspaces[i]->clip is reassigned, and we
* are called from wDockCreate inside that gap). */
int collapsed = aicon->dock ? aicon->dock->collapsed : 0;
WMColor *color;
if (wPreferences.flags.noclip || !workspace->clip->collapsed)
if (wPreferences.flags.noclip || !collapsed)
color = scr->clip_title_color[CLIP_NORMAL];
else
color = scr->clip_title_color[CLIP_COLLAPSED];