Zac Zac - 1 month ago 20
C Question

Node FFI wrapper function fails when used synchronously, but works asynchronously

I'm trying to wrap a Rust library (that exposes a C API) with Node FFI. I have the following code wrapping two functions. One is a "constructor" that returns a pointer. The other takes a pointer and returns C string.

var libcomm = ffi.Library('lib/c_api/target/debug/libcomm', {
'comm_address_for_content': ['pointer', ['string']],
'comm_address_to_str': ['string', ['pointer']]
});


When I use the asynchronous invocation of
comm_address_to_str
the response is correct. However, when I try to call the function using the synchronous style, it returns garbage, or very very rarely, the correct result. The following nodeunit test exercises the scenario:

const comm = require("../").libcomm;

exports.testAddressForContent = function (test) {
const ptr = comm.comm_address_for_content('test');

const result = comm.comm_address_to_str(ptr);
test.equal(result, 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'); // always fails
console.log('sync', result); // random garbage

comm.comm_address_to_str.async(ptr, function(err, result) {
test.equal(result, 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'); // always passes
console.log('async', result); // 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'
test.done();
});
}


I can't figure out what causes this, but I need to be able to use the sync invocation style. The C API of the Rust library I am wrapping is here.

Answer

The problem, I am afraid, comes from the comm library, and specifically the comm_address_to_str function:

#[no_mangle]
pub extern "C" fn comm_address_to_str(address: *const Address) -> *const c_char {
    let string = unsafe { *address }.to_str();
    CString::new(string).unwrap().as_ptr()
}

The documentation of CString::as_ptr specifically calls out this pattern:

use std::ffi::{CString};

let ptr = CString::new("Hello").unwrap().as_ptr();
unsafe {
    // `ptr` is dangling
    *ptr;
}

The CString created on the last line is immediately destructed after taking a pointer into its internals, leading to a dangling pointer.

The comm library that you are using will need to be fixed, and it might be worth auditing the other functions as well.

The fix here would be to leak the CString, and then provide another function to be called with the leaked pointer which will take care of freeing it.

Comments