From b45dc886bd7c9cb4d1f070bc4a81d239ba741222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1lm=C3=A1n=20=E2=80=9EKAMI=E2=80=9D=20Szalai?= Date: Sat, 14 Mar 2026 12:39:41 +0100 Subject: [PATCH 1/4] wayland/single-pixel-buffer: Fix memory leak and stride for BGR_888 When a Wayland client creates a single-pixel buffer but never commits it to a surface, meta_wayland_buffer_realize() is never called, so buffer->single_pixel.single_pixel_buffer is never populated. The buffer finalizer therefore calls meta_wayland_single_pixel_buffer_free(NULL), leaking the MetaWaylandSinglePixelBuffer allocated in single_pixel_buffer_manager_create_1px_rgba32_buffer(). Fix this by registering single_pixel_buffer_resource_destroy() as the wl_buffer resource destructor, which calls g_free() on the user data. To avoid a double-free when the buffer *has* been realized (the buffer GObject finalizer would also free the struct), clear the wl_resource user data to NULL in meta_wayland_buffer_realize() immediately after ownership is transferred to the buffer object. g_free(NULL) is a no-op, so the destructor becomes safe in both paths. As a follow-on, update meta_wayland_single_pixel_buffer_attach() to read the pointer from buffer->single_pixel.single_pixel_buffer (which is always valid at that call site) rather than wl_resource_get_user_data(), which is now NULL after realize. Also fix the stride argument passed to cogl_texture_2d_new_from_data(): COGL_PIXEL_FORMAT_BGR_888 uses 3 bytes per pixel, not 4. Pass 3 for the opaque case and keep 4 for COGL_PIXEL_FORMAT_BGRA_8888_PRE. Co-Authored-By: Claude Sonnet 4.6 --- src/wayland/meta-wayland-buffer.c | 3 +++ src/wayland/meta-wayland-single-pixel-buffer.c | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/wayland/meta-wayland-buffer.c b/src/wayland/meta-wayland-buffer.c index 069269ef7..841f0fc12 100644 --- a/src/wayland/meta-wayland-buffer.c +++ b/src/wayland/meta-wayland-buffer.c @@ -190,6 +190,9 @@ meta_wayland_buffer_realize (MetaWaylandBuffer *buffer) { buffer->single_pixel.single_pixel_buffer = single_pixel_buffer; buffer->type = META_WAYLAND_BUFFER_TYPE_SINGLE_PIXEL; + /* Ownership is transferred to the buffer GObject. Clear the wl_resource + * user data so that the resource destructor doesn't double-free it. */ + wl_resource_set_user_data (buffer->resource, NULL); return TRUE; } diff --git a/src/wayland/meta-wayland-single-pixel-buffer.c b/src/wayland/meta-wayland-single-pixel-buffer.c index c63fbe3fb..487549d7b 100644 --- a/src/wayland/meta-wayland-single-pixel-buffer.c +++ b/src/wayland/meta-wayland-single-pixel-buffer.c @@ -43,6 +43,16 @@ buffer_destroy (struct wl_client *client, wl_resource_destroy (resource); } +static void +single_pixel_buffer_resource_destroy (struct wl_resource *resource) +{ + /* Free the MetaWaylandSinglePixelBuffer if no MetaWaylandBuffer GObject + * has taken ownership of it yet (i.e. the buffer was never committed to a + * surface). When realize does take ownership it sets the user data to NULL + * so that g_free(NULL) here becomes a safe no-op. */ + g_free (wl_resource_get_user_data (resource)); +} + static const struct wl_buffer_interface single_pixel_buffer_implementation = { buffer_destroy, @@ -77,7 +87,8 @@ single_pixel_buffer_manager_create_1px_rgba32_buffer (struct wl_client *client wl_resource_create (client, &wl_buffer_interface, 1, buffer_id); wl_resource_set_implementation (buffer_resource, &single_pixel_buffer_implementation, - single_pixel_buffer, NULL); + single_pixel_buffer, + single_pixel_buffer_resource_destroy); meta_wayland_buffer_from_resource (buffer_resource); } @@ -115,7 +126,7 @@ meta_wayland_single_pixel_buffer_attach (MetaWaylandBuffer *buffer, CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend); MetaWaylandSinglePixelBuffer *single_pixel_buffer = - wl_resource_get_user_data (buffer->resource); + buffer->single_pixel.single_pixel_buffer; uint8_t data[4]; CoglPixelFormat pixel_format; CoglTexture2D *tex_2d; @@ -140,7 +151,8 @@ meta_wayland_single_pixel_buffer_attach (MetaWaylandBuffer *buffer, tex_2d = cogl_texture_2d_new_from_data (cogl_context, 1, 1, pixel_format, - 4, data, + pixel_format == COGL_PIXEL_FORMAT_BGR_888 ? 3 : 4, + data, error); if (!tex_2d) return FALSE; From 8283b93b957c7ae2bf493af24aad3ca43159d96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1lm=C3=A1n=20=E2=80=9EKAMI=E2=80=9D=20Szalai?= Date: Sat, 14 Mar 2026 12:39:47 +0100 Subject: [PATCH 2/4] wayland/touch: Fix undefined behaviour in frame_slots bit-shift frame_slots is declared as guint64 but both sites that update it use the plain integer literal 1, which is a 32-bit int. For touch slot indices >= 32 the expression (1 << slot) shifts past the width of int, which is undefined behaviour in C. Replace both occurrences with ((guint64) 1 << slot) so the shift is always performed on a 64-bit value, matching the type of frame_slots. Co-Authored-By: Claude Sonnet 4.6 --- src/wayland/meta-wayland-touch.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wayland/meta-wayland-touch.c b/src/wayland/meta-wayland-touch.c index 9c2cfc9df..8c37a467b 100644 --- a/src/wayland/meta-wayland-touch.c +++ b/src/wayland/meta-wayland-touch.c @@ -415,7 +415,7 @@ check_send_frame_event (MetaWaylandTouch *touch, { sequence = clutter_event_get_event_sequence (event); slot = meta_event_native_sequence_get_slot (sequence); - touch->frame_slots &= ~(1 << slot); + touch->frame_slots &= ~((guint64) 1 << slot); if (touch->frame_slots == 0) send_frame_event = TRUE; @@ -532,7 +532,7 @@ evdev_filter_func (struct libinput_event *event, /* XXX: Could theoretically overflow, 64 slots should be * enough for most hw/usecases though. */ - touch->frame_slots |= (1 << slot); + touch->frame_slots |= ((guint64) 1 << slot); break; } case LIBINPUT_EVENT_TOUCH_CANCEL: From c2a2c9e2a0cbef7c79d08844429e4f079133e724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1lm=C3=A1n=20=E2=80=9EKAMI=E2=80=9D=20Szalai?= Date: Sat, 14 Mar 2026 12:39:52 +0100 Subject: [PATCH 3/4] wayland/pointer-warp: Store and destroy the wl_global on teardown The return value of wl_global_create() was discarded, so the global for wp_pointer_warp_v1 was never destroyed when the seat is freed. This leaves a dangling global registered in the Wayland display, which can cause issues during compositor teardown or restart. Store the wl_global* in MetaWaylandPointerWarp and call wl_global_destroy() in meta_wayland_pointer_warp_destroy(). Co-Authored-By: Claude Sonnet 4.6 --- src/wayland/meta-wayland-pointer-warp.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/wayland/meta-wayland-pointer-warp.c b/src/wayland/meta-wayland-pointer-warp.c index a5fafc024..54ee94a5b 100644 --- a/src/wayland/meta-wayland-pointer-warp.c +++ b/src/wayland/meta-wayland-pointer-warp.c @@ -32,6 +32,7 @@ struct _MetaWaylandPointerWarp { MetaWaylandSeat *seat; + struct wl_global *global; struct wl_list resource_list; }; @@ -121,10 +122,10 @@ meta_wayland_pointer_warp_new (MetaWaylandSeat *seat) pointer_warp->seat = seat; wl_list_init (&pointer_warp->resource_list); - wl_global_create (compositor->wayland_display, - &wp_pointer_warp_v1_interface, - META_WP_POINTER_WARP_VERSION, - pointer_warp, bind_pointer_warp); + pointer_warp->global = wl_global_create (compositor->wayland_display, + &wp_pointer_warp_v1_interface, + META_WP_POINTER_WARP_VERSION, + pointer_warp, bind_pointer_warp); return pointer_warp; } @@ -132,6 +133,7 @@ meta_wayland_pointer_warp_new (MetaWaylandSeat *seat) void meta_wayland_pointer_warp_destroy (MetaWaylandPointerWarp *pointer_warp) { + g_clear_pointer (&pointer_warp->global, wl_global_destroy); wl_list_remove (&pointer_warp->resource_list); g_free (pointer_warp); } From 8934990f381af32cd4cb4b884a3226d2f88c0702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1lm=C3=A1n=20=E2=80=9EKAMI=E2=80=9D=20Szalai?= Date: Sat, 14 Mar 2026 12:39:58 +0100 Subject: [PATCH 4/4] x11/display: Use snprintf instead of sprintf for WM_S atom name Replace sprintf() with snprintf() so the buffer size is enforced. The format string and input range make overflow impossible in practice, but using snprintf() matches modern C coding standards and silences compiler/static-analyser warnings. Co-Authored-By: Claude Sonnet 4.6 --- src/x11/meta-x11-display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/x11/meta-x11-display.c b/src/x11/meta-x11-display.c index 2af3b39de..965526f74 100644 --- a/src/x11/meta-x11-display.c +++ b/src/x11/meta-x11-display.c @@ -1296,7 +1296,7 @@ meta_x11_display_new (MetaDisplay *display, GError **error) xroot, PropertyChangeMask); - sprintf (buf, "WM_S%d", number); + snprintf (buf, sizeof (buf), "WM_S%d", number); wm_sn_atom = XInternAtom (xdisplay, buf, False); new_wm_sn_owner = take_manager_selection (x11_display, xroot, wm_sn_atom, timestamp, replace_current_wm);