GtkColumnViewCellWidget can expose floating reference to callbacks early, causing it to be sunken incorrectly
Originally reported to gtk-rs: https://github.com/gtk-rs/gtk4-rs/issues/1991, who told me this was probably an upstream GTK issue. I have a new, more concise reproducer:
use gtk::prelude::*;
use gtk::glib;
use gtk::gio;
fn main() {
gtk::init().unwrap();
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(|_, li| {
let label = gtk::Label::new(None);
label.connect_parent_notify(|label| {
let _ = label.parent(); // break here
});
li.set_child(Some(&label));
});
let store = gio::ListStore::new::<glib::Object>();
store.append(&glib::Object::new::<glib::Object>());
let model = gtk::NoSelection::new(Some(store));
let cv = gtk::ColumnView::new(Option::<gtk::SelectionModel>::None);
cv.append_column(
>k::ColumnViewColumn::builder()
.factory(&factory)
.build());
let window = gtk::Window::new();
window.set_child(Some(&cv));
cv.set_model(Some(&model));
}
This crashes due to what I believe to be a reference counting error resulting in the GtkColumnViewCellWidget
being freed too early.
What I think is happening is the GtkColumnViewCellWidget gets constructed via g_object_new
. Since it inherits from GInitiallyUnowned, it has a floating reference, which is supposed to be sunken in gtk_widget_reposition_after
when the cell widget is added to the row widget. However, if before the cell widget is added to the row widget, it is exposed to the outside world, the floating reference can accidentally be sunken elsewhere. This can happen if a list item factory callback obtains a reference to the cell widget through a method annotated with transfer none
.
If you break on line 12, you can go up in the call stack to find the address of the GtkColumnViewCellWidget
and set a watchpoint on the reference count.
Thread 1 "gtk-rs-parent-u" hit Breakpoint 1, gtk_rs_parent_uaf_demo::main::{closure#0}::{closure#0} (label=0x7fffffffc920) at src/main.rs:12
12 let _ = label.parent();
(gdb) up 9
#9 0x00007ffff759bd40 in gtk_widget_reposition_after (widget=0x555555729e50, parent=0x555555720120, previous_sibling=0x0) at ../gtk/gtkwidget.c:6105
6105 g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_PARENT]);
(gdb) p parent->parent_instance.ref_count
$1 = 1
(gdb) watch -l parent->parent_instance.ref_count
Hardware watchpoint 2: -location parent->parent_instance.ref_count
You'll see that the rust bindings' wrapper for gtk_widget_get_parent
calls gobject_ref_sink
on the returned reference, which increments then immediately decrements the reference count as it claims the floating reference. Then, when the parent goes out of scope in the rust code, the reference count is decreased to 0 and the GtkColumnViewCellWidget
is freed early.
Thread 1 "gtk-rs-parent-u" hit Hardware watchpoint 2: -location parent->parent_instance.ref_count
Old value = 1
New value = 0
0x00007ffff6f9c1a9 in g_object_unref () from /usr/lib/libgobject-2.0.so.0
(gdb) bt
#0 0x00007ffff6f9c1a9 in g_object_unref () at /usr/lib/libgobject-2.0.so.0
#1 0x00005555555db957 in glib::object::{impl#4}::drop (self=0x7fffffffc8e0) at src/object.rs:394
#2 0x00005555555daa4a in core::ptr::drop_in_place<glib::object::ObjectRef> () at /home/mayco/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:574
#3 0x00005555555dac8b in core::ptr::drop_in_place<glib::object::TypedObjectRef<*mut core::ffi::c_void, ()>> () at /home/mayco/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:574
#4 0x00005555555d63bb in core::ptr::drop_in_place<gtk4::auto::widget::Widget> () at /home/mayco/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:574
#5 0x00005555555d1f16 in core::ptr::drop_in_place<core::option::Option<gtk4::auto::widget::Widget>> () at /home/mayco/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:574
#6 0x00005555555d4eff in gtk_rs_parent_uaf_demo::main::{closure#0}::{closure#0} (label=0x7fffffffc920) at src/main.rs:12
#7 0x00005555555d4002 in gtk4::auto::widget::WidgetExt::connect_parent_notify::notify_parent_trampoline<gtk4::auto::label::Label, gtk_rs_parent_uaf_demo::main::{closure#0}::{closure_env#0}> (this=0x555555729e50, _param_spec=0x55555569cb80, f=0x1)
at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gtk4-0.9.6/src/auto/widget.rs:2461
#8 0x00007ffff6f8c82a in g_closure_invoke () at /usr/lib/libgobject-2.0.so.0
#9 0x00007ffff6fbd565 in ??? () at /usr/lib/libgobject-2.0.so.0
#10 0x00007ffff6fadca9 in ??? () at /usr/lib/libgobject-2.0.so.0
#11 0x00007ffff6fadf32 in g_signal_emit_valist () at /usr/lib/libgobject-2.0.so.0
#12 0x00007ffff6fadff4 in g_signal_emit () at /usr/lib/libgobject-2.0.so.0
#13 0x00007ffff6f98d16 in ??? () at /usr/lib/libgobject-2.0.so.0
#14 0x00007ffff6f9c544 in g_object_notify_by_pspec () at /usr/lib/libgobject-2.0.so.0
#15 0x00007ffff759bd40 in gtk_widget_reposition_after (widget=0x555555729e50, parent=0x555555720120, previous_sibling=0x0) at ../gtk/gtkwidget.c:6105
#16 0x00007ffff759bfd1 in gtk_widget_set_parent (widget=0x555555729e50, parent=0x555555720120) at ../gtk/gtkwidget.c:6166
#17 0x00007ffff75ebc98 in gtk_column_view_cell_widget_set_child (self=0x555555720120, child=0x555555729e50) at ../gtk/gtkcolumnviewcellwidget.c:420
#18 0x00007ffff75eb0d5 in gtk_column_view_cell_widget_setup_object (fw=0x555555720120, object=0x5555556c0220) at ../gtk/gtkcolumnviewcellwidget.c:134
#19 0x00007ffff7437628 in gtk_list_factory_widget_setup_func (object=0x5555556c0220, data=0x555555720120) at ../gtk/gtklistfactorywidget.c:97
#20 0x00007ffff743c26b in gtk_list_item_factory_default_setup (self=0x55555567ac20, item=0x5555556c0220, bind=0, func=0x7ffff74375fa <gtk_list_factory_widget_setup_func>, data=0x555555720120) at ../gtk/gtklistitemfactory.c:89
#21 0x00007ffff74e53bb in gtk_signal_list_item_factory_setup (factory=0x55555567ac20, item=0x5555556c0220, bind=0, func=0x7ffff74375fa <gtk_list_factory_widget_setup_func>, data=0x555555720120) at ../gtk/gtksignallistitemfactory.c:113
#22 0x00007ffff743c3fc in gtk_list_item_factory_setup (self=0x55555567ac20, item=0x5555556c0220, bind=0, func=0x7ffff74375fa <gtk_list_factory_widget_setup_func>, data=0x555555720120) at ../gtk/gtklistitemfactory.c:137
#23 0x00007ffff74376ab in gtk_list_factory_widget_setup_factory (self=0x555555720120) at ../gtk/gtklistfactorywidget.c:108
#24 0x00007ffff743888b in gtk_list_factory_widget_set_factory (self=0x555555720120, factory=0x55555567ac20) at ../gtk/gtklistfactorywidget.c:550
#25 0x00007ffff7437a9b in gtk_list_factory_widget_set_property (object=0x555555720120, property_id=2, value=0x7fffffffd520, pspec=0x5555557254a0) at ../gtk/gtklistfactorywidget.c:224
#26 0x00007ffff6f9df0b in ??? () at /usr/lib/libgobject-2.0.so.0
#27 0x00007ffff6f9e893 in ??? () at /usr/lib/libgobject-2.0.so.0
#28 0x00007ffff6fa0afb in g_object_new_valist () at /usr/lib/libgobject-2.0.so.0
#29 0x00007ffff6fa0eb0 in g_object_new () at /usr/lib/libgobject-2.0.so.0
#30 0x00007ffff75ebad4 in gtk_column_view_cell_widget_new (column=0x55555573b590, inert=0) at ../gtk/gtkcolumnviewcellwidget.c:367
#31 0x00007ffff735aa87 in gtk_column_list_view_create_list_widget (base=0x55555571a1c0) at ../gtk/gtkcolumnview.c:195
#32 0x00007ffff742ba84 in gtk_list_base_create_list_widget_func (widget=0x55555571a1c0) at ../gtk/gtklistbase.c:1985
#33 0x00007ffff743f1fa in gtk_list_item_manager_ensure_items (self=0x5555556fb980, change=0x7fffffffd990, update_start=4294967295, update_diff=0) at ../gtk/gtklistitemmanager.c:1374
#34 0x00007ffff74409f3 in gtk_list_item_tracker_set_position (self=0x5555556fb980, tracker=0x5555556faf40, position=0, n_before=2, n_after=202) at ../gtk/gtklistitemmanager.c:1938
#35 0x00007ffff742c486 in gtk_list_base_set_anchor (self=0x55555571a1c0, anchor_pos=0, anchor_align_across=0, anchor_side_across=GTK_PACK_START, anchor_align_along=0, anchor_side_along=GTK_PACK_START) at ../gtk/gtklistbase.c:2240
#36 0x00007ffff742c66c in gtk_list_base_set_model (self=0x55555571a1c0, model=0x555555680290) at ../gtk/gtklistbase.c:2317
#37 0x00007ffff7444992 in gtk_list_view_set_model (self=0x55555571a1c0, model=0x555555680290) at ../gtk/gtklistview.c:1081
#38 0x00007ffff735e469 in gtk_column_view_set_model (self=0x5555556bcaf0, model=0x555555680290) at ../gtk/gtkcolumnview.c:1561
#39 0x00005555555d2412 in gtk4::auto::column_view::ColumnView::set_model<gtk4::auto::no_selection::NoSelection> (self=0x7fffffffdbe8, model=...) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gtk4-0.9.6/src/auto/column_view.rs:224
#40 0x00005555555d33b0 in gtk_rs_parent_uaf_demo::main () at src/main.rs:30
Line numbers are for tagged 4.17.0 commit.
I believe the fix would be to not set the factory property on the GtkColumnViewCellWidget
object during construction, and to instead defer setting that property until after the floating reference has been sunken correctly by adding the cell to the row.
It's possible that a similar bug exists for GtkListView
, but I haven't checked.