bunpen: treelogger: implement log depth filtering

This commit is contained in:
2024-08-27 20:36:24 +00:00
parent 2ffacf0e44
commit fb894bb7a5
6 changed files with 63 additions and 41 deletions

View File

@@ -81,8 +81,8 @@ fn ingest_paths(into: *[]path::buffer, from: []str, base: (str | void), allow_ab
match (get_path(path_str, base, allow_abs)) {
case let p: path::buffer => append(into, p);
case let e: path::error =>
log::printfln("translate_opts: omitting path {}: {}", path_str, path::strerror(e));
case missing_base => log::printfln("translate_opts: omitting path {}: no base dir", path_str);
log::printfln("[translate_opts] omitting path {}: {}", path_str, path::strerror(e));
case missing_base => log::printfln("[translate_opts] omitting path {}: no base dir", path_str);
};
};
};

View File

@@ -10,11 +10,10 @@
// - `printfln("[module] says {}", ...)`: has depth=1
// - `printfln("[module/child] says {}", ...)`: has depth=2
// - `printfln("[module/child/grandchild] says {}", ...)`: has depth=3
//
// TODO: how are `println`'s handled?
use fmt;
use log;
use strings;
export type logger = struct {
// vtable (println, printfln)
@@ -51,15 +50,14 @@ export fn set_level(me: *logger, level: uint) void = {
fn log_println(base: *log::logger, fields: fmt::formattable...) void = {
let me = base: *logger;
if (me.level > 0) {
if (me.level > 0 && (len(fields) == 0 || get_depth(fields[0]) < me.level)) {
log::lprintln(get_forward(me), fields...);
};
};
fn log_printfln(base: *log::logger, fmt: str, fields: fmt::field...) void = {
let me = base: *logger;
if (me.level > 0) {
if (me.level != 0 && get_depth(fmt) < me.level)
log::lprintfln(get_forward(me), fmt, fields...);
};
};
fn get_forward(me: *logger) *log::logger = {
@@ -68,3 +66,23 @@ fn get_forward(me: *logger) *log::logger = {
case let l: *log::logger => yield l;
};
};
fn get_depth(label: fmt::formattable) uint = {
let label = match (label) {
case let l: str => yield l;
case => return 0; // nothing other than a string could reasonable hold a label
};
if (!strings::hasprefix(label, '['))
return 0; // no context
let depth: uint = 1;
let iter = strings::iter(label);
for (let c => strings::next(&iter)) {
if (c == ']')
break;
if (c == '/')
depth += 1;
};
return depth;
};

View File

@@ -36,7 +36,7 @@ export fn main() void = {
log::tree::install(tree::global);
if (req.debug) {
log::tree::set_level(tree::global, 1);
log::tree::set_level(tree::global, 2); // TODO: make more configurable
};
let what = restrict::resources {

View File

@@ -53,7 +53,7 @@ fn allow_path_fd(ruleset_fd: u64, path_fd: i32) (rt::errno | void) = {
export fn landlock_restrict(what: *resources) void = {
let abi = rtext::landlock_create_ruleset(null, rtext::LANDLOCK_CREATE_RULESET_VERSION)!;
log::printfln("landlock: found version {}", abi);
log::printfln("[landlock] found version {}", abi);
// determine the access modes we can ask this kernel to restrict on:
let ruleset_attr = rtext::landlock_ruleset_attr {
@@ -75,7 +75,7 @@ export fn landlock_restrict(what: *resources) void = {
if (what.net) {
// un-restrict net access
log::println("landlock: permit net");
log::println("[landlock] permit net");
ruleset_attr.handled_access_net = 0;
}; // XXX: `what.net` only affects TCP. UDP, and ICMP remain possible always
@@ -83,10 +83,10 @@ export fn landlock_restrict(what: *resources) void = {
for (let pathbuf .. what.paths) {
let pathstr = path::string(&pathbuf);
log::printfln("landlock: permit path: {}", pathstr);
log::printfln("[landlock] permit path: {}", pathstr);
match (rt::open(pathstr, rt::O_PATH | rt::O_CLOEXEC, 0)) { //< O_PATH allows for opening files which are `x` but not `r`
case rt::errno => log::printfln("landlock: omitting from sandbox (failed to `open`): {}", pathstr);
case let path_fd: int => rtext::swallow_error("landlock: allow_path_fd", allow_path_fd(ruleset_fd, path_fd));
case rt::errno => log::printfln("[landlock/path] omitting from sandbox (failed to `open`): {}", pathstr);
case let path_fd: int => rtext::swallow_error("[landlock/path] allow_path_fd", allow_path_fd(ruleset_fd, path_fd));
};
};

View File

@@ -26,11 +26,11 @@ export fn namespace_restrict(what: *resources) void = {
rtext::CLONE_NEWUTS
;
if (what.net) {
log::println("namespace: permit net");
log::println("[namespace] permit net");
what_to_unshare &= ~rtext::CLONE_NEWNET;
};
log::printfln("namespace: unshare {}", what_to_unshare);
log::printfln("[namespace] unshare {}", what_to_unshare);
rtext::unshare(what_to_unshare)!;
// before mounting anything, set up the uids and gids in this namespace.
@@ -45,7 +45,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));
rtext::swallow_error("[namespace] restore $PWD", os::chdir(pwd));
// TODO: CLONE_NEWPID (might not work without forking to also become reaper)
};
@@ -56,7 +56,7 @@ export fn namespace_restrict(what: *resources) void = {
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("reconfigure / as MS_SLAVE", rtext::mount("/", "/", "", rtext::MS_SLAVE | rtext::MS_REC, null));
rtext::check_error("[namespace] reconfigure / as MS_SLAVE", rtext::mount("/", "/", "", rtext::MS_SLAVE | rtext::MS_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
@@ -67,14 +67,14 @@ 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("mount -t tmpfs tmpfs /tmp", rtext::mount("tmpfs", "/tmp", "tmpfs", 0, null));
rtext::check_error("[namespace] mount -t tmpfs tmpfs /tmp", rtext::mount("tmpfs", "/tmp", "tmpfs", 0, 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("mkdir new", rt::mkdir("new", 0o755));
rtext::check_error("mount -t tmpfs tmpfs new", rtext::mount("tmpfs", "new", "tmpfs", 0, null));
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));
// 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
@@ -92,6 +92,8 @@ fn isolate_paths(paths: []path::buffer) void = {
// pivot into the new rootfs
pivot_into("new");
log::println("namespace restrictions activated");
};
// walk from root to `p`, creating any ancestors necessary and then binding the
@@ -117,14 +119,14 @@ fn isolate_paths(paths: []path::buffer) void = {
// 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: *path::buffer) void = {
let path_str = path::string(user_path);
log::printfln("namespace: permit path: {}", path_str);
log::printfln("[namespace] permit path: {}", path_str);
let it = path::iter(user_path);
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, path_str);
log::printfln("[namespace] not binding external path {} (of {})", cur_strpath, path_str);
return;
};
if (len(comp) >= 1 && strings::sub(comp, 0, 1) == "/") {
@@ -133,7 +135,7 @@ fn bind_leaf(old_fs: *fs::fs, new_fs: *fs::fs, user_path: *path::buffer) void =
};
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));
log::printfln("[namespace] unable to construct intermediate path for binding {} / {}: {}", cur_strpath, comp, path::strerror(e));
return;
case let other: str =>
yield other;
@@ -141,7 +143,7 @@ fn bind_leaf(old_fs: *fs::fs, new_fs: *fs::fs, user_path: *path::buffer) void =
match (bind_component(old_fs, new_fs, cur_strpath)) {
case let e: fs::error =>
log::printfln("unable to copy intermediate path {} of {}: {}", cur_strpath, path_str, fs::strerror(e));
log::printfln("[namespace] unable to copy intermediate path {} of {}: {}", cur_strpath, path_str, fs::strerror(e));
return;
case void => void;
};
@@ -150,20 +152,20 @@ fn bind_leaf(old_fs: *fs::fs, new_fs: *fs::fs, user_path: *path::buffer) void =
// and now, perform the actual bind:
let old_pathbuf = match (path::init("old", path_str)) {
case let e: path::error =>
log::printfln("unable to construct old path for binding {}: {}", path_str, path::strerror(e));
log::printfln("[namespace] unable to construct old path for binding {}: {}", path_str, path::strerror(e));
return;
case let other: path::buffer =>
yield other;
};
let new_pathbuf = match (path::init("new", path_str)) {
case let e: path::error =>
log::printfln("unable to construct new path for binding {}: {}", path_str, path::strerror(e));
log::printfln("[namespace] unable to construct new path for binding {}: {}", path_str, path::strerror(e));
return;
case let other: path::buffer =>
yield other;
};
log::printfln("namespace: mount {} {}", path::string(&old_pathbuf), path::string(&new_pathbuf));
rtext::swallow_error("namespace: bind_leaf: mount", rtext::mount(
log::printfln("[namespace/bind] mount {} {}", path::string(&old_pathbuf), path::string(&new_pathbuf));
rtext::swallow_error("[namespace/bind] bind_leaf: mount", rtext::mount(
path::string(&old_pathbuf), path::string(&new_pathbuf), "", rtext::MS_BIND | rtext::MS_REC, null
));
};
@@ -180,15 +182,15 @@ fn bind_component(old_fs: *fs::fs, new_fs: *fs::fs, strpath: str) (void | fs::er
if (fs::islink(st.mode)) {
let linktext = fs::readlink(old_fs, strpath)?;
log::printfln("namespace: ln new/{} -> {}", strpath, linktext);
log::printfln("[namespace/bind] ln new/{} -> {}", strpath, linktext);
fs::symlink(new_fs, linktext, strpath)?;
// TODO: lots of edge cases with symlinks i need to flesh out here.
} else if (fs::isdir(st.mode)) {
log::printfln("namespace: mkdir new/{}", strpath);
log::printfln("[namespace/bind] mkdir new/{}", strpath);
fs::mkdir(new_fs, strpath, st.mode)?;
} else {
// TODO: tune options (optional parameter; default is fs::flag::TRUNC)
log::printfln("namespace: creat new/{}", strpath);
log::printfln("[namespace/bind] creat new/{}", strpath);
fs::create(new_fs, strpath, st.mode)?;
};
};
@@ -196,18 +198,18 @@ fn bind_component(old_fs: *fs::fs, new_fs: *fs::fs, strpath: str) (void | fs::er
// 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));
log::printfln("[namespace] pivot_root {}", new_root);
rtext::check_error("[namespace] cd <new_root>", os::chdir(new_root));
match (stash_old_root) {
case let old: str =>
rtext::check_error("mkdir <stash_old_root>", rt::mkdir(old, 0o755));
rtext::check_error("pivot_root . <stash_old_root>", rtext::pivot_root(".", old));
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));
case void =>
rtext::check_error("pivot_root . .", rtext::pivot_root(".", "."));
rtext::check_error("[namespace] pivot_root . .", rtext::pivot_root(".", "."));
// drop the old rootfs. weird idiom, but documented in `man 2 pivot_root`.
rtext::check_error("umount .", rt::umount2(".", rtext::MNT_DETACH));
rtext::check_error("[namespace] umount .", rt::umount2(".", rtext::MNT_DETACH));
};
rtext::check_error("cd /", os::chdir("/"));
rtext::check_error("[namespace] cd /", os::chdir("/"));
};
fn write_uid_map(uid: unix::uid, gid: unix::gid) void = {

View File

@@ -15,9 +15,11 @@ 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));
// N.B.: use `println(...)` over `printfln("{}: {}")` so that my treelogger
// can extract the context.
case let e: fs::error => log::println(context, ": ", fs::strerror(e));
case let e: os::exec::error => log::println(context, ": ", os::exec::strerror(e));
case let e: rt::errno => log::println(context, ": ", rt::errname(e), ": ", rt::strerror(e));
case => void;
};
};