#1152754 ibus-gtk4: segfault in gdk_x11_surface_get_xid() when closing a GTK4 window

Package:
ibus-gtk4
Source:
ibus-gtk4
Description:
Intelligent Input Bus - GTK4 support
Submitter:
Jeffrey Ratcliffe
Date:
2026-11-01 07:07:02 UTC
Severity:
normal
Tags:
#1152754#5
Date:
2026-11-01 06:51:17 UTC
From:
To:
On X11, closing a GTK4 window while a GtkTextView in that window has input
focus segfaults inside libim-ibus.so.  It is reproducible 100% of the time
on this system.

The crash is in ibus-gtk4's input-method module, which calls
gdk_x11_surface_get_xid(NULL) from _set_cursor_location_internal() after the
window has been closed.  libgtk-4-1 is only the callee here.

Environment
-----------
Debian:              forky/sid, amd64
Kernel:              7.1.13+deb14-amd64
Session:             X11 (DISPLAY=:0)
GTK_IM_MODULE:       ibus
ibus-daemon:         1.5.35~rc1-1 (running, started with --daemonize --xim)
ibus-gtk4:           1.5.35~rc1-1
ibus:                1.5.35~rc1-1
libgtk-4-1:          4.24.1+ds-1
libglib2.0-0t64:     2.90.1-2
python3:             3.14.7-3
python3-gi:          3.58.1-1+b1
gir1.2-gtk-4.0:      4.24.1+ds-1

Steps to reproduce
------------------
Save this as gtk4-textview-close-crash.py:

    #!/usr/bin/env python3
    """Minimal GTK4 reproducer: close a window whose Gtk.TextView has focus."""

    import gi

    gi.require_version("Gtk", "4.0")
    from gi.repository import GLib, Gtk

    window = Gtk.Window()
    window.set_default_size(400, 200)
    view = Gtk.TextView()
    view.get_buffer().set_text("hello")
    window.set_child(view)
    window.present()

    def focus_view():
        view.grab_focus()
        print("focused:", type(window.get_focus()).__name__, flush=True)
        return GLib.SOURCE_REMOVE

    def close_window():
        print("closing window", flush=True)
        window.close()
        return GLib.SOURCE_REMOVE

    def quit_loop():
        loop.quit()
        return GLib.SOURCE_REMOVE

    GLib.timeout_add(300, focus_view)
    GLib.timeout_add(700, close_window)

    loop = GLib.MainLoop()
    GLib.timeout_add(1500, quit_loop)
    loop.run()
    print("survived", flush=True)

Run it with:

    DISPLAY=:0 GTK_IM_MODULE=ibus python3 gtk4-textview-close-crash.py
    echo $?

Result:

    focused: TextView
    closing window
    139

Expected result: the window closes and the script exits normally with status
0.  The "139" is SIGSEGV.  The process never reaches the final
print("survived").  The 1500 ms timeout is deliberate: it keeps the main loop
alive long enough for the teardown idle callback to run.  A program that
exits immediately after closing the window can hide the crash.

The same script exits 0 with either of these:

    GTK_IM_MODULE=gtk-im-context-simple python3 gtk4-textview-close-crash.py
    GTK_IM_MODULE=gtk-im-context-none   python3 gtk4-textview-close-crash.py

so the crash is specific to the ibus IM module.

Backtrace
---------
With ibus-gtk4-dbgsym and libgtk-4-1-dbgsym installed, gdb reports:

    Thread 1 "python3" received signal SIGSEGV, Segmentation fault.
    gdk_x11_surface_get_xid (surface=surface@entry=0x0)
        at ../../../gdk/x11/gdksurface-x11.c:4686
    #0  gdk_x11_surface_get_xid (surface=surface@entry=0x0)
        at ../../../gdk/x11/gdksurface-x11.c:4686
    #1  _set_cursor_location_internal (ibusimcontext=...)
        at ../client/gtk4/ibusimcontext.c:1749
    #2  ibus_im_context_set_cursor_location (context=..., area=...)
        at ../client/gtk4/ibusimcontext.c:1815
    #3  gtk_text_view_update_im_spot_location (text_view=...)
        at ../../../gtk/gtktextview.c:3047
    #4  gtk_text_view_update_im_spot_location (text_view=...)
        at ../../../gtk/gtktextview.c:3052
    #5  do_update_im_spot_location (text_view=...)
        at ../../../gtk/gtktextview.c:3058
    #6  ??? () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
    #7  ??? () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
    #8  g_main_loop_run () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0

