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 for fd in 3..1024 {
76 libc::close(fd);
77 }
78 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 if libc::unshare(libc::CLONE_NEWNS) != 0 {
87 panic!(
88 "Failed to unshare namespace: {}",
89 std::io::Error::last_os_error()
90 )
91 }
92 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 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 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 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}