3 unstable releases

Uses old Rust 2015

0.2.1 Oct 2, 2022
0.2.0 Apr 10, 2019
0.1.0 Jun 21, 2018

#1094 in Network programming

47 downloads per month
Used in tarssh

Unlicense

19KB
318 lines

rusty-sandbox unlicense

rusty-sandbox is, obviously, a sandboxing library for Rust that's not gaol.

It's based on a simple model where you can do the following in the sandbox:

  • any normal computation (not I/O)
  • I/O operations on existing file descriptors (i.e. files and sockets opened before entering the sandbox)
  • accepting connections on an existing socket (which creates new file descriptors)
  • opening files under pre-selected directories though the Sandbox/SandboxContext API (which creates new file descriptors)

All other ways of creating new file descriptors will fail in the sandbox! As well as other potentially dangerous interactions with the outside world such as sysctls, process signals (kill), etc. (platform dependent).

Underlying technology

rusty-sandbox strongly prefers simple sandboxing facilities that don't require any persistent and/or user-visible records (such as chroot directories and bind mounts like gaol does on Linux).

  • FreeBSD: Capsicum, the best-supported sandbox that really inspired the design of this library.
  • OpenBSD: pledge, still without the path whitelist thing unfortunately (opening files under select directories DOES NOT WORK), waiting for 6.4 for that
  • Apple OS X: Seatbelt/sandboxd, which Apple kinda wants to deprecate, in favor of App Store-only stuff I think?
  • Linux: TODO oh fuck. This is going to involve seccomp-bpf. Unfortunately, the openat O_BENEATH behavior proposed on capsicum-linux hasn't been accepted into the Linux kernel!

Usage

You can sandbox the current process:

extern crate rusty_sandbox;
use std::fs;
use std::io::Read;
use rusty_sandbox::Sandbox;

fn main() {
    let mut file = fs::File::open("README.md").unwrap();
    Sandbox::new().sandbox_this_process().expect("Couldn't enter sandbox");
    let mut buf = Vec::new();
    file.read_to_end(&mut buf).unwrap();
    println!("Read file: {}", String::from_utf8_lossy(&buf));
    fs::File::open("README.md").expect("But can't open!");
    // on FreeBSD:
    // thread '<main>' panicked at 'But can't open!: Error { repr: Os { code: 94, message: "Not permitted in capability mode" } }', src/libcore/result.rs:760
}

And here's an example for the forked process & allowed directory support. This silly sandboxed process reads files' first lines:

extern crate rusty_sandbox;
use std::io::{Write, BufRead, BufReader};
use rusty_sandbox::Sandbox;

fn main() {
    let mut process = Sandbox::new()
        .add_directory("repo", ".")
        .sandboxed_fork(|ctx, socket| {
            // This closure runs in a forked sandboxed process!
            let reader = BufReader::new(socket.try_clone().unwrap());
            for line in reader.lines() {
                let line = line.unwrap();
                if line == "" {
                    return;
                }
                // yes, this is an OpenOptions API!
                let file = ctx.directory("repo").unwrap()
                    .open_options().open(line).unwrap();
                socket.write_all(
                    BufReader::new(file).lines().next().unwrap().unwrap().as_bytes()
                ).unwrap();
                socket.write_all(b"\n").unwrap();
            }
        }).expect("Could not start the sandboxed process");
    process.socket.write_all(b"README.md\n").unwrap();
    let reader = BufReader::new(process.socket.try_clone().unwrap());
    println!("Line from the sandboxed process: {}", reader.lines().next().unwrap().unwrap());
    process.socket.write_all(b"\n").unwrap(); // The "stop" message
    process.wait().expect("Sandboxed process finished unsuccessfully");
}

(For a real service, use something like urpc!)

Of course, you can use the directories feature when sandboxing the current process too:

extern crate rusty_sandbox;
use std::io::Read;
use rusty_sandbox::Sandbox;

fn main() {
    let ctx = Sandbox::new()
        .add_directory("repo", ".")
        .sandbox_this_process()
        .unwrap();
    let mut file = ctx.directory("repo").unwrap()
        .open_options().open("README.md").unwrap();
    let mut buf = Vec::new();
    file.read_to_end(&mut buf).unwrap();
    println!("Read file: {}", String::from_utf8_lossy(&buf));
}

Fun fact: an early prototype of this library used a shared memory arena for communicating between processes, like the sandbox for the config parser in sandblast. Turns out it's not practical in any language that's higher level than C, because you can't just tell the language's standard library to allocate on an arena.

Contributing

Please feel free to submit pull requests!

By participating in this project you agree to follow the Contributor Code of Conduct.

The list of contributors is available on GitHub.

License

This is free and unencumbered software released into the public domain.
For more information, please refer to the UNLICENSE file or unlicense.org.

Dependencies

~91KB