bunpen: --bunpen-try-user will now raise the capabilities it needs, as part of that

This commit is contained in:
2024-09-07 17:00:34 +00:00
parent 454c109ef8
commit 7ce098f2bb
4 changed files with 112 additions and 26 deletions

View File

@@ -74,9 +74,13 @@ export fn check_int(context: str, what: (int | ...error), fmt_args: fmt::field..
}; };
}; };
export fn swallow(context: str, what: (void | ...error), fmt_args: fmt::field...) void = { // on error => log the error and return false.
// on non-error => return true.
export fn swallow(context: str, what: (void | ...error), fmt_args: fmt::field...) bool = {
return match (what) { return match (what) {
case void => yield void; case void => yield true;
case let e: error => nonfatal(context, e, fmt_args...); case let e: error =>
nonfatal(context, e, fmt_args...);
yield false;
}; };
}; };

View File

@@ -15,6 +15,10 @@ export fn caps_add(cs: *rt::ext::caps, cap: rt::ext::cap) void = {
*cs |= (1 << cap: u64); *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 = { fn caps_contains(cs: rt::ext::caps, cap: rt::ext::cap) bool = {
return (cs & (1 << cap: u64)) != 0; return (cs & (1 << cap: u64)) != 0;
}; };
@@ -27,11 +31,11 @@ export fn capability_restrict(what: *resources) void = {
// caps_add(&needed, rt::ext::cap::SETPCAP); // caps_add(&needed, rt::ext::cap::SETPCAP);
// let needed_now = rt::ext::CAPS_NONE; // let needed_now = rt::ext::CAPS_NONE;
// caps_add(&needed_now, rt::ext::cap::SETPCAP); // caps_add(&needed_now, rt::ext::cap::SETPCAP);
// errors::ext::check("[capability] capset early", rt::ext::capset( // errors::ext::check("[capability] capset early", rt::ext::capset(rt::ext::caps_eip {
// needed_now, // effective // effective = needed_now,
// needed, // permitted // inheritable = needed,
// needed, // inherited // permitted = needed,
// )); // }));
// drop unneeded caps from the bounding set before anything else. // drop unneeded caps from the bounding set before anything else.
// specifically, CAP_SETPCAP is required to *drop* caps from B; // specifically, CAP_SETPCAP is required to *drop* caps from B;
@@ -61,11 +65,11 @@ export fn capability_restrict(what: *resources) void = {
// set I/P precisely as asked, and drop everything from E since we don't need it anymore. // set I/P precisely as asked, and drop everything from E since we don't need it anymore.
// as long as a cap is both I and P, we can add it to Amb, at which point it // as long as a cap is both I and P, we can add it to Amb, at which point it
// will become E at the moment we `exec` into the wrapped program. // will become E at the moment we `exec` into the wrapped program.
errors::ext::check("[capability] capset", rt::ext::capset( errors::ext::check("[capability] capset", rt::ext::capset(rt::ext::caps_eip {
0, // effective effective = rt::ext::CAPS_NONE,
what.caps, // permitted inheritable = what.caps,
what.caps, // inherited permitted = what.caps,
)); }));
for (let cap = rt::ext::CAP_FIRST; cap <= rt::ext::CAP_LAST; cap += 1) { for (let cap = rt::ext::CAP_FIRST; cap <= rt::ext::CAP_LAST; cap += 1) {
if (caps_contains(what.caps, cap)) { if (caps_contains(what.caps, cap)) {

View File

@@ -21,7 +21,7 @@ export fn namespace_restrict(what: *resources) void = {
let gid = unix::getgid(); let gid = unix::getgid();
// unshare as much as possible, by default: // unshare as much as possible, by default:
let what_to_unshare = let what_to_unshare: rt::ext::clone_flags =
rt::ext::clone_flag::NEWCGROUP | rt::ext::clone_flag::NEWCGROUP |
rt::ext::clone_flag::NEWIPC | rt::ext::clone_flag::NEWIPC |
rt::ext::clone_flag::NEWNET | rt::ext::clone_flag::NEWNET |
@@ -41,12 +41,21 @@ export fn namespace_restrict(what: *resources) void = {
if (what.try_users) { if (what.try_users) {
log::println("[namespace] keeping user namespace *if possible*"); log::println("[namespace] keeping user namespace *if possible*");
let unshare_keep_users = what_to_unshare & ~rt::ext::clone_flag::NEWUSER; let unshare_keep_users = what_to_unshare & ~rt::ext::clone_flag::NEWUSER;
match (rt::ext::unshare(unshare_keep_users)) { if (try_unshare(unshare_keep_users)) {
case void => what_to_unshare = 0;
log::println("[namespace] unshared user namespace successfully"); } else {
log::println("[namespace] failed to unshare w/o user namespace. raising caps and trying again");
let raise_caps = rt::ext::CAPS_NONE;
if ((what_to_unshare & rt::ext::clone_flag::NEWNS) != 0) {
caps_add(&raise_caps, rt::ext::cap::SYS_ADMIN);
// i can't find that unsharing the netns requires CAP_NET_ADMIN,
// but empirically, it does (? e.g. seatd)
if ((what_to_unshare & rt::ext::clone_flag::NEWNET) != 0)
caps_add(&raise_caps, rt::ext::cap::NET_ADMIN);
};
if (try_unshare_with(unshare_keep_users, raise_caps)) {
what_to_unshare = 0; what_to_unshare = 0;
case let e: rt::errno => };
errors::ext::swallow("[namespace] unshare user namespace", e);
}; };
}; };
@@ -93,6 +102,45 @@ export fn namespace_restrict(what: *resources) void = {
errors::ext::swallow("namespace: restore $PWD", os::chdir(pwd)); errors::ext::swallow("namespace: restore $PWD", os::chdir(pwd));
}; };
// try to `unshare(flags)`, return true on success
fn try_unshare(flags: rt::ext::clone_flags) bool = {
return match (rt::ext::unshare(flags)) {
case void =>
log::println("[namespace] try_unshare successful");
yield true;
case let e: rt::errno =>
errors::ext::swallow("[namespace] try_unshare", e);
yield false;
};
};
// raise capabilities, unshare, and then restore effective capabilities to as before.
// returns true if we unshared.
fn try_unshare_with(flags: rt::ext::clone_flags, caps: rt::ext::caps) bool = {
let orig_caps = match (rt::ext::capget()) {
case let c: rt::ext::caps_eip => yield c;
case let err: rt::errno =>
errors::ext::swallow("[namespace] raise caps: capget failed", err);
return false;
};
let new_caps = orig_caps;
caps_add_caps(&new_caps.effective, caps);
errors::ext::swallow("[namespace] raise caps", rt::ext::capset(new_caps));
let unshared = try_unshare(flags);
// if we unshared a user ns, the caps in that ns have no relation to the
// original ns. but if we didn't unshare a user, or we failed the unshare,
// then restore caps
if (!unshared || (flags & rt::ext::clone_flag::NEWUSER) == 0) {
errors::ext::check("[namespace] restore caps", rt::ext::capset(orig_caps));
};
return unshared;
};
// everything inside this struct is borrowed // everything inside this struct is borrowed
type ns_ctx = struct { type ns_ctx = struct {
what: *resources, what: *resources,

View File

@@ -268,6 +268,12 @@ export const CAP_LAST: cap = cap::CHECKPOINT_RESTORE;
// e.g. `let c: caps = (1 << cap::SYS_ADMIN)` // e.g. `let c: caps = (1 << cap::SYS_ADMIN)`
export type caps = u64; export type caps = u64;
export type caps_eip = struct {
effective: caps,
permitted: caps,
inheritable: caps,
};
export const CAPS_NONE: caps = 0; export const CAPS_NONE: caps = 0;
export const CAPS_ALL: caps = ( export const CAPS_ALL: caps = (
(1 << cap::AUDIT_CONTROL: u64) | (1 << cap::AUDIT_CONTROL: u64) |
@@ -349,7 +355,7 @@ export fn no_new_privs() (void | rt::errno) = {
// set the Effective, Permitted, and Inheritable capability vectors. // set the Effective, Permitted, and Inheritable capability vectors.
// this does NOT effect the Ambient or Bounding capability sets; // this does NOT effect the Ambient or Bounding capability sets;
// see PR_CAP_AMBIENT and PR_CAPBSET_DROP for that. // see PR_CAP_AMBIENT and PR_CAPBSET_DROP for that.
export fn capset(eff: caps, prm: caps, inh: caps) (void | rt::errno) = { export fn capset(eip: caps_eip) (void | rt::errno) = {
let hdr = user_cap_header { let hdr = user_cap_header {
version = _LINUX_CAPABILITY_VERSION_3, version = _LINUX_CAPABILITY_VERSION_3,
pid = 0, //< 0 means to apply this to self pid = 0, //< 0 means to apply this to self
@@ -357,12 +363,12 @@ export fn capset(eff: caps, prm: caps, inh: caps) (void | rt::errno) = {
// the API has some legacy such that we have to provide the lower 32 caps and // the API has some legacy such that we have to provide the lower 32 caps and
// the upper 32 separately: // the upper 32 separately:
let eff_lo = (eff & 0xFFFFFFFF): u32; let eff_lo = (eip.effective & 0xFFFFFFFF): u32;
let eff_hi = ((eff >> 32) & 0xFFFFFFFF): u32; let eff_hi = ((eip.effective >> 32) & 0xFFFFFFFF): u32;
let prm_lo = (prm & 0xFFFFFFFF): u32; let prm_lo = (eip.permitted & 0xFFFFFFFF): u32;
let prm_hi = ((prm >> 32) & 0xFFFFFFFF): u32; let prm_hi = ((eip.permitted >> 32) & 0xFFFFFFFF): u32;
let inh_lo = (inh & 0xFFFFFFFF): u32; let inh_lo = (eip.inheritable & 0xFFFFFFFF): u32;
let inh_hi = ((inh >> 32) & 0xFFFFFFFF): u32; let inh_hi = ((eip.inheritable >> 32) & 0xFFFFFFFF): u32;
let data: [2]user_cap_data = [ let data: [2]user_cap_data = [
(user_cap_data { (user_cap_data {
effective = eff_lo, effective = eff_lo,
@@ -378,6 +384,30 @@ export fn capset(eff: caps, prm: caps, inh: caps) (void | rt::errno) = {
return syscall_0_on_success(rt::SYS_capset, &hdr: uintptr, &data: uintptr); return syscall_0_on_success(rt::SYS_capset, &hdr: uintptr, &data: uintptr);
}; };
export fn capget() (caps_eip | rt::errno) = {
let hdr = user_cap_header {
version = _LINUX_CAPABILITY_VERSION_3,
pid = 0, //< 0 means to apply this to self
};
// the API has some legacy such that we have to receive the lower 32 caps and
// the upper 32 separately:
let data: [2]user_cap_data = [
(user_cap_data {
...
})
...
];
syscall_0_on_success(rt::SYS_capget, &hdr: uintptr, &data: uintptr)?;
let lo = data[0];
let hi = data[1];
return caps_eip {
effective = lo.effective + (hi.effective: u64 << 32),
permitted = lo.permitted + (hi.permitted: u64 << 32),
inheritable = lo.inheritable + (hi.inheritable: u64 << 32),
};
};
// add the provided capability to the ambient set. // add the provided capability to the ambient set.
// the capability must already be in the permitted and inheritable sets (see `capset`). // the capability must already be in the permitted and inheritable sets (see `capset`).
// will fail if PR_CAP_AMBIENT_LOWER secure bit has been set. // will fail if PR_CAP_AMBIENT_LOWER secure bit has been set.