evalbox_sandbox/
workspace.rs1use std::fs;
23use std::io;
24use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
25use std::path::{Path, PathBuf};
26
27use tempfile::TempDir;
28
29#[derive(Debug)]
31pub struct Pipe {
32 pub read: OwnedFd,
33 pub write: OwnedFd,
34}
35
36impl Pipe {
37 pub fn new() -> io::Result<Self> {
38 let mut fds = [0i32; 2];
39 if unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) } != 0 {
41 return Err(io::Error::last_os_error());
42 }
43 Ok(Self {
45 read: unsafe { OwnedFd::from_raw_fd(fds[0]) },
46 write: unsafe { OwnedFd::from_raw_fd(fds[1]) },
47 })
48 }
49
50 #[inline]
51 pub fn read_fd(&self) -> RawFd { self.read.as_raw_fd() }
52
53 #[inline]
54 pub fn write_fd(&self) -> RawFd { self.write.as_raw_fd() }
55}
56
57#[derive(Debug)]
59pub struct SyncPair {
60 pub child_ready: OwnedFd,
61 pub parent_done: OwnedFd,
62}
63
64impl SyncPair {
65 pub fn new() -> io::Result<Self> {
66 let child_ready = unsafe { libc::eventfd(0, 0) };
67 if child_ready < 0 {
68 return Err(io::Error::last_os_error());
69 }
70 let parent_done = unsafe { libc::eventfd(0, 0) };
71 if parent_done < 0 {
72 unsafe { libc::close(child_ready) };
73 return Err(io::Error::last_os_error());
74 }
75 Ok(Self {
76 child_ready: unsafe { OwnedFd::from_raw_fd(child_ready) },
77 parent_done: unsafe { OwnedFd::from_raw_fd(parent_done) },
78 })
79 }
80
81 #[inline]
82 pub fn child_ready_fd(&self) -> RawFd { self.child_ready.as_raw_fd() }
83
84 #[inline]
85 pub fn parent_done_fd(&self) -> RawFd { self.parent_done.as_raw_fd() }
86}
87
88#[derive(Debug)]
90pub struct Pipes {
91 pub stdin: Pipe,
92 pub stdout: Pipe,
93 pub stderr: Pipe,
94 pub sync: SyncPair,
95}
96
97impl Pipes {
98 pub fn new() -> io::Result<Self> {
99 Ok(Self {
100 stdin: Pipe::new()?,
101 stdout: Pipe::new()?,
102 stderr: Pipe::new()?,
103 sync: SyncPair::new()?,
104 })
105 }
106}
107
108#[derive(Debug)]
110pub struct Workspace {
111 root: PathBuf,
112 pub pipes: Pipes,
113 _tempdir: TempDir,
114}
115
116impl Workspace {
117 pub fn new() -> io::Result<Self> {
118 Self::with_prefix("evalbox-")
119 }
120
121 pub fn with_prefix(prefix: &str) -> io::Result<Self> {
122 let tempdir = TempDir::with_prefix(prefix)?;
123 Ok(Self {
124 root: tempdir.path().to_path_buf(),
125 pipes: Pipes::new()?,
126 _tempdir: tempdir,
127 })
128 }
129
130 #[inline]
131 pub fn root(&self) -> &Path { &self.root }
132
133 pub fn write_file(&self, path: &str, content: &[u8], executable: bool) -> io::Result<PathBuf> {
134 use std::os::unix::fs::PermissionsExt;
135
136 let full = self.root.join(path);
137 if let Some(parent) = full.parent() {
138 fs::create_dir_all(parent)?;
139 }
140 fs::write(&full, content)?;
141
142 if executable {
143 fs::set_permissions(&full, fs::Permissions::from_mode(0o755))?;
145 }
146
147 Ok(full)
148 }
149
150 pub fn create_dir(&self, path: &str) -> io::Result<PathBuf> {
151 let full = self.root.join(path);
152 fs::create_dir_all(&full)?;
153 Ok(full)
154 }
155
156 pub fn setup_sandbox_dirs(&self) -> io::Result<()> {
157 for dir in ["proc", "dev", "tmp", "home", "work", "usr", "bin", "lib", "lib64", "etc"] {
158 self.create_dir(dir)?;
159 }
160 self.setup_minimal_etc()?;
161 Ok(())
162 }
163
164 pub fn setup_minimal_etc(&self) -> io::Result<()> {
169 let etc = self.root.join("etc");
170
171 fs::write(
173 etc.join("passwd"),
174 "nobody:x:65534:65534:Unprivileged user:/nonexistent:/usr/sbin/nologin\n",
175 )?;
176
177 fs::write(
179 etc.join("group"),
180 "nogroup:x:65534:\n",
181 )?;
182
183 fs::write(
185 etc.join("hosts"),
186 "127.0.0.1 localhost\n::1 localhost\n",
187 )?;
188
189 fs::write(
191 etc.join("nsswitch.conf"),
192 "passwd: files\ngroup: files\nhosts: files dns\n",
193 )?;
194
195 let host_ldcache = Path::new("/etc/ld.so.cache");
197 if host_ldcache.exists() {
198 if let Ok(content) = fs::read(host_ldcache) {
199 fs::write(etc.join("ld.so.cache"), content)?;
200 }
201 }
202
203 let ssl_dir = etc.join("ssl");
205 fs::create_dir_all(&ssl_dir)?;
206
207 fs::write(etc.join("resolv.conf"), "# DNS disabled in sandbox\n")?;
210
211 Ok(())
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn pipe_creation() {
221 let pipe = Pipe::new().unwrap();
222 assert!(pipe.read_fd() >= 0);
223 assert_ne!(pipe.read_fd(), pipe.write_fd());
224 }
225
226 #[test]
227 fn workspace_creation() {
228 let ws = Workspace::new().unwrap();
229 assert!(ws.root().exists());
230 }
231
232 #[test]
233 fn workspace_write_file() {
234 let ws = Workspace::new().unwrap();
235 let path = ws.write_file("test.txt", b"hello", false).unwrap();
236 assert!(path.exists());
237 }
238
239 #[test]
240 fn workspace_write_executable() {
241 use std::os::unix::fs::PermissionsExt;
242
243 let ws = Workspace::new().unwrap();
244 let path = ws.write_file("binary", b"\x7fELF", true).unwrap();
245 assert!(path.exists());
246 let perms = std::fs::metadata(&path).unwrap().permissions();
247 assert_eq!(perms.mode() & 0o777, 0o755);
248 }
249}