feat: implement kernel capability model

This commit is contained in:
daniel committed 2026-08-29 16:53:04 +02:00
1 parent 9051b8a16e
commit e7646bbbdb
47 files changed
+1507 -264

No files matched your search

@@ -6,33 +6,31 @@
#include "apps_common.hpp"
static void spawn_app(const char* path, const char* args = nullptr) {
if (path && path[0]) {
montauk::spawn(path, args);
}
static void spawn_app(DesktopState* ds, const char* path, const char* args = nullptr) {
if (path && path[0]) desktop_spawn_app(ds, path, args);
}
void open_terminal(DesktopState* ds) {
const char* home = (ds && ds->home_dir[0]) ? ds->home_dir : nullptr;
spawn_app("0:/apps/terminal/terminal.elf", home);
spawn_app(ds, "0:/apps/terminal/terminal.elf", home);
}
void open_calculator(DesktopState* ds) {
(void)ds;
spawn_app("0:/apps/calculator/calculator.elf");
spawn_app(ds, "0:/apps/calculator/calculator.elf");
}
void open_texteditor(DesktopState* ds) {
(void)ds;
spawn_app("0:/apps/texteditor/texteditor.elf");
spawn_app(ds, "0:/apps/texteditor/texteditor.elf");
}
void open_syslog(DesktopState* ds) {
(void)ds;
spawn_app("0:/apps/klog/syslog.elf");
spawn_app(ds, "0:/apps/syslog/syslog.elf");
}
void open_wordprocessor(DesktopState* ds) {
(void)ds;
spawn_app("0:/apps/wordprocessor/wordprocessor.elf");
spawn_app(ds, "0:/apps/wordprocessor/wordprocessor.elf");
}
+9 -3
View File
@@ -25,6 +25,12 @@ inline void* operator new(unsigned long, void* p) { return p; }
using namespace gui;
// Central launch policy implemented by desktop_catalog.cpp. Standalone app
// wrappers include this header directly, so keep the declaration here rather
// than only in desktop_internal.hpp.
int desktop_spawn_app(DesktopState* ds, const char* path,
const char* args = nullptr);
// ============================================================================
// Minimal snprintf
// ============================================================================
@@ -261,8 +267,8 @@ void open_sleep_dialog(DesktopState* ds);
// user returns to the login screen, where the shutdown stages run (Bluetooth
// teardown, filesystem flush) before the final ACPI power-off / reset. Pass
// montauk::abi::POWER_REQ_SHUTDOWN or montauk::abi::POWER_REQ_REBOOT.
[[noreturn]] inline void desktop_request_power(int action) {
montauk::power_request(action);
montauk::exit(0);
inline void desktop_request_power(int action) {
if (montauk::power_request(action) == 0)
montauk::exit(0);
}
bool desktop_poll_external_windows(DesktopState* ds);
+15 -2
View File
@@ -5,6 +5,7 @@
*/
#include "desktop_internal.hpp"
#include <montauk/capabilities.h>
namespace {
@@ -24,6 +25,18 @@ static void sort_item_indices(DesktopState* ds, int* indices, int count) {
} // namespace
int desktop_spawn_app(DesktopState* ds, const char* path, const char* args) {
if (!ds || !path || !path[0]) return -1;
// 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);
}
int desktop_list_item_indices(DesktopState* ds,
DesktopItemSection section,
int* out,
@@ -54,9 +67,9 @@ bool desktop_launch_item(DesktopState* ds, const DesktopItem* item) {
case DESKTOP_ITEM_LAUNCH_EXECUTABLE:
if (!item->binary_path[0]) return false;
if (item->launch_with_home) {
return montauk::spawn(item->binary_path, ds->home_dir) >= 0;
return desktop_spawn_app(ds, item->binary_path, ds->home_dir) >= 0;
}
return montauk::spawn(item->binary_path) >= 0;
return desktop_spawn_app(ds, item->binary_path) >= 0;
default:
return false;
}
+2 -2
View File
@@ -381,9 +381,9 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
} else if (!row.is_category) {
if (row.external) {
if (row.launch_with_home) {
montauk::spawn(row.binary_path, ds->home_dir);
desktop_spawn_app(ds, row.binary_path, ds->home_dir);
} else {
montauk::spawn(row.binary_path);
desktop_spawn_app(ds, row.binary_path);
}
} else {
desktop_launch_builtin(ds, row.app_id);
+13
View File
@@ -834,6 +834,19 @@ void gui::desktop_run(DesktopState* ds) {
if (now >= nextClockPollMs) {
nextClockPollMs = now + 1000;
// A power request can be posted by anything in this session that
// holds CAP_POWER_REQUEST -- the shell's shutdown builtin, run in
// a terminal window, is the common case. login only reads it once
// the session leader exits, so ending the session is our job; it
// is the same handoff desktop_request_power() performs, just
// reached from a request we did not originate.
int pendingPower = montauk::power_request_pending();
if (pendingPower == montauk::abi::POWER_REQ_SHUTDOWN ||
pendingPower == montauk::abi::POWER_REQ_REBOOT) {
montauk::exit(0);
}
uint64_t clockToken = desktop_clock_token();
if (clockToken != lastClockToken) {
lastClockToken = clockToken;
+1 -1
View File
@@ -328,7 +328,7 @@ bool desktop_wifi_poll(DesktopState* ds, uint64_t now) {
if (ds->wifi_dhcp_pending && info.connected && ds->cached_net_cfg.ipAddress == 0) {
ds->wifi_dhcp_pending = false;
ds->wifi_dhcp_waiting = true;
montauk::spawn("0:/os/dhcp.elf");
desktop_spawn_app(ds, "0:/os/dhcp.elf");
wifi_set_status(ds, "Requesting an address...");
changed = true;
} else if (ds->wifi_dhcp_pending && info.connected) {
+6 -1
View File
@@ -7,6 +7,7 @@
#include <montauk/syscall.h>
#include <montauk/config.h>
#include <montauk/capabilities.h>
#include <libc/stdio.h>
// ---- ANSI color codes ----
@@ -80,7 +81,11 @@ static bool run_service(const char* path, const char* name, bool wait = true) {
snprintf(msg, sizeof(msg), "Starting %s", name);
log_info(msg);
int pid = montauk::spawn(path);
// 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);
if (pid < 0) {
snprintf(msg, sizeof(msg), "Failed to start %s", name);
log_err(msg);
+17 -3
View File
@@ -52,16 +52,30 @@ void launch_session(LoginState* ls) {
montauk::memset(ls->password, 0, sizeof(ls->password));
ls->password_len = 0;
// A session may pass on what it holds and no more. Authority is never
// propagated implicitly by spawn(): the desktop and the console both
// consult 0:/config/capabilities.toml to decide which program receives
// which subset, and the kernel clamps that to this set. A standard
// session is delegable for the same reason an admin one is -- otherwise
// its own terminal could not hand the shell the suspend it already has.
montauk::abi::SpawnCapabilities caps{};
caps.permitted = montauk::user::is_admin(ls->username)
? montauk::abi::CAP_ADMIN_SESSION
: montauk::abi::CAP_STANDARD_SESSION;
caps.effective = caps.permitted;
caps.delegable = caps.permitted;
int pid;
if (ls->session_mode == SESSION_CONSOLE) {
char console_args[96];
build_console_args(ls, console_args, (int)sizeof(console_args));
pid = montauk::spawn("0:/apps/terminal/terminal.elf", console_args);
pid = montauk::spawn_with_caps("0:/apps/terminal/terminal.elf",
console_args, ls->username, caps);
} else {
pid = montauk::spawn("0:/os/desktop.elf", ls->username);
pid = montauk::spawn_with_caps("0:/os/desktop.elf", ls->username,
ls->username, caps);
}
if (pid >= 0) {
montauk::setuser(pid, ls->username);
montauk::waitpid(pid);
if (ls->session_mode == SESSION_DESKTOP) {
terminate_desktop_session(pid);
+8 -6
View File
@@ -250,10 +250,12 @@ void perform_graceful_shutdown(LoginState* ls, int action) {
// ==== Stage 4: dispatch the ACPI power-off / reset ====
show_stage(ls, heading, rebooting ? "Restarting now..." : "Powering off...");
if (rebooting) {
montauk::reset();
} else {
montauk::shutdown();
}
__builtin_unreachable();
int rc = rebooting ? montauk::reset() : montauk::shutdown();
// A successful power-control syscall never returns. If it does return,
// keep the trusted supervisor alive and make the authorization failure
// visible instead of falling through an unreachable-code assumption.
show_stage(ls, heading, rc == montauk::abi::SYS_ERR_PERMISSION
? "Power control permission denied."
: "Power control failed.");
for (;;) montauk::sleep_ms(1000);
}
+9 -2
View File
@@ -25,9 +25,16 @@ static void maybe_run_setup_session() {
montauk::user::set_session(user);
doc.destroy();
int pid = montauk::spawn("0:/os/desktop.elf", user);
montauk::abi::SpawnCapabilities caps{};
bool admin = montauk::streq(role, "admin");
caps.permitted = caps.effective = admin
? montauk::abi::CAP_ADMIN_SESSION
: montauk::abi::CAP_STANDARD_SESSION;
// Matches launch_session(): a session delegates from what it holds, and
// the grant table decides which program receives which part of it.
caps.delegable = caps.permitted;
int pid = montauk::spawn_with_caps("0:/os/desktop.elf", user, user, caps);
if (pid >= 0) {
montauk::setuser(pid, user);
montauk::waitpid(pid);
terminate_desktop_session(pid);
}
+7 -1
View File
@@ -951,7 +951,13 @@ static void render() {
}
static void launch_dhcp() {
int pid = montauk::spawn("0:/os/dhcp.elf");
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);
if (pid >= 0) {
set_status("DHCP client started");
} else {
+20 -1
View File
@@ -7,6 +7,7 @@
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/heap.h>
#include <montauk/capabilities.h>
#include <gui/mtk.hpp>
extern "C" {
@@ -392,8 +393,26 @@ static bool refresh_state(bool force) {
return true;
}
// Ending another process needs CAP_PROCESS_ADMIN; without it the kernel only
// permits killing our own descendants. Capabilities cannot change over a
// process lifetime and this is consulted from the render path, so resolve it
// once rather than per frame.
static bool g_process_admin = false;
static bool g_process_admin_known = false;
static bool process_admin_granted() {
if (!g_process_admin_known) {
montauk::abi::SpawnCapabilities mine;
montauk::caps::self(mine);
g_process_admin = (mine.effective & montauk::abi::CAP_PROCESS_ADMIN) != 0;
g_process_admin_known = true;
}
return g_process_admin;
}
static bool can_kill_selected() {
return g_pm.active_tab == PM_TAB_PROCESSES
return process_admin_granted()
&& g_pm.active_tab == PM_TAB_PROCESSES
&& g_pm.selected >= 0
&& g_pm.selected < g_pm.proc_count
&& g_pm.procs[g_pm.selected].pid != 0
+6 -1
View File
@@ -8,5 +8,10 @@
extern "C" void _start() {
montauk::print("Rebooting...\n");
montauk::reset();
int rc = montauk::reset();
if (rc == montauk::abi::SYS_ERR_PERMISSION)
montauk::print("reset: permission denied\n");
else
montauk::print("reset: reboot failed\n");
montauk::exit(1);
}
+3 -2
View File
@@ -18,6 +18,7 @@ endif
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
LIBDIR := ../../lib
OBJDIR := obj
# ---- C++ compiler flags ----
@@ -63,9 +64,9 @@ TARGET := $(BINDIR)/os/shell.elf
all: $(TARGET)
$(TARGET): $(OBJS) $(LINK_LD) Makefile
$(TARGET): $(OBJS) $(LINK_LD) Makefile $(LIBDIR)/libc/liblibc.a
mkdir -p $(BINDIR)/os
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) -o $@
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
$(OBJDIR)/%.o: %.cpp shell.h Makefile
mkdir -p $(OBJDIR)
+1
View File
@@ -68,6 +68,7 @@ void cmd_help() {
montauk::print(" fontscale [n] Set terminal font scale (1-8)\n");
montauk::print(" reset Reboot the system\n");
montauk::print(" shutdown Shut down the system\n");
montauk::print(" suspend Suspend the system\n");
montauk::print("\n");
montauk::print("Network commands:\n");
montauk::print(" ping <ip> Send ICMP echo requests\n");
+11 -1
View File
@@ -41,7 +41,17 @@ static void print_exit_code(int code) {
static bool try_exec(const char* path, const char* args) {
if (!file_exists(path)) return false;
int pid = montauk::spawn(path, args);
// 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);
if (pid < 0) return false;
print_exit_code(montauk::waitpid(pid));
return true;
+26
View File
@@ -13,6 +13,7 @@ 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];
@@ -46,6 +47,8 @@ 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 ----
@@ -184,6 +187,29 @@ static int process_command(const char* line) {
if (streq(cmd, "true")) { return 0; }
if (streq(cmd, "false")) { return 1; }
// Route interactive power actions through the trusted login supervisor.
// Posting the request and exiting lets terminal.elf close, after which
// login performs filesystem/Bluetooth cleanup and the final power syscall.
if (streq(cmd, "shutdown") || streq(cmd, "poweroff") || streq(cmd, "halt") ||
streq(cmd, "reboot") || streq(cmd, "reset")) {
int action = (streq(cmd, "reboot") || streq(cmd, "reset"))
? montauk::abi::POWER_REQ_REBOOT
: montauk::abi::POWER_REQ_SHUTDOWN;
int rc = montauk::power_request(action);
if (rc == 0) montauk::exit(0);
montauk::print(rc == montauk::abi::SYS_ERR_PERMISSION
? "power: permission denied\n"
: "power: request failed\n");
return 1;
}
if (streq(cmd, "suspend")) {
int rc = montauk::suspend();
if (rc == montauk::abi::SYS_ERR_PERMISSION)
montauk::print("suspend: permission denied\n");
return rc == 0 ? 0 : 1;
}
if (streq(cmd, "pwd")) {
sync_cwd();
char path[128];
+3 -1
View File
@@ -10,6 +10,7 @@
#include <montauk/string.h>
#include <montauk/heap.h>
#include <montauk/config.h>
#include <montauk/capabilities.h>
using montauk::slen;
using montauk::streq;
@@ -55,6 +56,7 @@ 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 ----
@@ -144,4 +146,4 @@ constexpr const char* shell_builtins[] = {
"true",
"false",
"exit"
};
};
+11 -2
View File
@@ -10,6 +10,15 @@ extern "C" void _start() {
montauk::print("Shutting down...\n");
// This low-level utility bypasses the login graceful-shutdown view, so flush
// pending writes and unmount disk-backed volumes here before powering off.
montauk::fs_sync();
montauk::shutdown();
int sync_rc = montauk::fs_sync();
if (sync_rc == montauk::abi::SYS_ERR_PERMISSION) {
montauk::print("shutdown: permission denied\n");
montauk::exit(1);
}
int rc = montauk::shutdown();
if (rc == montauk::abi::SYS_ERR_PERMISSION)
montauk::print("shutdown: permission denied\n");
else
montauk::print("shutdown: power-off failed\n");
montauk::exit(1);
}