Bug description
There seems to be some kind of reference-counting mishap near gtk::Widget::parent() that only happens under specific circumstances that I do not yet fully understand. Sample code is included below. It segfaults on my system. If this.parent() is replaced by this.property::<gtk::Widget>("parent"), it does not segfault and seems to behave correctly. If the model is provided during construction of the ColumnView instead of set later, it also doesn't crash.
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::glib;
use gtk::gio;
glib::wrapper! {
pub struct CustomWidget(ObjectSubclass<imp::CustomWidget>)
@extends gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
}
mod imp {
use super::*;
#[derive(Default)]
pub struct CustomWidget {
}
#[glib::object_subclass]
impl ObjectSubclass for CustomWidget {
const NAME: &'static str = "CustomWidget";
type Type = super::CustomWidget;
type ParentType = gtk::Widget;
}
impl ObjectImpl for CustomWidget {
fn constructed(&self) {
self.parent_constructed();
println!("custom widget constructed");
self.obj().connect_parent_notify(|this| {
println!("custom widget reparented");
let _ = this.parent(); // <-- bug happens here
});
}
}
impl WidgetImpl for CustomWidget {
}
}
fn main() {
gtk::init().unwrap();
CustomWidget::ensure_type();
let template = br#"
<?xml version='1.0' encoding='UTF-8'?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="CustomWidget">
</object>
</property>
</template>
</interface>
"#;
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()
.expand(true)
.resizable(true)
.title("test")
.factory(>k::BuilderListItemFactory::from_bytes(
gtk::BuilderScope::NONE,
&glib::Bytes::from_static(template)))
.build());
let window = gtk::Window::new();
window.set_child(Some(&cv));
cv.set_model(Some(&model));
}
Investigation/backtraces
By setting a breakpoint on println!("custom widget reparented");, I can pull out the address of the parent by looking in the backtrace:
(gdb) break 33
Breakpoint 2 at 0x5555555e3903: file src/main.rs, line 33.
(gdb) c
Continuing.
custom widget constructed
Thread 1 "gtk-rs-parent-u" hit Breakpoint 2, gtk_rs_parent_uaf_demo::imp::{impl#0}::constructed::{closure#0} (this=0x7fffffffb9e0) at src/main.rs:33
33 println!("custom widget reparented");
(gdb) bt
#0 gtk_rs_parent_uaf_demo::imp::{impl#0}::constructed::{closure#0} (this=0x7fffffffba20) at src/main.rs:33
#1 0x00005555555e5ae2 in gtk4::auto::widget::WidgetExt::connect_parent_notify::notify_parent_trampoline<gtk_rs_parent_uaf_demo::CustomWidget, gtk_rs_parent_uaf_demo::imp::{impl#0}::constructed::{closure_env#0}>
(this=0x555555736340, _param_spec=0x5555556bf990, f=0x1) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gtk4-0.9.6/src/auto/widget.rs:2461
#2 0x00007ffff6f8c82a in g_closure_invoke () at /usr/lib/libgobject-2.0.so.0
#3 0x00007ffff6fbd565 in ??? () at /usr/lib/libgobject-2.0.so.0
#4 0x00007ffff6fadca9 in ??? () at /usr/lib/libgobject-2.0.so.0
#5 0x00007ffff6fadf32 in g_signal_emit_valist () at /usr/lib/libgobject-2.0.so.0
#6 0x00007ffff6fadff4 in g_signal_emit () at /usr/lib/libgobject-2.0.so.0
#7 0x00007ffff6f98d16 in ??? () at /usr/lib/libgobject-2.0.so.0
#8 0x00005555555e00ab in glib::subclass::object::ObjectImplExt::parent_dispatch_properties_changed<gtk_rs_parent_uaf_demo::imp::CustomWidget> (self=0x5555557361f0, pspecs=...)
at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/subclass/object.rs:302
#9 0x00005555555dfdd7 in glib::subclass::object::ObjectImpl::dispatch_properties_changed<gtk_rs_parent_uaf_demo::imp::CustomWidget> (self=0x5555557361f0, pspecs=...)
at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/subclass/object.rs:85
#10 0x00005555555e53c5 in glib::subclass::object::dispatch_properties_changed<gtk_rs_parent_uaf_demo::imp::CustomWidget> (obj=0x555555736340, n_pspecs=1, pspecs=0x7fffffffc0d0)
at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/subclass/object.rs:152
#11 0x00007ffff6f9c544 in g_object_notify_by_pspec () at /usr/lib/libgobject-2.0.so.0
#12 0x00007ffff759bd40 in gtk_widget_reposition_after (widget=0x555555736340, parent=0x555555743120, previous_sibling=0x0) at ../gtk/gtkwidget.c:6105
#13 0x00007ffff759bfd1 in gtk_widget_set_parent (widget=0x555555736340, parent=0x555555743120) at ../gtk/gtkwidget.c:6166
(gdb) up 13
#13 0x00007ffff759bfd1 in gtk_widget_set_parent (widget=0x555555738360, parent=0x555555745040) at ../gtk/gtkwidget.c:6166
6166 gtk_widget_reposition_after (widget,
(gdb) p *parent
$2 = {parent_instance = {g_type_instance = {g_class = Python Exception <class 'gdb.error'>: No type named TypeNode.
}, ref_count = 1, qdata = 0x2}, priv = 0x555555742fd0}
I set a watchpoint on the reference count. It is incremented from 1 to 2 by the call to parent().
(gdb) watch -l parent->parent_instance.ref_count
Hardware watchpoint 3: -location parent->parent_instance.ref_count
(gdb) c
Continuing.
custom widget reparented
Thread 1 "gtk-rs-parent-u" hit Hardware watchpoint 2: -location parent->parent_instance.ref_count
Old value = 1
New value = 2
0x00007ffff6f97840 in ?? () from /usr/lib/libgobject-2.0.so.0
(gdb) bt
#0 0x00007ffff6f97840 in ??? () at /usr/lib/libgobject-2.0.so.0
#1 0x00007ffff6f993dc in g_object_ref () at /usr/lib/libgobject-2.0.so.0
#2 0x00007ffff6f9c58f in g_object_ref_sink () at /usr/lib/libgobject-2.0.so.0
#3 0x00005555555f715e in glib::object::{impl#13}::from_glib_none (ptr=0x555555743120) at src/object.rs:475
#4 0x00005555555fb35a in glib::translate::from_glib_none<*mut gobject_sys::GObject, glib::object::ObjectRef> (ptr=0x555555743120) at src/translate.rs:1630
#5 0x00005555555eefdc in gtk4::auto::widget::{impl#30}::from_glib_none (ptr=0x555555743120) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/object.rs:897
#6 0x00005555555ee19a in glib::translate::from_glib_none<*mut gtk4_sys::GtkWidget, gtk4::auto::widget::Widget> (ptr=0x555555743120) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/translate.rs:1630
#7 0x00005555555ee643 in glib::translate::{impl#62}::from_glib_none<*mut gtk4_sys::GtkWidget, gtk4::auto::widget::Widget> (ptr=0x555555743120) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/translate.rs:1657
#8 0x00005555555ee1ab in glib::translate::from_glib_none<*mut gtk4_sys::GtkWidget, core::option::Option<gtk4::auto::widget::Widget>> (ptr=0x555555743120)
at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/translate.rs:1630
#9 0x00005555555ed054 in gtk4::auto::widget::WidgetExt::parent<gtk_rs_parent_uaf_demo::CustomWidget> (self=0x7fffffffba20) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gtk4-0.9.6/src/auto/widget.rs:662
#10 0x00005555555e1e1a in gtk_rs_parent_uaf_demo::imp::{impl#0}::constructed::{closure#0} (this=0x7fffffffba20) at src/main.rs:34
Then from 2 back to 1 in the same call to parent(). This seems incorrect to me.
(gdb) c
Continuing.
Thread 1 "gtk-rs-parent-u" hit Hardware watchpoint 2: -location parent->parent_instance.ref_count
Old value = 2
New value = 1
0x00007ffff6f9c0f1 in g_object_unref () from /usr/lib/libgobject-2.0.so.0
(gdb) bt
#0 0x00007ffff6f9c0f1 in g_object_unref () at /usr/lib/libgobject-2.0.so.0
#1 0x00007ffff6f9c609 in g_object_ref_sink () at /usr/lib/libgobject-2.0.so.0
#2 0x00005555555f715e in glib::object::{impl#13}::from_glib_none (ptr=0x555555743120) at src/object.rs:475
#3 0x00005555555fb35a in glib::translate::from_glib_none<*mut gobject_sys::GObject, glib::object::ObjectRef> (ptr=0x555555743120) at src/translate.rs:1630
#4 0x00005555555eefdc in gtk4::auto::widget::{impl#30}::from_glib_none (ptr=0x555555743120) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/object.rs:897
#5 0x00005555555ee19a in glib::translate::from_glib_none<*mut gtk4_sys::GtkWidget, gtk4::auto::widget::Widget> (ptr=0x555555743120) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/translate.rs:1630
#6 0x00005555555ee643 in glib::translate::{impl#62}::from_glib_none<*mut gtk4_sys::GtkWidget, gtk4::auto::widget::Widget> (ptr=0x555555743120) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/translate.rs:1657
#7 0x00005555555ee1ab in glib::translate::from_glib_none<*mut gtk4_sys::GtkWidget, core::option::Option<gtk4::auto::widget::Widget>> (ptr=0x555555743120)
at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.9/src/translate.rs:1630
#8 0x00005555555ed054 in gtk4::auto::widget::WidgetExt::parent<gtk_rs_parent_uaf_demo::CustomWidget> (self=0x7fffffffba20) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gtk4-0.9.6/src/auto/widget.rs:662
#9 0x00005555555e1e1a in gtk_rs_parent_uaf_demo::imp::{impl#0}::constructed::{closure#0} (this=0x7fffffffba20) at src/main.rs:34
Then from 1 to 0 at the end of the closure, causing the parent object to be freed early and UAF'd.
(gdb) c
Continuing.
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 0x00005555555f70d7 in glib::object::{impl#4}::drop (self=0x7fffffffb9e0) at src/object.rs:394
#2 0x00005555555f4d0a 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 0x00005555555f520b 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 0x00005555555eebcb 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 0x00005555555ddd66 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 0x00005555555e1e29 in gtk_rs_parent_uaf_demo::imp::{impl#0}::constructed::{closure#0} (this=0x7fffffffba20) at src/main.rs:34
GDB backtrace of the segfault (unhelpful, since that's not where the bug actually occurs, but here for completeness):
#0 0x00007ffff758f23c in _gtk_widget_get_visible (widget=0x0) at ../gtk/gtkwidgetprivate.h:391
#1 0x00007ffff759bd53 in gtk_widget_reposition_after (widget=0x555555736340, parent=0x555555743120, previous_sibling=0x0) at ../gtk/gtkwidget.c:6108
#2 0x00007ffff759bfd1 in gtk_widget_set_parent (widget=0x555555736340, parent=0x555555743120) at ../gtk/gtkwidget.c:6166
#3 0x00007ffff75ebc98 in gtk_column_view_cell_widget_set_child (self=0x555555743120, child=0x555555736340) at ../gtk/gtkcolumnviewcellwidget.c:420
#4 0x00007ffff7360c0f in gtk_column_view_cell_set_child (self=0x5555557b9b60, child=0x555555736340) at ../gtk/gtkcolumnviewcell.c:321
#5 0x00007ffff73606f8 in gtk_column_view_cell_set_property (object=0x5555557b9b60, property_id=1, value=0x5555556f8c00, pspec=0x5555557b98c0) at ../gtk/gtkcolumnviewcell.c:134
#6 0x00007ffff6f9df0b in ??? () at /usr/lib/libgobject-2.0.so.0
#7 0x00007ffff6fa0ff3 in g_object_setv () at /usr/lib/libgobject-2.0.so.0
#8 0x00007ffff732c094 in _gtk_builder_apply_properties (builder=0x5555557ba230, info=0x5555557ba880, error=0x7fffffffc600) at ../gtk/gtkbuilder.c:1130
#9 0x00007ffff7333707 in builder_construct (data=0x7fffffffc740, object_info=0x5555557ba880, error=0x7fffffffc600) at ../gtk/gtkbuilderparser.c:484
#10 0x00007ffff73376d9 in end_element (context=0x7fffffffc768, element_name=0x5555556f1605 "template", user_data=0x7fffffffc740, error=0x7fffffffc600) at ../gtk/gtkbuilderparser.c:1950
#11 0x00007ffff7332e2b in proxy_end_element (gm_context=0x0, element_name=0x5555556f1605 "template", user_data=0x7fffffffc768, error=0x7fffffffc600) at ../gtk/gtkbuilderparser.c:104
#12 0x00007ffff75e3fdf in replay_end_element (context=0x7fffffffc768, tree=0x7fffffffc660, strings=0x5555556f15f5 "class", error=0x7fffffffc858) at ../gtk/gtkbuilderprecompile.c:660
#13 0x00007ffff75e420c in _gtk_buildable_parser_replay_precompiled (context=0x7fffffffc768, data=0x5555556f1652 "", data_len=124, error=0x7fffffffc858) at ../gtk/gtkbuilderprecompile.c:742
#14 0x00007ffff7333073 in gtk_buildable_parse_context_parse (context=0x7fffffffc768, text=0x5555556f15f0 "GBU", text_len=124, error=0x7fffffffc858) at ../gtk/gtkbuilderparser.c:191
#15 0x00007ffff7338569 in _gtk_builder_parser_parse_buffer (builder=0x5555557ba230, filename=0x5555557ba740 "<GtkListItem template>", buffer=0x5555556f15f0 "GBU", length=124, requested_objs=0x0, error=0x7fffffffc858) at ../gtk/gtkbuilderparser.c:2205
#16 0x00007ffff732d0e4 in gtk_builder_extend_with_template (builder=0x5555557ba230, object=0x5555557b9b60, template_type=Python Exception <class 'gdb.error'>: No type named TypeNode.
, buffer=0x5555556f15f0 "GBU", length=124, error=0x7fffffffc8e0) at ../gtk/gtkbuilder.c:1562
#17 0x00007ffff733204d in gtk_builder_list_item_factory_setup (factory=0x55555570b740, item=0x5555557b9b60, bind=0, func=0x7ffff74375fa <gtk_list_factory_widget_setup_func>, data=0x555555743120) at ../gtk/gtkbuilderlistitemfactory.c:109
#18 0x00007ffff743c3fc in gtk_list_item_factory_setup (self=0x55555570b740, item=0x5555557b9b60, bind=0, func=0x7ffff74375fa <gtk_list_factory_widget_setup_func>, data=0x555555743120) at ../gtk/gtklistitemfactory.c:137
#19 0x00007ffff74376ab in gtk_list_factory_widget_setup_factory (self=0x555555743120) at ../gtk/gtklistfactorywidget.c:108
#20 0x00007ffff743888b in gtk_list_factory_widget_set_factory (self=0x555555743120, factory=0x55555570b740) at ../gtk/gtklistfactorywidget.c:550
#21 0x00007ffff7437a9b in gtk_list_factory_widget_set_property (object=0x555555743120, property_id=2, value=0x7fffffffcd60, pspec=0x5555557484a0) at ../gtk/gtklistfactorywidget.c:224
#22 0x00007ffff6f9df0b in ??? () at /usr/lib/libgobject-2.0.so.0
#23 0x00007ffff6f9e893 in ??? () at /usr/lib/libgobject-2.0.so.0
#24 0x00007ffff6fa0afb in g_object_new_valist () at /usr/lib/libgobject-2.0.so.0
#25 0x00007ffff6fa0eb0 in g_object_new () at /usr/lib/libgobject-2.0.so.0
#26 0x00007ffff75ebad4 in gtk_column_view_cell_widget_new (column=0x55555571ae30, inert=0) at ../gtk/gtkcolumnviewcellwidget.c:367
#27 0x00007ffff735aa87 in gtk_column_list_view_create_list_widget (base=0x555555703790) at ../gtk/gtkcolumnview.c:195
#28 0x00007ffff742ba84 in gtk_list_base_create_list_widget_func (widget=0x555555703790) at ../gtk/gtklistbase.c:1985
#29 0x00007ffff743f1fa in gtk_list_item_manager_ensure_items (self=0x555555711660, change=0x7fffffffd1d0, update_start=4294967295, update_diff=0) at ../gtk/gtklistitemmanager.c:1374
#30 0x00007ffff74409f3 in gtk_list_item_tracker_set_position (self=0x555555711660, tracker=0x555555720790, position=0, n_before=2, n_after=202) at ../gtk/gtklistitemmanager.c:1938
#31 0x00007ffff742c486 in gtk_list_base_set_anchor (self=0x555555703790, 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
#32 0x00007ffff742c66c in gtk_list_base_set_model (self=0x555555703790, model=0x5555556a36c0) at ../gtk/gtklistbase.c:2317
#33 0x00007ffff7444992 in gtk_list_view_set_model (self=0x555555703790, model=0x5555556a36c0) at ../gtk/gtklistview.c:1081
#34 0x00007ffff735e469 in gtk_column_view_set_model (self=0x55555568fa50, model=0x5555556a36c0) at ../gtk/gtkcolumnview.c:1561
#35 0x00005555555de8a2 in gtk4::auto::column_view::ColumnView::set_model<gtk4::auto::no_selection::NoSelection> (self=0x7fffffffd420, model=...) at /home/mayco/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gtk4-0.9.6/src/auto/column_view.rs:224
#36 0x00005555555e2af5 in gtk_rs_parent_uaf_demo::main () at src/main.rs:76
Bug description
There seems to be some kind of reference-counting mishap near
gtk::Widget::parent()that only happens under specific circumstances that I do not yet fully understand. Sample code is included below. It segfaults on my system. Ifthis.parent()is replaced bythis.property::<gtk::Widget>("parent"), it does not segfault and seems to behave correctly. If the model is provided during construction of theColumnViewinstead of set later, it also doesn't crash.Investigation/backtraces
By setting a breakpoint on
println!("custom widget reparented");, I can pull out the address of the parent by looking in the backtrace:I set a watchpoint on the reference count. It is incremented from
1to2by the call toparent().Then from 2 back to 1 in the same call to
parent(). This seems incorrect to me.Then from 1 to 0 at the end of the closure, causing the parent object to be freed early and UAF'd.
GDB backtrace of the segfault (unhelpful, since that's not where the bug actually occurs, but here for completeness):