Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible UB in get_api function #53

Open
vnrst opened this issue Jun 21, 2023 · 7 comments
Open

Possible UB in get_api function #53

vnrst opened this issue Jun 21, 2023 · 7 comments

Comments

@vnrst
Copy link

vnrst commented Jun 21, 2023

Hi,

In libpulse_binding::mainloop::api::standard::Mainloop (this line) and libpulse_binding::mainloop::api::threaded::Mainloop (this line), there is the function get_api which returns a borrow to the API vtable. I think the lifetime annotations of this function are incorrect and could lead to unexpected behavior.

pub fn get_api<'a>(&self) -> &'a ::mainloop::api::MainloopApi {
    ...
}

The returned borrow can live even after the Mainloop is freed and the Api is destroyed. As the function's docstring says, "No need to free the API as it is owned by the loop and is destroyed when the loop is freed."

I believe that something like this shouldn't be allowed to compile:

let mut mainloop = Mainloop::new().expect("Failed to create Mainoop");
let api: &MainloopApi = mainloop.get_api();
drop(mainloop); // Also frees API vtable
// Use api here

Changing the function signature to this would solve the issue and prevent the above code from compiling.

pub fn get_api(&self) -> & ::mainloop::api::MainloopApi
@jnqnfe
Copy link
Owner

jnqnfe commented Jun 22, 2023

Hi,

Yes, if what the reference points to is indeed dynamically allocated and is freed when the mainloop object is dropped, then the example you give presents a use-after-free vulnerability.

It's been quite a while since I put this together and I can't recall exactly why I set the lifetime up this way. I clearly chose to deliberately separate the lifetime of the returned api reference from the lifetime of the mainloop object reference, but why escapes my recollection.

I think either:

  1. When I dug into the PA C code I perhaps found that it's in fact pointing to static functions (mainloop implementation specific) and thus there was no such concern and I didn't want to impose an unnecessary lifetime restriction, not knowing how people might use this and not wanting to create problems for any C codebases being converted to Rust. The documentation comment you highlight would seem to contradict this though, unless it's just badly worded, but then what else would I have meant by it?
  2. Or I accepted that it was dodgy but felt that it was necessary to get around potential borrow issues - If this function was written as you suggest it should be, then if you used this function, creating an immutable borrow against the mainloop object, then that latter borrow would have to outlive the api reference, which would block use of any methods that require a mutable reference, for as long as the api reference lives. Perhaps I felt it ugly to force users to have to drop and re-obtain the reference in order to use methods requiring a mutable reference? A regrettable decision if so because the security implications should outweigh such concern of possible ugliness. Or perhaps it prevented compiling the crate somehow. I probably considered marking it as an unsafe function, but seemingly chose not to do this for some reason.
  3. Or, possibly it was a result of confusion trying to get my head fully around how to use the lifetime concept.

I'm leaning towards the second possibility. With respect to it I recall that the rust borrow checker became smarter several releases ago; I believe it now can assess what attributes of an object are being borrowed, thus allowing more code to compile. So perhaps the issue of an api reference blocking use of mutable reference methods is no longer an issue?

So:

  • Perhaps I should try to dig back into the PA C codebase to checkout the first possibility, though it's been a long time since I looked at it and I'm not sure my heads in the best place for doing that. If that is the case then the documentation needs to be clarified.
  • If it's the second possibility above, then perhaps, especially in light of borrow checker improvements, I should now implement the change you suggest. If this doesn't work out for some reason then perhaps at very least a big fat warning should be added to this method's documentation and 'unsafe' should be reconsidered.

@vnrst
Copy link
Author

vnrst commented Jun 25, 2023

So I looked into it a bit more, and it maybe seems like the thing that it points to is maybe not freed when the mainloop object is dropped.

I tried this code

 let mut mainloop = Mainloop::new().expect("Failed to create Mainoop");
 let api: &MainloopApi = mainloop.get_api();
 drop(mainloop);
 api.quit.unwrap()(api as *const MainloopApi, 0 as i32);

And I got the following error message:

