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,
|
cmd: []str,
|
||||||
// `--bunpen-debug`
|
// `--bunpen-debug`
|
||||||
debug: uint,
|
debug: uint,
|
||||||
|
disable: bool,
|
||||||
drop_shell: bool,
|
drop_shell: bool,
|
||||||
// `--bunpen-help`
|
// `--bunpen-help`
|
||||||
help: bool,
|
help: bool,
|
||||||
@@ -80,6 +81,13 @@ export fn usage() void = {
|
|||||||
export fn parse_args(args: []str) (cli_opts | errors::invalid) = {
|
export fn parse_args(args: []str) (cli_opts | errors::invalid) = {
|
||||||
let parsed = cli_opts { autodetect = void, ... };
|
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) {
|
for (let idx: size = 0; idx < len(args); idx += 1) {
|
||||||
let arg = args[idx];
|
let arg = args[idx];
|
||||||
let next: nullable *str = null;
|
let next: nullable *str = null;
|
||||||
|
@@ -15,44 +15,61 @@ use rt::ext;
|
|||||||
export type help = void;
|
export type help = void;
|
||||||
|
|
||||||
export type cli_request = struct {
|
export type cli_request = struct {
|
||||||
// args to invoke the binary with.
|
exec_params: exec_params,
|
||||||
// `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,
|
|
||||||
// what to keep in the restricted environment (paths, network, etc)
|
// what to keep in the restricted environment (paths, network, etc)
|
||||||
resources: restrict::resources,
|
resources: restrict::resources,
|
||||||
};
|
};
|
||||||
|
|
||||||
export fn ingest_cli_opts(opts: cli_opts) (cli_request | help) = {
|
export type exec_params = struct {
|
||||||
let req = cli_request { ... };
|
// 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` ----//
|
//---- ingest `cmd` ----//
|
||||||
if (len(os::args) > 0) {
|
if (len(os::args) > 0) {
|
||||||
// forward argv0
|
// forward argv0
|
||||||
append(req.exec_args, os::args[0]);
|
append(params.args, os::args[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
let saw_bin_path = false;
|
let saw_bin_path = false;
|
||||||
for (let arg .. opts.cmd) {
|
for (let arg .. opts.cmd) {
|
||||||
if (saw_bin_path) {
|
if (saw_bin_path) {
|
||||||
append(req.exec_args, arg);
|
append(params.args, arg);
|
||||||
} else {
|
} else {
|
||||||
req.exec_bin = arg;
|
params.bin = arg;
|
||||||
saw_bin_path = true;
|
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` ----//
|
//---- ingest `help` ----//
|
||||||
if (opts.help) {
|
if (opts.help) {
|
||||||
return 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` ----//
|
//---- ingest `caps` ----//
|
||||||
req.resources.caps = restrict::cap_array_to_caps(opts.keep_caps);
|
req.resources.caps = restrict::cap_array_to_caps(opts.keep_caps);
|
||||||
|
|
||||||
|
|
||||||
//---- ingest `home_paths` ----//
|
//---- ingest `home_paths` ----//
|
||||||
ingest_paths(&req.resources.paths, opts.home_paths, os::getenv("HOME"));
|
ingest_paths(&req.resources.paths, opts.home_paths, os::getenv("HOME"));
|
||||||
//---- ingest `keep_all_caps` ----//
|
//---- ingest `keep_all_caps` ----//
|
||||||
@@ -71,12 +88,12 @@ export fn ingest_cli_opts(opts: cli_opts) (cli_request | help) = {
|
|||||||
//---- ingest `run_paths` ----//
|
//---- ingest `run_paths` ----//
|
||||||
ingest_paths(&req.resources.paths, opts.run_paths, os::getenv("XDG_RUNTIME_DIR"));
|
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) {
|
match (opts.autodetect) {
|
||||||
case let method: autodetect =>
|
case let method: autodetect =>
|
||||||
// N.B.: skip first arg, since that's the name of the executable and
|
// N.B.: skip first arg, since that's the name of the executable and
|
||||||
// surely not an argument
|
// 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;
|
case void => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,9 +101,9 @@ export fn ingest_cli_opts(opts: cli_opts) (cli_request | help) = {
|
|||||||
if (opts.drop_shell) {
|
if (opts.drop_shell) {
|
||||||
// ignore the original command, and overwrite it with an interactive shell.
|
// ignore the original command, and overwrite it with an interactive shell.
|
||||||
// TODO: respect the user's `$SHELL`.
|
// TODO: respect the user's `$SHELL`.
|
||||||
req.exec_bin = "/bin/sh";
|
req.exec_params.bin = "/bin/sh";
|
||||||
delete(req.exec_args[..]);
|
delete(req.exec_params.args[..]);
|
||||||
append(req.exec_args, "sh");
|
append(req.exec_params.args, "sh");
|
||||||
};
|
};
|
||||||
|
|
||||||
return req;
|
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:
|
// 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 = {
|
export fn main() void = {
|
||||||
// install my logger, but defaulted to no logging.
|
// install my logger, but defaulted to no logging.
|
||||||
log::tree::install(tree::global);
|
log::tree::install(tree::global);
|
||||||
@@ -36,29 +55,17 @@ export fn main() void = {
|
|||||||
case let other: config::cli_opts => yield other;
|
case let other: config::cli_opts => yield other;
|
||||||
};
|
};
|
||||||
|
|
||||||
// configure logging early
|
let exec_params = match (config::ingest_cli_opts(opts)) {
|
||||||
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)) {
|
|
||||||
case config::help =>
|
case config::help =>
|
||||||
config::usage();
|
config::usage();
|
||||||
os::exit(0);
|
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
|
errors::ext::check("exec <user command>", do_exec(exec_params.bin, exec_params.args));
|
||||||
// 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));
|
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user