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

+124
View File
@@ -0,0 +1,124 @@
/*
* ProtectedPaths.cpp
* Capability required to modify paths on the booted system volume
* Copyright (c) 2026 Daniel Hammer
*
* Split out of Ipc.cpp: this is filesystem security policy, not IPC. It
* lived there only because OpenFileHandleForSlot was its first caller.
*/
#include "ProtectedPaths.hpp"
#include <Api/Syscall.hpp>
#include <Terminal/Terminal.hpp>
namespace Fs {
// ==== Protected system paths ====
// The capability required to modify (create, write, delete or rename) a
// path. The kernel only ever enumerates paths here; it never parses a
// policy file. Userspace grant policy lives in 0:/config/capabilities.toml
// and can only ever narrow what the kernel already delegated, so no input
// to that file can produce authority this table does not already allow.
//
// Rules apply ONLY to the system volume. Drive 0 is always the boot
// ramdisk (Fs/Boot.cpp registers it unconditionally); every other drive is
// a partition discovered at probe time, in probe order. A user data disk
// that happens to contain an "apps" or "config" directory must not inherit
// system protection, and an installed system's files on another volume are
// inert data until that disk is booted -- at which point its contents are
// themselves the drive-0 ramdisk.
// Anything the kernel itself reads belongs here:
// guarding only the syscall leaves the file as an unguarded second path to
// the same state (bluetooth.toml feeds the BD_ADDR override at bring-up).
struct ProtectedPath {
const char* pattern; // drive-relative, leading '/'
bool prefix; // also match everything beneath the pattern
uint64_t capability;
};
static constexpr ProtectedPath g_protectedPaths[] = {
// Authentication, first-boot administrator creation, trusted service
// activation and capability grants. Readable by anyone; writable only
// with administrative authority. The bare directory is listed so it
// cannot be renamed or deleted out from under the files inside it.
{"/config", false, montauk::abi::CAP_USER_ADMIN},
{"/config/users.toml", false, montauk::abi::CAP_USER_ADMIN},
{"/config/setup.toml", false, montauk::abi::CAP_USER_ADMIN},
{"/config/init.toml", false, montauk::abi::CAP_USER_ADMIN},
{"/config/ssh.toml", false, montauk::abi::CAP_USER_ADMIN},
{"/config/capabilities.toml",false, montauk::abi::CAP_USER_ADMIN},
// Read by the Bluetooth driver at controller bring-up.
{"/config/bluetooth.toml", false, montauk::abi::CAP_DEVICE_ADMIN},
// Program images. Capability grants are keyed on binary path, so a
// writable image would let an unprivileged process substitute a binary
// and inherit the grant the next time a privileged launcher runs it.
// This is CAP_SYSTEM_IMAGE and not CAP_STORAGE_ADMIN precisely because
// it is the trusted computing base: partitioning and formatting a data
// volume is an ordinary administrative act, while replacing the image
// of login.elf is a route to every capability the system can issue.
{"/apps", true, montauk::abi::CAP_SYSTEM_IMAGE},
{"/os", true, montauk::abi::CAP_SYSTEM_IMAGE},
};
static char LowerAscii(char c) {
return (c >= 'A' && c <= 'Z') ? (char)(c + ('a' - 'A')) : c;
}
// The volume the running system was booted from.
static constexpr uint64_t SystemDrive = 0;
// Strip the "<digits>:" prefix, but only for the system volume. Returns
// nullptr for any other drive, meaning no rule applies to it.
static const char* SystemRelativePath(const char* path) {
if (path == nullptr) return nullptr;
const char* p = path;
if (*p < '0' || *p > '9') return nullptr;
uint64_t drive = 0;
while (*p >= '0' && *p <= '9') {
drive = drive * 10 + (uint64_t)(*p - '0');
if (drive > 0xFFFF) return nullptr; // absurd; cannot be a drive
p++;
}
if (*p != ':' || drive != SystemDrive) return nullptr;
return p + 1;
}
// Case-insensitive: FAT32 resolves differing cases to the same file, so a
// case-sensitive rule would be trivially sidestepped.
static bool ProtectedPathMatches(const char* path, const ProtectedPath& rule) {
const char* p = path;
const char* q = rule.pattern;
while (*q) {
if (LowerAscii(*p) != LowerAscii(*q)) return false;
p++;
q++;
}
if (*p == '\0') return true; // the pattern itself
return rule.prefix && *p == '/'; // something beneath it
}
uint64_t RequiredFileWriteCapability(const char* path) {
const char* relative = SystemRelativePath(path);
if (relative == nullptr) return 0; // not the system volume
// Overlapping rules accumulate: HasCapability() requires every bit, so
// a path covered by two rules demands both.
uint64_t required = 0;
for (const auto& rule : g_protectedPaths) {
if (ProtectedPathMatches(relative, rule)) required |= rule.capability;
}
return required;
}
void LogProtectedPaths() {
for (const auto& rule : g_protectedPaths) {
Kt::KernelLogStream(Kt::INFO, "IPC") << "Protected path "
<< rule.pattern << (rule.prefix ? "/* " : " ")
<< "requires capability mask "
<< kcp::hex << rule.capability << kcp::dec;
}
}
}
+20
View File
@@ -0,0 +1,20 @@
/*
* ProtectedPaths.hpp
* Capability required to modify paths on the booted system volume
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Fs {
// Capability a process must hold to create, write, delete, rename or
// re-timestamp `path`. Returns 0 when the path is unprotected.
uint64_t RequiredFileWriteCapability(const char* path);
// Log the rule table at boot, so a path that should be protected and is
// not is visible rather than silently missing.
void LogProtectedPaths();
}