pa_write() failed while trying to wake up the mainloop: Bad file descriptor

This is indeed an error, but not a use-after free. Looks like the API vtable object is still around, because I'm able to call api.quit without a use-after free.

@vnrst
Copy link
Author

vnrst commented Jun 25, 2023

Another concern is, even if the API object is still around, the associated data might not be. There is a field in MainloopApi called userdata: *mut c_void - it might be possible to call one of the API functions that relies on data in that field, and if it's been freed then that would be a use-after-free.

I have no idea what that would look like from the C side though.

@powpingdone
Copy link

Taking a peek into here, ASAN and Valgrind cried foul on the sample program vrnst made.
Program was

use libpulse_binding::mainloop::{api::MainloopApi, standard::Mainloop};

fn main() {
    let mut mainloop = Mainloop::new().expect("Failed to create Mainoop");
    let api: &MainloopApi = mainloop.get_api();
    drop(mainloop);
    api.quit.unwrap()(api as *const MainloopApi, 0_i32);
}

And the angry Valgrind gave:

❯ valgrind target/debug/pulsebinding
==592008== Memcheck, a memory error detector
==592008== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==592008== Using Valgrind-3.21.0 and LibVEX; rerun with -h for copyright info
==592008== Command: target/debug/pulsebinding
==592008== 
==592008== Invalid read of size 8
==592008==    at 0x110951: pulsebinding::main (main.rs:8)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008==  Address 0x5348db0 is 192 bytes inside a block of size 248 free'd
==592008==    at 0x484412F: free (vg_replace_malloc.c:974)
==592008==    by 0x4898E54: UnknownInlinedFun (xmalloc.c:129)
==592008==    by 0x4898E54: pa_xfree (xmalloc.c:122)
==592008==    by 0x110EA7: libpulse_binding::mainloop::standard::<impl libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>::drop_actual (standard.rs:287)
==592008==    by 0x111245: <libpulse_binding::mainloop::api::MainloopInner<T> as core::ops::drop::Drop>::drop (api.rs:97)
==592008==    by 0x1112BA: core::ptr::drop_in_place<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>> (mod.rs:497)
==592008==    by 0x110B31: <alloc::rc::Rc<T> as core::ops::drop::Drop>::drop (rc.rs:1609)
==592008==    by 0x110A49: core::ptr::drop_in_place<alloc::rc::Rc<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>> (mod.rs:497)
==592008==    by 0x110A59: core::ptr::drop_in_place<libpulse_binding::mainloop::standard::Mainloop> (mod.rs:497)
==592008==    by 0x110CCC: core::mem::drop (mod.rs:987)
==592008==    by 0x110949: pulsebinding::main (main.rs:7)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==  Block was alloc'd at
==592008==    at 0x48469B3: calloc (vg_replace_malloc.c:1554)
==592008==    by 0x489B6D3: pa_xmalloc0 (xmalloc.c:74)
==592008==    by 0x4884695: UnknownInlinedFun (xmalloc.h:75)
==592008==    by 0x4884695: pa_mainloop_new (mainloop.c:470)
==592008==    by 0x110EBC: libpulse_binding::mainloop::standard::Mainloop::new (standard.rs:294)
==592008==    by 0x1108E1: pulsebinding::main (main.rs:5)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008== 
==592008== Invalid read of size 8
==592008==    at 0x48826FD: mainloop_quit (mainloop.c:426)
==592008==    by 0x110973: pulsebinding::main (main.rs:8)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008==  Address 0x5348d48 is 88 bytes inside a block of size 248 free'd
==592008==    at 0x484412F: free (vg_replace_malloc.c:974)
==592008==    by 0x4898E54: UnknownInlinedFun (xmalloc.c:129)
==592008==    by 0x4898E54: pa_xfree (xmalloc.c:122)
==592008==    by 0x110EA7: libpulse_binding::mainloop::standard::<impl libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>::drop_actual (standard.rs:287)
==592008==    by 0x111245: <libpulse_binding::mainloop::api::MainloopInner<T> as core::ops::drop::Drop>::drop (api.rs:97)
==592008==    by 0x1112BA: core::ptr::drop_in_place<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>> (mod.rs:497)
==592008==    by 0x110B31: <alloc::rc::Rc<T> as core::ops::drop::Drop>::drop (rc.rs:1609)
==592008==    by 0x110A49: core::ptr::drop_in_place<alloc::rc::Rc<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>> (mod.rs:497)
==592008==    by 0x110A59: core::ptr::drop_in_place<libpulse_binding::mainloop::standard::Mainloop> (mod.rs:497)
==592008==    by 0x110CCC: core::mem::drop (mod.rs:987)
==592008==    by 0x110949: pulsebinding::main (main.rs:7)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==  Block was alloc'd at
==592008==    at 0x48469B3: calloc (vg_replace_malloc.c:1554)
==592008==    by 0x489B6D3: pa_xmalloc0 (xmalloc.c:74)
==592008==    by 0x4884695: UnknownInlinedFun (xmalloc.h:75)
==592008==    by 0x4884695: pa_mainloop_new (mainloop.c:470)
==592008==    by 0x110EBC: libpulse_binding::mainloop::standard::Mainloop::new (standard.rs:294)
==592008==    by 0x1108E1: pulsebinding::main (main.rs:5)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008== 
==592008== Invalid read of size 1
==592008==    at 0x4882689: pa_mainloop_quit (mainloop.c:975)
==592008==    by 0x110973: pulsebinding::main (main.rs:8)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008==  Address 0x5348dbc is 204 bytes inside a block of size 248 free'd
==592008==    at 0x484412F: free (vg_replace_malloc.c:974)
==592008==    by 0x4898E54: UnknownInlinedFun (xmalloc.c:129)
==592008==    by 0x4898E54: pa_xfree (xmalloc.c:122)
==592008==    by 0x110EA7: libpulse_binding::mainloop::standard::<impl libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>::drop_actual (standard.rs:287)
==592008==    by 0x111245: <libpulse_binding::mainloop::api::MainloopInner<T> as core::ops::drop::Drop>::drop (api.rs:97)
==592008==    by 0x1112BA: core::ptr::drop_in_place<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>> (mod.rs:497)
==592008==    by 0x110B31: <alloc::rc::Rc<T> as core::ops::drop::Drop>::drop (rc.rs:1609)
==592008==    by 0x110A49: core::ptr::drop_in_place<alloc::rc::Rc<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>> (mod.rs:497)
==592008==    by 0x110A59: core::ptr::drop_in_place<libpulse_binding::mainloop::standard::Mainloop> (mod.rs:497)
==592008==    by 0x110CCC: core::mem::drop (mod.rs:987)
==592008==    by 0x110949: pulsebinding::main (main.rs:7)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==  Block was alloc'd at
==592008==    at 0x48469B3: calloc (vg_replace_malloc.c:1554)
==592008==    by 0x489B6D3: pa_xmalloc0 (xmalloc.c:74)
==592008==    by 0x4884695: UnknownInlinedFun (xmalloc.h:75)
==592008==    by 0x4884695: pa_mainloop_new (mainloop.c:470)
==592008==    by 0x110EBC: libpulse_binding::mainloop::standard::Mainloop::new (standard.rs:294)
==592008==    by 0x1108E1: pulsebinding::main (main.rs:5)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008== 
==592008== Invalid write of size 4
==592008==    at 0x4882690: pa_mainloop_quit (mainloop.c:976)
==592008==    by 0x110973: pulsebinding::main (main.rs:8)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008==  Address 0x5348db8 is 200 bytes inside a block of size 248 free'd
==592008==    at 0x484412F: free (vg_replace_malloc.c:974)
==592008==    by 0x4898E54: UnknownInlinedFun (xmalloc.c:129)
==592008==    by 0x4898E54: pa_xfree (xmalloc.c:122)
==592008==    by 0x110EA7: libpulse_binding::mainloop::standard::<impl libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>::drop_actual (standard.rs:287)
==592008==    by 0x111245: <libpulse_binding::mainloop::api::MainloopInner<T> as core::ops::drop::Drop>::drop (api.rs:97)
==592008==    by 0x1112BA: core::ptr::drop_in_place<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>> (mod.rs:497)
==592008==    by 0x110B31: <alloc::rc::Rc<T> as core::ops::drop::Drop>::drop (rc.rs:1609)
==592008==    by 0x110A49: core::ptr::drop_in_place<alloc::rc::Rc<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>> (mod.rs:497)
==592008==    by 0x110A59: core::ptr::drop_in_place<libpulse_binding::mainloop::standard::Mainloop> (mod.rs:497)
==592008==    by 0x110CCC: core::mem::drop (mod.rs:987)
==592008==    by 0x110949: pulsebinding::main (main.rs:7)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==  Block was alloc'd at
==592008==    at 0x48469B3: calloc (vg_replace_malloc.c:1554)
==592008==    by 0x489B6D3: pa_xmalloc0 (xmalloc.c:74)
==592008==    by 0x4884695: UnknownInlinedFun (xmalloc.h:75)
==592008==    by 0x4884695: pa_mainloop_new (mainloop.c:470)
==592008==    by 0x110EBC: libpulse_binding::mainloop::standard::Mainloop::new (standard.rs:294)
==592008==    by 0x1108E1: pulsebinding::main (main.rs:5)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008== 
==592008== Invalid read of size 4
==592008==    at 0x4881FA3: pa_mainloop_wakeup (mainloop.c:792)
==592008==    by 0x110973: pulsebinding::main (main.rs:8)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008==  Address 0x5348dc4 is 212 bytes inside a block of size 248 free'd
==592008==    at 0x484412F: free (vg_replace_malloc.c:974)
==592008==    by 0x4898E54: UnknownInlinedFun (xmalloc.c:129)
==592008==    by 0x4898E54: pa_xfree (xmalloc.c:122)
==592008==    by 0x110EA7: libpulse_binding::mainloop::standard::<impl libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>::drop_actual (standard.rs:287)
==592008==    by 0x111245: <libpulse_binding::mainloop::api::MainloopInner<T> as core::ops::drop::Drop>::drop (api.rs:97)
==592008==    by 0x1112BA: core::ptr::drop_in_place<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>> (mod.rs:497)
==592008==    by 0x110B31: <alloc::rc::Rc<T> as core::ops::drop::Drop>::drop (rc.rs:1609)
==592008==    by 0x110A49: core::ptr::drop_in_place<alloc::rc::Rc<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>> (mod.rs:497)
==592008==    by 0x110A59: core::ptr::drop_in_place<libpulse_binding::mainloop::standard::Mainloop> (mod.rs:497)
==592008==    by 0x110CCC: core::mem::drop (mod.rs:987)
==592008==    by 0x110949: pulsebinding::main (main.rs:7)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==  Block was alloc'd at
==592008==    at 0x48469B3: calloc (vg_replace_malloc.c:1554)
==592008==    by 0x489B6D3: pa_xmalloc0 (xmalloc.c:74)
==592008==    by 0x4884695: UnknownInlinedFun (xmalloc.h:75)
==592008==    by 0x4884695: pa_mainloop_new (mainloop.c:470)
==592008==    by 0x110EBC: libpulse_binding::mainloop::standard::Mainloop::new (standard.rs:294)
==592008==    by 0x1108E1: pulsebinding::main (main.rs:5)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008== 
==592008== Invalid read of size 4
==592008==    at 0x4AED4FC: pa_write (core-util.c:445)
==592008==    by 0x4881FAE: pa_mainloop_wakeup (mainloop.c:792)
==592008==    by 0x110973: pulsebinding::main (main.rs:8)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008==  Address 0x5348dc8 is 216 bytes inside a block of size 248 free'd
==592008==    at 0x484412F: free (vg_replace_malloc.c:974)
==592008==    by 0x4898E54: UnknownInlinedFun (xmalloc.c:129)
==592008==    by 0x4898E54: pa_xfree (xmalloc.c:122)
==592008==    by 0x110EA7: libpulse_binding::mainloop::standard::<impl libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>::drop_actual (standard.rs:287)
==592008==    by 0x111245: <libpulse_binding::mainloop::api::MainloopInner<T> as core::ops::drop::Drop>::drop (api.rs:97)
==592008==    by 0x1112BA: core::ptr::drop_in_place<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>> (mod.rs:497)
==592008==    by 0x110B31: <alloc::rc::Rc<T> as core::ops::drop::Drop>::drop (rc.rs:1609)
==592008==    by 0x110A49: core::ptr::drop_in_place<alloc::rc::Rc<libpulse_binding::mainloop::api::MainloopInner<libpulse_sys::mainloop::standard::pa_mainloop>>> (mod.rs:497)
==592008==    by 0x110A59: core::ptr::drop_in_place<libpulse_binding::mainloop::standard::Mainloop> (mod.rs:497)
==592008==    by 0x110CCC: core::mem::drop (mod.rs:987)
==592008==    by 0x110949: pulsebinding::main (main.rs:7)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==  Block was alloc'd at
==592008==    at 0x48469B3: calloc (vg_replace_malloc.c:1554)
==592008==    by 0x489B6D3: pa_xmalloc0 (xmalloc.c:74)
==592008==    by 0x4884695: UnknownInlinedFun (xmalloc.h:75)
==592008==    by 0x4884695: pa_mainloop_new (mainloop.c:470)
==592008==    by 0x110EBC: libpulse_binding::mainloop::standard::Mainloop::new (standard.rs:294)
==592008==    by 0x1108E1: pulsebinding::main (main.rs:5)
==592008==    by 0x110A3A: core::ops::function::FnOnce::call_once (function.rs:250)
==592008==    by 0x110CDD: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:135)
==592008==    by 0x1108B0: std::rt::lang_start::{{closure}} (rt.rs:166)
==592008==    by 0x125104: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==592008==    by 0x125104: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:500)
==592008==    by 0x125104: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:142)
==592008==    by 0x125104: {closure#2} (rt.rs:148)
==592008==    by 0x125104: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:500)
==592008==    by 0x125104: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:464)
==592008==    by 0x125104: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:142)
==592008==    by 0x125104: std::rt::lang_start_internal (rt.rs:148)
==592008==    by 0x110889: std::rt::lang_start (rt.rs:165)
==592008==    by 0x1109CD: main (in /var/home/powpingdone/extern/pulsebinding/target/debug/pulsebinding)
==592008== 
pa_write() failed while trying to wake up the mainloop: Bad file descriptor
==592008== 
==592008== HEAP SUMMARY:
==592008==     in use at exit: 157 bytes in 6 blocks
==592008==   total heap usage: 2,415 allocs, 2,409 frees, 257,287 bytes allocated
==592008== 
==592008== LEAK SUMMARY:
==592008==    definitely lost: 0 bytes in 0 blocks
==592008==    indirectly lost: 0 bytes in 0 blocks
==592008==      possibly lost: 0 bytes in 0 blocks
==592008==    still reachable: 157 bytes in 6 blocks
==592008==         suppressed: 0 bytes in 0 blocks
==592008== Rerun with --leak-check=full to see details of leaked memory
==592008== 
==592008== For lists of detected and suppressed errors, rerun with: -s
==592008== ERROR SUMMARY: 7 errors from 6 contexts (suppressed: 0 from 0)

