bunpen: better (still incomplete) capability boxing
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
// vim: set shiftwidth=2 :
|
||||
use errors;
|
||||
|
||||
export type autodetect = enum {
|
||||
EXISTING,
|
||||
@@ -8,14 +9,14 @@ export type autodetect = enum {
|
||||
PARENT,
|
||||
};
|
||||
|
||||
fn autodetect_fromstr(v: str) (autodetect | error) = {
|
||||
fn autodetect_fromstr(v: str) (autodetect | errors::invalid) = {
|
||||
return switch (v) {
|
||||
case "existing" => yield autodetect::EXISTING;
|
||||
case "existingFile" => yield autodetect::EXISTING_FILE;
|
||||
case "existingFileOrParent" => yield autodetect::EXISTING_FILE_OR_PARENT;
|
||||
case "existingOrParent" => yield autodetect::EXISTING_OR_PARENT;
|
||||
case "parent" => yield autodetect::PARENT;
|
||||
case => yield error;
|
||||
case => yield errors::invalid;
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -1,129 +0,0 @@
|
||||
// vim: set shiftwidth=2 :
|
||||
|
||||
use ascii;
|
||||
use rt::ext;
|
||||
use strings;
|
||||
|
||||
fn capability_fromstr(v: str) (rt::ext::cap | error) = {
|
||||
// strip leading CAP_ and allow either form.
|
||||
if (len(v) > 4 && ascii::strcasecmp(strings::sub(v, 0, 4), "CAP_") == 0)
|
||||
v = strings::sub(v, 4);
|
||||
|
||||
if (ascii::strcasecmp(v, "AUDIT_CONTROL") == 0)
|
||||
return rt::ext::cap::AUDIT_CONTROL;
|
||||
if (ascii::strcasecmp(v, "AUDIT_READ") == 0)
|
||||
return rt::ext::cap::AUDIT_READ;
|
||||
if (ascii::strcasecmp(v, "AUDIT_WRITE") == 0)
|
||||
return rt::ext::cap::AUDIT_WRITE;
|
||||
if (ascii::strcasecmp(v, "BLOCK_SUSPEND") == 0)
|
||||
return rt::ext::cap::BLOCK_SUSPEND;
|
||||
if (ascii::strcasecmp(v, "BPF") == 0)
|
||||
return rt::ext::cap::BPF;
|
||||
if (ascii::strcasecmp(v, "CHECKPOINT_RESTORE") == 0)
|
||||
return rt::ext::cap::CHECKPOINT_RESTORE;
|
||||
if (ascii::strcasecmp(v, "CHOWN") == 0)
|
||||
return rt::ext::cap::CHOWN;
|
||||
if (ascii::strcasecmp(v, "DAC_OVERRIDE") == 0)
|
||||
return rt::ext::cap::DAC_OVERRIDE;
|
||||
if (ascii::strcasecmp(v, "DAC_READ_SEARCH") == 0)
|
||||
return rt::ext::cap::DAC_READ_SEARCH;
|
||||
if (ascii::strcasecmp(v, "FOWNER") == 0)
|
||||
return rt::ext::cap::FOWNER;
|
||||
if (ascii::strcasecmp(v, "FSETID") == 0)
|
||||
return rt::ext::cap::FSETID;
|
||||
if (ascii::strcasecmp(v, "IPC_LOCK") == 0)
|
||||
return rt::ext::cap::IPC_LOCK;
|
||||
if (ascii::strcasecmp(v, "IPC_OWNER") == 0)
|
||||
return rt::ext::cap::IPC_OWNER;
|
||||
if (ascii::strcasecmp(v, "KILL") == 0)
|
||||
return rt::ext::cap::KILL;
|
||||
if (ascii::strcasecmp(v, "LEASE") == 0)
|
||||
return rt::ext::cap::LEASE;
|
||||
if (ascii::strcasecmp(v, "LINUX_IMMUTABLE") == 0)
|
||||
return rt::ext::cap::LINUX_IMMUTABLE;
|
||||
if (ascii::strcasecmp(v, "MAC_ADMIN") == 0)
|
||||
return rt::ext::cap::MAC_ADMIN;
|
||||
if (ascii::strcasecmp(v, "MAC_OVERRIDE") == 0)
|
||||
return rt::ext::cap::MAC_OVERRIDE;
|
||||
if (ascii::strcasecmp(v, "MKNOD") == 0)
|
||||
return rt::ext::cap::MKNOD;
|
||||
if (ascii::strcasecmp(v, "NET_ADMIN") == 0)
|
||||
return rt::ext::cap::NET_ADMIN;
|
||||
if (ascii::strcasecmp(v, "NET_BIND_SERVICE") == 0)
|
||||
return rt::ext::cap::NET_BIND_SERVICE;
|
||||
if (ascii::strcasecmp(v, "NET_BROADCAST") == 0)
|
||||
return rt::ext::cap::NET_BROADCAST;
|
||||
if (ascii::strcasecmp(v, "NET_RAW") == 0)
|
||||
return rt::ext::cap::NET_RAW;
|
||||
if (ascii::strcasecmp(v, "PERFMON") == 0)
|
||||
return rt::ext::cap::PERFMON;
|
||||
if (ascii::strcasecmp(v, "SETFCAP") == 0)
|
||||
return rt::ext::cap::SETFCAP;
|
||||
if (ascii::strcasecmp(v, "SETGID") == 0)
|
||||
return rt::ext::cap::SETGID;
|
||||
if (ascii::strcasecmp(v, "SETPCAP") == 0)
|
||||
return rt::ext::cap::SETPCAP;
|
||||
if (ascii::strcasecmp(v, "SETUID") == 0)
|
||||
return rt::ext::cap::SETUID;
|
||||
if (ascii::strcasecmp(v, "SYS_ADMIN") == 0)
|
||||
return rt::ext::cap::SYS_ADMIN;
|
||||
if (ascii::strcasecmp(v, "SYS_BOOT") == 0)
|
||||
return rt::ext::cap::SYS_BOOT;
|
||||
if (ascii::strcasecmp(v, "SYS_CHROOT") == 0)
|
||||
return rt::ext::cap::SYS_CHROOT;
|
||||
if (ascii::strcasecmp(v, "SYS_MODULE") == 0)
|
||||
return rt::ext::cap::SYS_MODULE;
|
||||
if (ascii::strcasecmp(v, "SYS_NICE") == 0)
|
||||
return rt::ext::cap::SYS_NICE;
|
||||
if (ascii::strcasecmp(v, "SYS_PACCT") == 0)
|
||||
return rt::ext::cap::SYS_PACCT;
|
||||
if (ascii::strcasecmp(v, "SYS_PTRACE") == 0)
|
||||
return rt::ext::cap::SYS_PTRACE;
|
||||
if (ascii::strcasecmp(v, "SYS_RAWIO") == 0)
|
||||
return rt::ext::cap::SYS_RAWIO;
|
||||
if (ascii::strcasecmp(v, "SYS_RESOURCE") == 0)
|
||||
return rt::ext::cap::SYS_RESOURCE;
|
||||
if (ascii::strcasecmp(v, "SYS_TIME") == 0)
|
||||
return rt::ext::cap::SYS_TIME;
|
||||
if (ascii::strcasecmp(v, "SYS_TTY_CONFIG") == 0)
|
||||
return rt::ext::cap::SYS_TTY_CONFIG;
|
||||
if (ascii::strcasecmp(v, "SYSLOG") == 0)
|
||||
return rt::ext::cap::SYSLOG;
|
||||
if (ascii::strcasecmp(v, "WAKE_ALARM") == 0)
|
||||
return rt::ext::cap::WAKE_ALARM;
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
// return true if `s` parses to `expect`
|
||||
fn _parse_eq(s: str, expect: (rt::ext::cap | error)) bool = {
|
||||
let got = capability_fromstr(s);
|
||||
return match (expect) {
|
||||
case let c: rt::ext::cap => yield match (got) {
|
||||
case let c2: rt::ext::cap => yield c2 == c;
|
||||
case => yield false;
|
||||
};
|
||||
case error => yield match (got) {
|
||||
case error => yield true; // both errors
|
||||
case => yield false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@test fn cap_from_str_good() void = {
|
||||
assert(_parse_eq("SYS_ADMIN", rt::ext::cap::SYS_ADMIN));
|
||||
assert(_parse_eq("CAP_SYS_ADMIN", rt::ext::cap::SYS_ADMIN));
|
||||
|
||||
assert(_parse_eq("sys_admin", rt::ext::cap::SYS_ADMIN));
|
||||
assert(_parse_eq("cap_sys_admin", rt::ext::cap::SYS_ADMIN));
|
||||
|
||||
assert(_parse_eq("CAP_sys_admin", rt::ext::cap::SYS_ADMIN));
|
||||
assert(_parse_eq("cap_SYS_ADMIN", rt::ext::cap::SYS_ADMIN));
|
||||
};
|
||||
|
||||
@test fn cap_from_str_bad() void = {
|
||||
assert(_parse_eq("CAP_SYS_ADMIN_AND_MORE", error));
|
||||
assert(_parse_eq("SYS_ADMIN_CAP", error));
|
||||
assert(_parse_eq("SYS", error));
|
||||
assert(_parse_eq("", error));
|
||||
};
|
@@ -1,10 +1,9 @@
|
||||
// vim: set shiftwidth=2 :
|
||||
use errors;
|
||||
use fmt;
|
||||
use os;
|
||||
use rt::ext;
|
||||
|
||||
export type error = !void;
|
||||
|
||||
export type cli_opts = struct {
|
||||
autodetect: (void | autodetect),
|
||||
// command to `exec` within the sandbox
|
||||
@@ -78,7 +77,7 @@ export fn usage() void = {
|
||||
// fmt::println(" act as though the provided arg string appeared at the end of the CLI")!;
|
||||
};
|
||||
|
||||
export fn parse_args(args: []str) (cli_opts | error) = {
|
||||
export fn parse_args(args: []str) (cli_opts | errors::invalid) = {
|
||||
let parsed = cli_opts { autodetect = void, ... };
|
||||
|
||||
for (let idx: size = 0; idx < len(args); idx += 1) {
|
||||
@@ -110,17 +109,17 @@ export fn parse_args(args: []str) (cli_opts | error) = {
|
||||
return parsed;
|
||||
};
|
||||
|
||||
fn expect_arg(name: str, value: nullable *str) (str | error) = {
|
||||
fn expect_arg(name: str, value: nullable *str) (str | errors::invalid) = {
|
||||
return match (value) {
|
||||
case null => yield error;
|
||||
case null => yield errors::invalid;
|
||||
case let v: *str => yield *v;
|
||||
};
|
||||
};
|
||||
|
||||
fn parse_caparg(into: *cli_opts, arg: str) (void | error) = {
|
||||
fn parse_caparg(into: *cli_opts, arg: str) (void | errors::invalid) = {
|
||||
if (arg == "all") {
|
||||
into.keep_all_caps = true;
|
||||
} else {
|
||||
append(into.keep_caps, capability_fromstr(arg)?);
|
||||
append(into.keep_caps, rt::ext::cap_fromstr(arg)?);
|
||||
};
|
||||
};
|
||||
|
@@ -1,5 +1,6 @@
|
||||
// vim: set shiftwidth=2 :
|
||||
use config;
|
||||
use errors;
|
||||
use errors::ext;
|
||||
use log;
|
||||
use log::tree;
|
||||
@@ -30,7 +31,7 @@ export fn main() void = {
|
||||
|
||||
let name = os::args[0];
|
||||
let opts = match (config::parse_args(os::args[1..])) {
|
||||
case config::error =>
|
||||
case errors::invalid =>
|
||||
config::usage();
|
||||
os::exit(1);
|
||||
case let other: config::cli_opts => yield other;
|
||||
@@ -46,10 +47,14 @@ export fn main() void = {
|
||||
case let other: config::cli_request => yield other;
|
||||
};
|
||||
|
||||
errors::ext::check("no_new_privs", rt::ext::no_new_privs());
|
||||
restrict::namespace_restrict(&req.resources);
|
||||
if (!req.resources.all_caps)
|
||||
if (req.resources.all_caps) {
|
||||
// TODO: this probably isn't what i want? i think this actually results in having no caps
|
||||
log::printfln("not restricting capabilities");
|
||||
} else {
|
||||
restrict::capability_restrict(&req.resources);
|
||||
};
|
||||
errors::ext::check("no_new_privs", rt::ext::no_new_privs());
|
||||
// XXX: landlock prevents other sandboxers like `bwrap` from executing,
|
||||
// because it forbids all future `mount` syscalls. so don't landlock.
|
||||
// restrict::landlock_restrict(&req.resources);
|
||||
|
@@ -15,12 +15,42 @@ export fn caps_add(cs: *rt::ext::caps, cap: rt::ext::cap) void = {
|
||||
*cs |= (1 << cap: u64);
|
||||
};
|
||||
|
||||
fn caps_contains(cs: *rt::ext::caps, cap: rt::ext::cap) bool = {
|
||||
return (*cs & (1 << cap: u64)) != 0;
|
||||
};
|
||||
|
||||
export fn capability_restrict(what: *resources) void = {
|
||||
// TODO: call PR_CAP_AMBIENT and PR_CAPBSET_DROP
|
||||
errors::ext::check("capability", rt::ext::capset(
|
||||
what.caps, // effective
|
||||
what.caps, // permitted
|
||||
what.caps, // inherited
|
||||
));
|
||||
|
||||
// TODO: unless we acquire `CAP_SETPCAP`, all the `drop bounding` calls fail
|
||||
for (let cap = rt::ext::CAP_FIRST; cap <= rt::ext::CAP_LAST; cap += 1) {
|
||||
let cap_str = rt::ext::cap_tostring(cap);
|
||||
if (caps_contains(&what.caps, cap)) {
|
||||
log::printfln("[capability/restrict] raising {}", cap_str);
|
||||
errors::ext::swallow(
|
||||
"[capability] raise ambient {}",
|
||||
rt::ext::cap_ambient_raise(cap),
|
||||
cap_str,
|
||||
);
|
||||
} else {
|
||||
log::printfln("[capability/restrict] lowering {}", cap_str);
|
||||
// `swallow` when raising caps but `check` when lowering because messing
|
||||
// up the latter means running with too many privs.
|
||||
errors::ext::check(
|
||||
"[capability] drop ambient {}",
|
||||
rt::ext::cap_ambient_lower(cap),
|
||||
cap_str,
|
||||
);
|
||||
errors::ext::check(
|
||||
"[capability] drop bounding {}",
|
||||
rt::ext::capbset_drop(cap),
|
||||
cap_str,
|
||||
);
|
||||
};
|
||||
};
|
||||
log::println("capability restrictions activated");
|
||||
};
|
||||
|
@@ -1,5 +1,8 @@
|
||||
// vim: set shiftwidth=2 :
|
||||
use ascii;
|
||||
use errors;
|
||||
use rt;
|
||||
use strings;
|
||||
|
||||
export type cap = enum {
|
||||
// In a system with the [_POSIX_CHOWN_RESTRICTED] option defined, this
|
||||
@@ -258,6 +261,9 @@ export type cap = enum {
|
||||
// Allow writing to ns_last_pid
|
||||
CHECKPOINT_RESTORE = 40,
|
||||
};
|
||||
// iterate all caps via `for (let c = CAP_FIRST; c <= CAP_LAST; c += 1)`
|
||||
export const CAP_FIRST: cap = cap::CHOWN;
|
||||
export const CAP_LAST: cap = cap::CHECKPOINT_RESTORE;
|
||||
// bitset of the above. cap::FOO corresponds to the *index* of a bit here.
|
||||
// e.g. `let c: caps = (1 << cap::SYS_ADMIN)`
|
||||
export type caps = u64;
|
||||
@@ -342,3 +348,179 @@ export fn cap_ambient_lower(c: cap) (void | rt::errno) = {
|
||||
export fn capbset_drop(c: cap) (void | rt::errno) = {
|
||||
rt::prctl(rt::PR_CAPBSET_DROP, c: u64, 0, 0, 0)?;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export fn cap_tostring(c: cap) str = {
|
||||
switch (c) {
|
||||
case cap::AUDIT_CONTROL => return "CAP_AUDIT_CONTROL";
|
||||
case cap::AUDIT_READ => return "CAP_AUDIT_READ";
|
||||
case cap::AUDIT_WRITE => return "CAP_AUDIT_WRITE";
|
||||
case cap::BLOCK_SUSPEND => return "CAP_BLOCK_SUSPEND";
|
||||
case cap::BPF => return "CAP_BPF";
|
||||
case cap::CHECKPOINT_RESTORE => return "CAP_CHECKPOINT_RESTORE";
|
||||
case cap::CHOWN => return "CAP_CHOWN";
|
||||
case cap::DAC_OVERRIDE => return "CAP_DAC_OVERRIDE";
|
||||
case cap::DAC_READ_SEARCH => return "CAP_DAC_READ_SEARCH";
|
||||
case cap::FOWNER => return "CAP_FOWNER";
|
||||
case cap::FSETID => return "CAP_FSETID";
|
||||
case cap::IPC_LOCK => return "CAP_IPC_LOCK";
|
||||
case cap::IPC_OWNER => return "CAP_IPC_OWNER";
|
||||
case cap::KILL => return "CAP_KILL";
|
||||
case cap::LEASE => return "CAP_LEASE";
|
||||
case cap::LINUX_IMMUTABLE => return "CAP_LINUX_IMMUTABLE";
|
||||
case cap::MAC_ADMIN => return "CAP_MAC_ADMIN";
|
||||
case cap::MAC_OVERRIDE => return "CAP_MAC_OVERRIDE";
|
||||
case cap::MKNOD => return "CAP_MKNOD";
|
||||
case cap::NET_ADMIN => return "CAP_NET_ADMIN";
|
||||
case cap::NET_BIND_SERVICE => return "CAP_NET_BIND_SERVICE";
|
||||
case cap::NET_BROADCAST => return "CAP_NET_BROADCAST";
|
||||
case cap::NET_RAW => return "CAP_NET_RAW";
|
||||
case cap::PERFMON => return "CAP_PERFMON";
|
||||
case cap::SETFCAP => return "CAP_SETFCAP";
|
||||
case cap::SETGID => return "CAP_SETGID";
|
||||
case cap::SETPCAP => return "CAP_SETPCAP";
|
||||
case cap::SETUID => return "CAP_SETUID";
|
||||
case cap::SYSLOG => return "CAP_SYSLOG";
|
||||
case cap::SYS_ADMIN => return "CAP_SYS_ADMIN";
|
||||
case cap::SYS_BOOT => return "CAP_SYS_BOOT";
|
||||
case cap::SYS_CHROOT => return "CAP_SYS_CHROOT";
|
||||
case cap::SYS_MODULE => return "CAP_SYS_MODULE";
|
||||
case cap::SYS_NICE => return "CAP_SYS_NICE";
|
||||
case cap::SYS_PACCT => return "CAP_SYS_PACCT";
|
||||
case cap::SYS_PTRACE => return "CAP_SYS_PTRACE";
|
||||
case cap::SYS_RAWIO => return "CAP_SYS_RAWIO";
|
||||
case cap::SYS_RESOURCE => return "CAP_SYS_RESOURCE";
|
||||
case cap::SYS_TIME => return "CAP_SYS_TIME";
|
||||
case cap::SYS_TTY_CONFIG => return "CAP_SYS_TTY_CONFIG";
|
||||
case cap::WAKE_ALARM => return "CAP_WAKE_ALARM";
|
||||
};
|
||||
};
|
||||
|
||||
export fn cap_fromstr(v: str) (cap | errors::invalid) = {
|
||||
// strip leading CAP_ and allow either form.
|
||||
if (len(v) > 4 && ascii::strcasecmp(strings::sub(v, 0, 4), "CAP_") == 0)
|
||||
v = strings::sub(v, 4);
|
||||
|
||||
if (ascii::strcasecmp(v, "AUDIT_CONTROL") == 0)
|
||||
return cap::AUDIT_CONTROL;
|
||||
if (ascii::strcasecmp(v, "AUDIT_READ") == 0)
|
||||
return cap::AUDIT_READ;
|
||||
if (ascii::strcasecmp(v, "AUDIT_WRITE") == 0)
|
||||
return cap::AUDIT_WRITE;
|
||||
if (ascii::strcasecmp(v, "BLOCK_SUSPEND") == 0)
|
||||
return cap::BLOCK_SUSPEND;
|
||||
if (ascii::strcasecmp(v, "BPF") == 0)
|
||||
return cap::BPF;
|
||||
if (ascii::strcasecmp(v, "CHECKPOINT_RESTORE") == 0)
|
||||
return cap::CHECKPOINT_RESTORE;
|
||||
if (ascii::strcasecmp(v, "CHOWN") == 0)
|
||||
return cap::CHOWN;
|
||||
if (ascii::strcasecmp(v, "DAC_OVERRIDE") == 0)
|
||||
return cap::DAC_OVERRIDE;
|
||||
if (ascii::strcasecmp(v, "DAC_READ_SEARCH") == 0)
|
||||
return cap::DAC_READ_SEARCH;
|
||||
if (ascii::strcasecmp(v, "FOWNER") == 0)
|
||||
return cap::FOWNER;
|
||||
if (ascii::strcasecmp(v, "FSETID") == 0)
|
||||
return cap::FSETID;
|
||||
if (ascii::strcasecmp(v, "IPC_LOCK") == 0)
|
||||
return cap::IPC_LOCK;
|
||||
if (ascii::strcasecmp(v, "IPC_OWNER") == 0)
|
||||
return cap::IPC_OWNER;
|
||||
if (ascii::strcasecmp(v, "KILL") == 0)
|
||||
return cap::KILL;
|
||||
if (ascii::strcasecmp(v, "LEASE") == 0)
|
||||
return cap::LEASE;
|
||||
if (ascii::strcasecmp(v, "LINUX_IMMUTABLE") == 0)
|
||||
return cap::LINUX_IMMUTABLE;
|
||||
if (ascii::strcasecmp(v, "MAC_ADMIN") == 0)
|
||||
return cap::MAC_ADMIN;
|
||||
if (ascii::strcasecmp(v, "MAC_OVERRIDE") == 0)
|
||||
return cap::MAC_OVERRIDE;
|
||||
if (ascii::strcasecmp(v, "MKNOD") == 0)
|
||||
return cap::MKNOD;
|
||||
if (ascii::strcasecmp(v, "NET_ADMIN") == 0)
|
||||
return cap::NET_ADMIN;
|
||||
if (ascii::strcasecmp(v, "NET_BIND_SERVICE") == 0)
|
||||
return cap::NET_BIND_SERVICE;
|
||||
if (ascii::strcasecmp(v, "NET_BROADCAST") == 0)
|
||||
return cap::NET_BROADCAST;
|
||||
if (ascii::strcasecmp(v, "NET_RAW") == 0)
|
||||
return cap::NET_RAW;
|
||||
if (ascii::strcasecmp(v, "PERFMON") == 0)
|
||||
return cap::PERFMON;
|
||||
if (ascii::strcasecmp(v, "SETFCAP") == 0)
|
||||
return cap::SETFCAP;
|
||||
if (ascii::strcasecmp(v, "SETGID") == 0)
|
||||
return cap::SETGID;
|
||||
if (ascii::strcasecmp(v, "SETPCAP") == 0)
|
||||
return cap::SETPCAP;
|
||||
if (ascii::strcasecmp(v, "SETUID") == 0)
|
||||
return cap::SETUID;
|
||||
if (ascii::strcasecmp(v, "SYS_ADMIN") == 0)
|
||||
return cap::SYS_ADMIN;
|
||||
if (ascii::strcasecmp(v, "SYS_BOOT") == 0)
|
||||
return cap::SYS_BOOT;
|
||||
if (ascii::strcasecmp(v, "SYS_CHROOT") == 0)
|
||||
return cap::SYS_CHROOT;
|
||||
if (ascii::strcasecmp(v, "SYS_MODULE") == 0)
|
||||
return cap::SYS_MODULE;
|
||||
if (ascii::strcasecmp(v, "SYS_NICE") == 0)
|
||||
return cap::SYS_NICE;
|
||||
if (ascii::strcasecmp(v, "SYS_PACCT") == 0)
|
||||
return cap::SYS_PACCT;
|
||||
if (ascii::strcasecmp(v, "SYS_PTRACE") == 0)
|
||||
return cap::SYS_PTRACE;
|
||||
if (ascii::strcasecmp(v, "SYS_RAWIO") == 0)
|
||||
return cap::SYS_RAWIO;
|
||||
if (ascii::strcasecmp(v, "SYS_RESOURCE") == 0)
|
||||
return cap::SYS_RESOURCE;
|
||||
if (ascii::strcasecmp(v, "SYS_TIME") == 0)
|
||||
return cap::SYS_TIME;
|
||||
if (ascii::strcasecmp(v, "SYS_TTY_CONFIG") == 0)
|
||||
return cap::SYS_TTY_CONFIG;
|
||||
if (ascii::strcasecmp(v, "SYSLOG") == 0)
|
||||
return cap::SYSLOG;
|
||||
if (ascii::strcasecmp(v, "WAKE_ALARM") == 0)
|
||||
return cap::WAKE_ALARM;
|
||||
|
||||
return errors::invalid;
|
||||
};
|
||||
|
||||
// return true if `s` parses to `expect`
|
||||
fn _parse_eq(s: str, expect: (cap | errors::invalid)) bool = {
|
||||
let got = cap_fromstr(s);
|
||||
return match (expect) {
|
||||
case let c: cap => yield match (got) {
|
||||
case let c2: cap => yield c2 == c;
|
||||
case => yield false;
|
||||
};
|
||||
case errors::invalid => yield match (got) {
|
||||
case errors::invalid => yield true;
|
||||
case => yield false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@test fn cap_from_str_good() void = {
|
||||
assert(_parse_eq("SYS_ADMIN", cap::SYS_ADMIN));
|
||||
assert(_parse_eq("CAP_SYS_ADMIN", cap::SYS_ADMIN));
|
||||
|
||||
assert(_parse_eq("sys_admin", cap::SYS_ADMIN));
|
||||
assert(_parse_eq("cap_sys_admin", cap::SYS_ADMIN));
|
||||
|
||||
assert(_parse_eq("CAP_sys_admin", cap::SYS_ADMIN));
|
||||
assert(_parse_eq("cap_SYS_ADMIN", cap::SYS_ADMIN));
|
||||
};
|
||||
|
||||
@test fn cap_from_str_bad() void = {
|
||||
assert(_parse_eq("CAP_SYS_ADMIN_AND_MORE", errors::invalid));
|
||||
assert(_parse_eq("SYS_ADMIN_CAP", errors::invalid));
|
||||
assert(_parse_eq("SYS", errors::invalid));
|
||||
assert(_parse_eq("", errors::invalid));
|
||||
};
|
||||
|
||||
@test fn cap_roundtrip() void = {
|
||||
assert(_parse_eq(cap_tostring(cap::SYS_ADMIN), cap::SYS_ADMIN));
|
||||
};
|
||||
|
Reference in New Issue
Block a user