eval_stack/
exec.rs

1use std::{
2    fs,
3    os::unix::process::CommandExt,
4    path::PathBuf,
5    process::{Command, Stdio},
6    time::Duration,
7};
8
9use anyhow::Result;
10use seccompiler::{
11    BpfProgram, SeccompCmpArgLen, SeccompCmpOp, SeccompCondition, SeccompFilter, SeccompRule,
12};
13
14use crate::{
15    config::{JudgeOptions, TestCase},
16    judge::{Judge, JudgeResult},
17};
18
19pub fn seccomp_filter() -> anyhow::Result<BpfProgram> {
20    Ok(SeccompFilter::new(
21        vec![(
22            libc::SYS_write,
23            vec![SeccompRule::new(vec![
24                SeccompCondition::new(0, SeccompCmpArgLen::Dword, SeccompCmpOp::Ne, 1)?,
25                SeccompCondition::new(0, SeccompCmpArgLen::Dword, SeccompCmpOp::Ne, 2)?,
26            ])?],
27        )]
28        .into_iter()
29        .collect(),
30        seccompiler::SeccompAction::Allow,
31        seccompiler::SeccompAction::KillProcess,
32        seccompiler::TargetArch::x86_64,
33    )?
34    .try_into()?)
35}
36
37pub async fn execute<'a, B, E, I, O>(
38    base: B,
39    exec_path: E,
40    args: Option<&'a [&'a str]>,
41    options: &'a JudgeOptions,
42    case: TestCase<I, O>,
43    output_file: O,
44) -> Result<JudgeResult>
45where
46    B: Into<PathBuf>,
47    E: AsRef<str>,
48    I: Into<PathBuf>,
49    O: Into<PathBuf>,
50{
51    let base_path = base.into();
52    let input_file = case.input_file.into();
53    let output_file = output_file.into();
54    let expected_output_file = case.expected_output_file.into();
55
56    let mut command = Command::new(exec_path.as_ref());
57    if let Some(args) = args {
58        command.args(args);
59    }
60    command
61        .env_clear()
62        .current_dir(base_path)
63        .stdin(Stdio::from(fs::File::open(&input_file)?))
64        .stdout(Stdio::from(fs::File::create(&output_file)?))
65        .stderr(Stdio::piped());
66
67    let no_sys_as_limits = options.no_startup_limits;
68    let memory_limit = options.memory_limit;
69    let time_limit = options.time_limit.as_secs();
70    if !options.unsafe_mode {
71        unsafe {
72            command.pre_exec(move || {
73                use libc::{rlimit, setrlimit};
74                // Close all file descriptors except for stdin, stdout, and stderr
75                for fd in 3..1024 {
76                    libc::close(fd);
77                }
78                // Prevent child from gaining new privileges
79                if libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0 {
80                    panic!(
81                        "Failed to disable grant of additional privileges: {}",
82                        std::io::Error::last_os_error()
83                    )
84                }
85                // Unshare the mount namespace to prevent child from gaining new mounts
86                if libc::unshare(libc::CLONE_NEWNS) != 0 {
87                    panic!(
88                        "Failed to unshare namespace: {}",
89                        std::io::Error::last_os_error()
90                    )
91                }
92                // Set memory limit
93                if !no_sys_as_limits {
94                    let limit = rlimit {
95                        rlim_cur: memory_limit,
96                        rlim_max: memory_limit,
97                    };
98                    if setrlimit(libc::RLIMIT_AS, &limit) != 0 {
99                        panic!(
100                            "Failed to set memory limit: {}",
101                            std::io::Error::last_os_error()
102                        )
103                    }
104                    let filter = seccomp_filter().unwrap();
105                    seccompiler::apply_filter(&filter).unwrap();
106                }
107                // Set process limit
108                let proc_limit = rlimit {
109                    rlim_cur: 0,
110                    rlim_max: 0,
111                };
112                if setrlimit(libc::RLIMIT_NPROC, &proc_limit) != 0 {
113                    return Err(std::io::Error::last_os_error());
114                }
115                // Set CPU time limit
116                let cpu_limit = rlimit {
117                    rlim_cur: time_limit,
118                    rlim_max: time_limit,
119                };
120                if setrlimit(libc::RLIMIT_CPU, &cpu_limit) != 0 {
121                    return Err(std::io::Error::last_os_error());
122                }
123                // Disable core dumps
124                if setrlimit(
125                    libc::RLIMIT_CORE,
126                    &rlimit {
127                        rlim_cur: 0,
128                        rlim_max: 0,
129                    },
130                ) != 0
131                {
132                    return Err(std::io::Error::last_os_error());
133                }
134                Ok(())
135            })
136        };
137    };
138
139    let instant = tokio::time::Instant::now();
140    let child = command.spawn()?;
141
142    let id = child.id();
143
144    Judge {
145        child,
146        id,
147        time_limit: options.time_limit,
148        memory_limit: options.memory_limit,
149        instant,
150        memory_used: 0,
151        time_used: Duration::from_secs(0),
152        stdout_file: output_file,
153        expected_output_file,
154    }
155    .await
156}