rust-lang / rust-bindgen

Automatically generates Rust FFI bindings to C (and some C++) libraries.
https://rust-lang.github.io/rust-bindgen/
BSD 3-Clause "New" or "Revised" License
4.35k stars 687 forks source link

Segmentation Fault in C++ operator caused by argument being NULL #2222

Open Bqleine opened 2 years ago

Bqleine commented 2 years ago

Input C/C++ Header

class RAK_DLL_EXPORT RakPeer : public RakPeerInterface, public RNS2EventHandler
{
public:
    /// \brief Sends a block of data to the specified system that you are connected to.
    /// 
    /// Same as the above version, but takes a BitStream as input.
    /// \param[in] bitStream Bitstream to send
    /// \param[in] priority Priority level to send on.  See PacketPriority.h
    /// \param[in] reliability How reliably to send this data.  See PacketPriority.h
    /// \param[in] orderingChannel Channel to order the messages on, when using ordered or sequenced messages. Messages are only ordered relative to other messages on the same stream.
    /// \param[in] systemIdentifier System Address or RakNetGUID to send this packet to, or in the case of broadcasting, the address not to send it to.  Use UNASSIGNED_SYSTEM_ADDRESS to specify none.
    /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to.
    /// \param[in] forceReceipt If 0, will automatically determine the receipt number to return. If non-zero, will return what you give it.
    /// \return 0 on bad input. Otherwise a number that identifies this message. If \a reliability is a type that returns a receipt, on a later call to Receive() you will get ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS with bytes 1-4 inclusive containing this number
    /// \note COMMON MISTAKE: When writing the first byte, bitStream->Write((unsigned char) ID_MY_TYPE) be sure it is casted to a byte, and you are not writing a 4 byte enumeration.
    uint32_t Send( const RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, uint32_t forceReceiptNumber=0 );
}

Bindgen Invocation

    let bindings = bindgen::Builder::default()
        .header("wrapper.hpp")
        .enable_cxx_namespaces()
        .layout_tests(false)
        .generate_inline_functions(true)

        .allowlist_type("Rak.*")
        .allowlist_type("DefaultMessageIDTypes")
        .allowlist_type("UNASSIGNED.*")
        .allowlist_function(".*Undefined.*")

        .blocklist_function(".*BitStream_Write1")

        .generate()
        .expect("Unable to generate bindings");

Actual Results

Test crashes with

Signal: SIGSEGV (signal SIGSEGV: invalid address (fault address: 0x0))

Debugger stack at time of crash:

