judge_core/run/
sandbox.rs

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    /// Currently close all `stderr` and close `stdin`/`stdout` if redirect is not set
65    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    /// WARNING:   
104    /// Unsafe to use `println!()` (or `unwrap()`) in child process.
105    /// See more in `fork()` document.
106    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            // child process should not return to do things outside `spawn()`
116            Ok(ForkResult::Child) => {
117                // TODO: maybe customed error handler are needed
118                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];