feat: implement kernel capability model
This commit is contained in:
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");
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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"
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user