@jnqnfe Something is defo wrong here.

@vnrst
Copy link
Author

vnrst commented Jul 28, 2023

@powpingdone - thanks for the report. Just wondering, does Valgrind still complain if you remove the line with drop(mainloop);? If the error vanishes, then that pins the cause of the error specifically to the get_api function. Otherwise, it's possible that there's something weird happening independently in the api.quit call.

@powpingdone
Copy link

powpingdone commented Jul 31, 2023

==61300== Memcheck, a memory error detector
==61300== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==61300== Using Valgrind-3.21.0 and LibVEX; rerun with -h for copyright info
==61300== Command: target/debug/pulse
==61300== 
==61300== 
==61300== HEAP SUMMARY:
==61300==     in use at exit: 40 bytes in 1 blocks
==61300==   total heap usage: 16 allocs, 15 frees, 2,571 bytes allocated
==61300== 
==61300== LEAK SUMMARY:
==61300==    definitely lost: 0 bytes in 0 blocks
==61300==    indirectly lost: 0 bytes in 0 blocks
==61300==      possibly lost: 0 bytes in 0 blocks
==61300==    still reachable: 40 bytes in 1 blocks
==61300==         suppressed: 0 bytes in 0 blocks
==61300== Rerun with --leak-check=full to see details of leaked memory
==61300== 
==61300== For lists of detected and suppressed errors, rerun with: -s
==61300== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