The faulting instruction is:

    => 0x... <gdk_x11_surface_get_xid+4>: mov 0xd8(%rdi),%rax
    rdi = 0x0

Analysis
--------
In 1.5.35~rc1, _set_cursor_location_internal() already has a check for a
destroyed root:

    root = GTK_WIDGET (gtk_widget_get_root (ibusimcontext->client_window));
    /* The Window is closing */
    if (!root)
        return G_SOURCE_REMOVE;

That check is not sufficient for this case.  At the time of the crash the
root and the native are still non-NULL, but the native has already lost its
surface:

    (gdb) print gtk_widget_get_root(ibusimcontext->client_window)
    $1 = (GtkRoot *) 0x12329d0
    (gdb) print gtk_widget_get_native(ibusimcontext->client_window)
    $2 = (GtkNative *) 0x12329d0
    (gdb) info locals
    surface = 0x0

gtk_native_get_surface() is documented to return NULL, so the module must not
pass that value to GDK_SURFACE_XID().  The X11 branch does exactly that:

    GdkSurface *surface = gtk_native_get_surface
        (gtk_widget_get_native (ibusimcontext->client_window));
    ...
    XTranslateCoordinates (GDK_DISPLAY_XDISPLAY (display),
                           GDK_SURFACE_XID (surface),  /* -> get_xid(NULL) */
                           gdk_x11_display_get_xrootwindow (display),
                           0, 0, &tx, &ty,
                           &child);

This runs from the do_update_im_spot_location() idle callback after
gtk_window_close().  The text view's IM context still holds the widget as
client_window, and gtk_widget_get_root()/gtk_widget_get_native() still
return the native while the window is being torn down, but
gtk_native_get_surface() has already returned NULL.

Suggested fix
-------------
Before using the surface, null-check both the native and the surface in
_set_cursor_location_internal() and return G_SOURCE_REMOVE when either is
NULL.  For example:

    native = gtk_widget_get_native (ibusimcontext->client_window);
    if (!native)
        return G_SOURCE_REMOVE;

    surface = gtk_native_get_surface (native);
    if (!surface)
        return G_SOURCE_REMOVE;

The gtk_native_get_surface_transform() call above the X11 block should also
be guarded by the native check.

The same unguarded call is still present in current upstream master (checked
2026-11-01).  I am filing this downstream first; I will forward it upstream
once the Debian bug number is available.

Workaround
----------
For users who can avoid IBus for a given application:

    GTK_IM_MODULE=gtk-im-context-simple application
    # or
    GTK_IM_MODULE=gtk-im-context-none application

Both avoid the crash, but disable IBus input methods for that process.
Making the text view non-focusable is also an application-side workaround,
but it is not suitable for a text widget that users need to interact with.

Additional information
----------------------
I first hit this in scantpaper while dismissing an error dialog, but the
crash has nothing to do with that application: the 20-line GTK4 script above
reproduces it without any application code, and the same crash occurs on
unmodified application versions that predate the dialog change.

This may be related to upstream issue #2905, but that report was Wayland-only
and was closed after testing with 1.5.35~beta2.  The X11 GDK_SURFACE_XID()
path here still crashes with 1.5.35~rc1, and the missing surface check is
visible in the current upstream source.

#1152754#10
Date:
2026-11-01 07:04:01 UTC
From:
To:
Forwarded upstream as https://github.com/ibus/ibus/issues/2975.