fix: eliminate use-after-free crash on modal dialog close

- wevent_wl.c: W_IsValidView() validates _view_target against view_table
     at fetch boundary (wl_fetch, wl_check_mask, wl_check_typed_window);
     nulls target before returning event if view was destroyed
   - wevent_wl.c: W_WL_InvalidateView() nulls queue entries at destroy time
   - wview.c: set view->screen=NULL before wfree(view)
   - wevent.c: defense-in-depth: check !view->screen || alreadyDead before use
This commit is contained in:
Window Maker 2026-05-17 19:16:32 +02:00
parent 9943c05b4a
commit 15bec04b1b
5 changed files with 44 additions and 1 deletions

View File

@ -480,7 +480,10 @@ typedef struct W_EventBackend {
extern const W_EventBackend W_EventBackendX11;
extern const W_EventBackend W_EventBackendWL;
Bool W_IsValidView(struct W_View *view);
void W_WL_EnqueueEvent(struct W_Screen *scr, const WMEvent *ev, struct W_View *target);
void W_WL_InvalidateView(struct W_Screen *scr, struct W_View *target);
void W_WL_SetLoopDispatch(struct W_Screen *scr,
int loop_fd,
void (*dispatch_fn)(int timeout_ms, void *ud),

View File

@ -206,6 +206,9 @@ int WMHandleEvent(WMEvent * event)
return False;
}
if (!view->screen || view->flags.alreadyDead) /* view was destroyed between queue and dispatch */
return True;
view->screen->lastEventTime = getEventTime(view->screen, event);
toplevel = W_TopLevelOfView(view);

View File

@ -67,6 +67,8 @@ wl_fetch(void *ectx, WMEvent *ev)
WLQEntry *e = &ctx->buf[ctx->head];
*ev = e->ev;
ev->_view_target = e->target;
if (e->target && !W_IsValidView(e->target))
ev->_view_target = NULL;
if (e->target) {
W_Screen *scr = e->target->screen;
if (scr)
@ -110,6 +112,8 @@ wl_check_mask(void *ectx, long mask, WMEvent *ev)
unsigned long emask = wme_mask(ctx->buf[i].ev.type);
if (emask & (unsigned long)mask) {
*ev = ctx->buf[i].ev;
if (ev->_view_target && !W_IsValidView(ev->_view_target))
ev->_view_target = NULL;
/* Remove entry by shifting */
int next = (i + 1) % WL_EVENT_QUEUE_CAP;
while (next != ctx->tail) {
@ -134,6 +138,8 @@ wl_check_typed_window(void *ectx, WNativeWindow w, int type, WMEvent *ev)
if (ctx->buf[i].ev.type == type &&
ctx->buf[i].ev.window == w) {
*ev = ctx->buf[i].ev;
if (ev->_view_target && !W_IsValidView(ev->_view_target))
ev->_view_target = NULL;
int next = (i + 1) % WL_EVENT_QUEUE_CAP;
while (next != ctx->tail) {
ctx->buf[i] = ctx->buf[next];
@ -224,6 +230,21 @@ W_WL_SetLoopDispatch(W_Screen *scr,
}
/* Tick the Wayland compositor event loop once (used by modal loops). */
void
W_WL_InvalidateView(W_Screen *scr, W_View *target)
{
if (!scr || !scr->eventBackendCtx)
return;
WLEventCtx *ctx = scr->eventBackendCtx;
int i = ctx->head;
while (i != ctx->tail) {
if (ctx->buf[i].target == target) {
ctx->buf[i].target = NULL;
}
i = (i + 1) % WL_EVENT_QUEUE_CAP;
}
}
void
W_WL_DispatchLoop(W_Screen *scr, int timeout_ms)
{

View File

@ -374,6 +374,8 @@ static void destroyView(W_View * view)
if (view->backColor)
WMReleaseColor(view->backColor);
view->screen = NULL; /* invalidate for in-flight events */
wfree(view);
}

View File

@ -91,6 +91,20 @@ static void view_table_remove(Window id)
}
}
Bool W_IsValidView(W_View *view)
{
if (!view) return False;
unsigned idx = ((unsigned)view->window) % VIEW_TABLE_SIZE;
for (int i = 0; i < VIEW_TABLE_SIZE; i++) {
unsigned slot = (idx + i) % VIEW_TABLE_SIZE;
if (view_table[slot].view == view)
return True;
if (view_table[slot].id == 0)
return False;
}
return False;
}
/*
* W_RegisterBacking - register a pixman_image_t backing for any synthetic
* window ID (WINGs views OR WM frame_buf windows). Used by wl_backend.c so
@ -231,7 +245,7 @@ static void wl_noop_reparent(void *vctx, W_View *view, W_View *newParent, int x,
static void wl_destroy(void *vctx, W_View *view)
{
(void)vctx;
(void)view;
W_WL_InvalidateView(view->screen, view);
/* Nothing to free — synthetic ID only */
}