refactor: capabilities - add unified launch routine
This commit is contained in:
1 parent
638dd9ee06
commit
5b90869101
9 files changed
+78
-61
No files matched your search
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"version": 4,
|
||||
"configurations": [
|
||||
{
|
||||
"name": "MontaukOS (userspace)",
|
||||
"compilerPath": "${workspaceFolder}/toolchain/local/bin/x86_64-montauk-g++",
|
||||
"compilerArgs": ["-ffreestanding", "-fno-rtti", "-fno-exceptions"],
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "gnu++20",
|
||||
"intelliSenseMode": "gcc-x64",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/programs/include",
|
||||
"${workspaceFolder}/programs/include/libc",
|
||||
"${workspaceFolder}/programs/src/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MontaukOS (kernel)",
|
||||
"compilerPath": "${workspaceFolder}/toolchain/local/bin/x86_64-montauk-g++",
|
||||
"compilerArgs": ["-ffreestanding", "-fno-rtti", "-fno-exceptions"],
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "gnu++20",
|
||||
"intelliSenseMode": "gcc-x64",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/kernel/src",
|
||||
"${workspaceFolder}/kernel/freestnd-cxx-hdrs/x86_64/include"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -30,11 +30,8 @@ int desktop_spawn_app(DesktopState* ds, const char* path, const char* args) {
|
||||
// The grant table decides what each program may receive; the kernel bounds
|
||||
// it by what this session was actually delegated, so a non-admin session
|
||||
// clamps to nothing without the desktop having to decide that itself.
|
||||
montauk::abi::SpawnCapabilities caps =
|
||||
montauk::caps::for_binary(path, montauk::caps::self_delegable());
|
||||
// Applications inherit the desktop's kernel-owned owner identity. An
|
||||
// explicit user override is reserved for trusted session creators.
|
||||
return montauk::spawn_with_caps(path, args, nullptr, caps);
|
||||
// Applications inherit the desktop's kernel-owned owner identity.
|
||||
return montauk::caps::launch(path, {.args = args});
|
||||
}
|
||||
|
||||
int desktop_list_item_indices(DesktopState* ds,
|
||||
|
||||
@@ -83,9 +83,7 @@ static bool run_service(const char* path, const char* name, bool wait = true) {
|
||||
|
||||
// Grants are keyed on the executable, not the service id, so repointing a
|
||||
// privileged entry in init.toml at another program transfers no authority.
|
||||
montauk::abi::SpawnCapabilities caps =
|
||||
montauk::caps::for_binary(path, montauk::caps::self_delegable());
|
||||
int pid = montauk::spawn_with_caps(path, nullptr, "system", caps);
|
||||
int pid = montauk::caps::launch(path, {.user = "system"});
|
||||
if (pid < 0) {
|
||||
snprintf(msg, sizeof(msg), "Failed to start %s", name);
|
||||
log_err(msg);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/wifi.h>
|
||||
#include <montauk/capabilities.h>
|
||||
#include <gui/mtk.hpp>
|
||||
#include <gui/mtk/settings.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
@@ -951,13 +952,10 @@ static void render() {
|
||||
}
|
||||
|
||||
static void launch_dhcp() {
|
||||
montauk::abi::SpawnCapabilities caps{
|
||||
montauk::abi::CAP_NETWORK_ADMIN,
|
||||
montauk::abi::CAP_NETWORK_ADMIN,
|
||||
0
|
||||
};
|
||||
int pid = montauk::spawn_with_caps("0:/os/dhcp.elf", nullptr,
|
||||
nullptr, caps);
|
||||
// The grant table already declares network_admin for dhcp.elf, and this
|
||||
// process is delegable for exactly that, so the lookup reproduces the
|
||||
// mask that used to be spelled out here -- without a second copy to drift.
|
||||
int pid = montauk::caps::launch("0:/os/dhcp.elf");
|
||||
if (pid >= 0) {
|
||||
set_status("DHCP client started");
|
||||
} else {
|
||||
|
||||
@@ -41,17 +41,8 @@ static void print_exit_code(int code) {
|
||||
|
||||
static bool try_exec(const char* path, const char* args) {
|
||||
if (!file_exists(path)) return false;
|
||||
// Least-privilege hygiene, NOT a security boundary: this never decides
|
||||
// what may run, only which capabilities a tool receives. The user in an
|
||||
// admin console can already do everything these tools do, so the point is
|
||||
// to keep a bug in one tool from reaching authority it has no use for.
|
||||
// The shell delegates on the same terms as the desktop; the table decides
|
||||
// per binary, and the kernel re-checks the bound on every spawn.
|
||||
montauk::abi::SpawnCapabilities caps =
|
||||
montauk::caps::for_binary(path, shell_delegable_caps);
|
||||
int pid = (caps.permitted != 0)
|
||||
? montauk::spawn_with_caps(path, args, nullptr, caps)
|
||||
: montauk::spawn(path, args);
|
||||
|
||||
int pid = montauk::caps::launch(path, {.args = args});
|
||||
if (pid < 0) return false;
|
||||
print_exit_code(montauk::waitpid(pid));
|
||||
return true;
|
||||
|
||||
@@ -13,7 +13,6 @@ int current_drive = 0;
|
||||
int last_exit = 0;
|
||||
char session_user[32] = "";
|
||||
char session_home[64] = "";
|
||||
uint64_t shell_delegable_caps = 0;
|
||||
|
||||
void sync_cwd() {
|
||||
char abs[128];
|
||||
@@ -47,8 +46,6 @@ void read_session() {
|
||||
|
||||
scopy(session_home, "0:/users/", sizeof(session_home));
|
||||
scat(session_home, session_user, sizeof(session_home));
|
||||
|
||||
shell_delegable_caps = montauk::caps::self_delegable();
|
||||
}
|
||||
|
||||
// ---- Command history ----
|
||||
|
||||
@@ -56,7 +56,6 @@ extern int current_drive;
|
||||
extern int last_exit;
|
||||
extern char session_user[32];
|
||||
extern char session_home[64];
|
||||
extern uint64_t shell_delegable_caps;
|
||||
|
||||
// ---- Inline path helpers ----
|
||||
|
||||
|
||||
Reference in new issue
Block a user