Fix crashes when moving tab between OS windows (issue #9677)

Wayland (glfw/wl_window.c):
- Fix out-of-bounds access in send_drag_data: look up item by MIME type
  instead of using the data-request index i to index _glfw.drag.items[].
  The compositor calls drag_source_send once per target window entered,
  so _glfw.wl.drag.count grows independently of item_count, causing
  _glfw.drag.items[i] to be out-of-bounds on the second drag, yielding a
  garbage optional_data pointer that made write() fail with EFAULT.
- Fix protocol error "Drag has not ended": change on_fail and the
  GLFW_DRAG_DATA_REQUEST error path to call finish_drag_write(i)+return
  instead of cancel_drag(), which was calling wl_data_source_destroy()
  before the compositor ended the drag, violating the Wayland protocol.
- Fix double-free of dr.pending_data: null the pointer after free and
  add cleanup to finish_drag_write().
- Fix missing finish_drag_write() after a full write in data-request
  mode, which left the pipe open causing the target to wait for EOF.

X11 (glfw/x11_window.c):
- Wrap XSendEvent() calls in send_xdnd_enter/position/leave/drop with
  _glfwGrabErrorHandlerX11()/_glfwReleaseErrorHandlerX11(). A target
  window destroyed between discovery and message delivery produced a
  BadWindow error that hit the default X11 abort handler. Now handled
  gracefully by clearing current_target or cancelling the drag."

Fixes #9677
Fixes #9683
This commit is contained in:
copilot-swe-agent[bot] 2026-03-17 02:39:19 +00:00 committed by Kovid Goyal
parent ad53a5bdff
commit 877be65447
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
2 changed files with 56 additions and 18 deletions

52
glfw/wl_window.c vendored
View file

@ -3177,6 +3177,7 @@ finish_drag_write(size_t i) {
dr.watch_id = 0;
if (dr.fd > -1) safe_close(dr.fd);
dr.fd = -1;
free(dr.pending_data); dr.pending_data = NULL; dr.sz = 0; dr.offset = 0;
free((void*)dr.mime_type); dr.mime_type = NULL;
}
@ -3198,31 +3199,49 @@ write_as_much_as_possible(int fd, const char *data, size_t sz) {
static void
send_drag_data(_GLFWwindow *window, size_t i) {
ssize_t ret;
bool has_preset_data = _glfw.drag.items[i].data_size > 0;
// Find the item matching the mime type for this data request.
// We cannot use i directly to index _glfw.drag.items because the compositor
// may call drag_source_send multiple times (once per target entered), making
// data_requests grow independently of the items array.
size_t item_idx = _glfw.drag.item_count; // sentinel: not found
for (size_t j = 0; j < _glfw.drag.item_count; j++) {
if (dr.mime_type && _glfw.drag.items[j].mime_type &&
strcmp(_glfw.drag.items[j].mime_type, dr.mime_type) == 0) {
item_idx = j; break;
}
}
if (item_idx == _glfw.drag.item_count) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: compositor requested data for unrecognised MIME type: %s",
dr.mime_type ? dr.mime_type : "(null)");
}
bool has_preset_data = item_idx < _glfw.drag.item_count && _glfw.drag.items[item_idx].data_size > 0;
// On write error, only close this pipe; do NOT destroy the wl_data_source
// since the compositor must send cancelled/dnd_finished before that is safe.
#define on_fail _glfwInputError(\
GLFW_PLATFORM_ERROR, "Wayland: failed to write drag source data to pipe with error: %s", strerror(errno)); \
cancel_drag(GLFW_DRAG_CANCELLED);
finish_drag_write(i); return;
if (dr.sz > dr.offset) {
ret = write_as_much_as_possible(dr.fd, dr.pending_data + dr.offset, dr.sz - dr.offset);
if (ret < 0) { on_fail; } else {
dr.offset += ret;
if (dr.offset >= dr.sz) {
free(dr.pending_data); dr.sz = 0; dr.offset = 0;
if (has_preset_data) finish_drag_write(i);
free(dr.pending_data); dr.pending_data = NULL; dr.sz = 0; dr.offset = 0;
finish_drag_write(i);
}
}
} else if (has_preset_data) {
do { ret = write(dr.fd, _glfw.drag.items[i].optional_data, _glfw.drag.items[i].data_size); } while (ret < 0 && errno == EINTR);
do { ret = write(dr.fd, _glfw.drag.items[item_idx].optional_data, _glfw.drag.items[item_idx].data_size); } while (ret < 0 && errno == EINTR);
if (ret < 0) {
on_fail;
} else {
if ((size_t)ret >= _glfw.drag.items[i].data_size) {
if ((size_t)ret >= _glfw.drag.items[item_idx].data_size) {
finish_drag_write(i);
} else {
void *pending = malloc(_glfw.drag.items[i].data_size - ret);
if (!pending) { on_fail; } else {
dr.pending_data = pending; dr.sz = _glfw.drag.items[i].data_size - ret; dr.offset = 0;
void *pending = malloc(_glfw.drag.items[item_idx].data_size - ret);
if (!pending) { errno = ENOMEM; on_fail; } else {
dr.pending_data = pending; dr.sz = _glfw.drag.items[item_idx].data_size - ret; dr.offset = 0;
}
}
}
@ -3231,21 +3250,20 @@ send_drag_data(_GLFWwindow *window, size_t i) {
_glfwInputDragSourceRequest(window, &ev);
if (ev.err_num) {
if (ev.err_num == EAGAIN) { removeWatch(&_glfw.wl.eventLoopData, dr.watch_id); dr.watch_id = 0; }
else cancel_drag(GLFW_DRAG_CANCELLED);
else { finish_drag_write(i); return; }
} else {
if (ev.data_sz) {
ret = write_as_much_as_possible(dr.fd, ev.data, ev.data_sz);
if (ret >= 0) {
if ((size_t)ret < ev.data_sz) {
void *pending = malloc(ev.data_sz - ret);
if (!pending) { on_fail; } else {
dr.pending_data = pending; dr.sz = ev.data_sz - ret; dr.offset = 0;
memcpy(pending, ev.data + ret, dr.sz);
}
if (ret >= 0 && (size_t)ret < ev.data_sz) {
void *pending = malloc(ev.data_sz - ret);
if (!pending) { ret = -1; } else {
dr.pending_data = pending; dr.sz = ev.data_sz - ret; dr.offset = 0;
memcpy(pending, ev.data + ret, dr.sz);
}
}
_glfwInputDragSourceRequest(window, &ev);
if (ret < 0) { on_fail; }
else if ((size_t)ret >= ev.data_sz) { finish_drag_write(i); }
} else finish_drag_write(i);
}
}

22
glfw/x11_window.c vendored
View file

@ -4048,8 +4048,11 @@ send_xdnd_enter(Window target, int version) {
}
}
_glfwGrabErrorHandlerX11();
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
XFlush(_glfw.x11.display);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode == BadWindow) _glfw.x11.drag.current_target = None;
}
// Send XdndPosition message to target window
@ -4068,9 +4071,12 @@ send_xdnd_position(Window target, int root_x, int root_y, Time timestamp) {
event.xclient.data.l[3] = timestamp;
event.xclient.data.l[4] = _glfw.x11.drag.action_atom;
_glfwGrabErrorHandlerX11();
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
XFlush(_glfw.x11.display);
_glfw.x11.drag.waiting_for_status = true;
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode == BadWindow) _glfw.x11.drag.current_target = None;
else _glfw.x11.drag.waiting_for_status = true;
}
// Send XdndLeave message to target window
@ -4085,8 +4091,11 @@ send_xdnd_leave(Window target) {
event.xclient.format = 32;
event.xclient.data.l[0] = _glfw.x11.drag.source_window;
_glfwGrabErrorHandlerX11();
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
XFlush(_glfw.x11.display);
_glfwReleaseErrorHandlerX11();
// BadWindow on leave is benign the target window is already gone
}
// Send XdndDrop message to target window
@ -4103,8 +4112,19 @@ send_xdnd_drop(Window target, Time timestamp) {
event.xclient.data.l[1] = 0; // Reserved
event.xclient.data.l[2] = timestamp;
_glfwGrabErrorHandlerX11();
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
XFlush(_glfw.x11.display);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode == BadWindow) {
// Target window was destroyed; cancel the drag gracefully
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
if (window) {
GLFWDragEvent ev = {.type = GLFW_DRAG_CANCELLED};
_glfwInputDragSourceRequest(window, &ev);
}
_glfwFreeDragSourceData();
}
}
// Render thumbnail pixels into _glfw.x11.drag.thumbnail_pixmap / thumbnail_gc.