Moving a `GlobalObject` out of the registries global callback and using its props causes UB.
Moving a GlobalObject
out of the registries global callback and then accessing its properties causes undefined behaviour, I suspect that the properties are deallocated when the callback returns, but they are still pointed at within the ForeignDict
struct contained in the GlobalObject
.
Code that triggers the UB:
use std::{cell::RefCell, rc::Rc};
use pipewire as pw;
use pw::registry::GlobalObject;
use spa::dict::ReadableDict;
fn main() {
pw::init();
let mainloop = pw::MainLoop::new().expect("Failed to create Pipewire Mainloop");
let context = pw::Context::new(&mainloop).expect("Failed to create Pipewire Context");
let core = context
.connect(None)
.expect("Failed to connect to Pipewire Core");
let registry = core.get_registry();
let global: Rc<RefCell<Option<GlobalObject>>> = Rc::new(RefCell::new(Option::None));
// Save a `GlobalObject` that has properties into the `global` variable.
let global_clone = global.clone();
let mainloop_clone = mainloop.clone();
let _listener = registry
.add_listener_local()
.global(move |global| {
if global.props.is_some() {
*global_clone.borrow_mut() = Some(global);
mainloop_clone.quit();
}
})
.register();
mainloop.run();
// `global` now contains a GlobalObject that has props that are deallocated,
// as the props seem to be valid only while in the callback.
global
.borrow()
.as_ref()
.expect("No global with props found")
.props
.as_ref()
.unwrap()
.get("foo"); // <-- SIGSEGV / UB
}
Perhaps we should only provide a borrow to a GlobalObject
to the callback.
EDIT: Valgrind output, obtained with RUST_BACKTRACE=1 valgrind --tool=memcheck --leak-check=full --leak-resolution=high ./target/debug/examples/issue7
:
==30795== Memcheck, a memory error detector
==30795== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==30795== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
==30795== Command: ./target/debug/examples/issue7
==30795==
==30795== Invalid read of size 8
==30795== at 0x11646E: libspa::dict::ReadableDict::iter_cstr (dict.rs:11)
==30795== by 0x110D0E: libspa::dict::ReadableDict::iter (dict.rs:23)
==30795== by 0x110C47: libspa::dict::ReadableDict::get (dict.rs:69)
==30795== by 0x1122D1: issue7::main (issue7.rs:34)
==30795== by 0x1117AA: core::ops::function::FnOnce::call_once (function.rs:227)
==30795== by 0x110DAD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:125)
==30795== by 0x10F610: std::rt::lang_start::{{closure}} (rt.rs:66)
==30795== by 0x1300F6: call_once<(),Fn<()>> (function.rs:259)
==30795== by 0x1300F6: do_call<&Fn<()>,i32> (panicking.rs:381)
==30795== by 0x1300F6: try<i32,&Fn<()>> (panicking.rs:345)
==30795== by 0x1300F6: catch_unwind<&Fn<()>,i32> (panic.rs:396)
==30795== by 0x1300F6: std::rt::lang_start_internal (rt.rs:51)
==30795== by 0x10F5E6: std::rt::lang_start (rt.rs:65)
==30795== by 0x1124A9: main (in /home/tomwagner/Code/pw/pipewire-rs/target/debug/examples/issue7)
==30795== Address 0x1ffefffd78 is on thread 1's stack
==30795== 840 bytes below stack pointer
==30795==
==30795== Invalid read of size 4
==30795== at 0x116491: libspa::dict::ReadableDict::iter_cstr (dict.rs:14)
==30795== by 0x110D0E: libspa::dict::ReadableDict::iter (dict.rs:23)
==30795== by 0x110C47: libspa::dict::ReadableDict::get (dict.rs:69)
==30795== by 0x1122D1: issue7::main (issue7.rs:34)
==30795== by 0x1117AA: core::ops::function::FnOnce::call_once (function.rs:227)
==30795== by 0x110DAD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:125)
==30795== by 0x10F610: std::rt::lang_start::{{closure}} (rt.rs:66)
==30795== by 0x1300F6: call_once<(),Fn<()>> (function.rs:259)
==30795== by 0x1300F6: do_call<&Fn<()>,i32> (panicking.rs:381)
==30795== by 0x1300F6: try<i32,&Fn<()>> (panicking.rs:345)
==30795== by 0x1300F6: catch_unwind<&Fn<()>,i32> (panic.rs:396)
==30795== by 0x1300F6: std::rt::lang_start_internal (rt.rs:51)
==30795== by 0x10F5E6: std::rt::lang_start (rt.rs:65)
==30795== by 0x1124A9: main (in /home/tomwagner/Code/pw/pipewire-rs/target/debug/examples/issue7)
==30795== Address 0x1ffefffd74 is on thread 1's stack
==30795== 844 bytes below stack pointer
==30795==
==30795==
==30795== HEAP SUMMARY:
==30795== in use at exit: 30,520 bytes in 48 blocks
==30795== total heap usage: 847 allocs, 799 frees, 238,096 bytes allocated
==30795==
==30795== LEAK SUMMARY:
==30795== definitely lost: 0 bytes in 0 blocks
==30795== indirectly lost: 0 bytes in 0 blocks
==30795== possibly lost: 0 bytes in 0 blocks
==30795== still reachable: 30,520 bytes in 48 blocks
==30795== suppressed: 0 bytes in 0 blocks
==30795== Reachable blocks (those to which a pointer was found) are not shown.
==30795== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==30795==
==30795== For lists of detected and suppressed errors, rerun with: -s
==30795== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
Edited by Tom Wagner