diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..7be8a06 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -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" + ] + } + ] +} diff --git a/programs/include/gui/terminal.hpp b/programs/include/gui/terminal.hpp index 01b0a3b..6534bcc 100644 --- a/programs/include/gui/terminal.hpp +++ b/programs/include/gui/terminal.hpp @@ -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); } diff --git a/programs/include/montauk/capabilities.h b/programs/include/montauk/capabilities.h index 9b41519..bba9fad 100644 --- a/programs/include/montauk/capabilities.h +++ b/programs/include/montauk/capabilities.h @@ -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 diff --git a/programs/src/desktop/desktop_catalog.cpp b/programs/src/desktop/desktop_catalog.cpp index d08d284..0bfd653 100644 --- a/programs/src/desktop/desktop_catalog.cpp +++ b/programs/src/desktop/desktop_catalog.cpp @@ -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, diff --git a/programs/src/init/main.cpp b/programs/src/init/main.cpp index 2cdb03a..83e34be 100644 --- a/programs/src/init/main.cpp +++ b/programs/src/init/main.cpp @@ -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); diff --git a/programs/src/network/main.cpp b/programs/src/network/main.cpp index 600ec7e..2b8fc14 100644 --- a/programs/src/network/main.cpp +++ b/programs/src/network/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -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 { diff --git a/programs/src/shell/exec.cpp b/programs/src/shell/exec.cpp index 5c8c647..294df8d 100644 --- a/programs/src/shell/exec.cpp +++ b/programs/src/shell/exec.cpp @@ -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; diff --git a/programs/src/shell/main.cpp b/programs/src/shell/main.cpp index 27ff89f..fff8605 100644 --- a/programs/src/shell/main.cpp +++ b/programs/src/shell/main.cpp @@ -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 ---- diff --git a/programs/src/shell/shell.h b/programs/src/shell/shell.h index f94896f..86c52c1 100644 --- a/programs/src/shell/shell.h +++ b/programs/src/shell/shell.h @@ -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 ----