3
\$\begingroup\$

I'm writing a Rust library to facilitate implementing the following C API in Rust. That is, it should be possible to create a Rust-idiomatic implementation by depending on my wrapper library.

// Called first. Implementer may allocate memory here.
void init();

// For each input: start is called, then run is called repeatedly, finally stop
// is called. The input pointer is valid until the end of stop.
void start(const char* input, size_t size);
void run();
void stop();

// Called last. Implementer should free all allocated memory.
void deinit();

To clarify the required order of calls, the following example code calls the API as specified:

#include <stdlib.h>
#include <string.h>

#include "api.h"

void run_input(const char* s) {
  size_t size = strlen(s);
  char* data = malloc(size);
  memcpy(data, s, size);
  start(data, size);
  run();
  run();
  stop();
  free(data);
}

int main(int argc, char** argv) {
  init();
  run_input("abcde");
  run_input("xyz");
  deinit();
}

Note: neither the API nor the code calling it are mine. Only the Rust wrapper code (below) is mine. The API is well established with multiple existing implementations and callers; I cannot change it.

Because the lifetime of each input spans multiple calls into Rust, it's difficult to ensure the implementation has access to it for right lifetime. I also want to support implementations which persist mutable data between different inputs. Here is my current solution:

//! A helper library for implementing the C API. The implementer should
//! implement the Runner and Factory traits:
//!     struct RunnerImpl;
//!     impl Runner for RunnerImpl {
//!         // ...
//!     }
//!     struct FactoryImpl;
//!     impl Factory for FactoryImpl {
//!         // ...
//!     }
//! and call the macro:
//!     factory!(FactoryImpl);

use std::borrow::BorrowMut;
use std::mem::transmute;
use std::slice;

pub trait Runner {
    fn run(&mut self);
    fn stop(&mut self);
}

pub trait Factory {
    fn default() -> Self
    where
        Self: Sized;
    fn start_runner<'a>(&'a mut self, input: &'a [u8]) -> Box<dyn Runner + 'a>;
}

struct Wrapper {
    factory: Box<dyn Factory>,
    runner: Option<Box<dyn Runner>>,
}

impl Wrapper {
    fn new<F: Factory + 'static>() -> Self {
        Wrapper {
            factory: Box::new(F::default()),
            runner: None,
        }
    }

    fn runner(&mut self) -> &mut (dyn Runner + 'static) {
        self.runner.as_mut().unwrap().borrow_mut()
    }
}

static mut INSTANCE: *mut Wrapper = 0 as *mut _;
unsafe fn instance() -> &'static mut Wrapper {
    assert_ne!(INSTANCE, 0 as *mut _);
    &mut *INSTANCE
}

#[doc(hidden)]
pub unsafe fn init_impl<F: Factory + 'static>() {
    INSTANCE = Box::into_raw(Box::new(Wrapper::new::<F>()));
}

#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn start(input: *const libc::c_char, size: libc::size_t) {
    // The actual lifetime of input is until the stop() call. Because this spans multiple FFI calls
    // into rust code, the lifetime can't be expressed in rust. Therefore pretend it is 'static. In
    // stop() we drop Runner, preventing the reference from leaking (?).
    // The Factory reference should have the same lifetime, and again we pretend it is 'static.
    // The wrapper doesn't access factory while running, so it's safe to pretend that runner has
    // exclusive access to it.
    let input = slice::from_raw_parts(input as *const u8, size);
    let f: &mut dyn Factory = instance().factory.borrow_mut();
    let fs: &'static mut dyn Factory = transmute(f);
    instance().runner = Some(fs.start_runner(input));
}

#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn run() {
    instance().runner().run()
}

#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn stop() {
    instance().runner().stop();
    instance().runner = None;
}

#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn deinit() {
    std::mem::drop(Box::from_raw(INSTANCE));
    INSTANCE = 0 as *mut _;
}

#[macro_export]
macro_rules! factory {
    ($factory: path) => {
        #[doc(hidden)]
        #[no_mangle]
        pub unsafe extern "C" fn init() {
            $crate::init_impl::<$factory>()
        }
    };
}

My intention is that each Runner can hold a reference to the Factory to persist data between inputs, and also hold a reference to input for use in run() and stop(), but can't leak input beyond its lifetime.

I mainly want to know whether the lifetime munging is safe. Assuming the API is called as specified, can an implementation of Runner and Factory cause use-after-free access to input? Can the transmute cause any trouble?

\$\endgroup\$
  • \$\begingroup\$ Can you provide an example of usage of this API? \$\endgroup\$ – PitaJ Dec 5 '19 at 21:00
  • \$\begingroup\$ Can there only be a single instance? Regardless, you should return a pointer from init and require your user to pass that pointer to start, run, stop, and deinit. \$\endgroup\$ – PitaJ Dec 5 '19 at 21:08
  • \$\begingroup\$ @PitaJ IIUC you are suggesting changes to the C API. I can't change that part, it's given. Only the Rust code is mine. Are you asking for example code calling the C API, or example code implementing the Runner and Factory traits? \$\endgroup\$ – stewbasic Dec 6 '19 at 2:25
  • \$\begingroup\$ Just to confirm: you absolutely can't modify the C API, is that correct? I was asking for an example of C API usage. \$\endgroup\$ – PitaJ Dec 6 '19 at 18:42
  • \$\begingroup\$ @PitaJ I added to the question example calling code and an explicit note that I can't change the API. btw I put the "api" tag on the question because I view the Runner and Factory traits as an API also; they specify the interface that the user of my wrapper must implement. \$\endgroup\$ – stewbasic Dec 8 '19 at 20:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.