mirror of
https://github.com/MeiK2333/river.git
synced 2025-09-26 22:49:11 +08:00
移除平台依赖代码
This commit is contained in:
parent
d667248050
commit
04566b44fb
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which cargo) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which cargo-clippy) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which cargo-fmt) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which cargo-miri) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which cargo-ndk) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which clippy-driver) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which rls) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which rust-gdb) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which rust-lldb) ${@:1}
|
@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
if [[ " ${@} " =~ " --target " ]]; then
|
||||
$(which rustc) ${@:1}
|
||||
else
|
||||
$(which rustc) ${@:1} --target "x86_64-unknown-linux-gnu"
|
||||
fi
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which rustdoc) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which rustfmt) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
$(which rustup) ${@:1}
|
@ -1,2 +0,0 @@
|
||||
[build]
|
||||
target = "x86_64-unknown-linux-gnu"
|
15
log4rs.yaml
15
log4rs.yaml
@ -5,10 +5,23 @@ appenders:
|
||||
encoder:
|
||||
pattern: "{d(%Y-%m-%d %H:%M:%S)} [{h({l})}] {m}{n}"
|
||||
file:
|
||||
kind: file
|
||||
kind: rolling_file
|
||||
path: "logs/river.log"
|
||||
append: true
|
||||
encoder:
|
||||
pattern: "{d(%Y-%m-%d %H:%M:%S)} [{l}] {m}{n}"
|
||||
policy:
|
||||
kind: compound
|
||||
|
||||
trigger:
|
||||
kind: size
|
||||
limit: 10 mb
|
||||
|
||||
roller:
|
||||
kind: fixed_window
|
||||
pattern: "logs/river.{}.log"
|
||||
count: 5
|
||||
base: 1
|
||||
root:
|
||||
level: info
|
||||
appenders:
|
||||
|
@ -1,60 +0,0 @@
|
||||
use crate::error::{Error, Result};
|
||||
use std::fs;
|
||||
use std::fs::{read_to_string, remove_dir};
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir_in;
|
||||
|
||||
pub struct CGroupOptions {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl CGroupOptions {
|
||||
pub fn new(base_path: &str) -> Result<CGroupOptions> {
|
||||
let pwd = try_io!(tempdir_in(base_path));
|
||||
Ok(CGroupOptions {
|
||||
path: pwd.path().to_path_buf(),
|
||||
})
|
||||
}
|
||||
|
||||
/// 将指定进程加入该 cgroup 组
|
||||
pub fn apply(&self, pid: i32) -> Result<()> {
|
||||
self.set("cgroup.procs", &format!("{}", pid))
|
||||
}
|
||||
|
||||
/// e.g `set("memory.limit_in_bytes", "67108864")`
|
||||
pub fn set(&self, key: &str, value: &str) -> Result<()> {
|
||||
try_io!(fs::write(self.path.join(key), value));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// e.g `get("memory.max_usage_in_bytes")`
|
||||
pub fn get(&self, key: &str) -> Result<String> {
|
||||
Ok(try_io!(read_to_string(self.path.join(key))))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CGroupOptions {
|
||||
fn drop(&mut self) {
|
||||
remove_dir(&self.path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// cgroup v1
|
||||
pub struct CGroupSet {
|
||||
// pub cpuset: CGroupOptions,
|
||||
pub memory: CGroupOptions,
|
||||
}
|
||||
|
||||
impl CGroupSet {
|
||||
pub fn new() -> Result<CGroupSet> {
|
||||
Ok(CGroupSet {
|
||||
// cpuset: CGroupOptions::new("/sys/fs/cgroup/cpuset")?,
|
||||
memory: CGroupOptions::new("/sys/fs/cgroup/memory")?,
|
||||
})
|
||||
}
|
||||
pub fn apply(&self, pid: i32) -> Result<()> {
|
||||
// self.cpuset.apply(pid)?;
|
||||
self.memory.apply(pid)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
use lazy_static::lazy_static;
|
||||
use num_cpus;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use num_cpus;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
pub static STDIN_FILENAME: &str = "stdin.txt";
|
||||
|
@ -1,12 +1,13 @@
|
||||
#![macro_use]
|
||||
|
||||
use libc::strerror;
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::{NulError, OsString};
|
||||
use std::ffi::CStr;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::result;
|
||||
|
||||
use libc::strerror;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
@ -1,86 +0,0 @@
|
||||
use super::error::{Error, Result};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(test)]
|
||||
use std::println as debug;
|
||||
|
||||
pub struct ExecArgs {
|
||||
pub pathname: *const libc::c_char,
|
||||
pub argv: *const *const libc::c_char,
|
||||
pub envp: *const *const libc::c_char,
|
||||
}
|
||||
|
||||
impl ExecArgs {
|
||||
pub fn build(cmd: &String) -> Result<ExecArgs> {
|
||||
let cmds: Vec<&str> = cmd.split_whitespace().collect();
|
||||
let pathname = cmds[0].clone();
|
||||
let pathname_str = match CString::new(pathname) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Err(Error::StringToCStringError(err)),
|
||||
};
|
||||
let pathname = pathname_str.as_ptr();
|
||||
|
||||
let mut argv_vec: Vec<*const libc::c_char> = vec![];
|
||||
for item in cmds.iter() {
|
||||
let cstr = match CString::new(item.clone()) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Err(Error::StringToCStringError(err)),
|
||||
};
|
||||
let cptr = cstr.as_ptr();
|
||||
// 需要使用 mem::forget 来标记
|
||||
// 否则在此次循环结束后,cstr 就会被回收,后续 exec 函数无法通过指针获取到字符串内容
|
||||
mem::forget(cstr);
|
||||
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;
|
||||
|
||||
// env 传递环境变量
|
||||
let mut envs: HashMap<&str, &str> = HashMap::new();
|
||||
envs.insert(
|
||||
"PATH",
|
||||
"/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
);
|
||||
envs.insert("HOME", "/tmp");
|
||||
// envs.insert("GOCACHE", "/tmp/.cache");
|
||||
envs.insert("TERM", "xterm");
|
||||
let mut envp_vec: Vec<*const libc::c_char> = vec![];
|
||||
for (key, value) in envs {
|
||||
let mut key = String::from(key);
|
||||
key.push_str("=");
|
||||
key.push_str(&value);
|
||||
let cstr = match CString::new(key) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Err(Error::StringToCStringError(err)),
|
||||
};
|
||||
let cptr = cstr.as_ptr();
|
||||
// 需要使用 mem::forget 来标记
|
||||
// 否则在此次循环结束后,cstr 就会被回收,后续 exec 函数无法通过指针获取到字符串内容
|
||||
mem::forget(cstr);
|
||||
envp_vec.push(cptr);
|
||||
}
|
||||
envp_vec.push(ptr::null());
|
||||
let envp = envp_vec.as_ptr() as *const *const libc::c_char;
|
||||
|
||||
mem::forget(pathname_str);
|
||||
mem::forget(argv_vec);
|
||||
mem::forget(envp_vec);
|
||||
Ok(ExecArgs {
|
||||
pathname,
|
||||
argv,
|
||||
envp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ExecArgs {
|
||||
fn drop(&mut self) {
|
||||
// TODO: 将不安全的指针类型转换回内置类型,以便由 Rust 自动回收资源
|
||||
// TODO: 优先级较低,因为目前只在子进程里进行这个操作,且操作后会很快 exec,操作系统会回收这些内存
|
||||
debug!("TODO");
|
||||
}
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
use std::path::Path;
|
||||
|
||||
use tokio::fs;
|
||||
use tokio::fs::read_to_string;
|
||||
|
||||
use crate::config::{CONFIG, CPU_SEMAPHORE, STDERR_FILENAME, STDIN_FILENAME, STDOUT_FILENAME};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::process::{Process, Runner};
|
||||
use crate::result::{
|
||||
accepted, compile_error, compile_success, memory_limit_exceeded, runtime_error,
|
||||
standard_result, time_limit_exceeded, wrong_answer,
|
||||
};
|
||||
use crate::river::{JudgeResponse, JudgeResultEnum, JudgeType};
|
||||
use std::path::Path;
|
||||
use tokio::fs;
|
||||
use tokio::fs::read_to_string;
|
||||
|
||||
pub async fn compile(language: &str, code: &str, path: &Path) -> Result<JudgeResponse> {
|
||||
info!("compile: language = `{}`", language);
|
||||
@ -18,34 +19,12 @@ pub async fn compile(language: &str, code: &str, path: &Path) -> Result<JudgeRes
|
||||
};
|
||||
try_io!(fs::write(path.join(&lang.code_file), &code).await);
|
||||
|
||||
let process = Process::new(
|
||||
String::from(&lang.compile_cmd),
|
||||
10000,
|
||||
1024 * 1024,
|
||||
path.to_path_buf(),
|
||||
);
|
||||
let semaphore = CPU_SEMAPHORE.clone();
|
||||
let permit = semaphore.acquire().await;
|
||||
let result = Runner::from(process)?;
|
||||
let status = result.await?;
|
||||
// TODO: run process
|
||||
drop(permit);
|
||||
let mem_used = if status.memory_used < status.cgroup_memory_used {
|
||||
status.memory_used
|
||||
} else {
|
||||
status.cgroup_memory_used
|
||||
};
|
||||
if status.exit_code != 0 || status.signal != 0 {
|
||||
// 合并 stdout 与 stderr 为 errmsg
|
||||
// 因为不同的语言、不同的编译器,错误信息输出到了不同的地方
|
||||
let errmsg = format!(
|
||||
"{}\n{}",
|
||||
try_io!(read_to_string(path.join(STDOUT_FILENAME)).await),
|
||||
try_io!(read_to_string(path.join(STDERR_FILENAME)).await),
|
||||
);
|
||||
return Ok(compile_error(status.time_used, mem_used, &errmsg));
|
||||
} else {
|
||||
return Ok(compile_success(status.time_used, mem_used));
|
||||
}
|
||||
|
||||
Ok(compile_success(0, 0))
|
||||
}
|
||||
|
||||
pub async fn judge(
|
||||
@ -66,60 +45,10 @@ pub async fn judge(
|
||||
Some(val) => val,
|
||||
None => return Err(Error::LanguageNotFound(String::from(language))),
|
||||
};
|
||||
let process = Process::new(
|
||||
String::from(&lang.run_cmd),
|
||||
time_limit,
|
||||
memory_limit,
|
||||
path.to_path_buf(),
|
||||
);
|
||||
// 信号量控制并发
|
||||
let semaphore = CPU_SEMAPHORE.clone();
|
||||
let permit = semaphore.acquire().await;
|
||||
let result = Runner::from(process)?;
|
||||
let status = result.await?;
|
||||
// TODO: run process
|
||||
drop(permit);
|
||||
// 为了更好的体验,此处内存用量取 getrusage 报告与 cgroup 报告中较小的一个
|
||||
let mem_used = if status.memory_used < status.cgroup_memory_used {
|
||||
status.memory_used
|
||||
} else {
|
||||
status.cgroup_memory_used
|
||||
};
|
||||
if status.time_used > time_limit.into()
|
||||
|| status.real_time_used as i64 > (time_limit * 2 + 2).into()
|
||||
{
|
||||
// TLE
|
||||
return Ok(time_limit_exceeded(status.time_used, mem_used));
|
||||
} else if mem_used > memory_limit.into() {
|
||||
// MLE
|
||||
return Ok(memory_limit_exceeded(status.time_used, mem_used));
|
||||
} else if status.signal != 0 {
|
||||
// RE
|
||||
return Ok(runtime_error(
|
||||
status.time_used,
|
||||
mem_used,
|
||||
&format!("Program was interrupted by signal: `{}`", status.signal),
|
||||
));
|
||||
} else if status.exit_code != 0 {
|
||||
// RE
|
||||
// 就算是用户自己返回的非零,也算 RE
|
||||
return Ok(runtime_error(
|
||||
status.time_used,
|
||||
mem_used,
|
||||
&format!("Exceptional program return code: `{}`", status.exit_code),
|
||||
));
|
||||
} else if judge_type == JudgeType::Standard as i32 {
|
||||
// 答案对比
|
||||
let out = try_io!(fs::read(path.join(STDOUT_FILENAME)).await);
|
||||
let ans = try_io!(fs::read(data_dir.join(&out_file)).await);
|
||||
let res = standard_result(&out, &ans)?;
|
||||
if res == JudgeResultEnum::Accepted {
|
||||
return Ok(accepted(status.time_used, mem_used));
|
||||
} else {
|
||||
return Ok(wrong_answer(status.time_used, mem_used));
|
||||
}
|
||||
}
|
||||
Err(Error::CustomError(String::from(format!(
|
||||
"Unknown JudgeType: {}",
|
||||
judge_type
|
||||
))))
|
||||
Ok(accepted(0, 0))
|
||||
}
|
||||
|
26
src/main.rs
26
src/main.rs
@ -2,31 +2,29 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
|
||||
use futures::StreamExt;
|
||||
use futures_core::Stream;
|
||||
use log4rs;
|
||||
use river::judge_request::Data;
|
||||
use river::river_server::{River, RiverServer};
|
||||
use tempfile::tempdir_in;
|
||||
use tokio::fs::read_dir;
|
||||
use tonic::{Request, Response, Status};
|
||||
use tonic::transport::Server;
|
||||
|
||||
use river::{
|
||||
Empty, JudgeRequest, JudgeResponse, JudgeResultEnum, LanguageConfigResponse, LanguageItem,
|
||||
LsCase, LsRequest, LsResponse,
|
||||
};
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use tempfile::tempdir_in;
|
||||
use tokio::fs::read_dir;
|
||||
use tonic::transport::Server;
|
||||
use tonic::{Request, Response, Status};
|
||||
use river::judge_request::Data;
|
||||
use river::river_server::{River, RiverServer};
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
|
||||
mod cgroup;
|
||||
mod exec_args;
|
||||
mod judger;
|
||||
mod process;
|
||||
mod result;
|
||||
mod seccomp;
|
||||
|
||||
pub mod river {
|
||||
tonic::include_proto!("river");
|
||||
@ -38,7 +36,7 @@ pub struct RiverService {}
|
||||
#[tonic::async_trait]
|
||||
impl River for RiverService {
|
||||
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(
|
||||
&self,
|
||||
@ -125,7 +123,7 @@ impl River for RiverService {
|
||||
});
|
||||
}
|
||||
let response = LanguageConfigResponse {
|
||||
languages: languages,
|
||||
languages,
|
||||
};
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
|
592
src/process.rs
592
src/process.rs
@ -1,592 +0,0 @@
|
||||
use crate::cgroup::CGroupSet;
|
||||
use crate::config::{STDERR_FILENAME, STDIN_FILENAME, STDOUT_FILENAME};
|
||||
use crate::error::{errno_str, Error, Result};
|
||||
use crate::exec_args::ExecArgs;
|
||||
use crate::seccomp;
|
||||
use libc;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CString;
|
||||
use std::fs::remove_file;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::task::{Context, Poll};
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
#[cfg(test)]
|
||||
use std::println as debug;
|
||||
|
||||
const STACK_SIZE: usize = 1024 * 1024;
|
||||
|
||||
macro_rules! syscall_or_panic {
|
||||
($expression:expr) => {
|
||||
if $expression < 0 {
|
||||
let err = io::Error::last_os_error().raw_os_error();
|
||||
panic!(errno_str(err));
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! c_str_ptr {
|
||||
($expression:expr) => {
|
||||
CString::new($expression).unwrap().as_ptr()
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Process {
|
||||
pub cmd: String,
|
||||
pub time_limit: i32,
|
||||
pub memory_limit: i32,
|
||||
pub workdir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RunnerStatus {
|
||||
pub rusage: libc::rusage,
|
||||
pub exit_code: i32,
|
||||
pub status: i32,
|
||||
pub signal: i32,
|
||||
pub time_used: i64,
|
||||
pub real_time_used: u128,
|
||||
pub memory_used: i64,
|
||||
pub cgroup_memory_used: i64,
|
||||
pub errmsg: String,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
pub fn new(cmd: String, time_limit: i32, memory_limit: i32, workdir: PathBuf) -> Process {
|
||||
let runner = Process {
|
||||
cmd: cmd,
|
||||
time_limit: time_limit,
|
||||
memory_limit: memory_limit,
|
||||
workdir: workdir,
|
||||
};
|
||||
runner
|
||||
}
|
||||
}
|
||||
|
||||
// 为 clone 的栈所需的指针创建 Send 与 Sync 包装
|
||||
// 因为 Rust 编译器不允许原始的 c_void 在线程间同步
|
||||
// 我们可以创建一个包装来规避这个问题,但前提是我们需要明白我们在做什么
|
||||
struct Stack {
|
||||
stack: *const libc::c_void,
|
||||
}
|
||||
|
||||
unsafe impl Send for Stack {}
|
||||
unsafe impl Sync for Stack {}
|
||||
|
||||
pub struct Runner {
|
||||
process: Process,
|
||||
pid: i32,
|
||||
tx: Arc<Mutex<mpsc::Sender<RunnerStatus>>>,
|
||||
rx: Arc<Mutex<mpsc::Receiver<RunnerStatus>>>,
|
||||
stack: Stack,
|
||||
cgroup_set: CGroupSet,
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
pub fn from(process: Process) -> Result<Runner> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let stack = unsafe {
|
||||
libc::mmap(
|
||||
ptr::null_mut(),
|
||||
STACK_SIZE,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_STACK,
|
||||
-1,
|
||||
0,
|
||||
)
|
||||
};
|
||||
debug!("mmap stack");
|
||||
Ok(Runner {
|
||||
process: process,
|
||||
pid: -1,
|
||||
tx: Arc::new(Mutex::new(tx)),
|
||||
rx: Arc::new(Mutex::new(rx)),
|
||||
stack: Stack { stack: stack },
|
||||
cgroup_set: CGroupSet::new()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn killpid(pid: i32) {
|
||||
let mut status = 0;
|
||||
|
||||
// > 0: 对应子进程退出但未回收资源
|
||||
// = 0: 对应子进程存在但未退出
|
||||
// 如果在运行过程中上层异常中断,则需要 kill 子进程并回收资源
|
||||
if libc::waitpid(pid, &mut status, libc::WNOHANG) >= 0 {
|
||||
libc::kill(pid, 9);
|
||||
libc::waitpid(pid, &mut status, 0);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Runner {
|
||||
fn drop(&mut self) {
|
||||
debug!("dropping");
|
||||
unsafe {
|
||||
libc::munmap(self.stack.stack as *mut libc::c_void, STACK_SIZE);
|
||||
debug!("munmap stack");
|
||||
killpid(self.pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Runner {
|
||||
type Output = Result<RunnerStatus>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<RunnerStatus>> {
|
||||
let runner = Pin::into_inner(self);
|
||||
let time_limit = (runner.process.time_limit / 1000 + 1) as u64 * 2;
|
||||
// 如果 pid == -1,则说明子进程还没有运行
|
||||
if runner.pid == -1 {
|
||||
let waker = cx.waker().clone();
|
||||
let pid = unsafe {
|
||||
libc::clone(
|
||||
runit,
|
||||
(runner.stack.stack as usize + STACK_SIZE) as *mut libc::c_void,
|
||||
libc::SIGCHLD
|
||||
| libc::CLONE_NEWUTS // 设置新的 UTS 名称空间(主机名、网络名等)
|
||||
| libc::CLONE_NEWNET // 设置新的网络空间,如果没有配置网络,则该沙盒内部将无法联网
|
||||
| libc::CLONE_NEWNS // 为沙盒内部设置新的 namespaces 空间
|
||||
| libc::CLONE_NEWIPC // IPC 隔离
|
||||
| libc::CLONE_NEWCGROUP // 在新的 CGROUP 中创建沙盒
|
||||
| libc::CLONE_NEWPID, // 外部进程对沙盒不可见
|
||||
&mut runner.process as *mut _ as *mut libc::c_void,
|
||||
)
|
||||
};
|
||||
debug!("pid = {}", pid);
|
||||
runner.pid = pid;
|
||||
// 设置 cgroup 限制
|
||||
// 此处为父进程做的策略,所以没有与子进程的安全策略放一块
|
||||
runner.cgroup_set.apply(pid).unwrap();
|
||||
// 阻止沙盒内部使用 swap
|
||||
runner.cgroup_set.memory.set(
|
||||
"memory.swappiness",
|
||||
"0",
|
||||
)?;
|
||||
runner.cgroup_set.memory.set(
|
||||
"memory.limit_in_bytes",
|
||||
&format!("{}", runner.process.memory_limit as i64 * 1024 * 10), // 本来应该乘以 1024,此处放宽限制,从而让用户体验更好
|
||||
)?;
|
||||
let tx = runner.tx.clone();
|
||||
|
||||
// 因为 wait 会阻塞等待结果,因此此处使用 thread::spawn 来 wait,以防止主流程被阻塞
|
||||
thread::spawn(move || {
|
||||
// 监控程序超时的任务
|
||||
// 此限制相对宽松(sec * 2 + 2),尽可能在保证系统安全的前提下给评测最好的体验
|
||||
thread::spawn(move || {
|
||||
thread::sleep(Duration::from_secs(time_limit));
|
||||
unsafe {
|
||||
killpid(pid);
|
||||
}
|
||||
});
|
||||
let status = Runner::waitpid(pid);
|
||||
// 子流程运行结束后通知主流程
|
||||
let status_tx = tx.lock().unwrap();
|
||||
status_tx.send(status).unwrap();
|
||||
waker.wake();
|
||||
});
|
||||
return Poll::Pending;
|
||||
} else {
|
||||
let mut status = runner.rx.lock().unwrap().recv().unwrap();
|
||||
let mem_used = match runner
|
||||
.cgroup_set
|
||||
.memory
|
||||
.get("memory.max_usage_in_bytes")?
|
||||
.trim()
|
||||
.parse::<i64>()
|
||||
{
|
||||
Ok(val) => val,
|
||||
Err(e) => return Poll::Ready(Err(Error::ParseIntError(e))),
|
||||
};
|
||||
status.cgroup_memory_used = mem_used / 1024;
|
||||
debug!("cpu time used: {} ms", status.time_used);
|
||||
debug!("real time used: {} ms", status.real_time_used);
|
||||
debug!("cgroup memory used: {} KiB", status.cgroup_memory_used);
|
||||
debug!("rusage memory used: {} KiB", status.memory_used);
|
||||
if status.signal < 0 {
|
||||
return Poll::Ready(Err(Error::SystemError(status.errmsg)));
|
||||
}
|
||||
return Poll::Ready(Ok(status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
fn waitpid(pid: i32) -> RunnerStatus {
|
||||
let mut status: i32 = 0;
|
||||
let mut rusage = new_rusage();
|
||||
let start = SystemTime::now();
|
||||
unsafe {
|
||||
if libc::wait4(pid, &mut status, 0, &mut rusage) < 0 {
|
||||
return judge_system_error(String::from("wait4 failure!"));
|
||||
}
|
||||
}
|
||||
let real_time_used = match start.elapsed() {
|
||||
Ok(elapsed) => elapsed.as_millis(),
|
||||
Err(_) => return judge_system_error(String::from("real time elapsed failure!")),
|
||||
};
|
||||
let mut exit_code = 0;
|
||||
let exited = libc::WIFEXITED(status);
|
||||
if exited {
|
||||
exit_code = libc::WEXITSTATUS(status);
|
||||
}
|
||||
let signal = if libc::WIFSIGNALED(status) {
|
||||
libc::WTERMSIG(status)
|
||||
} else if libc::WIFSTOPPED(status) {
|
||||
libc::WSTOPSIG(status)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let time_used = rusage.ru_utime.tv_sec * 1000
|
||||
+ i64::from(rusage.ru_utime.tv_usec) / 1000
|
||||
+ rusage.ru_stime.tv_sec * 1000
|
||||
+ i64::from(rusage.ru_stime.tv_usec) / 1000;
|
||||
let memory_used = rusage.ru_maxrss;
|
||||
|
||||
RunnerStatus {
|
||||
errmsg: String::from(""),
|
||||
memory_used: memory_used,
|
||||
cgroup_memory_used: -1,
|
||||
time_used: time_used,
|
||||
real_time_used: real_time_used,
|
||||
exit_code: exit_code,
|
||||
signal: signal,
|
||||
status: status,
|
||||
rusage: rusage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn runit(process: *mut libc::c_void) -> i32 {
|
||||
let process = unsafe { &mut *(process as *mut Process) };
|
||||
debug!("cmd = {}", process.cmd);
|
||||
let exec_args = ExecArgs::build(&process.cmd).unwrap();
|
||||
unsafe {
|
||||
security(&process);
|
||||
fd_dup();
|
||||
syscall_or_panic!(libc::execve(
|
||||
exec_args.pathname,
|
||||
exec_args.argv,
|
||||
exec_args.envp
|
||||
));
|
||||
// 理论上并不会到这里,因此如果到这里,直接 kill 掉
|
||||
syscall_or_panic!(libc::kill(libc::getpid(), libc::SIGKILL));
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// 为评测沙盒提供安全保障
|
||||
///
|
||||
/// 包括以下策略:
|
||||
///
|
||||
/// - `mount` 隔离运行目录,安全的将内部数据传递出去
|
||||
/// - `chdir` && `chroot`,将评测沙盒与宿主机的文件系统隔离
|
||||
/// - `sethostname` && `setdomainname`,不暴露真实机器名
|
||||
/// - `setgid` && `setuid`,修改运行用户为低权限的 `nobody`,配合文件权限,防止代码对沙盒内部进行恶意修改
|
||||
/// - `seccomp` 阻止执行危险的系统调用
|
||||
/// - `CLONE_NEWNET` 禁止沙盒内部连接网络
|
||||
/// - `CLONE_NEWPID` 隔离内外进程空间
|
||||
unsafe fn security(process: &Process) {
|
||||
// 全局默认权限 755,为运行目录设置特权
|
||||
syscall_or_panic!(libc::chmod(
|
||||
c_str_ptr!(process.workdir.to_str().unwrap()),
|
||||
0o777,
|
||||
));
|
||||
|
||||
// 等同于 mount --make-rprivate /
|
||||
// 不将挂载传播到其他空间,以免造成挂载混淆
|
||||
syscall_or_panic!(libc::mount(
|
||||
c_str_ptr!(""),
|
||||
c_str_ptr!("/"),
|
||||
c_str_ptr!(""),
|
||||
libc::MS_PRIVATE | libc::MS_REC,
|
||||
ptr::null_mut()
|
||||
));
|
||||
|
||||
// 挂载 /proc 目录,有些语言(比如 rust)依赖此目录
|
||||
syscall_or_panic!(libc::mount(
|
||||
c_str_ptr!("proc"),
|
||||
c_str_ptr!("runtime/rootfs/proc"),
|
||||
c_str_ptr!("proc"),
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
));
|
||||
|
||||
// 挂载运行文件夹,除此目录外程序没有其他目录的写权限
|
||||
syscall_or_panic!(libc::mount(
|
||||
c_str_ptr!(process.workdir.to_str().unwrap()),
|
||||
c_str_ptr!("runtime/rootfs/tmp"),
|
||||
c_str_ptr!("none"),
|
||||
libc::MS_BIND | libc::MS_PRIVATE,
|
||||
ptr::null_mut(),
|
||||
));
|
||||
|
||||
// chdir && chroot,隔离文件系统
|
||||
syscall_or_panic!(libc::chdir(c_str_ptr!("runtime/rootfs")));
|
||||
syscall_or_panic!(libc::chroot(c_str_ptr!(".")));
|
||||
syscall_or_panic!(libc::chdir(c_str_ptr!("/tmp")));
|
||||
|
||||
// 设置主机名
|
||||
syscall_or_panic!(libc::sethostname(c_str_ptr!("river"), 5));
|
||||
syscall_or_panic!(libc::setdomainname(c_str_ptr!("river"), 5));
|
||||
|
||||
// 修改用户为 nobody
|
||||
syscall_or_panic!(libc::setgid(65534));
|
||||
syscall_or_panic!(libc::setuid(65534));
|
||||
|
||||
let filter = seccomp::SeccompFilter::new(
|
||||
deny_syscalls().into_iter().collect(),
|
||||
seccomp::SeccompAction::Allow,
|
||||
)
|
||||
.unwrap();
|
||||
seccomp::SeccompFilter::apply(filter.try_into().unwrap()).unwrap();
|
||||
}
|
||||
|
||||
/// 重定向 `stdin`、`stdout`、`stderr`
|
||||
unsafe fn fd_dup() {
|
||||
// 重定向文件描述符
|
||||
if Path::new(STDIN_FILENAME).exists() {
|
||||
dup(STDIN_FILENAME, libc::STDIN_FILENO, libc::O_RDONLY, 0o644);
|
||||
}
|
||||
if Path::new(STDOUT_FILENAME).exists() {
|
||||
remove_file(STDOUT_FILENAME).unwrap();
|
||||
}
|
||||
dup(
|
||||
STDOUT_FILENAME,
|
||||
libc::STDOUT_FILENO,
|
||||
libc::O_CREAT | libc::O_RDWR,
|
||||
0o644,
|
||||
);
|
||||
if Path::new(STDERR_FILENAME).exists() {
|
||||
remove_file(STDERR_FILENAME).unwrap();
|
||||
}
|
||||
dup(
|
||||
STDERR_FILENAME,
|
||||
libc::STDERR_FILENO,
|
||||
libc::O_CREAT | libc::O_RDWR,
|
||||
0o644,
|
||||
);
|
||||
}
|
||||
|
||||
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 = filename_str.as_ptr();
|
||||
let fd = libc::open(filename, flag, mode);
|
||||
if fd < 0 {
|
||||
let err = io::Error::last_os_error().raw_os_error();
|
||||
panic!(errno_str(err));
|
||||
}
|
||||
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) 一节
|
||||
fn deny_syscalls() -> Vec<seccomp::SyscallRuleSet> {
|
||||
vec![
|
||||
deny_syscall(libc::SYS_acct),
|
||||
deny_syscall(libc::SYS_add_key),
|
||||
deny_syscall(libc::SYS_bpf),
|
||||
deny_syscall(libc::SYS_clock_adjtime),
|
||||
deny_syscall(libc::SYS_clock_settime),
|
||||
deny_syscall(libc::SYS_create_module),
|
||||
deny_syscall(libc::SYS_delete_module),
|
||||
deny_syscall(libc::SYS_finit_module),
|
||||
deny_syscall(libc::SYS_get_kernel_syms),
|
||||
deny_syscall(libc::SYS_get_mempolicy),
|
||||
deny_syscall(libc::SYS_init_module),
|
||||
deny_syscall(libc::SYS_ioperm),
|
||||
deny_syscall(libc::SYS_iopl),
|
||||
deny_syscall(libc::SYS_kcmp),
|
||||
deny_syscall(libc::SYS_kexec_file_load),
|
||||
deny_syscall(libc::SYS_kexec_load),
|
||||
deny_syscall(libc::SYS_keyctl),
|
||||
deny_syscall(libc::SYS_lookup_dcookie),
|
||||
deny_syscall(libc::SYS_mbind),
|
||||
deny_syscall(libc::SYS_mount),
|
||||
deny_syscall(libc::SYS_move_pages),
|
||||
deny_syscall(libc::SYS_name_to_handle_at),
|
||||
deny_syscall(libc::SYS_nfsservctl),
|
||||
deny_syscall(libc::SYS_open_by_handle_at),
|
||||
deny_syscall(libc::SYS_perf_event_open),
|
||||
deny_syscall(libc::SYS_personality),
|
||||
deny_syscall(libc::SYS_pivot_root),
|
||||
deny_syscall(libc::SYS_process_vm_readv),
|
||||
deny_syscall(libc::SYS_process_vm_writev),
|
||||
deny_syscall(libc::SYS_ptrace),
|
||||
deny_syscall(libc::SYS_query_module),
|
||||
deny_syscall(libc::SYS_quotactl),
|
||||
deny_syscall(libc::SYS_reboot),
|
||||
deny_syscall(libc::SYS_request_key),
|
||||
deny_syscall(libc::SYS_set_mempolicy),
|
||||
deny_syscall(libc::SYS_setns),
|
||||
deny_syscall(libc::SYS_settimeofday),
|
||||
deny_syscall(libc::SYS_swapon),
|
||||
deny_syscall(libc::SYS_swapoff),
|
||||
deny_syscall(libc::SYS_sysfs),
|
||||
deny_syscall(libc::SYS__sysctl),
|
||||
deny_syscall(libc::SYS_umount2),
|
||||
deny_syscall(libc::SYS_unshare),
|
||||
deny_syscall(libc::SYS_uselib),
|
||||
deny_syscall(libc::SYS_userfaultfd),
|
||||
deny_syscall(libc::SYS_ustat),
|
||||
]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn deny_syscall(syscall_number: i64) -> seccomp::SyscallRuleSet {
|
||||
(
|
||||
syscall_number,
|
||||
vec![seccomp::SeccompRule::new(
|
||||
vec![],
|
||||
seccomp::SeccompAction::Kill,
|
||||
)],
|
||||
)
|
||||
}
|
||||
|
||||
/// 一个全为 `0` 的 `rusage`
|
||||
#[inline(always)]
|
||||
fn new_rusage() -> libc::rusage {
|
||||
libc::rusage {
|
||||
ru_utime: libc::timeval {
|
||||
tv_sec: 0 as libc::time_t,
|
||||
tv_usec: 0 as libc::suseconds_t,
|
||||
},
|
||||
ru_stime: libc::timeval {
|
||||
tv_sec: 0 as libc::time_t,
|
||||
tv_usec: 0 as libc::suseconds_t,
|
||||
},
|
||||
ru_maxrss: 0 as libc::c_long,
|
||||
ru_ixrss: 0 as libc::c_long,
|
||||
ru_idrss: 0 as libc::c_long,
|
||||
ru_isrss: 0 as libc::c_long,
|
||||
ru_minflt: 0 as libc::c_long,
|
||||
ru_majflt: 0 as libc::c_long,
|
||||
ru_nswap: 0 as libc::c_long,
|
||||
ru_inblock: 0 as libc::c_long,
|
||||
ru_oublock: 0 as libc::c_long,
|
||||
ru_msgsnd: 0 as libc::c_long,
|
||||
ru_msgrcv: 0 as libc::c_long,
|
||||
ru_nsignals: 0 as libc::c_long,
|
||||
ru_nvcsw: 0 as libc::c_long,
|
||||
ru_nivcsw: 0 as libc::c_long,
|
||||
}
|
||||
}
|
||||
|
||||
/// 由于评测系统本身异常而产生的错误
|
||||
///
|
||||
/// 因为正常程序返回 `signal` 不能为负数,因此此处使用负数的 `signal` 标识系统错误
|
||||
#[inline(always)]
|
||||
fn judge_system_error(errmsg: String) -> RunnerStatus {
|
||||
RunnerStatus {
|
||||
rusage: new_rusage(),
|
||||
exit_code: -1,
|
||||
status: -1,
|
||||
signal: -1,
|
||||
time_used: -1,
|
||||
memory_used: -1,
|
||||
cgroup_memory_used: -1,
|
||||
real_time_used: 0,
|
||||
errmsg: errmsg,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::tempdir_in;
|
||||
#[tokio::test]
|
||||
async fn test_echo() {
|
||||
let pwd = tempdir_in("/tmp").unwrap();
|
||||
let process = Process::new(
|
||||
String::from("/bin/echo Hello World!"),
|
||||
1000,
|
||||
65535,
|
||||
pwd.path().to_path_buf(),
|
||||
);
|
||||
let result = Runner::from(process).unwrap().await.unwrap();
|
||||
assert_eq!(result.exit_code, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sleep() {
|
||||
let pwd = tempdir_in("/tmp").unwrap();
|
||||
let process = Process::new(
|
||||
String::from("/bin/sleep 1"),
|
||||
2000,
|
||||
65535,
|
||||
pwd.path().to_path_buf(),
|
||||
);
|
||||
let result = Runner::from(process).unwrap().await.unwrap();
|
||||
assert!(result.real_time_used > 1000);
|
||||
assert!(result.real_time_used < 1500);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_output() {
|
||||
let pwd = tempdir_in("/tmp").unwrap();
|
||||
let process = Process::new(
|
||||
String::from("/bin/echo Hello World!"),
|
||||
1000,
|
||||
65535,
|
||||
pwd.path().to_path_buf(),
|
||||
);
|
||||
let runner = Runner::from(process).unwrap();
|
||||
let _ = runner.await.unwrap();
|
||||
let out = std::fs::read_to_string(pwd.path().join(STDOUT_FILENAME)).unwrap();
|
||||
assert_eq!(out, "Hello World!\n");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn time_limit() {
|
||||
let pwd = tempdir_in("/tmp").unwrap();
|
||||
let process = Process::new(
|
||||
String::from("/bin/sleep 100"),
|
||||
1000,
|
||||
65535,
|
||||
pwd.path().to_path_buf(),
|
||||
);
|
||||
let result = Runner::from(process).unwrap().await.unwrap();
|
||||
assert!(result.time_used < 2000);
|
||||
assert!(result.real_time_used < 5000);
|
||||
assert!(result.real_time_used >= 1000);
|
||||
assert_ne!(result.signal, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ls() {
|
||||
let pwd = tempdir_in("/tmp").unwrap();
|
||||
let process = Process::new(
|
||||
String::from("/bin/ls -lah /dev"),
|
||||
1000,
|
||||
65535,
|
||||
pwd.path().to_path_buf(),
|
||||
);
|
||||
let result = Runner::from(process).unwrap().await.unwrap();
|
||||
assert_eq!(result.signal, 0);
|
||||
let out = std::fs::read_to_string(pwd.path().join(STDOUT_FILENAME)).unwrap();
|
||||
println!("{}", out);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dev_null() {
|
||||
let pwd = tempdir_in("/tmp").unwrap();
|
||||
let process = Process::new(
|
||||
String::from("/bin/cat /dev/null"),
|
||||
1000,
|
||||
65535,
|
||||
pwd.path().to_path_buf(),
|
||||
);
|
||||
let result = Runner::from(process).unwrap().await.unwrap();
|
||||
assert_eq!(result.signal, 0);
|
||||
let out = std::fs::read_to_string(pwd.path().join(STDOUT_FILENAME)).unwrap();
|
||||
println!("{}", out);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use crate::error::Error;
|
||||
use crate::error::Result;
|
||||
use crate::river::judge_response::State;
|
||||
use crate::river::{JudgeResponse, JudgeResult, JudgeResultEnum, JudgeStatus};
|
||||
use crate::river::judge_response::State;
|
||||
|
||||
pub fn system_error(err: Error) -> JudgeResponse {
|
||||
warn!("{}", err);
|
||||
@ -30,8 +30,8 @@ pub fn running() -> JudgeResponse {
|
||||
pub fn compile_error(time_used: i64, memory_used: i64, errmsg: &str) -> JudgeResponse {
|
||||
JudgeResponse {
|
||||
state: Some(State::Result(JudgeResult {
|
||||
time_used: time_used,
|
||||
memory_used: memory_used,
|
||||
time_used,
|
||||
memory_used,
|
||||
result: JudgeResultEnum::CompileError as i32,
|
||||
errmsg: String::from(errmsg),
|
||||
})),
|
||||
@ -61,8 +61,8 @@ pub fn memory_limit_exceeded(time_used: i64, memory_used: i64) -> JudgeResponse
|
||||
pub fn runtime_error(time_used: i64, memory_used: i64, errmsg: &str) -> JudgeResponse {
|
||||
JudgeResponse {
|
||||
state: Some(State::Result(JudgeResult {
|
||||
time_used: time_used,
|
||||
memory_used: memory_used,
|
||||
time_used,
|
||||
memory_used,
|
||||
result: JudgeResultEnum::RuntimeError as i32,
|
||||
errmsg: String::from(errmsg),
|
||||
})),
|
||||
@ -72,8 +72,8 @@ pub fn runtime_error(time_used: i64, memory_used: i64, errmsg: &str) -> JudgeRes
|
||||
fn judge_result(time_used: i64, memory_used: i64, result: JudgeResultEnum) -> JudgeResponse {
|
||||
JudgeResponse {
|
||||
state: Some(State::Result(JudgeResult {
|
||||
time_used: time_used,
|
||||
memory_used: memory_used,
|
||||
time_used,
|
||||
memory_used,
|
||||
result: result as i32,
|
||||
errmsg: String::from(""),
|
||||
})),
|
||||
@ -173,6 +173,7 @@ fn next_line(v: &[u8], offset: usize, len: usize) -> (usize, usize, bool) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test1() {
|
||||
let v: &[u8] = "Hello World!".as_bytes();
|
||||
@ -258,6 +259,7 @@ mod tests {
|
||||
JudgeResultEnum::WrongAnswer
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test9() {
|
||||
let ans: &[u8] = "".as_bytes();
|
||||
|
1170
src/seccomp.rs
1170
src/seccomp.rs
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user