WIP: bunpen: refactor to facilitate future work of placing pasta and user program in mutually distinct PID namespaces
TODO: fix pasta, e.g. > PATH=/nix/store/ylld0m96sqf497vs2g7ca8nw9x1q4ycm-bunpen-0.1.0/bin:$PATH gnome-calls --bunpen-drop-shell --bunpen-debug=3 for now the user program is in a sub-pidspace of pasta. moving pasta to its own PID namespace is slightly more involved than expected, because one can't (reliably) unshare PID NS more than once.
This commit is contained in:
@@ -8,7 +8,7 @@ use log;
|
||||
use os;
|
||||
use path;
|
||||
use regex;
|
||||
use restrict;
|
||||
use resources;
|
||||
use rt;
|
||||
use rt::ext;
|
||||
use strings;
|
||||
@@ -19,7 +19,7 @@ export type help = void;
|
||||
export type cli_request = struct {
|
||||
exec_params: exec_params,
|
||||
// what to keep in the restricted environment (paths, network, etc)
|
||||
resources: restrict::resources,
|
||||
resources: resources::resources,
|
||||
};
|
||||
|
||||
export type exec_params = struct {
|
||||
@@ -96,7 +96,7 @@ export fn ingest_cli_opts(opts: cli_opts) (cli_request | exec_params | help) = {
|
||||
|
||||
let env = env_new();
|
||||
let req = cli_request {
|
||||
resources = restrict::resources { net = restrict::net_none, ... },
|
||||
resources = resources::resources { net = resources::net_none, ... },
|
||||
...
|
||||
};
|
||||
|
||||
@@ -120,7 +120,7 @@ export fn ingest_cli_opts(opts: cli_opts) (cli_request | exec_params | help) = {
|
||||
};
|
||||
|
||||
//---- ingest `caps` ----//
|
||||
req.resources.caps = restrict::cap_array_to_caps(opts.keep_caps);
|
||||
req.resources.caps = resources::cap_array_to_caps(opts.keep_caps);
|
||||
|
||||
//---- ingest `keep_all_caps` ----//
|
||||
if (opts.keep_all_caps)
|
||||
@@ -130,7 +130,7 @@ export fn ingest_cli_opts(opts: cli_opts) (cli_request | exec_params | help) = {
|
||||
req.resources.ipc = opts.keep_ipc;
|
||||
|
||||
//---- ingest `net_dev`, `net_gateway`, `dns` ----//
|
||||
let net_subset = restrict::net_subset {
|
||||
let net_subset = resources::net_subset {
|
||||
dev = opts.net_dev,
|
||||
dns = opts.dns,
|
||||
gateway = opts.net_gateway,
|
||||
@@ -141,7 +141,7 @@ export fn ingest_cli_opts(opts: cli_opts) (cli_request | exec_params | help) = {
|
||||
|
||||
//---- ingest `keep_net` ----//
|
||||
if (opts.keep_net)
|
||||
req.resources.net = restrict::net_all;
|
||||
req.resources.net = resources::net_all;
|
||||
|
||||
//---- ingest `keep_pid` ----//
|
||||
req.resources.pid = opts.keep_pid;
|
||||
|
@@ -208,7 +208,7 @@ test_09_reap_children() {
|
||||
|
||||
test_10_tmpfs_root_is_introspectable() {
|
||||
echo from_parent > test_file0
|
||||
bunpen --bunpen-path /nix/store --bunpen-path $PWD bash -c "test -f $PWD/test_file0 && echo 'from_sandbox' > /test_file1 && sleep 2" &
|
||||
bunpen --bunpen-debug=3 --bunpen-keep-pid --bunpen-path /nix/store --bunpen-path $PWD bash -c "set -x; test -f $PWD/test_file0 && echo 'from_sandbox' > /test_file1 && sleep 2" &
|
||||
local sbox_pid=$!
|
||||
sleep 0.5
|
||||
|
||||
@@ -224,6 +224,10 @@ test_10_tmpfs_root_is_introspectable() {
|
||||
echo -n "/proc/$sbox_pid/root: " && ls /proc/$sbox_pid/root
|
||||
echo -n "/proc/$sbox_pid/root/unbacked: " && ls /proc/$sbox_pid/root/unbacked
|
||||
echo -n "/proc/$sbox_pid/root/unbacked/test_file1: " && echo "$test_file1"
|
||||
|
||||
# even more diagnostics, to see that `unbacked` does actually have stuff at least
|
||||
echo -n "/proc/$sbox_pid/root/unbacked/$(dirname $(dirname $PWD)): " && ls -l /proc/$sbox_pid/root/unbacked/$(dirname $(dirname $PWD))
|
||||
echo -n "/proc/$sbox_pid/root/unbacked/$(dirname $PWD): " && ls -l /proc/$sbox_pid/root/unbacked/$(dirname $PWD)
|
||||
echo -n "/proc/$sbox_pid/root/unbacked/$PWD: " && ls -l /proc/$sbox_pid/root/unbacked/$PWD
|
||||
|
||||
test "$test_file1" = from_sandbox
|
||||
|
@@ -37,8 +37,7 @@ fn prepare_env(req: config::cli_request) config::exec_params = {
|
||||
// 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::ns::namespace_restrict(&req.resources);
|
||||
restrict::capability_restrict(&req.resources);
|
||||
restrict::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);
|
||||
|
@@ -15,7 +15,7 @@ use unix::signal;
|
||||
// - in the child: continue execution as normal
|
||||
// - in the parent: wait for the child, then propagate its exit status.
|
||||
// forward any signals, including SIGKILL, to the child.
|
||||
fn fork_and_propagate() (void | os::exec::error | rt::errno) = {
|
||||
export fn fork_and_propagate() (void | os::exec::error | rt::errno) = {
|
||||
let outer_pid = rt::getpid();
|
||||
return match (fork_and_die_with_parent()?) {
|
||||
case let child_pid: os::exec::process => yield wait_and_propagate(child_pid);
|
||||
@@ -26,7 +26,7 @@ fn fork_and_propagate() (void | os::exec::error | rt::errno) = {
|
||||
|
||||
// fork, but configured so that the child receives SIGKILL upon the parent's exit,
|
||||
// ensuring that the child may never outlive the parent.
|
||||
fn fork_and_die_with_parent() (os::exec::process | void | os::exec::error | rt::errno) = {
|
||||
export fn fork_and_die_with_parent() (os::exec::process | void | os::exec::error | rt::errno) = {
|
||||
let outer_pid = rt::getpid();
|
||||
return match (os::exec::fork()?) {
|
||||
case let child_pid: os::exec::process => yield child_pid;
|
22
pkgs/by-name/bunpen/resources/caps.ha
Normal file
22
pkgs/by-name/bunpen/resources/caps.ha
Normal file
@@ -0,0 +1,22 @@
|
||||
// vim: set shiftwidth=2 :
|
||||
|
||||
use rt::ext;
|
||||
|
||||
export fn cap_array_to_caps(cap_arr: []rt::ext::cap) rt::ext::caps = {
|
||||
let cs: rt::ext::caps = 0;
|
||||
for (let cap .. cap_arr)
|
||||
caps_add(&cs, cap);
|
||||
return cs;
|
||||
};
|
||||
|
||||
export fn caps_add(cs: *rt::ext::caps, cap: rt::ext::cap) void = {
|
||||
*cs |= (1 << cap: u64);
|
||||
};
|
||||
|
||||
export fn caps_add_caps(cs: *rt::ext::caps, cs2: rt::ext::caps) void = {
|
||||
*cs |= cs2;
|
||||
};
|
||||
|
||||
export fn caps_contains(cs: rt::ext::caps, cap: rt::ext::cap) bool = {
|
||||
return (cs & (1 << cap: u64)) != 0;
|
||||
};
|
@@ -3,31 +3,13 @@
|
||||
use errors::ext;
|
||||
use log;
|
||||
use rt::ext;
|
||||
use resources;
|
||||
|
||||
export fn cap_array_to_caps(cap_arr: []rt::ext::cap) rt::ext::caps = {
|
||||
let cs: rt::ext::caps = 0;
|
||||
for (let cap .. cap_arr)
|
||||
caps_add(&cs, cap);
|
||||
return cs;
|
||||
};
|
||||
|
||||
export fn caps_add(cs: *rt::ext::caps, cap: rt::ext::cap) void = {
|
||||
*cs |= (1 << cap: u64);
|
||||
};
|
||||
|
||||
export fn caps_add_caps(cs: *rt::ext::caps, cs2: rt::ext::caps) void = {
|
||||
*cs |= cs2;
|
||||
};
|
||||
|
||||
fn caps_contains(cs: rt::ext::caps, cap: rt::ext::cap) bool = {
|
||||
return (cs & (1 << cap: u64)) != 0;
|
||||
};
|
||||
|
||||
export fn capability_restrict(what: *resources) void = {
|
||||
export fn capability_restrict(what: rt::ext::caps) void = {
|
||||
//// for clarity, `needed_now` and `needed` are *specifically* what we need at
|
||||
//// this point. however this code is not required, because we have something
|
||||
//// akin to E=CAPS_ALL, P=CAPS_ALL, I=CAPS_NONE just from entering the namespace.
|
||||
// let needed = what.caps;
|
||||
// let needed = what;
|
||||
// caps_add(&needed, rt::ext::cap::SETPCAP);
|
||||
// let needed_now = rt::ext::CAPS_NONE;
|
||||
// caps_add(&needed_now, rt::ext::cap::SETPCAP);
|
||||
@@ -44,7 +26,7 @@ export fn capability_restrict(what: *resources) void = {
|
||||
//
|
||||
// dropping a privilege from the B set seems to *not* remove it from E/I/P/Amb.
|
||||
for (let cap = rt::ext::CAP_FIRST; cap <= rt::ext::CAP_LAST; cap += 1) {
|
||||
if (!caps_contains(what.caps, cap)) {
|
||||
if (!resources::caps_contains(what, cap)) {
|
||||
let cap_str = rt::ext::cap_tostring(cap);
|
||||
log::printfln("[capability/restrict] lowering {}", cap_str);
|
||||
errors::ext::check(
|
||||
@@ -69,12 +51,12 @@ export fn capability_restrict(what: *resources) void = {
|
||||
// will become E at the moment we `exec` into the wrapped program.
|
||||
errors::ext::check("[capability] capset", rt::ext::capset(rt::ext::caps_eip {
|
||||
effective = rt::ext::CAPS_NONE,
|
||||
inheritable = what.caps,
|
||||
permitted = what.caps,
|
||||
inheritable = what,
|
||||
permitted = what,
|
||||
}));
|
||||
|
||||
for (let cap = rt::ext::CAP_FIRST; cap <= rt::ext::CAP_LAST; cap += 1) {
|
||||
if (caps_contains(what.caps, cap)) {
|
||||
if (resources::caps_contains(what, cap)) {
|
||||
let cap_str = rt::ext::cap_tostring(cap);
|
||||
log::printfln("[capability/restrict] raising {}", cap_str);
|
||||
errors::ext::swallow(
|
||||
|
@@ -4,6 +4,7 @@ use fs;
|
||||
use log;
|
||||
use os;
|
||||
use path;
|
||||
use resources;
|
||||
use rt;
|
||||
use rt::ext;
|
||||
|
||||
@@ -57,7 +58,7 @@ fn allow_path(ruleset_fd: u64, path_str: str) (void | fs::error | rt::errno) = {
|
||||
allow_path_fd(ruleset_fd, path_fd)?;
|
||||
};
|
||||
|
||||
export fn landlock_restrict(what: *resources) void = {
|
||||
export fn landlock_restrict(what: *resources::resources) void = {
|
||||
let abi = errors::ext::check_u64(
|
||||
"query landlock kernel ABI version",
|
||||
rt::ext::landlock_create_ruleset(null, rt::ext::landlock_create_ruleset_flag::VERSION),
|
||||
@@ -84,10 +85,10 @@ export fn landlock_restrict(what: *resources) void = {
|
||||
|
||||
// XXX: `what.net` only affects TCP. UDP, and ICMP remain possible always
|
||||
match (what.net) {
|
||||
case net_none => void;
|
||||
case let subset: net_subset =>
|
||||
case resources::net_none => void;
|
||||
case let subset: resources::net_subset =>
|
||||
log::println("[landlock] unable to retain just a subset of net resources");
|
||||
case net_all =>
|
||||
case resources::net_all =>
|
||||
log::println("[landlock] permit net");
|
||||
ruleset_attr.handled_access_net = 0;
|
||||
};
|
||||
|
@@ -3,11 +3,17 @@ use log;
|
||||
use fs;
|
||||
use os;
|
||||
use path;
|
||||
use restrict;
|
||||
use rt;
|
||||
use rt::ext;
|
||||
use strings;
|
||||
|
||||
// everything inside this struct is borrowed
|
||||
type ns_ctx = struct {
|
||||
what: *ns_resources,
|
||||
old_fs: *fs::fs,
|
||||
new_fs: *fs::fs,
|
||||
};
|
||||
|
||||
// reconfigures all the mounts so that after this call the only paths accessible
|
||||
// are those reachable from the provided `paths`.
|
||||
// N.B.: this function does NOT preserve the current working directory.
|
||||
@@ -16,7 +22,7 @@ use strings;
|
||||
// i don't know if this really matters anywhere (maybe `/` and `/proc`?),
|
||||
// `sanebox` behavior is to gather all paths, expand their symlinks,
|
||||
// and then only bind-mount the top-most path in the case of overlap.
|
||||
fn isolate_paths(what: *restrict::resources) void = {
|
||||
fn isolate_paths(what: *ns_resources) void = {
|
||||
// allow new mounts to propagate from the parent namespace into the child
|
||||
// namespace, but not vice versa (this is a requirement to use pivot_root)
|
||||
errors::ext::check("[namespace] reconfigure / as MS_SLAVE", rt::ext::mount("/", "/", "", rt::ext::mount_flag::SLAVE | rt::ext::mount_flag::REC, null));
|
||||
|
@@ -5,13 +5,23 @@ use fs;
|
||||
use io;
|
||||
use log;
|
||||
use os;
|
||||
use restrict;
|
||||
use path;
|
||||
use ps;
|
||||
use resources;
|
||||
use rt;
|
||||
use rt::ext;
|
||||
use strings;
|
||||
use unix;
|
||||
|
||||
export fn namespace_restrict(what: *restrict::resources) void = {
|
||||
export type ns_resources = struct {
|
||||
paths: []path::buffer,
|
||||
ipc: bool,
|
||||
net: bool,
|
||||
pid: bool,
|
||||
try_users: bool,
|
||||
};
|
||||
|
||||
export fn namespace_restrict(what: *ns_resources) void = {
|
||||
// record the uid and gid of the initial namespace, so that we can re-map them
|
||||
// in the new ns.
|
||||
let uid = unix::getuid();
|
||||
@@ -21,7 +31,7 @@ export fn namespace_restrict(what: *restrict::resources) void = {
|
||||
let unshare_early: rt::ext::clone_flags =
|
||||
rt::ext::clone_flag::NEWCGROUP |
|
||||
rt::ext::clone_flag::NEWIPC |
|
||||
// rt::ext::clone_flag::NEWNET |
|
||||
rt::ext::clone_flag::NEWNET |
|
||||
rt::ext::clone_flag::NEWNS |
|
||||
rt::ext::clone_flag::NEWPID |
|
||||
rt::ext::clone_flag::NEWUSER |
|
||||
@@ -33,7 +43,13 @@ export fn namespace_restrict(what: *restrict::resources) void = {
|
||||
unshare_early &= ~rt::ext::clone_flag::NEWIPC;
|
||||
};
|
||||
|
||||
if (what.net) {
|
||||
log::println("[namespace] keeping net namespace");
|
||||
unshare_early &= ~rt::ext::clone_flag::NEWNET;
|
||||
};
|
||||
|
||||
if (what.pid) {
|
||||
log::println("[namespace] keeping pid namespace");
|
||||
unshare_early &= ~rt::ext::clone_flag::NEWPID;
|
||||
};
|
||||
|
||||
@@ -46,7 +62,7 @@ export fn namespace_restrict(what: *restrict::resources) void = {
|
||||
log::println("[namespace] failed to unshare w/o user namespace. raising CAP_SYS_ADMIN and trying again");
|
||||
|
||||
let raise_caps = rt::ext::CAPS_NONE;
|
||||
restrict::caps_add(&raise_caps, rt::ext::cap::SYS_ADMIN);
|
||||
resources::caps_add(&raise_caps, rt::ext::cap::SYS_ADMIN);
|
||||
if (try_unshare_with(unshare_keep_users, raise_caps))
|
||||
unshare_early = 0;
|
||||
};
|
||||
@@ -85,8 +101,8 @@ export fn namespace_restrict(what: *restrict::resources) void = {
|
||||
// should be handled by it v.s. relayed to the user code. the only way to
|
||||
// determine if a signal was/will be handled seems to be to parse /proc/$CHILD/status.
|
||||
// if signal forwarding is unimportant, this second fork can be safely removed.
|
||||
errors::ext::check("[namespace/fork] forking new PID 1", fork_and_propagate());
|
||||
errors::ext::check("[namespace/fork] forking second time, child will become user process", fork_and_propagate());
|
||||
errors::ext::check("[namespace/fork] forking new PID 1", ps::fork_and_propagate());
|
||||
errors::ext::check("[namespace/fork] forking second time, child will become user process", ps::fork_and_propagate());
|
||||
};
|
||||
|
||||
// isolate the mounts. we have to do this *after* forking into the new PID
|
||||
@@ -95,27 +111,12 @@ export fn namespace_restrict(what: *restrict::resources) void = {
|
||||
// and then mounting just /proc here, but that's complicated particularly by
|
||||
// the many circumstances in which we might want to mount *portions* of the
|
||||
// outside /proc over the new /proc.
|
||||
{
|
||||
let pwd = strings::dup(os::getcwd()); // dup because API uses a static buffer
|
||||
defer(free(pwd));
|
||||
isolate_paths(what);
|
||||
// try to change to the old working directory;
|
||||
// this can fail if it's not within the sandbox.
|
||||
errors::ext::swallow("namespace: restore $PWD", os::chdir(pwd));
|
||||
};
|
||||
|
||||
// at this point we don't know how many processes there are;
|
||||
// it could be just one, or it could be 3 (pid namespacing).
|
||||
// either way, the code executing here is the child.
|
||||
// freely drop the net namespace unless the user code specificaly needs *full* net access
|
||||
match (what.net) {
|
||||
case restrict::net_none =>
|
||||
errors::ext::check("namespace: unshare net", rt::ext::unshare(rt::ext::clone_flag::NEWNET));
|
||||
case let subset: restrict::net_subset =>
|
||||
setup_pasta(subset);
|
||||
case restrict::net_all =>
|
||||
log::println("[namespace] keeping net namespace");
|
||||
};
|
||||
};
|
||||
|
||||
// try to `unshare(flags)`, return true on success
|
||||
@@ -141,7 +142,7 @@ fn try_unshare_with(flags: rt::ext::clone_flags, caps: rt::ext::caps) bool = {
|
||||
};
|
||||
|
||||
let new_caps = orig_caps;
|
||||
restrict::caps_add_caps(&new_caps.effective, caps);
|
||||
resources::caps_add_caps(&new_caps.effective, caps);
|
||||
errors::ext::swallow("[namespace] raise caps", rt::ext::capset(new_caps));
|
||||
|
||||
let unshared = try_unshare(flags);
|
||||
@@ -156,14 +157,6 @@ fn try_unshare_with(flags: rt::ext::clone_flags, caps: rt::ext::caps) bool = {
|
||||
return unshared;
|
||||
};
|
||||
|
||||
|
||||
// everything inside this struct is borrowed
|
||||
type ns_ctx = struct {
|
||||
what: *restrict::resources,
|
||||
old_fs: *fs::fs,
|
||||
new_fs: *fs::fs,
|
||||
};
|
||||
|
||||
// these id maps are writable *once*.
|
||||
// - uid_map, gid_map: tell the kernel how uid's from the parent namespace
|
||||
// should be presented to members of the current namespace,
|
||||
|
@@ -8,13 +8,15 @@ use io;
|
||||
use log;
|
||||
use os;
|
||||
use os::exec;
|
||||
use restrict;
|
||||
use ps;
|
||||
use resources;
|
||||
use restrict::ns;
|
||||
use rt;
|
||||
use rt::ext;
|
||||
use strings;
|
||||
use unix;
|
||||
|
||||
fn setup_pasta(net: restrict::net_subset) void = {
|
||||
fn pasta_restrict(net: resources::net_subset) void = {
|
||||
// `pasta PID [options]`: creates a device in the netns of PID.
|
||||
// ordering:
|
||||
// 1. fork into (P, C1)
|
||||
@@ -32,12 +34,12 @@ fn setup_pasta(net: restrict::net_subset) void = {
|
||||
|
||||
// unshare PID space here so that the fork'd child (pasta) is isolated.
|
||||
// TODO: stronger isolation (mount namespace).
|
||||
errors::ext::check("[namespace/pasta] unshare PID", rt::ext::unshare(rt::ext::clone_flag::NEWPID));
|
||||
// errors::ext::check("[namespace/pasta] unshare PID", rt::ext::unshare(rt::ext::clone_flag::NEWPID));
|
||||
|
||||
let (pipe_parent_rd, pipe_child_wr) = unix::pipe()!;
|
||||
let (pipe_child_rd, pipe_parent_wr) = unix::pipe()!;
|
||||
log::printfln("[namespace/pasta]: forking: child will launch pasta while parent will exec user code");
|
||||
match (fork_and_die_with_parent()) {
|
||||
match (ps::fork_and_die_with_parent()) {
|
||||
case let child_pid: os::exec::process =>
|
||||
// close the pipe ends which aren't ours
|
||||
io::close(pipe_child_wr)!;
|
||||
@@ -55,10 +57,7 @@ fn setup_pasta(net: restrict::net_subset) void = {
|
||||
|
||||
// drop enough caps so that pasta has permissions to enter our new netns.
|
||||
// TODO: just set the desired caps here, and then don't try to do it again.
|
||||
let res = restrict::resources {
|
||||
caps = rt::ext::CAPS_NONE, net = restrict::net_all, ...
|
||||
};
|
||||
restrict::capability_restrict(&res);
|
||||
capability_restrict(rt::ext::CAPS_NONE);
|
||||
// let the other thread know we're ready for pasta to attach to us
|
||||
io::write(pipe_parent_wr, [1])!;
|
||||
|
||||
@@ -92,7 +91,7 @@ fn setup_pasta(net: restrict::net_subset) void = {
|
||||
};
|
||||
|
||||
// spawn pasta as a separate process, and have it attach to the netns of the given pid.
|
||||
fn attach_pasta(net: restrict::net_subset, notify_fd: io::file) (void | os::exec::error | rt::errno) = {
|
||||
fn attach_pasta(net: resources::net_subset, notify_fd: io::file) (void | os::exec::error | rt::errno) = {
|
||||
// move the notification pipe to be one of the special fd's which *don't*
|
||||
// get closed on exec (e.g. /dev/fd/0, stdin).
|
||||
// this *shouldn't* be necessary: the pipe was created NOCLOEXEC;
|
||||
@@ -130,10 +129,7 @@ fn attach_pasta(net: restrict::net_subset, notify_fd: io::file) (void | os::exec
|
||||
|
||||
// pasta needs permissions to create a device in the netns (it apparently
|
||||
// won't raise those caps itself). TODO: reduce these resources!
|
||||
let res = restrict::resources {
|
||||
caps = rt::ext::CAPS_ALL, net = restrict::net_all, ...
|
||||
};
|
||||
restrict::capability_restrict(&res);
|
||||
capability_restrict(rt::ext::CAPS_ALL);
|
||||
|
||||
log::printfln("[namespace/pasta]: invoking pasta: {}", strings::join(" ", pasta_args...));
|
||||
return rt::ext::execvpe_or_default(
|
||||
@@ -144,17 +140,17 @@ fn attach_pasta(net: restrict::net_subset, notify_fd: io::file) (void | os::exec
|
||||
);
|
||||
};
|
||||
|
||||
fn config_routing_in_ns(net: restrict::net_subset) (void | os::exec::error | rt::errno) = {
|
||||
fn config_routing_in_ns(net: resources::net_subset) (void | os::exec::error | rt::errno) = {
|
||||
// raise CAP_NET_ADMIN so we can make calls like `iptables`, `ip ...`
|
||||
let caps = rt::ext::capget()?;
|
||||
restrict::caps_add(&caps.inheritable, rt::ext::cap::NET_ADMIN);
|
||||
resources::caps_add(&caps.inheritable, rt::ext::cap::NET_ADMIN);
|
||||
rt::ext::capset(caps)?;
|
||||
errors::ext::swallow("[namespace/pasta] raise CAP_NET_ADMIN to configure interface", rt::ext::cap_ambient_raise(rt::ext::cap::NET_ADMIN));
|
||||
|
||||
// forward dns to the desired endpoint
|
||||
let dnsdest = fmt::asprintf("{}:53", net.dns);
|
||||
defer free(dnsdest);
|
||||
let rc = shellvpe_or_default(
|
||||
let rc = ps::shellvpe_or_default(
|
||||
config::IPTABLES,
|
||||
"iptables",
|
||||
[
|
||||
@@ -178,7 +174,7 @@ fn config_routing_in_ns(net: restrict::net_subset) (void | os::exec::error | rt:
|
||||
// therefore, set it up *now*, and delete the addrs, and then since it's
|
||||
// already up they won't re-appear when we exec pasta.
|
||||
|
||||
let rc = shellvpe_or_default(
|
||||
let rc = ps::shellvpe_or_default(
|
||||
config::IP,
|
||||
"ip",
|
||||
[
|
||||
@@ -190,7 +186,7 @@ fn config_routing_in_ns(net: restrict::net_subset) (void | os::exec::error | rt:
|
||||
)?;
|
||||
log::printfln("[namespace/pasta] 'ip link set lo up' exited {}", rc.status);
|
||||
|
||||
let rc = shellvpe_or_default(
|
||||
let rc = ps::shellvpe_or_default(
|
||||
config::IP,
|
||||
"ip",
|
||||
[
|
54
pkgs/by-name/bunpen/restrict/restrict.ha
Normal file
54
pkgs/by-name/bunpen/restrict/restrict.ha
Normal file
@@ -0,0 +1,54 @@
|
||||
// vim: set shiftwidth=2 :
|
||||
use path;
|
||||
use resources;
|
||||
use restrict::ns;
|
||||
|
||||
export fn restrict(what: *resources::resources) void = {
|
||||
// map the user-requested networking into internal mechanisms:
|
||||
let (any_net, pasta_net): (bool, (void | resources::net_subset))
|
||||
= match (what.net)
|
||||
{
|
||||
case resources::net_none =>
|
||||
yield (false, void);
|
||||
case let subset: resources::net_subset =>
|
||||
yield (true, subset);
|
||||
case resources::net_all =>
|
||||
yield (true, void);
|
||||
};
|
||||
|
||||
// sandbox the entire process early, now, before any forking/exec'ing
|
||||
// PID unshares are costly (requires an entire extra long-running supervisor process),
|
||||
// so do that only once, as needed, instead of additionally at the top layer.
|
||||
// in turn, this means we have to bind all of `/proc`, both to access
|
||||
// `/proc/self` and the proc entries of children.
|
||||
let outer_pid = true;
|
||||
let outer_paths: []path::buffer = [];
|
||||
append(outer_paths, path::init("/proc")!);
|
||||
for (let p .. what.paths)
|
||||
append(outer_paths, p);
|
||||
let outer_ns_resources = ns::ns_resources {
|
||||
paths = outer_paths,
|
||||
ipc = what.ipc,
|
||||
net = any_net,
|
||||
pid = outer_pid,
|
||||
try_users = what.try_users,
|
||||
};
|
||||
ns::namespace_restrict(&outer_ns_resources);
|
||||
|
||||
match (pasta_net) {
|
||||
case void => void;
|
||||
case let subset: resources::net_subset =>
|
||||
pasta_restrict(subset);
|
||||
};
|
||||
|
||||
// sandbox the user process
|
||||
let user_ns_resources = ns::ns_resources {
|
||||
paths = what.paths,
|
||||
ipc = what.ipc,
|
||||
net = any_net,
|
||||
pid = what.pid,
|
||||
try_users = what.try_users,
|
||||
};
|
||||
ns::namespace_restrict(&user_ns_resources);
|
||||
capability_restrict(what.caps);
|
||||
};
|
Reference in New Issue
Block a user