cargo fmt

This commit is contained in:
MeiK2333 2021-01-12 11:28:42 +08:00
parent 211050a434
commit 5289506886
7 changed files with 1198 additions and 1195 deletions

View File

@ -8,60 +8,60 @@ use tempfile::tempdir_in;
use std::println as debug; use std::println as debug;
pub struct CGroupOptions { pub struct CGroupOptions {
path: PathBuf, path: PathBuf,
} }
impl CGroupOptions { impl CGroupOptions {
pub fn new(base_path: &str) -> Result<CGroupOptions> { pub fn new(base_path: &str) -> Result<CGroupOptions> {
let pwd = try_io!(tempdir_in(base_path)); let pwd = try_io!(tempdir_in(base_path));
debug!("register cgroup {:?}", pwd); debug!("register cgroup {:?}", pwd);
Ok(CGroupOptions { Ok(CGroupOptions {
path: pwd.path().to_path_buf(), path: pwd.path().to_path_buf(),
}) })
} }
/// 将指定进程加入该 cgroup 组 /// 将指定进程加入该 cgroup 组
pub fn apply(&self, pid: i32) -> Result<()> { pub fn apply(&self, pid: i32) -> Result<()> {
debug!("add {} to cgroup {:?}", pid, self.path); debug!("add {} to cgroup {:?}", pid, self.path);
self.set("cgroup.procs", &format!("{}", pid)) self.set("cgroup.procs", &format!("{}", pid))
} }
/// e.g `set("memory.limit_in_bytes", "67108864")` /// e.g `set("memory.limit_in_bytes", "67108864")`
pub fn set(&self, key: &str, value: &str) -> Result<()> { pub fn set(&self, key: &str, value: &str) -> Result<()> {
debug!("set {} values {}", key, value); debug!("set {} values {}", key, value);
try_io!(fs::write(self.path.join(key), value)); try_io!(fs::write(self.path.join(key), value));
Ok(()) Ok(())
} }
/// e.g `get("memory.max_usage_in_bytes")` /// e.g `get("memory.max_usage_in_bytes")`
pub fn get(&self, key: &str) -> Result<String> { pub fn get(&self, key: &str) -> Result<String> {
Ok(try_io!(read_to_string(self.path.join(key)))) Ok(try_io!(read_to_string(self.path.join(key))))
} }
} }
impl Drop for CGroupOptions { impl Drop for CGroupOptions {
fn drop(&mut self) { fn drop(&mut self) {
debug!("remove cgroup {:?}", self.path); debug!("remove cgroup {:?}", self.path);
remove_dir(&self.path).unwrap(); remove_dir(&self.path).unwrap();
} }
} }
/// cgroup v1 /// cgroup v1
pub struct CGroupSet { pub struct CGroupSet {
// pub cpuset: CGroupOptions, // pub cpuset: CGroupOptions,
pub memory: CGroupOptions, pub memory: CGroupOptions,
} }
impl CGroupSet { impl CGroupSet {
pub fn new() -> Result<CGroupSet> { pub fn new() -> Result<CGroupSet> {
Ok(CGroupSet { Ok(CGroupSet {
// cpuset: CGroupOptions::new("/sys/fs/cgroup/cpuset")?, // cpuset: CGroupOptions::new("/sys/fs/cgroup/cpuset")?,
memory: CGroupOptions::new("/sys/fs/cgroup/memory")?, memory: CGroupOptions::new("/sys/fs/cgroup/memory")?,
}) })
} }
pub fn apply(&self, pid: i32) -> Result<()> { pub fn apply(&self, pid: i32) -> Result<()> {
// self.cpuset.apply(pid)?; // self.cpuset.apply(pid)?;
self.memory.apply(pid)?; self.memory.apply(pid)?;
Ok(()) Ok(())
} }
} }

View File

@ -10,10 +10,10 @@ use std::result;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
IOError(io::Error), IOError(io::Error),
StringToCStringError(NulError), StringToCStringError(NulError),
ParseIntError(std::num::ParseIntError), ParseIntError(std::num::ParseIntError),
CreateTempDirError(io::Error), CreateTempDirError(io::Error),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
@ -21,30 +21,30 @@ pub type Result<T> = result::Result<T, Error>;
// 创建一个简单的包装 // 创建一个简单的包装
#[macro_export] #[macro_export]
macro_rules! try_io { macro_rules! try_io {
($expression:expr) => { ($expression:expr) => {
match $expression { match $expression {
Ok(val) => val, Ok(val) => val,
Err(e) => return Err(Error::IOError(e)), Err(e) => return Err(Error::IOError(e)),
};
}; };
};
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Error::IOError(ref e) => write!(f, "IOError: {}", errno_str(e.raw_os_error())), Error::IOError(ref e) => write!(f, "IOError: {}", errno_str(e.raw_os_error())),
_ => write!(f, "{:?}", self), _ => write!(f, "{:?}", self),
}
} }
}
} }
pub fn errno_str(errno: Option<i32>) -> String { pub fn errno_str(errno: Option<i32>) -> String {
match errno { match errno {
Some(no) => { Some(no) => {
let stre = unsafe { strerror(no) }; let stre = unsafe { strerror(no) };
let c_str: &CStr = unsafe { CStr::from_ptr(stre) }; let c_str: &CStr = unsafe { CStr::from_ptr(stre) };
c_str.to_str().unwrap().to_string() c_str.to_str().unwrap().to_string()
}
_ => "Unknown Error!".to_string(),
} }
_ => "Unknown Error!".to_string(),
}
} }

View File

