SDL: wayland: Validate surfaces and window data before sending touch events

From 1cadf4bab90b1b427c9f2bb4a19dfd179f5aa398 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Mon, 22 May 2023 12:34:40 -0400
Subject: [PATCH] wayland: Validate surfaces and window data before sending
 touch events

Ensure that incoming touch events originate from valid surfaces owned by SDL and have proper window data before forwarding them to the touch subsystem, or the window focus pointer that is sent with the event may not be a pointer to an SDL window.

Additionally, ensure that allocated touch events are always cleaned up on exit.
---
 src/video/wayland/SDL_waylandevents.c | 168 ++++++++++++--------------
 1 file changed, 77 insertions(+), 91 deletions(-)

diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index eace98072cef..050c3cc88dc0 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -84,105 +84,67 @@
 struct SDL_WaylandTouchPoint
 {
     SDL_TouchID id;
-    float x;
-    float y;
+    wl_fixed_t fx;
+    wl_fixed_t fy;
     struct wl_surface *surface;
 
-    struct SDL_WaylandTouchPoint *prev;
-    struct SDL_WaylandTouchPoint *next;
+    struct wl_list link;
 };
 
-struct SDL_WaylandTouchPointList
-{
-    struct SDL_WaylandTouchPoint *head;
-    struct SDL_WaylandTouchPoint *tail;
-};
-
-static struct SDL_WaylandTouchPointList touch_points = { NULL, NULL };
+static struct wl_list touch_points;
 
 static char *Wayland_URIToLocal(char *uri);
 
-static void touch_add(SDL_TouchID id, float x, float y, struct wl_surface *surface)
+static void touch_add(SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface *surface)
 {
     struct SDL_WaylandTouchPoint *tp = SDL_malloc(sizeof(struct SDL_WaylandTouchPoint));
 
+    SDL_zerop(tp);
     tp->id = id;
-    tp->x = x;
-    tp->y = y;
+    tp->fx = fx;
+    tp->fy = fy;
     tp->surface = surface;
 
-    if (touch_points.tail) {
-        touch_points.tail->next = tp;
-        tp->prev = touch_points.tail;
-    } else {
-        touch_points.head = tp;
-        tp->prev = NULL;
-    }
-
-    touch_points.tail = tp;
-    tp->next = NULL;
+    WAYLAND_wl_list_insert(&touch_points, &tp->link);
 }
 
