a1ien/rusb

Segfault in `Drop` implementation on macOS 12

dstaley opened this issue · 7 comments

I'm working on rewriting a C program using libusb into Rust with rusb. I have a version of my program that executes correctly, but ends with a segfault when performing the Drop on the libusb context when run on macOS.

My program roughly looks like the following:

fn main() {
    let mut context = Context::new().expect("couldn't instantiate libusb");
    context.set_log_level(rusb::LogLevel::Debug);
    let mut handle = context
        .open_device_with_vid_pid(0x05ac, 0x120a)
        .expect("could not open device");

    let endpoint_d2h_address: u8 = 0x82;
    let endpoint_h2d_address: u8 = 0x01;

    handle.set_auto_detach_kernel_driver(true);
    handle
        .claim_interface(0)
        .expect("could not claim interface");

    // perform a series of handle.write_bulk() and handle.read_bulk() calls

    handle
        .release_interface(0)
        .expect("could not release interface");

    drop(handle);
    println!("dropped handle");
    drop(context);
    println!("dropped context");
    println!("done");

    println!("reached end of main");
}

For debugging purposes, I've manually called drop on handle and context, but the issue occurs when relying on the auto Drop calls.

This issue doesn't occur on Linux, and doesn't occur (as far as I can tell) in my C program. Furthermore, it doesn't occur if I skip performing the write_bulk and read_bulk calls.

I've collected a few logs with the libusb log level set to debug:

Since this doesn't occur in my C program, I'm inclined to believe this is an issue with rusb not correctly disposing of things during Drop. In particular, one difference I noticed between the segfaulting Rust log and the working C log is that the C program calls libusb_close before releasing the interface, whereas rusb will always release the interface before calling libusb_close. Unfortunately my USB protocol knowledge isn't sufficient to say whether or not that's an issue.

System Info

OS: macos 12.3.1
libusb: v1.0.26 (Homebrew)
rusb: v0.9.1

rustc 1.60.0 (7737e0b5c 2022-04-04)
binary: rustc
commit-hash: 7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c
commit-date: 2022-04-04
host: x86_64-apple-darwin
release: 1.60.0
LLVM version: 14.0.0
a1ien commented

I believe that is same bugs as found here libusb/libusb#1124
And close libusb before releasing the interface is incorrect

I still get the issue when linking against a version of libusb built from the current commit (libusb/libusb@ba69847).

$ otool -L target/debug/rs-ipod-sysinfo              
target/debug/rs-ipod-sysinfo:
        /usr/local/opt/libusb/lib/libusb-1.0.0.dylib (compatibility version 4.0.0, current version 4.0.0)
        /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)
        /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
$ readlink -f /usr/local/opt/libusb/lib/libusb-1.0.dylib
/usr/local/Cellar/libusb/HEAD-ba69847/lib/libusb-1.0.0.dylib
a1ien commented

Sorry for long waiting I in process of moving. Can you share example of C code and rust code?
I am still believe that's bug in libusb and not in rusb. If C code call libusb_release_interface after libusb_close that's bug in C code as describe in documentation to libusb_release_interface you should call it before libusb_close.

No worries, nothing to apologize for :)

Here is the code for both the Rust and C program. The C program actually doesn't manually release the interface, so maybe the call to libusb_close automatically does that?

a1ien commented

Can you try remove call handle.release_interface(0) in rust. When handle dropped they release interface. Also can you try add libusb_release_interface call to C code in usb_device_close function.
I haven't any macos device so it's hard make test on my side.

mcuee commented

I can not reproduce the issue with the following code, using latest libusb release 1.0.26 version, or libusb git (libusb/libusb@c5aec6b), tested under macOS Ventura 13.3.1 (latest macOS version) and Mac Mini m1.

mcuee@mcuees-Mac-mini rusb % cat ./examples/issue134.rs      
use rusb::{
    Context, Device, UsbContext,
};

fn main() {
    let mut context = Context::new().expect("couldn't instantiate libusb");
    context.set_log_level(rusb::LogLevel::Debug);
    let mut handle = context
        .open_device_with_vid_pid(0x04d8, 0xfa2e)
        .expect("could not open device");

    handle.set_auto_detach_kernel_driver(true);
    handle
        .claim_interface(0)
        .expect("could not claim interface");

    // perform a series of handle.write_bulk() and handle.read_bulk() calls

    handle
        .release_interface(0)
        .expect("could not release interface");

    drop(handle);
    println!("dropped handle");
    drop(context);
    println!("dropped context");
    println!("done");

    println!("reached end of main");
}

mcuee@mcuees-Mac-mini rusb % ./target/debug/examples/issue134
[timestamp] [threadID] facility level [function call] <message>
--------------------------------------------------------------------------------
[ 0.006918] [00010283] libusb: debug [libusb_get_device_list]  
[ 0.006949] [00010283] libusb: debug [libusb_get_device_descriptor]  
[ 0.006953] [00010283] libusb: debug [libusb_get_device_descriptor]  
[ 0.006955] [00010283] libusb: debug [libusb_get_device_descriptor]  
[ 0.006958] [00010283] libusb: debug [libusb_get_device_descriptor]  
[ 0.006961] [00010283] libusb: debug [libusb_open] open 2.7
[ 0.007036] [00010283] libusb: debug [darwin_open] device open for access
[ 0.007044] [00010283] libusb: debug [libusb_claim_interface] interface 0
[ 0.007427] [00010283] libusb: debug [get_endpoints] building table of endpoints.
[ 0.007442] [00010283] libusb: debug [get_endpoints] interface: 0 pipe 1: dir: 0 number: 1
[ 0.007449] [00010283] libusb: debug [get_endpoints] interface: 0 pipe 2: dir: 1 number: 1
[ 0.007471] [00010283] libusb: debug [darwin_claim_interface] interface opened
[ 0.007475] [00010283] libusb: debug [libusb_release_interface] interface 0
[ 0.007769] [00010283] libusb: debug [libusb_close]  
dropped handle
[ 0.007814] [00010283] libusb: debug [libusb_exit]  
[ 0.007818] [00010283] libusb: debug [libusb_unref_device] destroy device 2.11
[ 0.007821] [00010283] libusb: debug [libusb_unref_device] destroy device 2.9
[ 0.007824] [00010283] libusb: debug [libusb_unref_device] destroy device 2.8
[ 0.007826] [00010283] libusb: debug [libusb_unref_device] destroy device 2.7
[ 0.007829] [00010283] libusb: debug [libusb_unref_device] destroy device 2.6
[ 0.007832] [00010283] libusb: debug [libusb_unref_device] destroy device 2.5
[ 0.007834] [00010283] libusb: debug [libusb_unref_device] destroy device 2.4
[ 0.007837] [00010283] libusb: debug [libusb_unref_device] destroy device 2.3
[ 0.007840] [00010283] libusb: debug [libusb_unref_device] destroy device 2.2
[ 0.007842] [00010283] libusb: debug [libusb_unref_device] destroy device 2.1
[ 0.007866] [00010284] libusb: debug [darwin_event_thread_main] darwin event thread exiting
[ 0.008872] [00010283] libusb: debug [usbi_remove_event_source] remove fd 3
dropped context
done
reached end of main
mcuee commented

But my test is probably different as there is no transfer used.

If I look into the debug log, there is a complication here, libusb does not have exclusive access to the device. I am not so sure if that plays a part or not. The device is a USB mass storage device and by right libusb/rusb is not the right library to use for the device.

@dstaley

You may want to try what have been suggested by @a1ien to see if that helps. You can also try latest libusb git which have some fixes which may help.