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 { Ok(RunningSandbox {
75 pid: pid,
76 socket: parent_socket,
77 })
78 } else { 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") { 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}