@ -7,46 +7,46 @@ use std::ptr;
use std::println as debug; use std::println as debug;
pub struct ExecArgs { pub struct ExecArgs {
pub pathname: *const libc::c_char, pub pathname: *const libc::c_char,
pub argv: *const *const libc::c_char, pub argv: *const *const libc::c_char,
} }
impl ExecArgs { impl ExecArgs {
pub fn build(cmd: &String) -> Result<ExecArgs> { pub fn build(cmd: &String) -> Result<ExecArgs> {
let cmds: Vec<&str> = cmd.split_whitespace().collect(); let cmds: Vec<&str> = cmd.split_whitespace().collect();
let pathname = cmds[0].clone(); let pathname = cmds[0].clone();
let pathname_str = match CString::new(pathname) { let pathname_str = match CString::new(pathname) {
Ok(value) => value, Ok(value) => value,
Err(err) => return Err(Error::StringToCStringError(err)), Err(err) => return Err(Error::StringToCStringError(err)),
}; };
let pathname = pathname_str.as_ptr(); let pathname = pathname_str.as_ptr();
let mut argv_vec: Vec<*const libc::c_char> = vec![]; let mut argv_vec: Vec<*const libc::c_char> = vec![];
for item in cmds.iter() { for item in cmds.iter() {
let cstr = match CString::new(item.clone()) { let cstr = match CString::new(item.clone()) {
Ok(value) => value, Ok(value) => value,
Err(err) => return Err(Error::StringToCStringError(err)), Err(err) => return Err(Error::StringToCStringError(err)),
}; };
let cptr = cstr.as_ptr(); let cptr = cstr.as_ptr();
// 需要使用 mem::forget 来标记 // 需要使用 mem::forget 来标记
// 否则在此次循环结束后cstr 就会被回收,后续 exec 函数无法通过指针获取到字符串内容 // 否则在此次循环结束后cstr 就会被回收,后续 exec 函数无法通过指针获取到字符串内容
mem::forget(cstr); mem::forget(cstr);
argv_vec.push(cptr); argv_vec.push(cptr);
}
// argv 与 envp 的参数需要使用 NULL 来标记结束
argv_vec.push(ptr::null());
let argv: *const *const libc::c_char = argv_vec.as_ptr() as *const *const libc::c_char;
mem::forget(pathname_str);
mem::forget(argv_vec);
Ok(ExecArgs { pathname, argv })
} }
// argv 与 envp 的参数需要使用 NULL 来标记结束
argv_vec.push(ptr::null());
let argv: *const *const libc::c_char = argv_vec.as_ptr() as *const *const libc::c_char;
mem::forget(pathname_str);
mem::forget(argv_vec);
Ok(ExecArgs { pathname, argv })
}
} }
impl Drop for ExecArgs { impl Drop for ExecArgs {
fn drop(&mut self) { fn drop(&mut self) {
// TODO: 将不安全的指针类型转换回内置类型,以便由 Rust 自动回收资源 // TODO: 将不安全的指针类型转换回内置类型,以便由 Rust 自动回收资源
// TODO: 优先级较低,因为目前只在子进程里进行这个操作,且操作后会很快 exec操作系统会回收这些内存 // TODO: 优先级较低,因为目前只在子进程里进行这个操作,且操作后会很快 exec操作系统会回收这些内存
debug!("TODO"); debug!("TODO");
} }
} }

1
src/judger.rs Normal file
View File

@ -0,0 +1 @@

View File

@ -18,11 +18,12 @@ mod error;
mod cgroup; mod cgroup;
mod exec_args; mod exec_args;
mod judger;
mod process; mod process;
mod seccomp; mod seccomp;
pub mod river { pub mod river {
tonic::include_proto!("river"); tonic::include_proto!("river");
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -30,76 +31,78 @@ pub struct RiverService {}
#[tonic::async_trait] #[tonic::async_trait]
impl River for RiverService { impl River for RiverService {
type JudgeStream = type JudgeStream =
Pin<Box<dyn Stream<Item = Result<JudgeResponse, Status>> + Send + Sync + 'static>>; Pin<Box<dyn Stream<Item = Result<JudgeResponse, Status>> + Send + Sync + 'static>>;
async fn judge( async fn judge(
&self, &self,
request: Request<tonic::Streaming<JudgeRequest>>, request: Request<tonic::Streaming<JudgeRequest>>,
) -> Result<Response<Self::JudgeStream>, Status> { ) -> Result<Response<Self::JudgeStream>, Status> {
let mut stream = request.into_inner(); let mut stream = request.into_inner();
// 此处编译很慢 // 此处编译很慢
// 为啥,是 try_stream! 这个宏导致的吗? // 为啥,是 try_stream! 这个宏导致的吗?
let output = async_stream::try_stream! { // 同时内部代码无法被 cargo fmt 格式化
while let Some(req) = stream.next().await { // why?
let req = req?; let output = async_stream::try_stream! {
// TODO while let Some(req) = stream.next().await {
let pwd = tempdir_in("/tmp").unwrap(); let req = req?;
match &req.data { // TODO
Some(Data::CompileData(data)) => { let pwd = tempdir_in("/tmp").unwrap();
debug!("compile request"); match &req.data {
let language = &data.language; Some(Data::CompileData(data)) => {
let code = &data.code; debug!("compile request");
debug!("language: {}", language); let language = &data.language;
debug!("code: {}", code); let code = &data.code;
break; debug!("language: {}", language);
}, debug!("code: {}", code);
Some(Data::JudgeData(data)) => { break;
debug!("judge request"); },
let in_file = &data.in_file; Some(Data::JudgeData(data)) => {
let out_file = &data.out_file; debug!("judge request");
let time_limit = &data.time_limit; let in_file = &data.in_file;
let memory_limit = &data.memory_limit; let out_file = &data.out_file;
let judge_type = &data.judge_type; let time_limit = &data.time_limit;
debug!("in_file: {}", in_file); let memory_limit = &data.memory_limit;
debug!("out_file: {}", out_file); let judge_type = &data.judge_type;
debug!("time_limit: {}", time_limit); debug!("in_file: {}", in_file);
debug!("memory_limit: {}", memory_limit); debug!("out_file: {}", out_file);
debug!("judge_type: {}", judge_type); debug!("time_limit: {}", time_limit);
break; debug!("memory_limit: {}", memory_limit);
}, debug!("judge_type: {}", judge_type);
None => break, break;
_ => break, },
None => break,
_ => break,
};
}
yield JudgeResponse {
state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32))
};
}; };
}
yield JudgeResponse {
state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32))
};
};
Ok(Response::new(Box::pin(output) as Self::JudgeStream)) Ok(Response::new(Box::pin(output) as Self::JudgeStream))
} }
} }
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
let env = Env::default() let env = Env::default()
.filter_or("LOG_LEVEL", "debug,h2=info,hyper=info") .filter_or("LOG_LEVEL", "debug,h2=info,hyper=info")
.write_style_or("LOG_STYLE", "always"); .write_style_or("LOG_STYLE", "always");
env_logger::init_from_env(env); env_logger::init_from_env(env);
let addr = "0.0.0.0:4003".parse()?; let addr = "0.0.0.0:4003".parse()?;
let river = RiverService::default(); let river = RiverService::default();
info!("listen on: {}", addr); info!("listen on: {}", addr);
Server::builder() Server::builder()
.concurrency_limit_per_connection(5) .concurrency_limit_per_connection(5)
.add_service(RiverServer::new(river)) .add_service(RiverServer::new(river))
.serve(addr) .serve(addr)
.await?; .await?;
Ok(()) Ok(())
} }

