rusty_sandbox/
lib.rs

1extern crate libc;
2extern crate unix_socket;
3
4mod platform;
5pub mod fs;
6
7use std::{io, process, mem};
8use std::collections::BTreeMap;
9use std::path::Path;
10use unix_socket::UnixStream;
11
12pub struct RunningSandbox {
13    pid: libc::c_int,
14    pub socket: UnixStream,
15}
16
17impl RunningSandbox {
18    pub fn wait(self) -> io::Result<RunningSandbox> {
19        let mut status = 0;
20        unsafe { libc::waitpid(self.pid, &mut status, 0) };
21        if status == 0 {
22            Ok(self)
23        } else {
24            Err(io::Error::new(io::ErrorKind::Other, "Sandboxed child process exited with non-zero status"))
25        }
26    }
27}
28
29pub struct SandboxContext {
30    dirs: BTreeMap<String, fs::Directory>,
31}
32
33impl SandboxContext {
34    pub fn directory(&self, key: &str) -> Option<&fs::Directory> {
35        self.dirs.get(key)
36    }
37}
38
39pub struct Sandbox {
40    dirs: BTreeMap<String, fs::Directory>,
41}
42
43impl Sandbox {
44    pub fn new() -> Sandbox {
45        Sandbox {
46            dirs: BTreeMap::new()
47        }
48    }
49
50    pub fn add_directory<P: AsRef<Path>>(&mut self, key: &str, path: P) -> &mut Sandbox {
51        if let Some(dir) = fs::Directory::new(path) {
52            self.dirs.insert(key.to_owned(), dir);
53        }
54        self
55    }
56
57    pub fn sandbox_this_process(&self) -> Result<SandboxContext, ()> {
58        if platform::enter_sandbox(Box::new(self.dirs.values())) {
59            Ok(self.context())
60        } else {
61            Err(())
62        }
63    }
64
65    pub fn sandboxed_fork<F>(&self, fun: F) -> io::Result<RunningSandbox>
66    where F: Fn(&mut SandboxContext, &mut UnixStream) -> () {
67        let (parent_socket, child_socket) = try!(UnixStream::pair());
68
69        let pid = unsafe { libc::fork() };
70        if pid < 0 {
71            Err(io::Error::new(io::ErrorKind::Other, "fork() failed"))
72        } else if pid > 0 { // parent
73            // child_socket is dropped by going out of scope
74            Ok(RunningSandbox {
75                pid: pid,
76                socket: parent_socket,
77            })
78        } else { // child
79            mem::drop(parent_socket);
80            platform::enter_sandbox(Box::new(self.dirs.values()));
81            let mut socket = child_socket;
82            fun(&mut self.context(), &mut socket);
83            process::exit(0);
84        }
85    }
86
87    fn context(&self) -> SandboxContext {
88        SandboxContext {
89            dirs: self.dirs.to_owned(),
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use std::net;
98    use std::fs::File;
99    use std::io::{Read, Write, BufRead, BufReader};
100
101    #[test]
102    fn test_socket() {
103        let mut process = Sandbox::new()
104            .sandboxed_fork(|_, socket| {
105                let msg = BufReader::new(socket.try_clone().unwrap()).lines().next().unwrap().unwrap() + " > from sandbox\n";
106                socket.write_all(msg.as_bytes()).unwrap();
107                socket.flush().unwrap();
108            })
109            .unwrap();
110        process.socket.write_all(b"from parent\n").unwrap();
111        process.socket.flush().unwrap();
112        let msg = BufReader::new(process.socket.try_clone().unwrap()).lines().next().unwrap().unwrap();
113        assert_eq!(msg, "from parent > from sandbox");
114        process.wait().unwrap();
115    }
116
117    #[test]
118    fn test_preopened_file() {
119        let mut f = File::open("UNLICENSE").unwrap();
120        let mut process = Sandbox::new()
121            .sandboxed_fork(|_, socket| {
122                let mut buf = String::new();
123                BufReader::new(&f).read_line(&mut buf).unwrap();
124                let msg = buf.replace("\n", "") + " from sandbox\n";
125                socket.write_all(msg.as_bytes()).unwrap();
126                socket.flush().unwrap();
127            })
128            .unwrap();
129        let msg = BufReader::new(process.socket.try_clone().unwrap()).lines().next().unwrap().unwrap();
130        assert_eq!(msg, "This is free and unencumbered software released into the public domain. from sandbox");
131        process.wait().unwrap();
132    }
133
134    #[cfg(not(target_os = "openbsd"))]
135    #[test]
136    fn test_directory() {
137        Sandbox::new()
138            .add_directory("temp", "/tmp")
139            .sandboxed_fork(|ctx, _| {
140                let mut file = ctx.directory("temp").unwrap()
141                    .open_options().write(true).create(true)
142                    .open("hello_rusty_sandbox").unwrap();
143                file.write_all(b"Hello World").unwrap();
144            }).unwrap().wait().unwrap();
145        let mut buf = Vec::new();
146        let mut f = File::open("/tmp/hello_rusty_sandbox").unwrap();
147        f.read_to_end(&mut buf).unwrap();
148        assert_eq!(&buf[..], b"Hello World");
149    }
150
151    #[cfg(not(target_os = "openbsd"))]
152    #[test]
153    fn test_forbidden_file() {
154        let process = Sandbox::new()
155            .sandboxed_fork(|_, socket| {
156                socket.write_all(match File::open("README.md") {
157                    Ok(_) => b"ok",
158                    Err(_) => b"err",
159                }).unwrap();
160                socket.flush().unwrap();
161            })
162            .unwrap();
163        let msg = BufReader::new(process.socket.try_clone().unwrap()).lines().next().unwrap().unwrap();
164        assert_eq!(msg, "err");
165        process.wait().unwrap();
166    }
167
168    #[cfg(not(target_os = "openbsd"))]
169    #[test]
170    fn test_forbidden_socket() {
171        let process = Sandbox::new()
172            .sandboxed_fork(|_, socket| {
173                socket.write_all(match net::TcpStream::connect("8.8.8.8:53") { // yes it's available on tcp too
174                    Ok(_) => b"ok",
175                    Err(_) => b"err",
176                }).unwrap();
177                socket.flush().unwrap();
178            })
179            .unwrap();
180        let msg = BufReader::new(process.socket.try_clone().unwrap()).lines().next().unwrap().unwrap();
181        assert_eq!(msg, "err");
182        process.wait().unwrap();
183    }
184
185}