Wayland: NULL wl_pointer dereference in setCursorImage when seat drops pointer capability while an animated cursor is shown
Environment
- GLFW 3.4.0 (also present on current master, 92dcf4c)
- Ubuntu 26.04, GNOME/Mutter Wayland session, libwayland-client 1.24.0
- Statically linked into a closed-source application; crash reproduced from an Apport core dump
Summary
When the compositor removes WL_SEAT_CAPABILITY_POINTER from the seat (Mutter does this whenever the last pointer-capable device is removed, e.g. unplugging the only USB mouse or a Bluetooth mouse going to sleep), seatHandleCapabilities destroys _glfw.wl.pointer and sets it to NULL but leaves the rest of the pointer state untouched. If an animated cursor was active at that moment, the cursor timerfd is still armed, and on the next tick handleEvents calls incrementCursorImage -> setCursorImage -> wl_pointer_set_cursor(NULL, ...), which segfaults inside wl_proxy_marshal.
Backtrace
0 wl_proxy_marshal () from libwayland-client.so.0 (proxy == NULL, opcode == WL_POINTER_SET_CURSOR)
1 setCursorImage (src/wl_window.c, wl_pointer_set_cursor call)
2 incrementCursorImage
3 handleEvents (cursorTimerfd POLLIN branch)
4 _glfwPollEventsWayland
5 application main loop
Registers at the fault: rdi=0 (proxy), rsi=0 (opcode), rdx=<pointer enter serial>, r8/r9=<hotspot>.
Analysis (3.4 line numbers, master equivalents in parentheses)
seatHandleCapabilities(wl_window.c:1880, master:2072): on capability loss it doeswl_pointer_destroy(_glfw.wl.pointer); _glfw.wl.pointer = NULL;and nothing else._glfw.wl.pointerFocus(master:_glfw.wl.pointerSurface),window->wl.hoveredand_glfw.wl.cursorTimerfdall keep their previous state. The compositor does not sendwl_pointer.leavebefore the capability change, so nothing else clears them.handleEvents(wl_window.c:1288, master:1472): when the cursor timerfd fires it callsincrementCursorImage(_glfw.wl.pointerFocus)(master:incrementCursorImage()which reads_glfw.wl.pointerSurface). The stale focus passes the checks.setCursorImage(wl_window.c:1078, master:1248) re-arms the timer and callswl_pointer_set_cursor(_glfw.wl.pointer, ...)with_glfw.wl.pointer == NULL.
The timer is only armed for cursors whose wl_cursor_image.delay is non-zero, so a static arrow never triggers this; an animated theme cursor (e.g. "wait"/"progress") is required.
Steps to reproduce
- GNOME Wayland session with exactly one pointer device (e.g. one USB mouse, no touchpad).
- Run any GLFW 3.4 program and make it show an animated cursor (a
glfwCreateCursorcursor with multiple images, or a theme where the chosen standard cursor is animated) while the mouse hovers the window. - Unplug the mouse.
- The program expected to crash in
wl_proxy_marshalwithin one cursor frame delay.
Workaround for my application
Running with these env changes fixed this issue:
env -u WAYLAND_DISPLAY XDG_SESSION_TYPE=x11 my-appSuggested fix
In seatHandleCapabilities, when destroying/releasing the pointer, also:
- disarm the cursor timer (
timerfd_settimewith a zeroeditimerspec), - clear
_glfw.wl.pointerFocus/_glfw.wl.pointerSurface, - if the focused window had
wl.hoveredset, clear it and emit_glfwInputCursorEnter(window, GLFW_FALSE)so the application sees a cursor leave, mirroringpointerHandleLeave.
Additionally, setCursorImage (and the other wl_pointer_set_cursor call sites) could early-return when _glfw.wl.pointer == NULL as a defensive check.
Source: glfw/glfw