View File

@ -23,233 +23,233 @@ use std::println as debug;
const STACK_SIZE: usize = 1024 * 1024; const STACK_SIZE: usize = 1024 * 1024;
macro_rules! syscall_or_panic { macro_rules! syscall_or_panic {
($expression:expr) => { ($expression:expr) => {
if $expression < 0 { if $expression < 0 {
let err = io::Error::last_os_error().raw_os_error(); let err = io::Error::last_os_error().raw_os_error();
panic!(errno_str(err)); panic!(errno_str(err));
};
}; };
};
} }
macro_rules! c_str_ptr { macro_rules! c_str_ptr {
($expression:expr) => { ($expression:expr) => {
CString::new($expression).unwrap().as_ptr() CString::new($expression).unwrap().as_ptr()
}; };
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Process { pub struct Process {
pub cmd: String, pub cmd: String,
pub time_limit: i32, pub time_limit: i32,
pub memory_limit: i32, pub memory_limit: i32,
pub workdir: PathBuf, pub workdir: PathBuf,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct RunnerStatus { pub struct RunnerStatus {
pub rusage: libc::rusage, pub rusage: libc::rusage,
pub exit_code: i32, pub exit_code: i32,
pub status: i32, pub status: i32,
pub signal: i32, pub signal: i32,
pub time_used: i64, pub time_used: i64,
pub real_time_used: u128, pub real_time_used: u128,
pub memory_used: i64, pub memory_used: i64,
pub cgroup_memory_used: i64, pub cgroup_memory_used: i64,
pub errmsg: String, pub errmsg: String,
} }
impl Process { impl Process {
pub fn new(cmd: String, time_limit: i32, memory_limit: i32, workdir: PathBuf) -> Process { pub fn new(cmd: String, time_limit: i32, memory_limit: i32, workdir: PathBuf) -> Process {
let runner = Process { let runner = Process {
cmd: cmd, cmd: cmd,
time_limit: time_limit, time_limit: time_limit,
memory_limit: memory_limit, memory_limit: memory_limit,
workdir: workdir, workdir: workdir,
}; };
runner runner
} }
} }
struct Runner { struct Runner {
process: Process, process: Process,
pid: i32, pid: i32,
tx: Arc<Mutex<mpsc::Sender<RunnerStatus>>>, tx: Arc<Mutex<mpsc::Sender<RunnerStatus>>>,
rx: Arc<Mutex<mpsc::Receiver<RunnerStatus>>>, rx: Arc<Mutex<mpsc::Receiver<RunnerStatus>>>,
stack: *mut libc::c_void, stack: *mut libc::c_void,
cgroup_set: CGroupSet, cgroup_set: CGroupSet,
} }
impl Runner { impl Runner {
pub fn from(process: Process) -> Result<Runner> { pub fn from(process: Process) -> Result<Runner> {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let stack = unsafe { let stack = unsafe {
libc::mmap( libc::mmap(
ptr::null_mut(), ptr::null_mut(),
STACK_SIZE, STACK_SIZE,
libc::PROT_READ | libc::PROT_WRITE, libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_STACK, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_STACK,
-1, -1,
0, 0,
) )
}; };
Ok(Runner { Ok(Runner {
process: process, process: process,
pid: -1, pid: -1,
tx: Arc::new(Mutex::new(tx)), tx: Arc::new(Mutex::new(tx)),
rx: Arc::new(Mutex::new(rx)), rx: Arc::new(Mutex::new(rx)),
stack: stack, stack: stack,
cgroup_set: CGroupSet::new()?, cgroup_set: CGroupSet::new()?,
}) })
} }
} }
impl Drop for Runner { impl Drop for Runner {
fn drop(&mut self) { fn drop(&mut self) {
debug!("dropping"); debug!("dropping");
unsafe { unsafe {
libc::munmap(self.stack, STACK_SIZE); libc::munmap(self.stack, STACK_SIZE);
let mut status = 0; let mut status = 0;
let pid = libc::waitpid(self.pid, &mut status, libc::WNOHANG); let pid = libc::waitpid(self.pid, &mut status, libc::WNOHANG);
// > 0: 对应子进程退出但未回收资源 // > 0: 对应子进程退出但未回收资源
// = 0: 对应子进程存在但未退出 // = 0: 对应子进程存在但未退出
// 如果在运行过程中上层异常中断,则需要 kill 子进程并回收资源 // 如果在运行过程中上层异常中断,则需要 kill 子进程并回收资源
if pid >= 0 { if pid >= 0 {
libc::kill(self.pid, 9); libc::kill(self.pid, 9);
libc::waitpid(self.pid, &mut status, 0); libc::waitpid(self.pid, &mut status, 0);
} }
}
} }
}
} }
impl Future for Runner { impl Future for Runner {
type Output = Result<RunnerStatus>; type Output = Result<RunnerStatus>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<RunnerStatus>> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<RunnerStatus>> {
let runner = Pin::into_inner(self); let runner = Pin::into_inner(self);
// 如果 pid == -1则说明子进程还没有运行 // 如果 pid == -1则说明子进程还没有运行
if runner.pid == -1 { if runner.pid == -1 {
let waker = cx.waker().clone(); let waker = cx.waker().clone();
let pid = unsafe { let pid = unsafe {
libc::clone( libc::clone(
runit, runit,
(runner.stack as usize + STACK_SIZE) as *mut libc::c_void, (runner.stack as usize + STACK_SIZE) as *mut libc::c_void,
libc::SIGCHLD libc::SIGCHLD
| libc::CLONE_NEWUTS // 设置新的 UTS 名称空间(主机名、网络名等) | libc::CLONE_NEWUTS // 设置新的 UTS 名称空间(主机名、网络名等)
| libc::CLONE_NEWNET // 设置新的网络空间,如果没有配置网络,则该沙盒内部将无法联网 | libc::CLONE_NEWNET // 设置新的网络空间,如果没有配置网络,则该沙盒内部将无法联网
| libc::CLONE_NEWNS // 为沙盒内部设置新的 namespaces 空间 | libc::CLONE_NEWNS // 为沙盒内部设置新的 namespaces 空间
| libc::CLONE_NEWIPC // IPC 隔离 | libc::CLONE_NEWIPC // IPC 隔离
| libc::CLONE_NEWCGROUP // 在新的 CGROUP 中创建沙盒 | libc::CLONE_NEWCGROUP // 在新的 CGROUP 中创建沙盒
| libc::CLONE_NEWPID, // 外部进程对沙盒不可见 | libc::CLONE_NEWPID, // 外部进程对沙盒不可见
&mut runner.process as *mut _ as *mut libc::c_void, &mut runner.process as *mut _ as *mut libc::c_void,
) )
}; };
debug!("pid = {}", pid); debug!("pid = {}", pid);
runner.pid = pid; runner.pid = pid;
// 设置 cgroup 限制 // 设置 cgroup 限制
// 此处为父进程做的策略,所以没有与子进程的安全策略放一块 // 此处为父进程做的策略,所以没有与子进程的安全策略放一块
runner.cgroup_set.apply(pid).unwrap(); runner.cgroup_set.apply(pid).unwrap();
runner.cgroup_set.memory.set( runner.cgroup_set.memory.set(
"memory.limit_in_bytes", "memory.limit_in_bytes",
&format!("{}", runner.process.memory_limit as i64 * 1024), &format!("{}", runner.process.memory_limit as i64 * 1024),
)?; )?;
let tx = runner.tx.clone(); let tx = runner.tx.clone();
// 因为 wait 会阻塞等待结果,因此此处使用 thread::spawn 来 wait以防止主流程被阻塞 // 因为 wait 会阻塞等待结果,因此此处使用 thread::spawn 来 wait以防止主流程被阻塞
thread::spawn(move || { thread::spawn(move || {
let status = Runner::waitpid(pid); let status = Runner::waitpid(pid);
// 子流程运行结束后通知主流程 // 子流程运行结束后通知主流程
let status_tx = tx.lock().unwrap(); let status_tx = tx.lock().unwrap();
status_tx.send(status).unwrap(); status_tx.send(status).unwrap();
waker.wake(); waker.wake();
}); });
return Poll::Pending; return Poll::Pending;
} else { } else {
let mut status = runner.rx.lock().unwrap().recv().unwrap(); let mut status = runner.rx.lock().unwrap().recv().unwrap();
let mem_used = match runner let mem_used = match runner
.cgroup_set .cgroup_set
.memory .memory
.get("memory.max_usage_in_bytes")? .get("memory.max_usage_in_bytes")?
.trim() .trim()
.parse::<i64>() .parse::<i64>()
{ {
Ok(val) => val, Ok(val) => val,
Err(e) => return Poll::Ready(Err(Error::ParseIntError(e))), Err(e) => return Poll::Ready(Err(Error::ParseIntError(e))),
}; };
status.cgroup_memory_used = mem_used / 1024; status.cgroup_memory_used = mem_used / 1024;
debug!("cpu time used: {} ms", status.time_used); debug!("cpu time used: {} ms", status.time_used);
debug!("real time used: {} ms", status.real_time_used); debug!("real time used: {} ms", status.real_time_used);
debug!("cgroup memory used: {} KiB", status.cgroup_memory_used); debug!("cgroup memory used: {} KiB", status.cgroup_memory_used);
debug!("rusage memory used: {} KiB", status.memory_used); debug!("rusage memory used: {} KiB", status.memory_used);
return Poll::Ready(Ok(status)); return Poll::Ready(Ok(status));
}
} }
}
} }
impl Runner { impl Runner {
fn waitpid(pid: i32) -> RunnerStatus { fn waitpid(pid: i32) -> RunnerStatus {
let mut status: i32 = 0; let mut status: i32 = 0;
let mut rusage = new_rusage(); let mut rusage = new_rusage();
let start = SystemTime::now(); let start = SystemTime::now();
unsafe { unsafe {
if libc::wait4(pid, &mut status, 0, &mut rusage) < 0 { if libc::wait4(pid, &mut status, 0, &mut rusage) < 0 {
return judge_system_error(String::from("wait4 failure!")); return judge_system_error(String::from("wait4 failure!"));
} }
} }
let real_time_used = match start.elapsed() { let real_time_used = match start.elapsed() {
Ok(elapsed) => elapsed.as_millis(), Ok(elapsed) => elapsed.as_millis(),
Err(_) => return judge_system_error(String::from("real time elapsed failure!")), Err(_) => return judge_system_error(String::from("real time elapsed failure!")),
}; };
let mut exit_code = 0; let mut exit_code = 0;
let exited = libc::WIFEXITED(status); let exited = libc::WIFEXITED(status);
if exited { if exited {
exit_code = libc::WEXITSTATUS(status); exit_code = libc::WEXITSTATUS(status);
} }
let signal = if libc::WIFSIGNALED(status) { let signal = if libc::WIFSIGNALED(status) {
libc::WTERMSIG(status) libc::WTERMSIG(status)
} else if libc::WIFSTOPPED(status) { } else if libc::WIFSTOPPED(status) {
libc::WSTOPSIG(status) libc::WSTOPSIG(status)
} else { } else {
0 0
}; };
let time_used = rusage.ru_utime.tv_sec * 1000 let time_used = rusage.ru_utime.tv_sec * 1000
+ i64::from(rusage.ru_utime.tv_usec) / 1000 + i64::from(rusage.ru_utime.tv_usec) / 1000
+ rusage.ru_stime.tv_sec * 1000 + rusage.ru_stime.tv_sec * 1000
+ i64::from(rusage.ru_stime.tv_usec) / 1000; + i64::from(rusage.ru_stime.tv_usec) / 1000;
let memory_used = rusage.ru_maxrss; let memory_used = rusage.ru_maxrss;
RunnerStatus { RunnerStatus {
errmsg: String::from(""), errmsg: String::from(""),
memory_used: memory_used, memory_used: memory_used,
cgroup_memory_used: -1, cgroup_memory_used: -1,
time_used: time_used, time_used: time_used,
real_time_used: real_time_used, real_time_used: real_time_used,
exit_code: exit_code, exit_code: exit_code,
signal: signal, signal: signal,
status: status, status: status,
rusage: rusage, rusage: rusage,
}
} }
}
} }
extern "C" fn runit(process: *mut libc::c_void) -> i32 { extern "C" fn runit(process: *mut libc::c_void) -> i32 {
let process = unsafe { &mut *(process as *mut Process) }; let process = unsafe { &mut *(process as *mut Process) };
debug!("cmd = {}", process.cmd); debug!("cmd = {}", process.cmd);
let exec_args = ExecArgs::build(&process.cmd).unwrap(); let exec_args = ExecArgs::build(&process.cmd).unwrap();
unsafe { unsafe {
security(&process); security(&process);
fd_dup(); fd_dup();
syscall_or_panic!(libc::execve( syscall_or_panic!(libc::execve(
exec_args.pathname, exec_args.pathname,
exec_args.argv, exec_args.argv,
// 因为运行是在隔离的环境内,原有的环境变量已经没啥用了,因此这里直接传 null // 因为运行是在隔离的环境内,原有的环境变量已经没啥用了,因此这里直接传 null
ptr::null_mut() ptr::null_mut()
)); ));
// 理论上并不会到这里,因此如果到这里,直接 kill 掉 // 理论上并不会到这里,因此如果到这里,直接 kill 掉
syscall_or_panic!(libc::kill(libc::getpid(), libc::SIGKILL)); syscall_or_panic!(libc::kill(libc::getpid(), libc::SIGKILL));
} }
0 0
} }
/// 为评测沙盒提供安全保障 /// 为评测沙盒提供安全保障
@ -264,181 +264,181 @@ extern "C" fn runit(process: *mut libc::c_void) -> i32 {
/// - `CLONE_NEWNET` 禁止沙盒内部连接网络 /// - `CLONE_NEWNET` 禁止沙盒内部连接网络
/// - `CLONE_NEWPID` 隔离内外进程空间 /// - `CLONE_NEWPID` 隔离内外进程空间
unsafe fn security(process: &Process) { unsafe fn security(process: &Process) {
// 全局默认权限 755为运行目录设置特权 // 全局默认权限 755为运行目录设置特权
syscall_or_panic!(libc::chmod( syscall_or_panic!(libc::chmod(
c_str_ptr!(process.workdir.to_str().unwrap()), c_str_ptr!(process.workdir.to_str().unwrap()),
0o777, 0o777,
)); ));
// 等同于 mount --make-rprivate / // 等同于 mount --make-rprivate /
// 不将挂载传播到其他空间,以免造成挂载混淆 // 不将挂载传播到其他空间,以免造成挂载混淆
syscall_or_panic!(libc::mount( syscall_or_panic!(libc::mount(
c_str_ptr!(""), c_str_ptr!(""),
c_str_ptr!("/"), c_str_ptr!("/"),
c_str_ptr!(""), c_str_ptr!(""),
libc::MS_PRIVATE | libc::MS_REC, libc::MS_PRIVATE | libc::MS_REC,
ptr::null_mut() ptr::null_mut()
)); ));
// 挂载运行文件夹,除此目录外程序没有其他目录的写权限 // 挂载运行文件夹,除此目录外程序没有其他目录的写权限
syscall_or_panic!(libc::mount( syscall_or_panic!(libc::mount(
c_str_ptr!(process.workdir.to_str().unwrap()), c_str_ptr!(process.workdir.to_str().unwrap()),
c_str_ptr!("runtime/rootfs/tmp"), c_str_ptr!("runtime/rootfs/tmp"),
c_str_ptr!("none"), c_str_ptr!("none"),
libc::MS_BIND | libc::MS_PRIVATE, libc::MS_BIND | libc::MS_PRIVATE,
ptr::null_mut(), ptr::null_mut(),
)); ));
// chdir && chroot隔离文件系统 // chdir && chroot隔离文件系统
syscall_or_panic!(libc::chdir(c_str_ptr!("runtime/rootfs"))); syscall_or_panic!(libc::chdir(c_str_ptr!("runtime/rootfs")));
syscall_or_panic!(libc::chroot(c_str_ptr!("."))); syscall_or_panic!(libc::chroot(c_str_ptr!(".")));
syscall_or_panic!(libc::chdir(c_str_ptr!("/tmp"))); syscall_or_panic!(libc::chdir(c_str_ptr!("/tmp")));
// 设置主机名 // 设置主机名
syscall_or_panic!(libc::sethostname(c_str_ptr!("river"), 5)); syscall_or_panic!(libc::sethostname(c_str_ptr!("river"), 5));
syscall_or_panic!(libc::setdomainname(c_str_ptr!("river"), 5)); syscall_or_panic!(libc::setdomainname(c_str_ptr!("river"), 5));
// 修改用户为 nobody // 修改用户为 nobody
syscall_or_panic!(libc::setgid(65534)); syscall_or_panic!(libc::setgid(65534));
syscall_or_panic!(libc::setuid(65534)); syscall_or_panic!(libc::setuid(65534));
let filter = seccomp::SeccompFilter::new( let filter = seccomp::SeccompFilter::new(
deny_syscalls().into_iter().collect(), deny_syscalls().into_iter().collect(),
seccomp::SeccompAction::Allow, seccomp::SeccompAction::Allow,
) )
.unwrap(); .unwrap();
seccomp::SeccompFilter::apply(filter.try_into().unwrap()).unwrap(); seccomp::SeccompFilter::apply(filter.try_into().unwrap()).unwrap();
} }
/// 重定向 `stdin`、`stdout`、`stderr` /// 重定向 `stdin`、`stdout`、`stderr`
unsafe fn fd_dup() { unsafe fn fd_dup() {
// 重定向文件描述符 // 重定向文件描述符
if Path::new(STDIN_FILENAME).exists() { if Path::new(STDIN_FILENAME).exists() {
dup(STDIN_FILENAME, libc::STDIN_FILENO, libc::O_RDONLY, 0o644); dup(STDIN_FILENAME, libc::STDIN_FILENO, libc::O_RDONLY, 0o644);
} }
if Path::new(STDOUT_FILENAME).exists() { if Path::new(STDOUT_FILENAME).exists() {
remove_file(STDOUT_FILENAME).unwrap(); remove_file(STDOUT_FILENAME).unwrap();
} }
dup( dup(
STDOUT_FILENAME, STDOUT_FILENAME,
libc::STDOUT_FILENO, libc::STDOUT_FILENO,
libc::O_CREAT | libc::O_RDWR, libc::O_CREAT | libc::O_RDWR,
0o644, 0o644,
); );
if Path::new(STDERR_FILENAME).exists() { if Path::new(STDERR_FILENAME).exists() {
remove_file(STDERR_FILENAME).unwrap(); remove_file(STDERR_FILENAME).unwrap();
} }
dup( dup(
STDERR_FILENAME, STDERR_FILENAME,
libc::STDERR_FILENO, libc::STDERR_FILENO,
libc::O_CREAT | libc::O_RDWR, libc::O_CREAT | libc::O_RDWR,
0o644, 0o644,
); );
} }
unsafe fn dup(filename: &str, to: libc::c_int, flag: libc::c_int, mode: libc::c_int) { unsafe fn dup(filename: &str, to: libc::c_int, flag: libc::c_int, mode: libc::c_int) {
let filename_str = CString::new(filename).unwrap(); let filename_str = CString::new(filename).unwrap();
let filename = filename_str.as_ptr(); let filename = filename_str.as_ptr();
let fd = libc::open(filename, flag, mode); let fd = libc::open(filename, flag, mode);
if fd < 0 { if fd < 0 {
let err = io::Error::last_os_error().raw_os_error(); let err = io::Error::last_os_error().raw_os_error();
panic!(errno_str(err)); panic!(errno_str(err));
} }
syscall_or_panic!(libc::dup2(fd, to)); syscall_or_panic!(libc::dup2(fd, to));
} }
/// 阻止危险的系统调用 /// 阻止危险的系统调用
/// ///
/// 参照 Docker 文档 [significant-syscalls-blocked-by-the-default-profile](https://docs.docker.com/engine/security/seccomp/#significant-syscalls-blocked-by-the-default-profile) 一节 /// 参照 Docker 文档 [significant-syscalls-blocked-by-the-default-profile](https://docs.docker.com/engine/security/seccomp/#significant-syscalls-blocked-by-the-default-profile) 一节
fn deny_syscalls() -> Vec<seccomp::SyscallRuleSet> { fn deny_syscalls() -> Vec<seccomp::SyscallRuleSet> {
vec![ vec![
deny_syscall(libc::SYS_acct), deny_syscall(libc::SYS_acct),
deny_syscall(libc::SYS_add_key), deny_syscall(libc::SYS_add_key),
deny_syscall(libc::SYS_bpf), deny_syscall(libc::SYS_bpf),
deny_syscall(libc::SYS_clock_adjtime), deny_syscall(libc::SYS_clock_adjtime),
deny_syscall(libc::SYS_clock_settime), deny_syscall(libc::SYS_clock_settime),
deny_syscall(libc::SYS_create_module), deny_syscall(libc::SYS_create_module),
deny_syscall(libc::SYS_delete_module), deny_syscall(libc::SYS_delete_module),
deny_syscall(libc::SYS_finit_module), deny_syscall(libc::SYS_finit_module),
deny_syscall(libc::SYS_get_kernel_syms), deny_syscall(libc::SYS_get_kernel_syms),
deny_syscall(libc::SYS_get_mempolicy), deny_syscall(libc::SYS_get_mempolicy),
deny_syscall(libc::SYS_init_module), deny_syscall(libc::SYS_init_module),
deny_syscall(libc::SYS_ioperm), deny_syscall(libc::SYS_ioperm),
deny_syscall(libc::SYS_iopl), deny_syscall(libc::SYS_iopl),
deny_syscall(libc::SYS_kcmp), deny_syscall(libc::SYS_kcmp),
deny_syscall(libc::SYS_kexec_file_load), deny_syscall(libc::SYS_kexec_file_load),
deny_syscall(libc::SYS_kexec_load), deny_syscall(libc::SYS_kexec_load),
deny_syscall(libc::SYS_keyctl), deny_syscall(libc::SYS_keyctl),
deny_syscall(libc::SYS_lookup_dcookie), deny_syscall(libc::SYS_lookup_dcookie),
deny_syscall(libc::SYS_mbind), deny_syscall(libc::SYS_mbind),
deny_syscall(libc::SYS_mount), deny_syscall(libc::SYS_mount),
deny_syscall(libc::SYS_move_pages), deny_syscall(libc::SYS_move_pages),
deny_syscall(libc::SYS_name_to_handle_at), deny_syscall(libc::SYS_name_to_handle_at),
deny_syscall(libc::SYS_nfsservctl), deny_syscall(libc::SYS_nfsservctl),
deny_syscall(libc::SYS_open_by_handle_at), deny_syscall(libc::SYS_open_by_handle_at),
deny_syscall(libc::SYS_perf_event_open), deny_syscall(libc::SYS_perf_event_open),
deny_syscall(libc::SYS_personality), deny_syscall(libc::SYS_personality),
deny_syscall(libc::SYS_pivot_root), deny_syscall(libc::SYS_pivot_root),
deny_syscall(libc::SYS_process_vm_readv), deny_syscall(libc::SYS_process_vm_readv),
deny_syscall(libc::SYS_process_vm_writev), deny_syscall(libc::SYS_process_vm_writev),
deny_syscall(libc::SYS_ptrace), deny_syscall(libc::SYS_ptrace),
deny_syscall(libc::SYS_query_module), deny_syscall(libc::SYS_query_module),
deny_syscall(libc::SYS_quotactl), deny_syscall(libc::SYS_quotactl),
deny_syscall(libc::SYS_reboot), deny_syscall(libc::SYS_reboot),
deny_syscall(libc::SYS_request_key), deny_syscall(libc::SYS_request_key),
deny_syscall(libc::SYS_set_mempolicy), deny_syscall(libc::SYS_set_mempolicy),
deny_syscall(libc::SYS_setns), deny_syscall(libc::SYS_setns),
deny_syscall(libc::SYS_settimeofday), deny_syscall(libc::SYS_settimeofday),
deny_syscall(libc::SYS_swapon), deny_syscall(libc::SYS_swapon),
deny_syscall(libc::SYS_swapoff), deny_syscall(libc::SYS_swapoff),
deny_syscall(libc::SYS_sysfs), deny_syscall(libc::SYS_sysfs),
deny_syscall(libc::SYS__sysctl), deny_syscall(libc::SYS__sysctl),
deny_syscall(libc::SYS_umount2), deny_syscall(libc::SYS_umount2),
deny_syscall(libc::SYS_unshare), deny_syscall(libc::SYS_unshare),
deny_syscall(libc::SYS_uselib), deny_syscall(libc::SYS_uselib),
deny_syscall(libc::SYS_userfaultfd), deny_syscall(libc::SYS_userfaultfd),
deny_syscall(libc::SYS_ustat), deny_syscall(libc::SYS_ustat),
] ]
} }
#[inline(always)] #[inline(always)]
fn deny_syscall(syscall_number: i64) -> seccomp::SyscallRuleSet { fn deny_syscall(syscall_number: i64) -> seccomp::SyscallRuleSet {
( (
syscall_number, syscall_number,
vec![seccomp::SeccompRule::new( vec![seccomp::SeccompRule::new(
vec![], vec![],
seccomp::SeccompAction::Kill, seccomp::SeccompAction::Kill,
)], )],
) )
} }
/// 一个全为 `0` 的 `rusage` /// 一个全为 `0` 的 `rusage`
#[inline(always)] #[inline(always)]
fn new_rusage() -> libc::rusage { fn new_rusage() -> libc::rusage {
libc::rusage { libc::rusage {
ru_utime: libc::timeval { ru_utime: libc::timeval {
tv_sec: 0 as libc::time_t, tv_sec: 0 as libc::time_t,
tv_usec: 0 as libc::suseconds_t, tv_usec: 0 as libc::suseconds_t,
}, },
ru_stime: libc::timeval { ru_stime: libc::timeval {
tv_sec: 0 as libc::time_t, tv_sec: 0 as libc::time_t,
tv_usec: 0 as libc::suseconds_t, tv_usec: 0 as libc::suseconds_t,
}, },
ru_maxrss: 0 as libc::c_long, ru_maxrss: 0 as libc::c_long,
ru_ixrss: 0 as libc::c_long, ru_ixrss: 0 as libc::c_long,
ru_idrss: 0 as libc::c_long, ru_idrss: 0 as libc::c_long,
ru_isrss: 0 as libc::c_long, ru_isrss: 0 as libc::c_long,
ru_minflt: 0 as libc::c_long, ru_minflt: 0 as libc::c_long,
ru_majflt: 0 as libc::c_long, ru_majflt: 0 as libc::c_long,
ru_nswap: 0 as libc::c_long, ru_nswap: 0 as libc::c_long,
ru_inblock: 0 as libc::c_long, ru_inblock: 0 as libc::c_long,
ru_oublock: 0 as libc::c_long, ru_oublock: 0 as libc::c_long,
ru_msgsnd: 0 as libc::c_long, ru_msgsnd: 0 as libc::c_long,
ru_msgrcv: 0 as libc::c_long, ru_msgrcv: 0 as libc::c_long,
ru_nsignals: 0 as libc::c_long, ru_nsignals: 0 as libc::c_long,
ru_nvcsw: 0 as libc::c_long, ru_nvcsw: 0 as libc::c_long,
ru_nivcsw: 0 as libc::c_long, ru_nivcsw: 0 as libc::c_long,
} }
} }
/// 由于评测系统本身异常而产生的错误 /// 由于评测系统本身异常而产生的错误
@ -446,65 +446,65 @@ fn new_rusage() -> libc::rusage {
/// 因为正常程序返回 `signal` 不能为负数,因此此处使用负数的 `signal` 标识系统错误 /// 因为正常程序返回 `signal` 不能为负数,因此此处使用负数的 `signal` 标识系统错误
#[inline(always)] #[inline(always)]
fn judge_system_error(errmsg: String) -> RunnerStatus { fn judge_system_error(errmsg: String) -> RunnerStatus {
RunnerStatus { RunnerStatus {
rusage: new_rusage(), rusage: new_rusage(),
exit_code: -1, exit_code: -1,
status: -1, status: -1,
signal: -1, signal: -1,
time_used: -1, time_used: -1,
memory_used: -1, memory_used: -1,
cgroup_memory_used: -1, cgroup_memory_used: -1,
real_time_used: 0, real_time_used: 0,
errmsg: errmsg, errmsg: errmsg,
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use tempfile::tempdir_in; use tempfile::tempdir_in;
#[tokio::test] #[tokio::test]
async fn test_echo() { async fn test_echo() {
let pwd = tempdir_in("/tmp").unwrap(); let pwd = tempdir_in("/tmp").unwrap();
println!("{:?}", pwd); println!("{:?}", pwd);
let process = Process::new( let process = Process::new(
String::from("/bin/echo Hello World!"), String::from("/bin/echo Hello World!"),
1000, 1000,
65535, 65535,
pwd.path().to_path_buf(), pwd.path().to_path_buf(),
); );
let result = Runner::from(process).unwrap().await.unwrap(); let result = Runner::from(process).unwrap().await.unwrap();
assert_eq!(result.exit_code, 0); assert_eq!(result.exit_code, 0);
} }
#[tokio::test] #[tokio::test]
async fn test_sleep() { async fn test_sleep() {
let pwd = tempdir_in("/tmp").unwrap(); let pwd = tempdir_in("/tmp").unwrap();
println!("{:?}", pwd); println!("{:?}", pwd);
let process = Process::new( let process = Process::new(
String::from("/bin/sleep 1"), String::from("/bin/sleep 1"),
2000, 2000,
65535, 65535,
pwd.path().to_path_buf(), pwd.path().to_path_buf(),
); );
let result = Runner::from(process).unwrap().await.unwrap(); let result = Runner::from(process).unwrap().await.unwrap();
assert!(result.real_time_used > 1000); assert!(result.real_time_used > 1000);
assert!(result.real_time_used < 1500); assert!(result.real_time_used < 1500);
} }
#[tokio::test] #[tokio::test]
async fn test_output() { async fn test_output() {
let pwd = tempdir_in("/tmp").unwrap(); let pwd = tempdir_in("/tmp").unwrap();
println!("{:?}", pwd); println!("{:?}", pwd);
let process = Process::new( let process = Process::new(
String::from("/bin/echo Hello World!"), String::from("/bin/echo Hello World!"),
1000, 1000,
65535, 65535,
pwd.path().to_path_buf(), pwd.path().to_path_buf(),
); );
let runner = Runner::from(process).unwrap(); let runner = Runner::from(process).unwrap();
let _ = runner.await.unwrap(); let _ = runner.await.unwrap();
let out = std::fs::read_to_string(pwd.path().join(STDOUT_FILENAME)).unwrap(); let out = std::fs::read_to_string(pwd.path().join(STDOUT_FILENAME)).unwrap();
assert_eq!(out, "Hello World!\n"); assert_eq!(out, "Hello World!\n");
} }
} }

File diff suppressed because it is too large Load Diff