bunpen: implement BUNPEN_DISABLE=1 env var to bypass sandboxing
This commit is contained in:
@@ -10,6 +10,7 @@ export type cli_opts = struct {
|
||||
cmd: []str,
|
||||
// `--bunpen-debug`
|
||||
debug: uint,
|
||||
disable: bool,
|
||||
drop_shell: bool,
|
||||
// `--bunpen-help`
|
||||
help: bool,
|
||||
@@ -80,6 +81,13 @@ export fn usage() void = {
|
||||
export fn parse_args(args: []str) (cli_opts | errors::invalid) = {
|
||||
let parsed = cli_opts { autodetect = void, ... };
|
||||
|
||||
let dis = match (os::getenv("BUNPEN_DISABLE")) {
|
||||
case let d: str => yield d;
|
||||
case void => yield "";
|
||||
};
|
||||
if (dis != "" && dis != "0")
|
||||
parsed.disable = true;
|
||||
|
||||
for (let idx: size = 0; idx < len(args); idx += 1) {
|
||||
let arg = args[idx];
|
||||
let next: nullable *str = null;
|
||||
|
@@ -15,44 +15,61 @@ use rt::ext;
|
||||
export type help = void;
|
||||
|
||||
export type cli_request = struct {
|
||||
// args to invoke the binary with.
|
||||
// `exec_args[0]` is, by convention, the name of the executable,
|
||||
// relevant for multi-call binaries like `busybox`.
|
||||
exec_args: []str,
|
||||
// path to the binary to be exec'd inside the sandbox.
|
||||
// if the user requested `--bunpen-drop-shell`, this will be their shell (e.g. /bin/sh).
|
||||
exec_bin: str,
|
||||
exec_params: exec_params,
|
||||
// what to keep in the restricted environment (paths, network, etc)
|
||||
resources: restrict::resources,
|
||||
};
|
||||
|
||||
export fn ingest_cli_opts(opts: cli_opts) (cli_request | help) = {
|
||||
let req = cli_request { ... };
|
||||
export type exec_params = struct {
|
||||
// args to invoke the binary with.
|
||||
// `args[0]` is, by convention, the name of the executable,
|
||||
// relevant for multi-call binaries like `busybox`.
|
||||
args: []str,
|
||||
// path to the binary to be exec'd inside the sandbox.
|
||||
// if the user requested `--bunpen-drop-shell`, this will be their shell (e.g. /bin/sh), assuming `BUNPEN_DISABLE=1` wasn't specified.
|
||||
bin: str,
|
||||
};
|
||||
|
||||
fn cli_opts_get_exec_params(opts: cli_opts) exec_params = {
|
||||
let params = exec_params { ... };
|
||||
//---- ingest `cmd` ----//
|
||||
if (len(os::args) > 0) {
|
||||
// forward argv0
|
||||
append(req.exec_args, os::args[0]);
|
||||
append(params.args, os::args[0]);
|
||||
};
|
||||
|
||||
let saw_bin_path = false;
|
||||
for (let arg .. opts.cmd) {
|
||||
if (saw_bin_path) {
|
||||
append(req.exec_args, arg);
|
||||
append(params.args, arg);
|
||||
} else {
|
||||
req.exec_bin = arg;
|
||||
params.bin = arg;
|
||||
saw_bin_path = true;
|
||||
};
|
||||
};
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
export fn ingest_cli_opts(opts: cli_opts) (cli_request | exec_params | help) = {
|
||||
let req = cli_request { ... };
|
||||
|
||||
//---- ingest `help` ----//
|
||||
if (opts.help) {
|
||||
return help;
|
||||
};
|
||||
|
||||
//---- ingest opts.cmd ----//
|
||||
req.exec_params = cli_opts_get_exec_params(opts);
|
||||
|
||||
//---- ingest `disable` ----//
|
||||
if (opts.disable)
|
||||
return req.exec_params;
|
||||
|
||||
//---- ingest `caps` ----//
|
||||
req.resources.caps = restrict::cap_array_to_caps(opts.keep_caps);
|
||||
|
||||
|
||||
//---- ingest `home_paths` ----//
|
||||
ingest_paths(&req.resources.paths, opts.home_paths, os::getenv("HOME"));
|
||||
//---- ingest `keep_all_caps` ----//
|
||||
@@ -71,12 +88,12 @@ export fn ingest_cli_opts(opts: cli_opts) (cli_request | help) = {
|
||||
//---- ingest `run_paths` ----//
|
||||
ingest_paths(&req.resources.paths, opts.run_paths, os::getenv("XDG_RUNTIME_DIR"));
|
||||
|
||||
//---- ingest `autodetect` (must be done after exec_args) ----//
|
||||
//---- ingest `autodetect` (must be done after exec_params) ----//
|
||||
match (opts.autodetect) {
|
||||
case let method: autodetect =>
|
||||
// N.B.: skip first arg, since that's the name of the executable and
|
||||
// surely not an argument
|
||||
ingest_paths(&req.resources.paths, req.exec_args[1..], os::getcwd(), true, method);
|
||||
ingest_paths(&req.resources.paths, req.exec_params.args[1..], os::getcwd(), true, method);
|
||||
case void => void;
|
||||
};
|
||||
|
||||
@@ -84,9 +101,9 @@ export fn ingest_cli_opts(opts: cli_opts) (cli_request | help) = {
|
||||
if (opts.drop_shell) {
|
||||
// ignore the original command, and overwrite it with an interactive shell.
|
||||
// TODO: respect the user's `$SHELL`.
|
||||
req.exec_bin = "/bin/sh";
|
||||
delete(req.exec_args[..]);
|
||||
append(req.exec_args, "sh");
|
||||
req.exec_params.bin = "/bin/sh";
|
||||
delete(req.exec_params.args[..]);
|
||||
append(req.exec_params.args, "sh");
|
||||
};
|
||||
|
||||
return req;
|
||||
|
@@ -25,6 +25,25 @@ fn do_exec(path: str, args: []str) (os::exec::error | void) = {
|
||||
// work if you don't care about that:
|
||||
};
|
||||
|
||||
fn prepare_env(req: config::cli_request) config::exec_params = {
|
||||
{
|
||||
let argv_str = strings::join(" ", os::args[1..]...);
|
||||
defer free(argv_str);
|
||||
log::printfln("invoked with: {}", argv_str);
|
||||
};
|
||||
|
||||
// set no_new_privs early. this is a flag which prevents us from gaining privs
|
||||
// via SUID/SGID executables, which we never intend to do.
|
||||
errors::ext::check("no_new_privs", rt::ext::no_new_privs());
|
||||
restrict::namespace_restrict(&req.resources);
|
||||
restrict::capability_restrict(&req.resources);
|
||||
// XXX: landlock prevents other sandboxers like `bwrap` from executing,
|
||||
// because it forbids all future `mount` syscalls. so don't landlock.
|
||||
// restrict::landlock_restrict(&req.resources);
|
||||
|
||||
return req.exec_params;
|
||||
};
|
||||
|
||||
export fn main() void = {
|
||||
// install my logger, but defaulted to no logging.
|
||||
log::tree::install(tree::global);
|
||||
@@ -36,29 +55,17 @@ export fn main() void = {
|
||||
case let other: config::cli_opts => yield other;
|
||||
};
|
||||
|
||||
// configure logging early
|
||||
log::tree::set_level(tree::global, opts.debug);
|
||||
|
||||
{
|
||||
let argv_str = strings::join(" ", os::args[1..]...);
|
||||
defer free(argv_str);
|
||||
log::printfln("invoked with: {}", argv_str);
|
||||
};
|
||||
|
||||
let req = match (config::ingest_cli_opts(opts)) {
|
||||
let exec_params = match (config::ingest_cli_opts(opts)) {
|
||||
case config::help =>
|
||||
config::usage();
|
||||
os::exit(0);
|
||||
case let other: config::cli_request => yield other;
|
||||
case let p: config::exec_params => yield p; // run without sandboxing (BUNPEN_DISABLE=1)
|
||||
case let req: config::cli_request =>
|
||||
// configure logging early
|
||||
log::tree::set_level(tree::global, opts.debug);
|
||||
|
||||
yield prepare_env(req);
|
||||
};
|
||||
|
||||
// set no_new_privs early. this is a flag which prevents us from gaining privs
|
||||
// via SUID/SGID executables, which we never intend to do.
|
||||
errors::ext::check("no_new_privs", rt::ext::no_new_privs());
|
||||
restrict::namespace_restrict(&req.resources);
|
||||
restrict::capability_restrict(&req.resources);
|
||||
// XXX: landlock prevents other sandboxers like `bwrap` from executing,
|
||||
// because it forbids all future `mount` syscalls. so don't landlock.
|
||||
// restrict::landlock_restrict(&req.resources);
|
||||
errors::ext::check("exec <user command>", do_exec(req.exec_bin, req.exec_args));
|
||||
errors::ext::check("exec <user command>", do_exec(exec_params.bin, exec_params.args));
|
||||
};
|
||||
|
Reference in New Issue
Block a user