Use a custom sandbox to avoid OOM

This commit is contained in:
Tiffany Bennett 2016-10-04 20:22:52 -04:00
parent a37637a49f
commit 6c8b467958
4 changed files with 109 additions and 3 deletions

View file

@ -23,3 +23,5 @@ handlebars = "0.20.0"
handlebars-iron = "0.18.0"
staticfile = "0.3.0"
mount = "0.2.1"
ipc-channel = "0.5.1"
libc = "0.2.14"

View file

@ -10,6 +10,10 @@ extern crate handlebars;
extern crate handlebars_iron;
extern crate staticfile;
extern crate mount;
extern crate ipc_channel;
extern crate libc;
pub mod worker;
use iron::prelude::*;
use iron::status;
@ -21,6 +25,8 @@ use mount::Mount;
use staticfile::Static;
use std::collections::BTreeMap;
use params::{Params, Value};
use std::env;
use worker::eval;
fn root(req: &mut Request) -> IronResult<Response> {
let mut data = BTreeMap::new();
@ -28,7 +34,7 @@ fn root(req: &mut Request) -> IronResult<Response> {
let map = req.get_ref::<Params>().unwrap();
match map.find(&["q"]) {
Some(&Value::String(ref query)) => {
let reply = rink::one_line_sandbox(query);
let reply = eval(query);
data.insert("content".to_owned(), reply);
},
_ => (),
@ -46,12 +52,20 @@ fn api(req: &mut Request) -> IronResult<Response> {
_ => return Ok(Response::with((acao, status::BadRequest))),
};
let reply = rink::one_line_sandbox(query);
let reply = eval(query);
Ok(Response::with((acao, status::Ok, reply)))
}
fn main() {
let mut args = env::args();
args.next();
if args.next().map(|x| x == "--sandbox").unwrap_or(false) {
let server = args.next().unwrap();
let query = args.next().unwrap();
worker::worker(&server, &query);
}
let mut mount = Mount::new();
let mut router = Router::new();

88
web/src/worker.rs Normal file
View file

@ -0,0 +1,88 @@
use libc;
use std::io::Error;
use ipc_channel::ipc::{IpcOneShotServer, IpcSender};
use std::process::{Command, Stdio};
use std::env;
use rink;
use std::os::unix::process::ExitStatusExt;
pub fn worker(server_name: &str, query: &str) -> ! {
let tx = IpcSender::connect(server_name.to_owned()).unwrap();
tx.send("".to_owned()).unwrap();
unsafe {
let limit = libc::rlimit {
// 100 megabytes
rlim_cur: 100_000_000,
rlim_max: 100_000_000,
};
let res = libc::setrlimit(libc::RLIMIT_AS, &limit);
if res == -1 {
panic!("Setrlimit RLIMIT_AS failed: {}", Error::last_os_error())
}
let limit = libc::rlimit {
// 15 seconds
rlim_cur: 15,
rlim_max: 15
};
let res = libc::setrlimit(libc::RLIMIT_CPU, &limit);
if res == -1 {
panic!("Setrlimit RLIMIT_AS failed: {}", Error::last_os_error())
}
}
let mut ctx = rink::load().unwrap();
ctx.short_output = true;
let reply = match rink::one_line(&mut ctx, query) {
Ok(v) => v,
Err(e) => e
};
tx.send(reply).unwrap();
::std::process::exit(0)
}
pub fn eval(query: &str) -> String {
let (server, server_name) = IpcOneShotServer::new().unwrap();
let res = Command::new(env::current_exe().unwrap())
.arg("--sandbox")
.arg(server_name)
.arg(query)
.stdin(Stdio::null())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.env("RUST_BACKTRACE", "1")
.spawn()
.map_err(|x| format!("{}", x));
let child = match res {
Ok(s) => s,
Err(e) => return format!("Failed to run sandbox: {}", e)
};
let (rx, _) = server.accept().unwrap();
match rx.recv() {
Ok(s) => s,
Err(e) => {
let output = match child.wait_with_output() {
Ok(v) => v,
Err(e) => return format!("{}", e)
};
match output.status.signal() {
Some(libc::SIGXCPU) => return format!("Calculation went over time limit"),
_ => ()
};
format!(
"Receiving reply from sandbox failed: {}\n\
Signal: {}\n\
Sandbox stdout: {}\n\
Sandbox stderr: {}",
e,
output.status.signal().map(|x| format!("{}", x)).unwrap_or("None".to_owned()),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)
}
}
}

View file

@ -41,7 +41,9 @@
</nav>
<div class="container">
<pre>
{{content}}
</pre>
</div>
</body>
</html>