bunpen: namespace: bind all requested user paths, and create requisite directories
This commit is contained in:
@@ -4,6 +4,7 @@ use fs;
|
||||
use io;
|
||||
use log;
|
||||
use os;
|
||||
use path;
|
||||
use rt;
|
||||
use rtext;
|
||||
use strings;
|
||||
@@ -61,22 +62,138 @@ export fn namespace_restrict(what: *resources) void = {
|
||||
rtext::check_error("mkdir new", rt::mkdir("new", 0o755));
|
||||
rtext::check_error("mount -t tmpfs tmpfs new", rtext::mount("tmpfs", "new", "tmpfs", 0, null));
|
||||
|
||||
// bind old/$dir to new/$dir for every `dir` we want available in the sandbox:
|
||||
// TODO: mount the paths the user asks for, but until then hardcode stuff.
|
||||
rtext::check_error("mkdir new/bin", rt::mkdir("new/bin", 0o755));
|
||||
rtext::check_error("mount old/bin new/bin", rtext::mount("old/bin", "new/bin", "", rtext::MS_BIND | rtext::MS_REC, null));
|
||||
rtext::check_error("mkdir new/nix", rt::mkdir("new/nix", 0o755));
|
||||
rtext::check_error("mount old/nix new/nix", rtext::mount("old/nix", "new/nix", "", rtext::MS_BIND | rtext::MS_REC, 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
|
||||
// of that virtual fs.
|
||||
let old_fd = rt::open("old", rt::O_RDONLY | rt::O_CLOEXEC, rt::RESOLVE_NO_SYMLINKS: uint)!;
|
||||
let old_fs = os::dirfdopen(old_fd);
|
||||
defer(free(old_fs));
|
||||
let new_fd = rt::open("new", rt::O_RDONLY | rt::O_CLOEXEC, rt::RESOLVE_NO_SYMLINKS: uint)!;
|
||||
let new_fs = os::dirfdopen(new_fd);
|
||||
defer(free(new_fs));
|
||||
|
||||
for (let path .. what.paths) {
|
||||
bind_leaf(old_fs, new_fs, path);
|
||||
};
|
||||
};
|
||||
|
||||
// pivot into the new rootfs
|
||||
pivot_into("new");
|
||||
|
||||
// TODO: `chdir` back to the original dir we were executed from
|
||||
|
||||
// TODO: CLONE_NEWPID (might not work without forking to also become reaper)
|
||||
};
|
||||
|
||||
// walk from root to `p`, creating any ancestors necessary and then binding the
|
||||
// leaf from the old fs into the new fs.
|
||||
//
|
||||
// cases handled:
|
||||
// - [x] `p` is already present in the new fs. no-op.
|
||||
// - [x] ancestors of `p` are all ordinary directories in the old fs:
|
||||
// corresponding directories will be created in the new fs.
|
||||
// mountpoints are treated as directories for this case.
|
||||
// - [ ] ancestors of `p` are symlinks, such that `p != realpath(p)`.
|
||||
// corresponding symlinks will be created in the new fs, as well as
|
||||
// exactly as many underlying directories necessary to bind `p`.
|
||||
// - [ ] `p` itself is a symlink in the old fs, rather than a file/directory.
|
||||
// an equivalent symlink will be created, and then its target will be
|
||||
// bound as per the logic described above.
|
||||
// - [x] `p` is not canonicalized. it will be canonicalized *before* any of the
|
||||
// above processing.
|
||||
//
|
||||
// failure modes handled:
|
||||
// - [x] path is too long => does not create the leaf *nor any ancestors*.
|
||||
// - [x] canonical path points outside the fs (e.g. `..`, or `../new/proc`).
|
||||
// does not create the leaf *nor any of its ancestors* at/after the `..`.
|
||||
fn bind_leaf(old_fs: *fs::fs, new_fs: *fs::fs, user_path: str) void = {
|
||||
log::printfln("namespace: permit path: {}", user_path);
|
||||
// parse the path, which has the effect also of canonicalizing it.
|
||||
// returns error if the path is too long.
|
||||
// may point to `.` or `../`
|
||||
let p = match (path::init(user_path)) {
|
||||
case let e: path::error =>
|
||||
// should happen only if the path is absurdly long. swallow it and return.
|
||||
log::printfln("unable to bind {}: {}", user_path, path::strerror(e));
|
||||
return;
|
||||
case let other: path::buffer =>
|
||||
yield other;
|
||||
};
|
||||
|
||||
let it = path::iter(&p);
|
||||
let cur_path = path::init()!;
|
||||
let cur_strpath = "";
|
||||
for (let comp => path::nextiter(&it)) {
|
||||
if (comp == "..") {
|
||||
log::printfln("not binding external path {} (of {})", cur_strpath, user_path);
|
||||
return;
|
||||
};
|
||||
if (len(comp) >= 1 && strings::sub(comp, 0, 1) == "/") {
|
||||
// dirfd doesn't do well will absolute paths.
|
||||
comp = strings::sub(comp, 1, strings::end);
|
||||
};
|
||||
cur_strpath = match (path::push(&cur_path, comp)) {
|
||||
case let e: path::error =>
|
||||
log::printfln("unable to construct intermediate path for binding {} / {}: {}", cur_strpath, comp, path::strerror(e));
|
||||
return;
|
||||
case let other: str =>
|
||||
yield other;
|
||||
};
|
||||
|
||||
match (bind_component(old_fs, new_fs, cur_strpath)) {
|
||||
case let e: fs::error =>
|
||||
log::printfln("unable to copy intermediate path {} of {}: {}", cur_strpath, user_path, fs::strerror(e));
|
||||
return;
|
||||
case void => void;
|
||||
};
|
||||
};
|
||||
|
||||
// and now, perform the actual bind:
|
||||
let old_path = p;
|
||||
let new_path = p;
|
||||
let old_strpath = match (path::prepend(&old_path, "old")) {
|
||||
case let e: path::error =>
|
||||
log::printfln("unable to construct old path for binding {}: {}", user_path, path::strerror(e));
|
||||
return;
|
||||
case let other: str =>
|
||||
yield other;
|
||||
};
|
||||
let new_strpath = match (path::prepend(&new_path, "new")) {
|
||||
case let e: path::error =>
|
||||
log::printfln("unable to construct new path for binding {}: {}", user_path, path::strerror(e));
|
||||
return;
|
||||
case let other: str =>
|
||||
yield other;
|
||||
};
|
||||
rtext::swallow_error("bind_leaf: mount", rtext::mount(old_strpath, new_strpath, "", rtext::MS_BIND | rtext::MS_REC, null));
|
||||
};
|
||||
|
||||
fn bind_component(old_fs: *fs::fs, new_fs: *fs::fs, strpath: str) (void | fs::error) = {
|
||||
match (fs::stat(new_fs, strpath)) {
|
||||
case let e: fs::error => void;
|
||||
case let other: fs::filestat => return; // already created
|
||||
};
|
||||
let st = fs::stat(old_fs, strpath)?;
|
||||
// if (st.mode == mode_new) {
|
||||
// return; // already created
|
||||
// };
|
||||
|
||||
if (fs::islink(st.mode)) {
|
||||
log::printfln("TODO: support symlinks ({})", strpath);
|
||||
return;
|
||||
} else if (fs::isdir(st.mode)) {
|
||||
fs::mkdir(new_fs, strpath, st.mode)?;
|
||||
} else {
|
||||
// TODO: tune options (optional parameter; default is fs::flag::TRUNC)
|
||||
fs::create(new_fs, strpath, st.mode)?;
|
||||
};
|
||||
};
|
||||
|
||||
// make `new_root` the new `/`, and optionally make the old root accessible
|
||||
// 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("cd <new_root>", os::chdir(new_root));
|
||||
match (stash_old_root) {
|
||||
case let old: str =>
|
||||
|
@@ -13,3 +13,12 @@ export fn check_error(context: str, what: (void | fs::error | os::exec::error |
|
||||
};
|
||||
};
|
||||
|
||||
export fn swallow_error(context: str, what: (void | fs::error | os::exec::error | rt::errno)) void = {
|
||||
match (what) {
|
||||
case let e: fs::error => log::printfln("{}: {}", context, fs::strerror(e));
|
||||
case let e: os::exec::error => log::printfln("{}: {}", context, os::exec::strerror(e));
|
||||
case let e: rt::errno => log::printfln("{}: {}: {}", context, rt::errname(e), rt::strerror(e));
|
||||
case => void;
|
||||
};
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user