Files
MontaukOS/template/sysroot/include/montauk/capabilities.h
T

218 lines
8.1 KiB
C++

/*
* capabilities.h
* Shared reader for the capability grant table (0:/config/capabilities.toml)
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <montauk/syscall.h>
#include <montauk/toml.h>
#include <montauk/config.h>
#include <montauk/heap.h>
namespace montauk {
namespace caps {
inline constexpr const char* GRANT_CONFIG = "capabilities";
inline constexpr const char* GRANT_PREFIX = "grant.";
struct CapName {
const char* name;
uint64_t bit;
};
// Names as they appear in the config file. Kept in the same order as the
// CAP_* bit definitions in Api/Syscall.hpp.
inline constexpr CapName NAMES[] = {
{"process_admin", montauk::abi::CAP_PROCESS_ADMIN},
{"power_request", montauk::abi::CAP_POWER_REQUEST},
{"power_control", montauk::abi::CAP_POWER_CONTROL},
{"suspend", montauk::abi::CAP_SUSPEND},
{"storage_admin", montauk::abi::CAP_STORAGE_ADMIN},
{"raw_storage", montauk::abi::CAP_RAW_STORAGE},
{"network_admin", montauk::abi::CAP_NETWORK_ADMIN},
{"set_time", montauk::abi::CAP_SET_TIME},
{"user_admin", montauk::abi::CAP_USER_ADMIN},
{"display_admin", montauk::abi::CAP_DISPLAY_ADMIN},
{"device_admin", montauk::abi::CAP_DEVICE_ADMIN},
{"log_read", montauk::abi::CAP_LOG_READ},
{"system_image", montauk::abi::CAP_SYSTEM_IMAGE},
{"compositor", montauk::abi::CAP_COMPOSITOR},
{"brightness", montauk::abi::CAP_BRIGHTNESS},
};
// Never implied by "all": each must be named explicitly in a grant.
inline constexpr uint64_t EXPLICIT_ONLY =
montauk::abi::CAP_SYSTEM_IMAGE | montauk::abi::CAP_COMPOSITOR;
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".
if (montauk::streq(name, "all"))
return montauk::abi::CAP_ALL & ~EXPLICIT_ONLY;
for (const auto& entry : NAMES) {
if (montauk::streq(entry.name, name)) return entry.bit;
}
return 0;
}
// True when a grant for `path` can apply: the program is installed under
// 0:/os or 0:/apps, which cannot be rewritten without system_image. The
// kernel enforces this (Fs::IsSystemImage) by starting any other program
// without capabilities; this copy of the rule is only for display.
inline bool is_installed_path(const char* path) {
if (path == nullptr) return false;
const char* p = path;
uint64_t drive = 0;
if (*p < '0' || *p > '9') return false;
while (*p >= '0' && *p <= '9') drive = drive * 10 + (uint64_t)(*p++ - '0');
if (*p != ':' || drive != 0) return false;
p++;
static const char* const roots[] = {"/os", "/apps"};
for (const char* root : roots) {
int i = 0;
while (root[i]) {
char c = p[i];
if (c >= 'A' && c <= 'Z') c = (char)(c + ('a' - 'A'));
if (c != root[i]) break;
i++;
}
if (root[i] == '\0' && (p[i] == '\0' || p[i] == '/')) return true;
}
return false;
}
// 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;
uint64_t mask = 0;
for (int i = 0; i < arr->array.count; i++) {
montauk::toml::Value* item = arr->array.items[i];
if (item == nullptr || item->type != montauk::toml::Type::String) continue;
mask |= bit_for_name(item->str);
}
return mask;
}
// Append `suffix` to the "grant.<id>." stem of `path_key`.
// Returns false if the key is not of that shape or does not fit.
inline bool build_sibling_key(const char* path_key, const char* suffix,
char* out, int outSz) {
int prefixLen = 0;
for (; GRANT_PREFIX[prefixLen]; prefixLen++) {
if (path_key[prefixLen] != GRANT_PREFIX[prefixLen]) return false;
}
// Copy through the final '.' so "grant.foo.path" yields "grant.foo.".
int lastDot = -1;
for (int i = 0; path_key[i]; i++) {
if (path_key[i] == '.') lastDot = i;
}
if (lastDot < prefixLen) return false;
int n = 0;
for (; n <= lastDot && n < outSz - 1; n++) out[n] = path_key[n];
for (int i = 0; suffix[i] && n < outSz - 1; i++) out[n++] = suffix[i];
out[n] = '\0';
return true;
}
// Look up the grant declared for `binary_path`. Returns false when the
// path has no entry, which is the common case and means "no authority".
inline bool lookup(const char* binary_path,
montauk::abi::SpawnCapabilities& out) {
out = {0, 0};
if (binary_path == nullptr || binary_path[0] == '\0') return false;
montauk::toml::Doc doc = montauk::config::load(GRANT_CONFIG);
bool found = false;
for (int i = 0; i < doc.entries.count && !found; i++) {
montauk::toml::Value* entry = doc.entries.items[i];
if (entry == nullptr || entry->key == nullptr) continue;
if (entry->type != montauk::toml::Type::String) continue;
char sibling[128];
if (!build_sibling_key(entry->key, "path", sibling, sizeof(sibling))) continue;
if (!montauk::streq(sibling, entry->key)) continue;
if (!montauk::streq(entry->str, binary_path)) continue;
build_sibling_key(entry->key, "effective", sibling, sizeof(sibling));
uint64_t effective = mask_from_key(doc, sibling);
build_sibling_key(entry->key, "delegable", sibling, sizeof(sibling));
uint64_t delegable = mask_from_key(doc, sibling);
out.effective = effective;
out.delegable = delegable;
found = true;
}
doc.destroy();
return found;
}
// The calling process's own capability masks.
inline bool self(montauk::abi::SpawnCapabilities& out) {
out = {0, 0};
return montauk::getcaps(&out) == 0;
}
inline uint64_t self_delegable() {
montauk::abi::SpawnCapabilities mine;
return self(mine) ? mine.delegable : 0;
}
// Build a spawn request for `binary_path`, clamped to what the caller may
// actually delegate.
inline montauk::abi::SpawnCapabilities for_binary(const char* binary_path,
uint64_t caller_delegable) {
montauk::abi::SpawnCapabilities caps{0, 0};
if (caller_delegable == 0) return caps;
if (!lookup(binary_path, caps)) return caps;
caps.effective &= caller_delegable;
caps.delegable &= caller_delegable;
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