Commit: patch 9.2.0616: GTK4: use-after-free on clipboard read timeout

1 view
Skip to first unread message

Christian Brabandt

unread,
4:45 PM (3 hours ago) 4:45 PM
to vim...@googlegroups.com
patch 9.2.0616: GTK4: use-after-free on clipboard read timeout

Commit: https://github.com/vim/vim/commit/2d3e3fe7846410f6ab4d9e09c9f1ef5c6672a2d2
Author: Yasuhiro Matsumoto <matt...@gmail.com>
Date: Wed Jun 10 20:37:11 2026 +0000

patch 9.2.0616: GTK4: use-after-free on clipboard read timeout

Problem: clip_mch_request_selection() stack-allocates ClipReadData
and waits up to 3 seconds for the async callback to fire.
If the timeout expires before the callback runs, the
function returns and the stack frame is gone, but the async
read is still pending; when the callback eventually fires
it a use-after-free.
Solution: Heap-allocate ClipReadData and add an "abandoned" flag
(Yasuhiro Matsumoto).

closes: #20467

Co-Authored-by: Claude <nor...@anthropic.com>
Signed-off-by: Yasuhiro Matsumoto <matt...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c
index 9c913bc71..dd4946793 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -3554,6 +3554,7 @@ gtk4_get_clipboard(Clipboard_T *cbd, GdkContentProvider **provider)
typedef struct {
Clipboard_T *cbd;
gboolean done;
+ gboolean abandoned; // requester timed out, callback owns "crd"
} ClipReadData;

/*
@@ -3596,7 +3597,7 @@ clip_read_cb(GdkClipboard *cb, GAsyncResult *result, ClipReadData *crd)
len = (long)arr->len;
actual = final = g_byte_array_free(arr, FALSE);

- if (clip_convert_data(&final, &len, &motion_type,
+ if (!crd->abandoned && clip_convert_data(&final, &len, &motion_type,
STRCMP(mime_type, VIM_MIMETYPE_NAME) == 0,
STRCMP(mime_type, VIMENC_MIMETYPE_NAME) == 0, &tofree) == OK)
clip_yank_selection(motion_type, final, len, cbd);
@@ -3606,7 +3607,11 @@ clip_read_cb(GdkClipboard *cb, GAsyncResult *result, ClipReadData *crd)
exit:
if (in_stream != NULL)
g_object_unref(in_stream);
- crd->done = TRUE;
+ // free "crd" if the requester gave up, else mark the read complete
+ if (crd->abandoned)
+ vim_free(crd);
+ else
+ crd->done = TRUE;
}

/*
@@ -3623,25 +3628,37 @@ clip_mch_request_selection(Clipboard_T *cbd)
NULL
};
GdkClipboard *clipboard;
- ClipReadData crd;
+ ClipReadData *crd;
time_t start;

clipboard = gtk4_get_clipboard(cbd, NULL);
if (clipboard == NULL)
return;

- crd.cbd = cbd;
- crd.done = FALSE;
+ // Heap-allocate: on a timeout this returns before the read completes,
+ // so "crd" must outlive this stack frame.
+ crd = ALLOC_ONE(ClipReadData);
+ if (crd == NULL)
+ return;
+ crd->cbd = cbd;
+ crd->done = FALSE;
+ crd->abandoned = FALSE;

gdk_clipboard_read_async(
clipboard, clip_html ? supported_mimes : mimes_no_html,
- G_PRIORITY_HIGH, NULL, (GAsyncReadyCallback)clip_read_cb, &crd);
+ G_PRIORITY_HIGH, NULL, (GAsyncReadyCallback)clip_read_cb, crd);

// Spin until the async callback fires, with a 3-second wall-clock
// timeout as a safety net.
start = time(NULL);
- while (!crd.done && time(NULL) < start + 3)
+ while (!crd->done && time(NULL) < start + 3)
g_main_context_iteration(NULL, TRUE);
+
+ if (crd->done)
+ vim_free(crd);
+ else
+ // timed out: hand ownership to the callback, which frees "crd"
+ crd->abandoned = TRUE;
}

static int in_clipboard_set = FALSE;
diff --git a/src/version.c b/src/version.c
index 319f43ba6..7790231b8 100644
--- a/src/version.c
+++ b/src/version.c
@@ -754,6 +754,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 616,
/**/
615,
/**/
Reply all
Reply to author
Forward
0 new messages