-static void touch_update(SDL_TouchID id, float x, float y)
+static void touch_update(SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface **surface)
 {
-    struct SDL_WaylandTouchPoint *tp = touch_points.head;
+    struct SDL_WaylandTouchPoint *tp;
 
-    while (tp) {
+    wl_list_for_each (tp, &touch_points, link) {
         if (tp->id == id) {
-            tp->x = x;
-            tp->y = y;
+            tp->fx = fx;
+            tp->fy = fy;
+            if (surface) {
+                *surface = tp->surface;
+            }
+            break;
         }
-
-        tp = tp->next;
     }
 }
 
-static void touch_del(SDL_TouchID id, float *x, float *y, struct wl_surface **surface)
+static void touch_del(SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_surface **surface)
 {
-    struct SDL_WaylandTouchPoint *tp = touch_points.head;
+    struct SDL_WaylandTouchPoint *tp;
 
-    while (tp) {
+    wl_list_for_each (tp, &touch_points, link) {
         if (tp->id == id) {
-            *x = tp->x;
-            *y = tp->y;
-            *surface = tp->surface;
-
-            if (tp->prev) {
-                tp->prev->next = tp->next;
-            } else {
-                touch_points.head = tp->next;
+            if (fx) {
+                *fx = tp->fx;
             }
-
-            if (tp->next) {
-                tp->next->prev = tp->prev;
-            } else {
-                touch_points.tail = tp->prev;
+            if (fy) {
+                *fy = tp->fy;
             }
-
-            {
-                struct SDL_WaylandTouchPoint *next = tp->next;
-                SDL_free(tp);
-                tp = next;
+            if (surface) {
+                *surface = tp->surface;
             }
-        } else {
-            tp = tp->next;
-        }
-    }
-}
 
-static struct wl_surface *touch_surface(SDL_TouchID id)
-{
-    struct SDL_WaylandTouchPoint *tp = touch_points.head;
-
-    while (tp) {
-        if (tp->id == id) {
-            return tp->surface;
+            WAYLAND_wl_list_remove(&tp->link);
+            SDL_free(tp);
+            break;
         }
-
-        tp = tp->next;
     }
-
-    return NULL;
 }
 
 static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp)
@@ -958,51 +920,67 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
                                int id, wl_fixed_t fx, wl_fixed_t fy)
 {
     struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
-    SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
-    const double dblx = wl_fixed_to_double(fx) * window_data->pointer_scale_x;
-    const double dbly = wl_fixed_to_double(fy) * window_data->pointer_scale_y;
-    const float x = dblx / window_data->sdlwindow->w;
-    const float y = dbly / window_data->sdlwindow->h;
+    SDL_WindowData *window_data;
+
+    /* Check that this surface belongs to one of the SDL windows */
+    if (!SDL_WAYLAND_own_surface(surface)) {
+        return;
+    }
 
-    touch_add(id, x, y, surface);
+    touch_add(id, fx, fy, surface);
     input->touch_down_serial = serial;
+    window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
+
+    if (window_data) {
+        const float x = wl_fixed_to_double(fx) / window_data->wl_window_width;
+        const float y = wl_fixed_to_double(fy) / window_data->wl_window_height;
 
-    SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
-                  (SDL_FingerID)id, window_data->sdlwindow, SDL_TRUE, x, y, 1.0f);
+        SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
+                      (SDL_FingerID)id, window_data->sdlwindow, SDL_TRUE, x, y, 1.0f);
+    }
 }
 
 static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial,
                              uint32_t timestamp, int id)
 {
     struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
-    float x = 0, y = 0;
+    wl_fixed_t fx = 0, fy = 0;
     struct wl_surface *surface = NULL;
-    SDL_Window *window = NULL;
 
-    touch_del(id, &x, &y, &surface);
+    touch_del(id, &fx, &fy, &surface);
 
     if (surface) {
         SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
-        window = window_data->sdlwindow;
-    }
 
-    SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
-                  (SDL_FingerID)id, window, SDL_FALSE, x, y, 0.0f);
+        if (window_data) {
+            const float x = wl_fixed_to_double(fx) / window_data->wl_window_width;
+            const float y = wl_fixed_to_double(fy) / window_data->wl_window_height;
+
+            SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
+                          (SDL_FingerID)id, window_data->sdlwindow, SDL_FALSE, x, y, 0.0f);
+        }
+    }
 }
 
 static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t timestamp,
                                  int id, wl_fixed_t fx, wl_fixed_t fy)
 {
     struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
-    SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(touch_surface(id));
-    const double dblx = wl_fixed_to_double(fx) * window_data->pointer_scale_x;
-    const double dbly = wl_fixed_to_double(fy) * window_data->pointer_scale_y;
-    const float x = dblx / window_data->sdlwindow->w;
-    const float y = dbly / window_data->sdlwindow->h;
+    struct wl_surface *surface = NULL;
+
+    touch_update(id, fx, fy, &surface);
+
+    if (surface) {
+        SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
+
+        if (window_data) {
+            const float x = wl_fixed_to_double(fx) / window_data->wl_window_width;
+            const float y = wl_fixed_to_double(fy) / window_data->wl_window_height;
 
-    touch_update(id, x, y);
-    SDL_SendTouchMotion(Wayland_GetPointerTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
-                        (SDL_FingerID)id, window_data->sdlwindow, x, y, 1.0f);
+            SDL_SendTouchMotion(Wayland_GetPointerTimestamp(input, timestamp), (SDL_TouchID)(intptr_t)touch,
+                                (SDL_FingerID)id, window_data->sdlwindow, x, y, 1.0f);
+        }
+    }
 }
 
 static void touch_handler_frame(void *data, struct wl_touch *touch)
@@ -1626,6 +1604,7 @@ static void seat_handle_capabilities(void *data, struct wl_seat *seat,
     }
 
     if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch) {
+        WAYLAND_wl_list_init(&touch_points);
         input->touch = wl_seat_get_touch(seat);
         SDL_AddTouch((SDL_TouchID)(intptr_t)input->touch, SDL_TOUCH_DEVICE_DIRECT, "wayland_touch");
         wl_touch_set_user_data(input->touch, input);
@@ -2757,12 +2736,19 @@ void Wayland_display_destroy_input(SDL_VideoData *d)
     }
 
     if (input->touch) {
+        struct SDL_WaylandTouchPoint *tp, *tmp;
+
         SDL_DelTouch(1);
         if (wl_touch_get_version(input->touch) >= WL_TOUCH_RELEASE_SINCE_VERSION) {
             wl_touch_release(input->touch);
         } else {
             wl_touch_destroy(input->touch);
         }
+
+        wl_list_for_each_safe (tp, tmp, &touch_points, link) {
+            WAYLAND_wl_list_remove(&tp->link);
+            SDL_free(tp);
+        }
     }
 
     if (input->tablet) {