Environment
- Package:
desktop_webview_window 0.3.0 (pub.dev)
- Platform: Linux (Ubuntu 24.04, GTK3, WebKitGTK 4.1, aarch64), Wayland session
- Flutter 3.44.1
Bug: deterministic use-after-free on window close (SIGSEGV)
Every time a WebviewWindow on Linux is closed (both via the in-app "close"
call and via the window manager's own close button), the app segfaults.
Reproduced consistently while building a login flow that opens a webview,
waits for an OAuth redirect, then closes the window.
Backtrace (gdb, -batch -ex run -ex "bt full"):
Thread 1 "sentry_recovery" received signal SIGSEGV, Segmentation fault.
0x0000fffff47f1448 in g_type_check_instance_cast () from /lib/aarch64-linux-gnu/libgobject-2.0.so.0
#0 g_type_check_instance_cast () at /lib/aarch64-linux-gnu/libgobject-2.0.so.0
#1 FL_METHOD_CHANNEL (ptr=0xeb41cf438ea94eb0) at linux/webview_window.cc:24 <fl_method_channel.h>
#2 WebviewWindow::WebviewWindow(...)::$_0::operator()(_GtkWidget*, void*) const
at linux/webview_window.cc:109
#3 ... ::$_0::__invoke(...)
#4 g_closure_invoke ()
#5-8 (libgobject internals)
#9 g_signal_emit_valist ()
#10 g_signal_emit ()
#11 (libgtk-3 internals)
#12 g_object_run_dispose ()
#13 gtk_main_do_event ()
...
#19 main () at linux/runner/main.cc
ptr in FL_METHOD_CHANNEL() is garbage — clearly freed/reused memory, not
a valid pointer.
Root cause
In linux/webview_window.cc, the constructor connects a "destroy" signal
handler on window_ that captures this and, on the way out, invokes
on_close_callback_() before using window->method_channel_:
g_signal_connect(G_OBJECT(window_), "destroy",
G_CALLBACK(+[](GtkWidget *, gpointer arg) {
auto *window = static_cast<WebviewWindow *>(arg);
if (window->on_close_callback_) {
window->on_close_callback_();
}
auto *args = fl_value_new_map();
fl_value_set(args, fl_value_new_string("id"),
fl_value_new_int(window->window_id_));
fl_method_channel_invoke_method(
FL_METHOD_CHANNEL(window->method_channel_),
"onWindowClose", args, nullptr, nullptr, nullptr);
}),
this);
In linux/desktop_webview_window_plugin.cc, on_close_callback_ is set to:
auto webview = std::make_unique<WebviewWindow>(
self->method_channel, window_id,
[self, window_id]() {
self->windows->erase(window_id); // <-- destructs the unique_ptr,
// i.e. `delete this` for the
// WebviewWindow being erased
g_object_unref(self);
},
title, width, height, title_bar_height);
self->windows is a std::map<int64_t, std::unique_ptr<WebviewWindow>>.
Calling .erase(window_id) synchronously destructs the WebviewWindow
(~WebviewWindow() → this freed) while the "destroy" signal handler is
still executing. The handler then keeps using the now-dangling window
pointer (window->window_id_, window->method_channel_) to fire
"onWindowClose" — a same-call-frame use-after-free, not a race. It
reproduces on essentially every window close.
Suggested fix
Capture what's needed into locals before invoking on_close_callback_(),
and use only those locals afterward:
g_signal_connect(G_OBJECT(window_), "destroy",
G_CALLBACK(+[](GtkWidget *, gpointer arg) {
auto *window = static_cast<WebviewWindow *>(arg);
auto *method_channel = window->method_channel_;
auto window_id = window->window_id_;
g_object_ref(method_channel);
if (window->on_close_callback_) {
window->on_close_callback_(); // may free `window`
}
auto *args = fl_value_new_map();
fl_value_set(args, fl_value_new_string("id"),
fl_value_new_int(window_id));
fl_method_channel_invoke_method(
FL_METHOD_CHANNEL(method_channel),
"onWindowClose", args, nullptr, nullptr, nullptr);
g_object_unref(method_channel);
}),
this);
As a defensive second layer, ~WebviewWindow() also never disconnects this
"destroy" handler (which captures this) before the object is destroyed —
worth adding g_signal_handler_find/g_signal_handler_disconnect for it
in the destructor too, in case of a deferred/re-entrant "destroy"
emission on an already-freed window.
I'm currently carrying both fixes as a vendored local patch to unblock a
Linux port. Happy to open a PR with this change if useful — let me know.
Environment
desktop_webview_window0.3.0 (pub.dev)Bug: deterministic use-after-free on window close (SIGSEGV)
Every time a
WebviewWindowon Linux is closed (both via the in-app "close"call and via the window manager's own close button), the app segfaults.
Reproduced consistently while building a login flow that opens a webview,
waits for an OAuth redirect, then closes the window.
Backtrace (gdb,
-batch -ex run -ex "bt full"):ptrinFL_METHOD_CHANNEL()is garbage — clearly freed/reused memory, nota valid pointer.
Root cause
In
linux/webview_window.cc, the constructor connects a"destroy"signalhandler on
window_that capturesthisand, on the way out, invokeson_close_callback_()before usingwindow->method_channel_:In
linux/desktop_webview_window_plugin.cc,on_close_callback_is set to:self->windowsis astd::map<int64_t, std::unique_ptr<WebviewWindow>>.Calling
.erase(window_id)synchronously destructs theWebviewWindow(
~WebviewWindow()→thisfreed) while the "destroy" signal handler isstill executing. The handler then keeps using the now-dangling
windowpointer (
window->window_id_,window->method_channel_) to fire"onWindowClose"— a same-call-frame use-after-free, not a race. Itreproduces on essentially every window close.
Suggested fix
Capture what's needed into locals before invoking
on_close_callback_(),and use only those locals afterward:
As a defensive second layer,
~WebviewWindow()also never disconnects this"destroy"handler (which capturesthis) before the object is destroyed —worth adding
g_signal_handler_find/g_signal_handler_disconnectfor itin the destructor too, in case of a deferred/re-entrant
"destroy"emission on an already-freed window.
I'm currently carrying both fixes as a vendored local patch to unblock a
Linux port. Happy to open a PR with this change if useful — let me know.