From 84cd92d7055ca74c17a29d7b84aaab598f86bff9 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Mon, 7 Sep 2026 11:35:33 -0400
Subject: [PATCH] wayland: Avoid dereferencing a nil data offer
Pass the data device to the selection offer update functions instead of pulling it from the offer, as the offer may be nil.
---
src/video/wayland/SDL_waylandclipboard.c | 2 +-
src/video/wayland/SDL_waylanddatamanager.c | 39 +++++++++++++++-------
src/video/wayland/SDL_waylanddatamanager.h | 4 +--
src/video/wayland/SDL_waylandevents.c | 8 +----
4 files changed, 31 insertions(+), 22 deletions(-)
diff --git a/src/video/wayland/SDL_waylandclipboard.c b/src/video/wayland/SDL_waylandclipboard.c
index 9e786afd228f8..c5aa38318db54 100644
--- a/src/video/wayland/SDL_waylandclipboard.c
+++ b/src/video/wayland/SDL_waylandclipboard.c
@@ -65,7 +65,7 @@ bool Wayland_SetClipboardData(SDL_VideoDevice *_this)
SDL_WaylandDataSource *source = Wayland_DataSourceCreate(video_data);
Wayland_DataSourceSetCallback(source, _this->clipboard_callback, _this->clipboard_userdata, _this->clipboard_sequence);
- result = Wayland_DataDeviceSetSelection(data_device, source, (const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types);
+ result = Wayland_DataDeviceSetSelectionSource(data_device, source, (const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types);
if (!result) {
Wayland_DataSourceDestroy(source);
}
diff --git a/src/video/wayland/SDL_waylanddatamanager.c b/src/video/wayland/SDL_waylanddatamanager.c
index cb54753192da2..f30719c361612 100644
--- a/src/video/wayland/SDL_waylanddatamanager.c
+++ b/src/video/wayland/SDL_waylanddatamanager.c
@@ -463,6 +463,8 @@ void Wayland_PrimarySelectionSourceDestroy(SDL_WaylandPrimarySelectionSource *so
}
}
+static void SelectionOfferNotifyFromMIMEs(SDL_WaylandDataDevice *data_device, bool check_origin);
+
static void offer_source_done_handler(void *data, struct wl_callback *callback, uint32_t callback_data)
{
if (!callback) {
@@ -485,7 +487,7 @@ static void offer_source_done_handler(void *data, struct wl_callback *callback,
const bool source_is_external = SDL_strncmp(offer->data_device->id_str, id, length) != 0;
SDL_free(id);
if (source_is_external) {
- Wayland_DataOfferNotifyFromMIMEs(offer, false);
+ SelectionOfferNotifyFromMIMEs(offer->data_device, false);
} else {
// Recursive data offer; just destroy it.
SDL_WaylandDataDevice *data_device = offer->data_device;
@@ -528,9 +530,9 @@ static void DataOfferCheckSource(SDL_WaylandDataOffer *offer, const char *mime_t
}
}
-static void SetCurrentClipboardOffer(SDL_WaylandDataOffer *offer)
+static void UpdateSeatOffers(SDL_WaylandDataDevice *data_device)
{
- SDL_WaylandSeat *offer_seat = offer->data_device->seat;
+ SDL_WaylandSeat *offer_seat = data_device->seat;
SDL_VideoData *video_data = offer_seat->display;
// Clear any existing references to the existing clipboard data before replacing the current offer.
@@ -551,10 +553,12 @@ static void SetCurrentClipboardOffer(SDL_WaylandDataOffer *offer)
video_data->current_data_offer_seat = offer_seat;
}
-void Wayland_DataOfferNotifyFromMIMEs(SDL_WaylandDataOffer *offer, bool check_origin)
+static void SelectionOfferNotifyFromMIMEs(SDL_WaylandDataDevice *data_device, bool check_origin)
{
- int nformats = 0;
+ SDL_WaylandDataOffer *offer = data_device->selection_offer;
char **new_mime_types = NULL;
+ size_t num_formats = 0;
+
if (offer) {
size_t alloc_size = 0;
@@ -571,11 +575,11 @@ void Wayland_DataOfferNotifyFromMIMEs(SDL_WaylandDataOffer *offer, bool check_or
return;
}
- ++nformats;
+ ++num_formats;
alloc_size += SDL_strlen(item->mime_type) + 1;
}
- alloc_size += (nformats + 1) * sizeof(char *);
+ alloc_size += (num_formats + 1) * sizeof(char *);
new_mime_types = SDL_AllocateTemporaryMemory(alloc_size);
if (!new_mime_types) {
@@ -584,7 +588,7 @@ void Wayland_DataOfferNotifyFromMIMEs(SDL_WaylandDataOffer *offer, bool check_or
}
// Second pass to fill.
- char *strPtr = (char *)(new_mime_types + nformats + 1);
+ char *strPtr = (char *)(new_mime_types + num_formats + 1);
item = NULL;
int i = 0;
wl_list_for_each(item, &offer->mimes, link) {
@@ -596,11 +600,22 @@ void Wayland_DataOfferNotifyFromMIMEs(SDL_WaylandDataOffer *offer, bool check_or
strPtr = stpcpy(strPtr, item->mime_type) + 1;
i++;
}
- new_mime_types[nformats] = NULL;
+ new_mime_types[num_formats] = NULL;
}
- SetCurrentClipboardOffer(offer);
- SDL_SendClipboardUpdate(false, new_mime_types, nformats);
+ UpdateSeatOffers(data_device);
+ SDL_SendClipboardUpdate(false, new_mime_types, num_formats);
+}
+
+void Wayland_DataDeviceSetSelectionOffer(SDL_WaylandDataDevice *data_device, SDL_WaylandDataOffer *offer)
+{
+ // Don't notify when clearing the old selection offer if doing so will inadvertently clear the selection source.
+ const bool notify = offer || (!offer && data_device->selection_offer && (!data_device->selection_offer->callback || !data_device->selection_source));
+ Wayland_DataOfferDestroy(data_device->selection_offer);
+ data_device->selection_offer = offer;
+ if (notify) {
+ SelectionOfferNotifyFromMIMEs(data_device, true);
+ }
}
void *Wayland_DataOfferReceive(SDL_WaylandDataOffer *offer, const char *mime_type, size_t *length, bool extended_timeout)
@@ -726,7 +741,7 @@ void Wayland_PrimarySelectionOfferDestroy(SDL_WaylandPrimarySelectionOffer *offe
}
}
-bool Wayland_DataDeviceSetSelection(SDL_WaylandDataDevice *data_device, SDL_WaylandDataSource *source, const char **mime_types, size_t mime_count)
+bool Wayland_DataDeviceSetSelectionSource(SDL_WaylandDataDevice *data_device, SDL_WaylandDataSource *source, const char **mime_types, size_t mime_count)
{
if (!data_device) {
return SDL_SetError("Invalid Data Device");
diff --git a/src/video/wayland/SDL_waylanddatamanager.h b/src/video/wayland/SDL_waylanddatamanager.h
index 575cf37c123d2..aeba94d86626d 100644
--- a/src/video/wayland/SDL_waylanddatamanager.h
+++ b/src/video/wayland/SDL_waylanddatamanager.h
@@ -130,7 +130,7 @@ extern void Wayland_PrimarySelectionSourceDestroy(SDL_WaylandPrimarySelectionSou
extern void *Wayland_DataOfferReceive(SDL_WaylandDataOffer *offer, const char *mime_type, size_t *length, bool extended_timeout);
extern void *Wayland_PrimarySelectionOfferReceive(SDL_WaylandPrimarySelectionOffer *offer, const char *mime_type, size_t *length);
extern bool Wayland_DataOfferHasMIME(SDL_WaylandDataOffer *offer, const char *mime_type);
-extern void Wayland_DataOfferNotifyFromMIMEs(SDL_WaylandDataOffer *offer, bool check_origin);
+extern void Wayland_DataDeviceSetSelectionOffer(SDL_WaylandDataDevice *data_device, SDL_WaylandDataOffer *offer);
extern bool Wayland_PrimarySelectionOfferHasMIME(SDL_WaylandPrimarySelectionOffer *offer, const char *mime_type);
extern bool Wayland_DataOfferAddMIME(SDL_WaylandDataOffer *offer, const char *mime_type);
extern bool Wayland_PrimarySelectionOfferAddMIME(SDL_WaylandPrimarySelectionOffer *offer, const char *mime_type);
@@ -138,7 +138,7 @@ extern void Wayland_DataOfferDestroy(SDL_WaylandDataOffer *offer);
extern void Wayland_PrimarySelectionOfferDestroy(SDL_WaylandPrimarySelectionOffer *offer);
// Clipboard / Primary Selection
-extern bool Wayland_DataDeviceSetSelection(SDL_WaylandDataDevice *device, SDL_WaylandDataSource *source, const char **mime_types, size_t mime_count);
+extern bool Wayland_DataDeviceSetSelectionSource(SDL_WaylandDataDevice *device, SDL_WaylandDataSource *source, const char **mime_types, size_t mime_count);
extern bool Wayland_PrimarySelectionDeviceSetSelection(SDL_WaylandPrimarySelectionDevice *device, SDL_WaylandPrimarySelectionSource *source, const char *const *mime_types, size_t mime_count);
extern void Wayland_DataDeviceSetSerial(SDL_WaylandDataDevice *device, uint32_t serial);
extern void Wayland_PrimarySelectionDeviceSetSerial(SDL_WaylandPrimarySelectionDevice *device, uint32_t serial);
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 7c5c35fe482f9..9c597ebfd25cf 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -2943,13 +2943,7 @@ static void data_device_handle_selection(void *data, struct wl_data_device *wl_d
". In data_device_listener . data_device_handle_selection on data_offer 0x%08x",
(id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)id) : -1));
- // Don't notify when clearing the old selection offer if doing so will inadvertently clear the selection source.
- const bool notify = offer || (!offer && data_device->selection_offer && (!data_device->selection_offer->callback || !data_device->selection_source));
- Wayland_DataOfferDestroy(data_device->selection_offer);
- data_device->selection_offer = offer;
- if (notify) {
- Wayland_DataOfferNotifyFromMIMEs(offer, true);
- }
+ Wayland_DataDeviceSetSelectionOffer(data_device, offer);
}
static const struct wl_data_device_listener data_device_listener = {