RakNet::RakNetGUID::operator!=(const RakNet::RakNetGUID &) const RakNetTypes.cpp:762
RakNet::RakPeer::IsLoopbackAddress(const RakNet::AddressOrGUID &, bool) const RakPeer.cpp:4008
RakNet::RakPeer::Send(const RakNet::BitStream *, PacketPriority, PacketReliability, char, RakNet::AddressOrGUID, bool, unsigned int) RakPeer.cpp:1391
chat_example lib.rs:75
chat_client lib.rs:127
{closure#0} lib.rs:126
call_once<raknet::tests::chat_client::{closure_env#0}, ()> function.rs:230
[Inlined] core::ops::function::FnOnce::call_once function.rs:230
__rust_begin_short_backtrace<fn()> lib.rs:573
[Inlined] _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..FnOnce$LT$Args$GT$$GT$::call_once boxed.rs:1861
[Inlined] _$LT$core..panic..unwind_safe..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..function..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once unwind_safe.rs:271
[Inlined] std::panicking::try::do_call panicking.rs:492
[Inlined] std::panicking::try panicking.rs:456
[Inlined] std::panic::catch_unwind panic.rs:137
[Inlined] test::run_test_in_process lib.rs:596
{closure#0} lib.rs:490
[Inlined] test::run_test::run_test_inner::_$u7b$$u7b$closure$u7d$$u7d$ lib.rs:517
__rust_begin_short_backtrace<test::run_test::run_test_inner::{closure_env#1}, ()> backtrace.rs:122
[Inlined] std::thread::Builder::spawn_unchecked_::_$u7b$$u7b$closure$u7d$$u7d$::_$u7b$$u7b$closure$u7d$$u7d$ mod.rs:498
[Inlined] _$LT$core..panic..unwind_safe..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..function..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once unwind_safe.rs:271
[Inlined] std::panicking::try::do_call panicking.rs:492
[Inlined] std::panicking::try panicking.rs:456
[Inlined] std::panic::catch_unwind panic.rs:137
[Inlined] std::thread::Builder::spawn_unchecked_::_$u7b$$u7b$closure$u7d$$u7d$ mod.rs:497
call_once<std::thread::{impl#0}::spawn_unchecked_::{closure_env#1}<test::run_test::run_test_inner::{closure_env#1}, ()>, ()> function.rs:230
[Inlined] _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..FnOnce$LT$Args$GT$$GT$::call_once boxed.rs:1861
[Inlined] _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..FnOnce$LT$Args$GT$$GT$::call_once boxed.rs:1861
thread_start thread.rs:108
<unknown> 0x00007fb5da3e354d
__clone 0x00007fb5da468874

By looking at the debugger results, it seems the Send function is called with a NULL systemIdentifier even though it is instantiated and called in rust with this:

let system_identifier = AddressOrGUID::new2(&(*packet).systemAddress);

RakPeer_Send1(peer as *mut c_void, &bsOut as *const BitStream, PacketPriority_HIGH_PRIORITY, PacketReliability_RELIABLE_ORDERED, 0, system_identifier, false, 0);

The debugger also shows that all the other arguments are non-null and valid in the Send function.

I have tried other ways to instantiate the system_identifier but everything ends up with the same result, and I was able to verify that the system_identifier variable is valid on the rust side using the debugger.

Here are the generated bindings for some of the functions used:

#[link_name = "\u{1}_ZN6RakNet7RakPeer4SendEPKNS_9BitStreamE14PacketPriority17PacketReliabilitycNS_13AddressOrGUIDEbj"]
            pub fn RakPeer_Send1(
                this: *mut ::std::os::raw::c_void,
                bitStream: *const root::RakNet::BitStream,
                priority: root::PacketPriority,
                reliability: root::PacketReliability,
                orderingChannel: ::std::os::raw::c_char,
                systemIdentifier: root::RakNet::AddressOrGUID,
                broadcast: bool,
                forceReceiptNumber: u32,
            ) -> u32;

// I use new2 in this example but I also tried new3 and new4
#[inline]
            pub unsafe fn new() -> Self {
                let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit();
                AddressOrGUID_AddressOrGUID(__bindgen_tmp.as_mut_ptr());
                __bindgen_tmp.assume_init()
            }
            #[inline]
            pub unsafe fn new1(input: *const root::RakNet::AddressOrGUID) -> Self {
                let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit();
                AddressOrGUID_AddressOrGUID1(__bindgen_tmp.as_mut_ptr(), input);
                __bindgen_tmp.assume_init()
            }
            #[inline]
            pub unsafe fn new2(input: *const root::RakNet::SystemAddress) -> Self {
                let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit();
                AddressOrGUID_AddressOrGUID2(__bindgen_tmp.as_mut_ptr(), input);
                __bindgen_tmp.assume_init()
            }
            #[inline]
            pub unsafe fn new3(packet: *mut root::RakNet::Packet) -> Self {
                let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit();
                AddressOrGUID_AddressOrGUID3(__bindgen_tmp.as_mut_ptr(), packet);
                __bindgen_tmp.assume_init()
            }
            #[inline]
            pub unsafe fn new4(input: *const root::RakNet::RakNetGUID) -> Self {
                let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit();
                AddressOrGUID_AddressOrGUID4(__bindgen_tmp.as_mut_ptr(), input);
                __bindgen_tmp.assume_init()
            }

Expected Results

The send function is called with valid arguments and doesn't crash

emilio commented 2 years ago

How's AddressOrGUID defined? I think if it's not a POD type this might unfortunately be expected (because Rust doesn't have the C++ concept of non-trivial types).

Bqleine commented 2 years ago

Like this:

struct RAK_DLL_EXPORT AddressOrGUID
{
    RakNetGUID rakNetGuid;
    SystemAddress systemAddress;

    SystemIndex GetSystemIndex(void) const {if (rakNetGuid!=UNASSIGNED_RAKNET_GUID) return rakNetGuid.systemIndex; else return systemAddress.systemIndex;}
    bool IsUndefined(void) const {return rakNetGuid==UNASSIGNED_RAKNET_GUID && systemAddress==UNASSIGNED_SYSTEM_ADDRESS;}
    void SetUndefined(void) {rakNetGuid=UNASSIGNED_RAKNET_GUID; systemAddress=UNASSIGNED_SYSTEM_ADDRESS;}
    static unsigned long ToInteger( const AddressOrGUID &aog );
    const char *ToString(bool writePort=true) const;
    void ToString(bool writePort, char *dest) const;

    AddressOrGUID() {}
    AddressOrGUID( const AddressOrGUID& input )
    {
        rakNetGuid=input.rakNetGuid;
        systemAddress=input.systemAddress;
    }
    AddressOrGUID( const SystemAddress& input )
    {
        rakNetGuid=UNASSIGNED_RAKNET_GUID;
        systemAddress=input;
    }
    AddressOrGUID( Packet *packet );
    AddressOrGUID( const RakNetGUID& input )
    {
        rakNetGuid=input;
        systemAddress=UNASSIGNED_SYSTEM_ADDRESS;
    }
    AddressOrGUID& operator = ( const AddressOrGUID& input )
    {
        rakNetGuid=input.rakNetGuid;
        systemAddress=input.systemAddress;
        return *this;
    }

    AddressOrGUID& operator = ( const SystemAddress& input )
    {
        rakNetGuid=UNASSIGNED_RAKNET_GUID;
        systemAddress=input;
        return *this;
    }

    AddressOrGUID& operator = ( const RakNetGUID& input )
    {
        rakNetGuid=input;
        systemAddress=UNASSIGNED_SYSTEM_ADDRESS;
        return *this;
    }

    inline bool operator==( const AddressOrGUID& right ) const {return (rakNetGuid!=UNASSIGNED_RAKNET_GUID && rakNetGuid==right.rakNetGuid) || (systemAddress!=UNASSIGNED_SYSTEM_ADDRESS && systemAddress==right.systemAddress);}
};
Bqleine commented 2 years ago

rakNetGuid and systemAddress are supposed to be set to UNASSIGNED_RAKNET_GUID and UNASSIGNED_SYSTEM_ADDRESS respectively when the other is used.

Bqleine commented 2 years ago

Here's the RaknetGUID definition:

struct RAK_DLL_EXPORT RakNetGUID
{
    RakNetGUID();
    explicit RakNetGUID(uint64_t _g) {g=_g; systemIndex=(SystemIndex)-1;}
//  uint32_t g[6];
    uint64_t g;
...

And the AddressOrGuid definition:

struct RAK_DLL_EXPORT SystemAddress
{
    /// Constructors
    SystemAddress();
    SystemAddress(const char *str);
    SystemAddress(const char *str, unsigned short port);

    /// SystemAddress, with RAKNET_SUPPORT_IPV6 defined, holds both an sockaddr_in6 and a sockaddr_in
    union// In6OrIn4
    {
#if RAKNET_SUPPORT_IPV6==1
        struct sockaddr_storage sa_stor;
        sockaddr_in6 addr6;
#endif

        sockaddr_in addr4;
    } address;

    /// This is not used internally, but holds a copy of the port held in the address union, so for debugging it's easier to check what port is being held
    unsigned short debugPort;
...

And I might as well link the rest of the library if you want to look by yourself: https://github.com/facebookarchive/RakNet/blob/master/Source/RakNetTypes.h

NicolasHanus commented 1 year ago

Hello, Any update on this one ? Do you still have the solution, could you find a fix or a workaround ? Asking because I observe a very similar issue on my project: it ends up with signal: 11, SIGSEGV: invalid memory reference, where you have invalid address. And in more details, I observe the same as you on GDB, I have a 'self' which is valid and points to an address in Rust world, and when I arrive in the C++ world, the 'this' becomes 0x00 (null ptr -> crash)

Any hint would be appreciated. Thx

Bqleine commented 1 year ago

Hello! I switched to another solution so I did not have to fix that problem, I suppose though you could make a C wrapper around that C++ code and then use it in rust, or maybe modify the type to make it not POD.

As emilio said this is related to having a POD type so if that's not the case for you you should try another issue i guess. Good luck!