@vnrst

@Hawk777
Copy link

Hawk777 commented Dec 8, 2024

I think I just ran into this bug myself. I’m never actually calling get_api directly; rather, I pass the Mainloop object (I’m using the Glib one) into Context::new, then I hang onto the Context but drop the Mainloop (because I thought I didn’t need it any more because everything is refcounted, so if the Mainloop’s guts needed to stay alive for the lifetime of the Context then surely it would have been reffed internally). But then when I tried to connect the context, it entered the Connecting state but never advanced any further. Artificially hanging onto the Mainloop for to outlive the Context seems to have solved it, but I had no idea I was supposed to do that, because AFAICT it’s not documented anywhere, is it? If “hang onto an object you have no use for just to prevent it from being dropped too early, and which the language-level lifetimes don’t tell you you need to” is correct usage of this library, then IMO that needs a big notice in the docs!

I have a pretty simple example:

use libpulse_binding as pulse;
use libpulse_glib_binding as pulse_glib;
use std::cell::RefCell;
use std::rc::Rc;

fn do_stuff() {
	let ml = pulse_glib::Mainloop::new(None).expect("mainloop new failed");
	let ctx = pulse::context::Context::new(&ml, "foo").expect("context new failed");
	let ctx = Rc::new(RefCell::new(ctx));
	let ctx_clone = ctx.clone();
	ctx.borrow_mut().set_state_callback(Some(Box::new(move || {
		if let Ok(borrow) = ctx_clone.try_borrow() {
			println!("State CB: state is {:?}", borrow.get_state());
		} else {
			println!("State CB: cannot check state, context is borrowed");
		}
	})));
	let ctx_clone = ctx.clone();
	glib::source::timeout_add_local(std::time::Duration::from_secs(1), move || {
		if let Ok(borrow) = ctx_clone.try_borrow() {
			println!("Timeout CB: state is {:?}", borrow.get_state());
		} else {
			println!("Timeout CB: cannot check state, context is borrowed");
		}
		glib::ControlFlow::Continue
	});
	println!("Call connect.");
	ctx.borrow_mut().connect(None, pulse::context::FlagSet::NOFLAGS, None).expect("connect failed");
	println!("Connect returned, state is {:?}", ctx.borrow().get_state());
	//std::mem::forget(ml);
}

fn main() {
	let ml = glib::MainLoop::new(None, false);
	glib::source::idle_add_local_once(do_stuff);
	ml.run();
}

Run this as is, and the output is as follows:

Call connect.
State CB: cannot check state, context is borrowed
Connect returned, state is Connecting
Timeout CB: state is Connecting
Timeout CB: state is Connecting
Timeout CB: state is Connecting

Uncomment the std::mem::forget line to prevent ml from being dropped, and the output is as follows instead:

Call connect.
State CB: cannot check state, context is borrowed
Connect returned, state is Connecting
State CB: state is Authorizing
State CB: state is SettingName
State CB: state is Ready
Timeout CB: state is Ready
Timeout CB: state is Ready
Timeout CB: state is Ready

So, clearly the context is not hanging onto the MainLoop object internally, and just becomes randomly broken when you drop it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants