From e87a34e3fa96a5f46ca6645333a1cb71233e9079 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Sat, 1 Jun 2024 15:36:43 +0200 Subject: [PATCH] full bubblewrap --- backend/cmd/httpd/main.ha | 52 +++++++++++- backend/cmd/httpd/sandbox.ha | 150 +++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 backend/cmd/httpd/sandbox.ha diff --git a/backend/cmd/httpd/main.ha b/backend/cmd/httpd/main.ha index 2fd4070..74216f5 100644 --- a/backend/cmd/httpd/main.ha +++ b/backend/cmd/httpd/main.ha @@ -149,10 +149,60 @@ export fn handle_exec(buf: *io::stream, serv_req: *http::server_request) void = return; case let body: io::handle => let payload = json::load(body)!; + // defer json::finish(payload); yield payload; }; - json::dump(buf, payload)!; + defer json::finish(payload); + + let payload = match (payload) { + case let obj: json::object => yield obj; + case => + handle_notfound(buf, serv_req); + return; + }; + + let files = match (json::get(&payload, "files")) { + case void => + handle_notfound(buf, serv_req); + return; + case let j: *json::value => yield j; + }; + + let files = match (*files) { + case let o: json::object => yield o; + case => + handle_notfound(buf, serv_req); + return; + }; + + let code = match (json::get(&files, "")) { + case void => + handle_notfound(buf, serv_req); + return; + case let j: *json::value => yield j; + }; + + let code = match (*code) { + case let c: str => yield c; + case => + handle_notfound(buf, serv_req); + return; + }; + + let (stdout, stderr) = run_code(code); + defer free(stderr); + defer free(stdout); + + let obj = json::newobject(); + defer json::finish(obj); + json::set(&obj, "stdout", stdout); + json::set(&obj, "stderr", stderr); + json::set(&obj, "id", "123"); + json::set(&obj, "ok", true); + json::set(&obj, "duration", 0.0); + + json::dump(buf, obj)!; http::response_write( serv_req.socket, diff --git a/backend/cmd/httpd/sandbox.ha b/backend/cmd/httpd/sandbox.ha new file mode 100644 index 0000000..c2e656a --- /dev/null +++ b/backend/cmd/httpd/sandbox.ha @@ -0,0 +1,150 @@ +use fmt; +use io; +use os; +use os::exec; +use strings; +use temp; +use unix; + +const static_args = [ + "--ro-bind", "/usr", "/usr", + "--dir", "/tmp", + "--dir", "/var", + "--symlink", "../tmp", "/var/tmp", + "--proc", "/proc", + "--dev", "/dev", + "--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf", + "--symlink", "usr/lib", "/lib", + "--symlink", "usr/lib64", "/lib64", + "--symlink", "usr/bin", "/bin", + "--symlink", "usr/sbin", "/sbin", + "--unshare-all", + "--die-with-parent", + "--clearenv", + "--setenv", "PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "--setenv", "HAREPATH", "/usr/local/src/hare/stdlib", +]; + +const HARE_COMMAND = "/usr/local/bin/hare"; + +/// Default command timeout in seconds. +const DEFAULT_TIMEOUT = 30; + +export fn run_code(code: str) (str, str) = { + let (uid, gid) = getids(); + + let args = strings::dupall(static_args); + + let app_dir = shared_app_dir(&args); + // defer runs in reverse order. + defer os::rmdirall(app_dir)!; + + home_dir(&args, uid); + + let (passwd_r, group_r) = passwd_files(&args, uid, gid); + defer io::close(passwd_r)!; + defer io::close(group_r)!; + + let code_path = fmt::asprintf("{}/main.ha", app_dir); + defer free(code_path); + let code_fp = os::create(code_path, 0o644)!; + io::writeall(code_fp, strings::toutf8(code))!; + io::close(code_fp)!; + + command(&args, "/app/main.ha"); + + let cmd = exec::cmd("bwrap", args...)!; + + let stdout_pipe = exec::pipe(); + exec::addfile(&cmd, os::stdout_file, stdout_pipe.1); + + let stderr_pipe = exec::pipe(); + exec::addfile(&cmd, os::stderr_file, stderr_pipe.1); + + let proc = exec::start(&cmd)!; + io::close(stdout_pipe.1)!; + io::close(stderr_pipe.1)!; + + let stdout_data = io::drain(stdout_pipe.0)!; + io::close(stdout_pipe.0)!; + + let stderr_data = io::drain(stderr_pipe.0)!; + io::close(stderr_pipe.0)!; + + let status = exec::wait(&proc)!; + + let stdout = strings::fromutf8(stdout_data) as str; + let stderr = strings::fromutf8(stderr_data) as str; + + return (stdout, stderr); +}; + +fn passwd_files(args: *[]str, uid: u32, gid: u32) (io::file, io::file) = { + const passwd = `root:x:0:0:root:/root:/bin/bash +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin`; + const group = `root:x:0: +nogroup:x:65534:`; + + let uid = fmt::asprintf("{}", uid); + // defer free(uid); + + let gid = fmt::asprintf("{}", gid); + // defer free(gid); + + let (passwd_r, passwd_w) = exec::pipe(); + io::writeall(passwd_w, strings::toutf8(passwd))!; + io::close(passwd_w)!; + + let (group_r, group_w) = exec::pipe(); + io::writeall(group_w, strings::toutf8(group))!; + io::close(group_w)!; + + append(args, "--file"); + append(args, fmt::asprintf("{}", passwd_r: int)); + append(args, "/etc/passwd"); + + append(args, "--file"); + append(args, fmt::asprintf("{}", group_r: int)); + append(args, "/etc/group"); + + return (passwd_r, group_r); +}; + +fn home_dir(args: *[]str, uid: u32) void = { + let user_dir = fmt::asprintf("/run/user/{}", uid); + let home_dir = fmt::asprintf("{}/home", user_dir); + + append(args, "--dir"); + append(args, home_dir); + append(args, "--setenv"); + append(args, "HOME"); + append(args, home_dir); + append(args, "--setenv"); + append(args, "XDG_RUNTIME_DIR"); + append(args, user_dir); +}; + +fn shared_app_dir(args: *[]str) str = { + let tmp_app_dir = temp::dir(); + + append(args, "--bind"); + append(args, tmp_app_dir); + append(args, "/app"); + append(args, "--chdir"); + append(args, "/app"); + + return tmp_app_dir; +}; + +fn command(args: *[]str, app_file: str) void = { + append(args, "/usr/bin/timeout"); + let timeout = fmt::asprintf("{}", DEFAULT_TIMEOUT); + append(args, timeout); + append(args, HARE_COMMAND); + append(args, "run"); + append(args, app_file); +}; + +fn getids() (uint, uint) = { + return (unix::getuid(), unix::getgid()); +};