refactor: capabilities - add unified launch routine

This commit is contained in:
daniel committed 2026-09-09 12:35:00 +02:00
1 parent 638dd9ee06
commit 5b90869101
9 files changed
+78 -61

No files matched your search

+1 -12
View File
@@ -269,19 +269,8 @@ static inline void terminal_init(TerminalState* t, int cols, int rows) {
terminal_init_cells(t, cols, rows, TERM_MAX_SCROLLBACK);
t->cursor_visible = true;
// A console shell is a session launcher, exactly like the desktop, and is
// granted on exactly the same terms: 0:/config/capabilities.toml decides
// what shell.elf receives, and the kernel clamps that to what this session
// was actually delegated. Deriving the grant here instead would be a
// second, hardcoded list of "capabilities a console may confer" -- one the
// table cannot see and cannot keep in step with.
static constexpr const char* kShellPath = "0:/os/shell.elf";
montauk::abi::SpawnCapabilities caps =
montauk::caps::for_binary(kShellPath, montauk::caps::self_delegable());
t->child_pid = (caps.permitted != 0)
? montauk::spawn_redir_with_caps(kShellPath, nullptr, caps)
: montauk::spawn_redir(kShellPath);
t->child_pid = montauk::caps::launch(kShellPath, {.redirected = true});
if (t->child_pid > 0)
montauk::childio_settermsz(t->child_pid, cols, rows);
}
+37 -19
View File
@@ -42,23 +42,17 @@ namespace caps {
inline uint64_t bit_for_name(const char* name) {
if (name == nullptr || name[0] == '\0') return 0;
// "all" means "everything this launcher may pass on", which the
// caller-delegable clamp in for_binary() then narrows. It excludes
// CAP_SYSTEM_IMAGE: authority to rewrite a program image is never
// something a wildcard should hand out, only an explicit name.
// "all" means "everything this launcher may pass on".
if (montauk::streq(name, "all"))
return montauk::abi::CAP_ALL & ~montauk::abi::CAP_SYSTEM_IMAGE;
for (const auto& entry : NAMES) {
if (montauk::streq(entry.name, name)) return entry.bit;
}
// Unknown names are ignored rather than rejected. Failing closed
// costs a program some authority; failing open would hand out
// authority nobody asked for.
return 0;
}
// Read an array-of-strings key into a capability mask. A missing key is
// an empty mask, which is the correct default for an absent grant.
// Read an array-of-strings key into a capability mask.
inline uint64_t mask_from_key(const montauk::toml::Doc& doc, const char* key) {
montauk::toml::Value* arr = doc.get_array(key);
if (arr == nullptr) return 0;
@@ -138,10 +132,6 @@ namespace caps {
}
// The calling process's own capability masks.
//
// There is no syscall to ask "what am I?", so this scans the process table
// for our own PID. The buffer is heap-allocated because ProcInfo is large
// enough that MAX_SCAN_PROCS of them would be a ~29 KB stack frame.
inline bool self(montauk::abi::SpawnCapabilities& out) {
out = {0, 0, 0};
@@ -172,16 +162,11 @@ namespace caps {
}
// Build a spawn request for `binary_path`, clamped to what the caller may
// actually delegate. The kernel enforces the same bound; clamping here
// means a launcher that holds less authority than the table declares
// degrades to a reduced grant instead of failing the spawn outright.
// actually delegate.
inline montauk::abi::SpawnCapabilities for_binary(const char* binary_path,
uint64_t caller_delegable) {
montauk::abi::SpawnCapabilities caps{0, 0, 0};
// A caller with nothing to delegate cannot produce a non-empty grant,
// so skip the file read entirely. This is the common case: every
// unprivileged session, on every launch.
if (caller_delegable == 0) return caps;
if (!lookup(binary_path, caps)) return caps;
@@ -192,5 +177,38 @@ namespace caps {
return caps;
}
// The calling process's delegable set, cached.
inline uint64_t my_delegable() {
static uint64_t cached = 0;
static bool loaded = false;
if (!loaded) {
cached = self_delegable();
loaded = true;
}
return cached;
}
struct LaunchOptions {
const char* args = nullptr;
// Owner identity for the child. Requires CAP_USER_ADMIN and is
// reserved for session creators (init, login);
const char* user = nullptr;
// True for a console host that reads and writes the child's I/O
// through the childio calls rather than sharing its own.
bool redirected = false;
};
// Unified launch helper.
inline int launch(const char* path, const LaunchOptions& opt = {}) {
if (path == nullptr || path[0] == '\0') return -1;
if (opt.redirected && opt.user != nullptr) return -1;
montauk::abi::SpawnCapabilities caps = for_binary(path, my_delegable());
return opt.redirected
? spawn_redir_with_caps(path, opt.args, caps)
: spawn_with_caps(path, opt.args, opt.user, caps);
}
} // namespace caps
} // namespace montauk