1use crate::error::JudgeCoreError;
2use libc::{c_int, rusage, wait4, WEXITSTATUS, WSTOPPED, WTERMSIG};
3use libseccomp::{ScmpAction, ScmpFilterContext, ScmpSyscall};
4use nix::unistd::dup2;
5use nix::unistd::{fork, ForkResult};
6use nix::{
7 sys::resource::{
8 setrlimit,
9 Resource::{RLIMIT_AS, RLIMIT_CPU, RLIMIT_STACK},
10 },
11 unistd::close,
12};
13use serde_derive::{Deserialize, Serialize};
14use std::io;
15use std::os::unix::io::{AsRawFd, RawFd};
16use std::time::{Duration, Instant};
17
18use super::executor::Executor;
19
20pub struct Sandbox {
21 executor: Executor,
22 rlimit_configs: RlimitConfigs,
23 scmp_filter: ScmpFilterContext,
24 input_redirect: Option<RawFd>,
25 output_redirect: Option<RawFd>,
26 pub child_pid: i32,
27 begin_time: Instant,
28}
29
30impl Sandbox {
31 pub fn new(
32 executor: Executor,
33 rlimit_configs: RlimitConfigs,
34 input_redirect: Option<RawFd>,
35 output_redirect: Option<RawFd>,
36 restricted: bool,
37 ) -> Result<Self, JudgeCoreError> {
38 log::debug!("Create sandbox with restricted={}", restricted);
39 let mut scmp_filter = match restricted {
40 true => ScmpFilterContext::new_filter(ScmpAction::KillProcess)?,
41 false => ScmpFilterContext::new_filter(ScmpAction::Allow)?,
42 };
43 if restricted {
44 let white_list = DEFAULT_SCMP_WHITELIST;
45 for s in white_list.iter() {
46 let syscall = ScmpSyscall::from_name(s)?;
47 scmp_filter.add_rule_exact(ScmpAction::Allow, syscall)?;
48 }
49 }
50
51 let child_pid = -1;
52 let begin_time = Instant::now();
53 Ok(Self {
54 executor,
55 rlimit_configs,
56 scmp_filter,
57 input_redirect,
58 output_redirect,
59 child_pid,
60 begin_time,
61 })
62 }
63
64 fn load_io(&self) -> Result<(), JudgeCoreError> {
66 let stderr_raw_fd = io::stderr().as_raw_fd();
67 close(stderr_raw_fd)?;
68
69 let stdin_raw_fd = io::stdin().as_raw_fd();
70 let stdout_raw_fd = io::stdout().as_raw_fd();
71 if let Some(input_redirect) = self.input_redirect {
72 dup2(input_redirect, stdin_raw_fd)?;
73 } else {
74 close(stdin_raw_fd)?;
75 }
76
77 if let Some(output_redirect) = self.output_redirect {
78 dup2(output_redirect, stdout_raw_fd)?;
79 } else {
80 close(stdout_raw_fd)?;
81 }
82 Ok(())
83 }
84
85 pub fn wait(&self) -> Result<RawRunResultInfo, JudgeCoreError> {
86 let mut status: c_int = 0;
87 let mut usage: rusage = get_default_rusage();
88 unsafe {
89 wait4(self.child_pid, &mut status, WSTOPPED, &mut usage);
90 }
91
92 log::info!("Detected process pid={} exit", self.child_pid);
93
94 Ok(RawRunResultInfo {
95 exit_status: status,
96 exit_signal: WTERMSIG(status),
97 exit_code: WEXITSTATUS(status),
98 real_time_cost: self.begin_time.elapsed(),
99 resource_usage: Rusage::from(usage),
100 })
101 }
102
103 pub fn spawn(&mut self) -> Result<i32, JudgeCoreError> {
107 let now = Instant::now();
108 match unsafe { fork() } {
109 Ok(ForkResult::Parent { child, .. }) => {
110 log::info!("Forked child pid={}", child);
111 self.child_pid = child.as_raw();
112 self.begin_time = now;
113 Ok(child.as_raw())
114 }
115 Ok(ForkResult::Child) => {
117 self.load_io().expect("Failed to load io redirect");
119 self.rlimit_configs
120 .load()
121 .expect("Failed to load rlimit configs");
122 self.scmp_filter
123 .load()
124 .expect("Failed to load seccomp filter");
125
126 self.executor.exec().expect("Failed to exec");
127 unsafe { libc::_exit(0) };
128 }
129 Err(e) => Err(JudgeCoreError::NixErrno(e)),
130 }
131 }
132}
133
134#[derive(Debug, Serialize, Deserialize)]
135pub struct RawRunResultInfo {
136 pub exit_status: c_int,
137 pub exit_signal: c_int,
138 pub exit_code: c_int,
139 pub real_time_cost: Duration,
140 pub resource_usage: Rusage,
141}
142
143#[derive(Debug, Serialize, Deserialize)]
144pub struct Rusage {
145 pub user_time: Duration,
146 pub system_time: Duration,
147 pub max_rss: i64,
148 pub page_faults: i64,
149 pub involuntary_context_switches: i64,
150 pub voluntary_context_switches: i64,
151}
152
153impl From<rusage> for Rusage {
154 fn from(rusage: rusage) -> Self {
155 Self {
156 user_time: Duration::new(
157 rusage.ru_utime.tv_sec as u64,
158 rusage.ru_utime.tv_usec as u32 * 1000,
159 ),
160 system_time: Duration::new(
161 rusage.ru_stime.tv_sec as u64,
162 rusage.ru_stime.tv_usec as u32 * 1000,
163 ),
164 max_rss: rusage.ru_maxrss,
165 page_faults: rusage.ru_majflt,
166 involuntary_context_switches: rusage.ru_nivcsw,
167 voluntary_context_switches: rusage.ru_nvcsw,
168 }
169 }
170}
171
172#[derive(Default, Debug, Clone)]
173pub struct RlimitConfigs {
174 pub stack_limit: Option<(u64, u64)>,
175 pub as_limit: Option<(u64, u64)>,
176 pub cpu_limit: Option<(u64, u64)>,
177 pub nproc_limit: Option<(u64, u64)>,
178 pub fsize_limit: Option<(u64, u64)>,
179}
180
181impl RlimitConfigs {
182 pub fn load(&self) -> Result<(), JudgeCoreError> {
183 if let Some(stack_limit) = self.stack_limit {
184 log::debug!("Set stack limit: {:?}", stack_limit);
185 setrlimit(RLIMIT_STACK, stack_limit.0, stack_limit.1)?;
186 }
187 if let Some(as_limit) = self.as_limit {
188 log::debug!("Set as limit: {:?}", as_limit);
189 setrlimit(RLIMIT_AS, as_limit.0, as_limit.1)?;
190 }
191 if let Some(cpu_limit) = self.cpu_limit {
192 log::debug!("Set cpu limit: {:?}", cpu_limit);
193 setrlimit(RLIMIT_CPU, cpu_limit.0, cpu_limit.1)?;
194 }
195 Ok(())
196 }
197}
198
199pub const SCRIPT_LIMIT_CONFIG: RlimitConfigs = RlimitConfigs {
200 stack_limit: Some((16 * 1024 * 1024, 16 * 1024 * 1024)),
201 as_limit: Some((1024 * 1024 * 1024, 1024 * 1024 * 1024)),
202 cpu_limit: Some((60, 90)),
203 nproc_limit: Some((1, 1)),
204 fsize_limit: Some((1024, 1024)),
205};
206
207fn get_default_rusage() -> rusage {
208 rusage {
209 ru_utime: libc::timeval {
210 tv_sec: 0,
211 tv_usec: 0,
212 },
213 ru_stime: libc::timeval {
214 tv_sec: 0,
215 tv_usec: 0,
216 },
217 ru_maxrss: 0,
218 ru_ixrss: 0,
219 ru_idrss: 0,
220 ru_isrss: 0,
221 ru_minflt: 0,
222 ru_majflt: 0,
223 ru_nswap: 0,
224 ru_inblock: 0,
225 ru_oublock: 0,
226 ru_msgsnd: 0,
227 ru_msgrcv: 0,
228 ru_nsignals: 0,
229 ru_nvcsw: 0,
230 ru_nivcsw: 0,
231 }
232}
233
234const DEFAULT_SCMP_WHITELIST: [&str; 19] = [
235 "read",
236 "fstat",
237 "mmap",
238 "mprotect",
239 "munmap",
240 "uname",
241 "arch_prctl",
242 "brk",
243 "access",
244 "exit_group",
245 "close",
246 "readlink",
247 "sysinfo",
248 "write",
249 "writev",
250 "lseek",
251 "clock_gettime",
252 "pread64",
253 "execve",
254];