|
|
|
@@ -1,4 +1,5 @@
|
|
|
|
|
// vim: set shiftwidth=2 :
|
|
|
|
|
use errors::ext;
|
|
|
|
|
use fmt;
|
|
|
|
|
use fs;
|
|
|
|
|
use io;
|
|
|
|
@@ -60,8 +61,8 @@ export fn namespace_restrict(what: *resources) void = {
|
|
|
|
|
// (and the wrapper only exits once *all* orphaned children die).
|
|
|
|
|
// i don't need children to outlive the main process, so i fork once and let
|
|
|
|
|
// the sandboxed program be reaper for all its children.
|
|
|
|
|
rtext::check_error("[namespace/fork] forking new PID 1", fork_and_propagate());
|
|
|
|
|
// rtext::check_error("[namespace/fork] forking second time", fork_and_propagate());
|
|
|
|
|
errors::ext::check("[namespace/fork] forking new PID 1", fork_and_propagate());
|
|
|
|
|
// errors::ext::check("[namespace/fork] forking second time", fork_and_propagate());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let pwd = strings::dup(os::getcwd()); // dup because API uses a static buffer
|
|
|
|
@@ -69,7 +70,7 @@ export fn namespace_restrict(what: *resources) void = {
|
|
|
|
|
isolate_paths(what.paths);
|
|
|
|
|
// try to change to the old working directory;
|
|
|
|
|
// this can fail if it's not within the sandbox.
|
|
|
|
|
rtext::swallow_error("namespace: restore $PWD", os::chdir(pwd));
|
|
|
|
|
errors::ext::swallow("namespace: restore $PWD", os::chdir(pwd));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// fork and:
|
|
|
|
@@ -91,7 +92,7 @@ fn fork_and_propagate() (void | os::exec::error) = {
|
|
|
|
|
fn isolate_paths(paths: []path::buffer) void = {
|
|
|
|
|
// allow new mounts to propagate from the parent namespace into the child
|
|
|
|
|
// namespace, but not vice versa:
|
|
|
|
|
rtext::check_error("[namespace] reconfigure / as MS_SLAVE", rtext::mount("/", "/", "", rtext::mount_flag::SLAVE | rtext::mount_flag::REC, null));
|
|
|
|
|
errors::ext::check("[namespace] reconfigure / as MS_SLAVE", rtext::mount("/", "/", "", rtext::mount_flag::SLAVE | rtext::mount_flag::REC, null));
|
|
|
|
|
|
|
|
|
|
// in order to mount ANY directory from the old root into the new root,
|
|
|
|
|
// they have to be totally disparate. if we kept the old root at / and the new
|
|
|
|
@@ -102,16 +103,16 @@ fn isolate_paths(paths: []path::buffer) void = {
|
|
|
|
|
// 2. create a new rootfs at `new` and bind stuff into it.
|
|
|
|
|
// 3. then pivot a 2nd time, into `new` (and drop `old` altogether)
|
|
|
|
|
|
|
|
|
|
rtext::check_error("[namespace] mount -t tmpfs tmpfs /tmp", rtext::mount("tmpfs", "/tmp", "tmpfs", rtext::mount_flag::NODEV | rtext::mount_flag::NOSUID, null));
|
|
|
|
|
errors::ext::check("[namespace] mount -t tmpfs tmpfs /tmp", rtext::mount("tmpfs", "/tmp", "tmpfs", rtext::mount_flag::NODEV | rtext::mount_flag::NOSUID, null));
|
|
|
|
|
|
|
|
|
|
pivot_into("/tmp", "old");
|
|
|
|
|
// now we have `/`, empty except for the old rootfs available at `/old`
|
|
|
|
|
|
|
|
|
|
// prepare a new rootfs. it has to be its own mount (tmpfs), not just a dir.
|
|
|
|
|
rtext::check_error("[namespace] mkdir new", rt::mkdir("new", 0o755));
|
|
|
|
|
rtext::check_error("[namespace] mount -t tmpfs tmpfs new", rtext::mount("tmpfs", "new", "tmpfs", 0, null));
|
|
|
|
|
// rtext::check_error("[namespace] mount -t tmpfs tmpfs new", rtext::mount("tmpfs", "new", "tmpfs", rtext::mount_flag::NODEV | rtext::mount_flag::NOSUID, null));
|
|
|
|
|
// rtext::check_error("[namespace] mount -o rbind new new", rtext::mount("new", "new", "", rtext::mount_flag::BIND | rtext::mount_flag::REC, null));
|
|
|
|
|
errors::ext::check("[namespace] mkdir new", rt::mkdir("new", 0o755));
|
|
|
|
|
errors::ext::check("[namespace] mount -t tmpfs tmpfs new", rtext::mount("tmpfs", "new", "tmpfs", 0, null));
|
|
|
|
|
// errors::ext::check("[namespace] mount -t tmpfs tmpfs new", rtext::mount("tmpfs", "new", "tmpfs", rtext::mount_flag::NODEV | rtext::mount_flag::NOSUID, null));
|
|
|
|
|
// errors::ext::check("[namespace] mount -o rbind new new", rtext::mount("new", "new", "", rtext::mount_flag::BIND | rtext::mount_flag::REC, null));
|
|
|
|
|
|
|
|
|
|
// try to mount a new /proc.
|
|
|
|
|
// - this is "safe" because we're not doing anything
|
|
|
|
@@ -125,15 +126,15 @@ fn isolate_paths(paths: []path::buffer) void = {
|
|
|
|
|
// - bind-mounting /proc is _in theory_ safe (it's a namespace-aware fs),
|
|
|
|
|
// but in practice there are namespacing bugs at least as recently as 2021:
|
|
|
|
|
// <https://github.com/opencontainers/runc/issues/2826#issuecomment-915683044>
|
|
|
|
|
rtext::swallow_error("[namespace] mkdir new/proc", rt::mkdir("new/proc", 0o755));
|
|
|
|
|
rtext::swallow_error("[namespace] mount /new/proc", rtext::mount(
|
|
|
|
|
errors::ext::swallow("[namespace] mkdir new/proc", rt::mkdir("new/proc", 0o755));
|
|
|
|
|
errors::ext::swallow("[namespace] mount /new/proc", rtext::mount(
|
|
|
|
|
"proc", "new/proc", "proc", rtext::mount_flag::NOSUID | rtext::mount_flag::NOEXEC | rtext::mount_flag::NODEV, null
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// provide a new `/tmp` too.
|
|
|
|
|
rtext::swallow_error("[namespace] mkdir new/tmp", rt::mkdir("new/tmp", 0o777));
|
|
|
|
|
rtext::swallow_error("[namespace] mount -t tmpfs tmpfs new/tmp", rtext::mount("tmpfs", "new/tmp", "tmpfs", 0, null));
|
|
|
|
|
errors::ext::swallow("[namespace] mkdir new/tmp", rt::mkdir("new/tmp", 0o777));
|
|
|
|
|
errors::ext::swallow("[namespace] mount -t tmpfs tmpfs new/tmp", rtext::mount("tmpfs", "new/tmp", "tmpfs", 0, null));
|
|
|
|
|
|
|
|
|
|
// bind all the user-requested paths from `old/$p` into `new/$p`.
|
|
|
|
|
// use the `dirfd` abstraction so that paths meant for `old` can't crawl out
|
|
|
|
@@ -231,7 +232,7 @@ fn bind_leaf(old_fs: *fs::fs, new_fs: *fs::fs, user_path: *path::buffer) void =
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
log::printfln("[namespace/bind] mount {} {}", path::string(&old_pathbuf), path::string(&new_pathbuf));
|
|
|
|
|
rtext::swallow_error("[namespace/bind] bind_leaf: mount", rtext::mount(
|
|
|
|
|
errors::ext::swallow("[namespace/bind] bind_leaf: mount", rtext::mount(
|
|
|
|
|
path::string(&old_pathbuf), path::string(&new_pathbuf), "", rtext::mount_flag::BIND | rtext::mount_flag::REC, null
|
|
|
|
|
));
|
|
|
|
|
};
|
|
|
|
@@ -281,17 +282,17 @@ fn bind_component(old_fs: *fs::fs, new_fs: *fs::fs, strpath: str, remaining: str
|
|
|
|
|
// at some directory (to be created) underneath it.
|
|
|
|
|
fn pivot_into(new_root: str, stash_old_root: (str|void) = void) void = {
|
|
|
|
|
log::printfln("[namespace] pivot_root {}", new_root);
|
|
|
|
|
rtext::check_error("[namespace] cd <new_root>", os::chdir(new_root));
|
|
|
|
|
errors::ext::check("[namespace] cd <new_root>", os::chdir(new_root));
|
|
|
|
|
match (stash_old_root) {
|
|
|
|
|
case let old: str =>
|
|
|
|
|
rtext::check_error("[namespace] mkdir <stash_old_root>", rt::mkdir(old, 0o755));
|
|
|
|
|
rtext::check_error("[namespace] pivot_root . <stash_old_root>", rtext::pivot_root(".", old));
|
|
|
|
|
errors::ext::check("[namespace] mkdir <stash_old_root>", rt::mkdir(old, 0o755));
|
|
|
|
|
errors::ext::check("[namespace] pivot_root . <stash_old_root>", rtext::pivot_root(".", old));
|
|
|
|
|
case void =>
|
|
|
|
|
rtext::check_error("[namespace] pivot_root . .", rtext::pivot_root(".", "."));
|
|
|
|
|
errors::ext::check("[namespace] pivot_root . .", rtext::pivot_root(".", "."));
|
|
|
|
|
// drop the old rootfs. weird idiom, but documented in `man 2 pivot_root`.
|
|
|
|
|
rtext::check_error("[namespace] umount .", rt::umount2(".", rtext::umount_flag::MNT_DETACH));
|
|
|
|
|
errors::ext::check("[namespace] umount .", rt::umount2(".", rtext::umount_flag::MNT_DETACH));
|
|
|
|
|
};
|
|
|
|
|
rtext::check_error("[namespace] cd /", os::chdir("/"));
|
|
|
|
|
errors::ext::check("[namespace] cd /", os::chdir("/"));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fn write_uid_map(uid: unix::uid, gid: unix::gid) void = {
|
|
|
|
|