From 95e8a739d4fe9fd2ddd7250861694143a3227036 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sat, 19 Sep 2026 10:28:54 +0200 Subject: [PATCH] feat: add compositor capability --- kernel/src/Api/BuildNo.hpp | 2 +- kernel/src/Api/Syscall.hpp | 12 +++++++++++- kernel/src/Api/Window.hpp | 6 ++++++ programs/GNUmakefile | 12 ++++++++++-- programs/data/config/capabilities.toml | 25 ++++--------------------- programs/include/Api/Syscall.hpp | 12 +++++++++++- programs/include/montauk/capabilities.h | 7 ++++++- programs/src/capabilities/main.cpp | 1 + programs/src/login/login_input.cpp | 4 ++++ programs/src/login/main.cpp | 3 +++ 10 files changed, 57 insertions(+), 27 deletions(-) diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 961f425..9e15c66 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 236 +#define MONTAUK_BUILD_NUMBER 239 diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index cb4c713..f6f7db2 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -372,7 +372,14 @@ namespace montauk::abi { whatever that image is granted at its next launch. Formatting a data volume must not carry that authority with it. */ static constexpr uint64_t CAP_SYSTEM_IMAGE = 1ULL << 12; - static constexpr uint64_t CAP_ALL = (1ULL << 13) - 1; + /* Map other processes' windows and deliver input events to them + (SYS_WINMAP, SYS_WINSENDEVENT): the desktop compositor's authority. + Anything holding it can read every window's pixels and click on any + window, including a consent prompt it is itself waiting on, so it is + never part of a session mask, never part of "all", and never + delegated by the compositor to what it launches. */ + static constexpr uint64_t CAP_COMPOSITOR = 1ULL << 13; + static constexpr uint64_t CAP_ALL = (1ULL << 14) - 1; static constexpr uint64_t CAP_STANDARD_SESSION = CAP_POWER_REQUEST | CAP_SUSPEND; static constexpr uint64_t CAP_ADMIN_SESSION = CAP_STANDARD_SESSION | CAP_PROCESS_ADMIN | CAP_STORAGE_ADMIN | @@ -384,6 +391,9 @@ namespace montauk::abi { static_assert((CAP_ADMIN_SESSION & CAP_SYSTEM_IMAGE) == 0, "an admin session must not imply authority to rewrite the " "programs it launches; grant CAP_SYSTEM_IMAGE per binary"); + static_assert((CAP_ADMIN_SESSION & CAP_COMPOSITOR) == 0, + "a session must not delegate the compositor's authority; " + "login grants it to the desktop directly"); static constexpr int SYS_ERR_PERMISSION = -13; struct SpawnCapabilities { diff --git a/kernel/src/Api/Window.hpp b/kernel/src/Api/Window.hpp index 1675592..9b7d83e 100644 --- a/kernel/src/Api/Window.hpp +++ b/kernel/src/Api/Window.hpp @@ -45,7 +45,12 @@ namespace montauk::abi { return WinServer::Enumerate(outArray, maxCount); } + // Mapping another process's window and injecting input into it are the + // compositor's job alone. Left open, any app could read every window's + // pixels or click on any window -- including a prompt asking the user + // whether to grant that same app authority. static uint64_t Sys_WinMap(int windowId) { + if (!Sched::HasCapability(CAP_COMPOSITOR)) return 0; auto* proc = Sched::GetCurrentProcessPtr(); if (proc == nullptr) return 0; return WinServer::Map(windowId, proc->pid, proc->pml4Phys, proc->heapNext); @@ -58,6 +63,7 @@ namespace montauk::abi { } static int Sys_WinSendEvent(int windowId, const WinEvent* event) { + if (!Sched::HasCapability(CAP_COMPOSITOR)) return SYS_ERR_PERMISSION; if (event == nullptr) return -1; return WinServer::SendEvent(windowId, event); } diff --git a/programs/GNUmakefile b/programs/GNUmakefile index 1e93775..628d565 100644 --- a/programs/GNUmakefile +++ b/programs/GNUmakefile @@ -404,11 +404,19 @@ $(BINDIR)/os/wallpapers/%: $(WPDIR)/% cp $< $@ # Build each system program from its source files. -$(BINDIR)/os/%.elf: src/%/main.cpp link.ld GNUmakefile $(LIBC_LIB) +# -MMD records the headers main.cpp pulled in, so editing a shared header +# (montauk/capabilities.h, Api/Syscall.hpp, ...) rebuilds every program that +# uses it. A missing depfile matches the empty rule below, which forces a +# rebuild; that is how programs built before this tracking existed catch up. +$(BINDIR)/os/%.elf: src/%/main.cpp link.ld GNUmakefile $(LIBC_LIB) obj/%/main.elf.d mkdir -p $(BINDIR)/os obj/$* - $(CXX) $(CXXFLAGS) -c src/$*/main.cpp -o obj/$*/main.o + $(CXX) $(CXXFLAGS) -MMD -MP -MT $@ -MF obj/$*/main.elf.d -c src/$*/main.cpp -o obj/$*/main.o $(CXX) $(CXXFLAGS) $(LDFLAGS) obj/$*/main.o $(LIBC_LIB) -o $@ +obj/%/main.elf.d: ; +.PRECIOUS: obj/%/main.elf.d +-include $(wildcard obj/*/main.elf.d) + # ============================================================================ # Native development kit (Montauk SDK): binutils built for MontaukOS # plus a target sysroot (headers, libc.a, crt objects) under 0:/sdk. diff --git a/programs/data/config/capabilities.toml b/programs/data/config/capabilities.toml index 9c529db..28d9f14 100644 --- a/programs/data/config/capabilities.toml +++ b/programs/data/config/capabilities.toml @@ -3,18 +3,7 @@ # # Capability names: process_admin, power_request, power_control, suspend, # storage_admin, raw_storage, network_admin, set_time, user_admin, -# display_admin, device_admin, log_read, system_image, and "all". -# -# "all" is every capability except system_image, which must always be named -# explicitly: it is write access to 0:/os and 0:/apps, and since grants are -# keyed on binary path, holding it is equivalent to holding every capability -# the system can issue from the next launch onwards. Nothing is granted it. -# -# A grant is always clamped by the kernel to what the launching process may -# actually delegate, so "all" in a launcher entry means "whatever this session -# was given", not "root". -# -# +# display_admin, device_admin, log_read, system_image, compositor and "all". # ==== System services (started by init) ==== @@ -28,7 +17,7 @@ effective = ["set_time"] [grant.login] path = "0:/os/login.elf" -permitted = ["all"] +permitted = ["all", "compositor"] effective = [ "power_control", "storage_admin", "device_admin", "user_admin", "process_admin", "log_read", @@ -36,7 +25,7 @@ effective = [ delegable = [ "power_request", "suspend", "process_admin", "storage_admin", "raw_storage", "network_admin", "set_time", "user_admin", - "display_admin", "device_admin", "log_read", + "display_admin", "device_admin", "log_read", "compositor", ] [grant.sshd] @@ -48,7 +37,7 @@ path = "0:/os/desktop.elf" effective = [ "power_request", "suspend", "process_admin", "storage_admin", "raw_storage", "network_admin", "set_time", "user_admin", - "display_admin", "device_admin", "log_read", + "display_admin", "device_admin", "log_read", "compositor", ] delegable = [ "power_request", "suspend", "process_admin", "storage_admin", @@ -56,12 +45,6 @@ delegable = [ "display_admin", "device_admin", "log_read", ] -# The console session launchers. terminal.elf needs no authority of its own; -# it exists to pass the session's authority to the shell it hosts. The shell -# in turn exercises only power_request and suspend, but delegates on the same -# terms as the desktop so that the tools below work from a console. Both are -# clamped to the launching session: a standard session narrows this to -# power_request and suspend, and an unprivileged one to nothing. [grant.terminal] path = "0:/apps/terminal/terminal.elf" delegable = ["all"] diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 753396f..b69e418 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -285,7 +285,14 @@ namespace montauk::abi { whatever that image is granted at its next launch. Formatting a data volume must not carry that authority with it. */ static constexpr uint64_t CAP_SYSTEM_IMAGE = 1ULL << 12; - static constexpr uint64_t CAP_ALL = (1ULL << 13) - 1; + /* Map other processes' windows and deliver input events to them + (SYS_WINMAP, SYS_WINSENDEVENT): the desktop compositor's authority. + Anything holding it can read every window's pixels and click on any + window, including a consent prompt it is itself waiting on, so it is + never part of a session mask, never part of "all", and never + delegated by the compositor to what it launches. */ + static constexpr uint64_t CAP_COMPOSITOR = 1ULL << 13; + static constexpr uint64_t CAP_ALL = (1ULL << 14) - 1; static constexpr uint64_t CAP_STANDARD_SESSION = CAP_POWER_REQUEST | CAP_SUSPEND; static constexpr uint64_t CAP_ADMIN_SESSION = CAP_STANDARD_SESSION | CAP_PROCESS_ADMIN | CAP_STORAGE_ADMIN | @@ -297,6 +304,9 @@ namespace montauk::abi { static_assert((CAP_ADMIN_SESSION & CAP_SYSTEM_IMAGE) == 0, "an admin session must not imply authority to rewrite the " "programs it launches; grant CAP_SYSTEM_IMAGE per binary"); + static_assert((CAP_ADMIN_SESSION & CAP_COMPOSITOR) == 0, + "a session must not delegate the compositor's authority; " + "login grants it to the desktop directly"); static constexpr int SYS_ERR_PERMISSION = -13; struct SpawnCapabilities { diff --git a/programs/include/montauk/capabilities.h b/programs/include/montauk/capabilities.h index 8f70a2f..1a851c9 100644 --- a/programs/include/montauk/capabilities.h +++ b/programs/include/montauk/capabilities.h @@ -37,13 +37,18 @@ namespace caps { {"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}, }; + // 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 & ~montauk::abi::CAP_SYSTEM_IMAGE; + return montauk::abi::CAP_ALL & ~EXPLICIT_ONLY; for (const auto& entry : NAMES) { if (montauk::streq(entry.name, name)) return entry.bit; } diff --git a/programs/src/capabilities/main.cpp b/programs/src/capabilities/main.cpp index 9559819..df51796 100644 --- a/programs/src/capabilities/main.cpp +++ b/programs/src/capabilities/main.cpp @@ -60,6 +60,7 @@ static const char* const kCapDescriptions[CAP_COUNT] = { "Manage hardware devices (Bluetooth, radio)", "Read the kernel log", "Write to 0:/os and 0:/apps (never part of \"all\")", + "Read and send input to other windows (never part of \"all\")", }; // ==== Grant table model ==== diff --git a/programs/src/login/login_input.cpp b/programs/src/login/login_input.cpp index fbcdd01..0e7e9d9 100644 --- a/programs/src/login/login_input.cpp +++ b/programs/src/login/login_input.cpp @@ -72,6 +72,10 @@ void launch_session(LoginState* ls) { pid = montauk::spawn_with_caps("0:/apps/terminal/terminal.elf", console_args, ls->username, caps); } else { + // The desktop is also the compositor. That authority is not the + // session's to pass on, so it is added here and not delegable. + caps.permitted |= montauk::abi::CAP_COMPOSITOR; + caps.effective |= montauk::abi::CAP_COMPOSITOR; pid = montauk::spawn_with_caps("0:/os/desktop.elf", ls->username, ls->username, caps); } diff --git a/programs/src/login/main.cpp b/programs/src/login/main.cpp index cea7301..2ea5bbe 100644 --- a/programs/src/login/main.cpp +++ b/programs/src/login/main.cpp @@ -33,6 +33,9 @@ static void maybe_run_setup_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; + // Compositor authority for the desktop itself, never delegable onwards. + caps.permitted |= montauk::abi::CAP_COMPOSITOR; + caps.effective |= montauk::abi::CAP_COMPOSITOR; int pid = montauk::spawn_with_caps("0:/os/desktop.elf", user, user, caps); if (pid >= 0) { montauk::waitpid(pid);