Rust is a memory safe language but once in a while you may find yourself writing unsafe code, or using a library you don’t trust, or executing code over FFI. In these situations the type safety normally provided may not be enough for your requirements.

I’ve had the idea for a while now to provide a very simple sandboxing library. I finally started working on that yesterday and built up a simple proof of concept.

The goal is to provide a dead-simple interface for sandboxing code. Using it looks like this (for now - this is all still very rough).

let sandbox = Sandbox::new_with_descriptors(vec![
    Box::new(
        UnixUserGroupSandboxBuilder::new()
        .with_uid(UidOrGid::Nobody)
        .with_gid(UidOrGid::Nobody)
        .into_descriptor()
        )
    ]);

let value = "yo";

let result = sandbox.execute(|| {
    assert_eq!(getuid(), 65534);
    assert_eq!(getgid(), 65534);
    // Do scary things!
    format!("{}, I'm running in a sandbox", value) // capture variables
});

assert_eq!(result.unwrap(), "yo, I'm running in a sandbox");

Essentially, you create a Sandbox object with sandbox descriptors, you pass the sandbox a function or, in this case a closure, and you wait for it to return.

Internally, the execute function looks pretty simple:

pub fn execute<F, T>(mut self, closure: F) -> SandboxResult<T>
    where F: Fn() -> T,
          T: Serialize + Deserialize
{
    let (snd, rcv) = ipc::channel().unwrap();

    if let Fork::Parent(_) = fork().unwrap() {
        return match rcv.recv() {
            Ok(t) => SandboxResult::Ok(t),
            Err(_) => SandboxResult::Err,
        };
    }

    for descriptor in self.descriptors.iter_mut() {
        descriptor.execute();
    }

    snd.send(closure()).unwrap();
    std::process::exit(0);
}

Fork into a child process, the child process executes the sandboxes serially, the closure is executed, and the result returned to the parent.

The sandbox descriptor in this case is a UnixUserGroupSandbox, which moves your process over to a new user and group - in this case the Nobody user.

Here is the implementation of the execute function for the UnixUserGroupSandbox, which is currently incomplete.

fn execute(&mut self) -> Result<(), Box<Error>> {
    if let Some(gid) = self.gid {
        setgid(gid.into()).unwrap();
    };

    if let Some(uid) = self.uid {
        setuid(uid.into()).unwrap();
    };
    Ok(())
}

The code is dead simple. You could easily incorporate this into any existing rust program. If you wanted to get a ‘whole program’ sandbox you could wrap ‘main’ in the sandbox code. If you want to choose your capabilities at the granularity of a function, you can do that.

Of course there are downsides - as you stack sandbox descriptors you may run into compatibility problems. I’ve done nothing to handle platform specific cases. And there is a cost of a fork as well as serialization between processes, so you would probably want to think about how you can group unsafe actions into your sandbox, etc.

Speaking of that serialization between processes, that’s also your attack surface in this scenario, and I’ve never looked at the internals of the ipc-channel crate that I’m using. The security of this sandbox relies pretty heavily on it.

There’s also another rust project, Gaol, that does sandboxing. After writing this up I decided to have a look. Gaol is actually really cool, in particular it is focused on building a description of allowed operations, whereas my approach is very heavy on providing sandboxing implementations that implicitly restrict operations.

Having looked at Gaol I can really see the beauty of their approach - users are not necessarily familiar with what a separate linux user/group will restrict them from doing, but they know that they want to open file A and never write to file B. As I move forward with the project I may try to write higher level descriptors that have a focus on capabilities.

I think there are drawbacks and benefits to both approaches and I will enjoy exploring this approach further. I would love to hear any feedback on the project.

The code can be found here: https://github.com/insanitybit/sandbox



blog comments powered by Disqus

Published

11 June 2016

Categories