bunpen: --bunpen-try-user will now raise the capabilities it needs, as part of that
This commit is contained in:
@@ -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) {
|
||||
case void => yield void;
|
||||
case let e: error => nonfatal(context, e, fmt_args...);
|
||||
case void => yield true;
|
||||
case let e: error =>
|
||||
nonfatal(context, e, fmt_args...);
|
||||
yield false;
|
||||
};
|
||||
};
|
||||
|
@@ -15,6 +15,10 @@ 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;
|
||||
};
|
||||
@@ -27,11 +31,11 @@ export fn capability_restrict(what: *resources) void = {
|
||||
// caps_add(&needed, rt::ext::cap::SETPCAP);
|
||||
// let needed_now = rt::ext::CAPS_NONE;
|
||||
// caps_add(&needed_now, rt::ext::cap::SETPCAP);
|
||||
// errors::ext::check("[capability] capset early", rt::ext::capset(
|
||||
// needed_now, // effective
|
||||
// needed, // permitted
|
||||
// needed, // inherited
|
||||
// ));
|
||||
// errors::ext::check("[capability] capset early", rt::ext::capset(rt::ext::caps_eip {
|
||||
// effective = needed_now,
|
||||
// inheritable = needed,
|
||||
// permitted = needed,
|
||||
// }));
|
||||
|
||||
// drop unneeded caps from the bounding set before anything else.
|
||||
// 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.
|
||||
// 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.
|
||||
errors::ext::check("[capability] capset", rt::ext::capset(
|
||||
0, // effective
|
||||
what.caps, // permitted
|
||||
what.caps, // inherited
|
||||
));
|
||||
errors::ext::check("[capability] capset", rt::ext::capset(rt::ext::caps_eip {
|
||||
effective = rt::ext::CAPS_NONE,
|
||||
inheritable = what.caps,
|
||||
permitted = what.caps,
|
||||
}));
|
||||
|
||||
for (let cap = rt::ext::CAP_FIRST; cap <= rt::ext::CAP_LAST; cap += 1) {
|
||||
if (caps_contains(what.caps, cap)) {
|
||||
|
@@ -21,7 +21,7 @@ export fn namespace_restrict(what: *resources) void = {
|
||||
let gid = unix::getgid();
|
||||
|
||||
// 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::NEWIPC |
|
||||
rt::ext::clone_flag::NEWNET |
|
||||
@@ -41,12 +41,21 @@ export fn namespace_restrict(what: *resources) void = {
|
||||
if (what.try_users) {
|
||||
log::println("[namespace] keeping user namespace *if possible*");
|
||||
let unshare_keep_users = what_to_unshare & ~rt::ext::clone_flag::NEWUSER;
|
||||
match (rt::ext::unshare(unshare_keep_users)) {
|
||||
case void =>
|
||||
log::println("[namespace] unshared user namespace successfully");
|
||||
if (try_unshare(unshare_keep_users)) {
|
||||
what_to_unshare = 0;
|
||||
} 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;
|
||||
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));
|
||||
};
|
||||
|
||||
// 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
|
||||
type ns_ctx = struct {
|
||||
what: *resources,
|
||||
|
@@ -268,6 +268,12 @@ export const CAP_LAST: cap = cap::CHECKPOINT_RESTORE;
|
||||
// e.g. `let c: caps = (1 << cap::SYS_ADMIN)`
|
||||
export type caps = u64;
|
||||
|
||||
export type caps_eip = struct {
|
||||
effective: caps,
|
||||
permitted: caps,
|
||||
inheritable: caps,
|
||||
};
|
||||
|
||||
export const CAPS_NONE: caps = 0;
|
||||
export const CAPS_ALL: caps = (
|
||||
(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.
|
||||
// this does NOT effect the Ambient or Bounding capability sets;
|
||||
// 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 {
|
||||
version = _LINUX_CAPABILITY_VERSION_3,
|
||||
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 upper 32 separately:
|
||||
let eff_lo = (eff & 0xFFFFFFFF): u32;
|
||||
let eff_hi = ((eff >> 32) & 0xFFFFFFFF): u32;
|
||||
let prm_lo = (prm & 0xFFFFFFFF): u32;
|
||||
let prm_hi = ((prm >> 32) & 0xFFFFFFFF): u32;
|
||||
let inh_lo = (inh & 0xFFFFFFFF): u32;
|
||||
let inh_hi = ((inh >> 32) & 0xFFFFFFFF): u32;
|
||||
let eff_lo = (eip.effective & 0xFFFFFFFF): u32;
|
||||
let eff_hi = ((eip.effective >> 32) & 0xFFFFFFFF): u32;
|
||||
let prm_lo = (eip.permitted & 0xFFFFFFFF): u32;
|
||||
let prm_hi = ((eip.permitted >> 32) & 0xFFFFFFFF): u32;
|
||||
let inh_lo = (eip.inheritable & 0xFFFFFFFF): u32;
|
||||
let inh_hi = ((eip.inheritable >> 32) & 0xFFFFFFFF): u32;
|
||||
let data: [2]user_cap_data = [
|
||||
(user_cap_data {
|
||||
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);
|
||||
};
|
||||
|
||||
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.
|
||||
// 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.
|
||||
|
Reference in New Issue
Block a user