feat: implement kernel capability model
This commit is contained in:
+72
-148
@@ -8,11 +8,13 @@
|
||||
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Fs/Vfs.hpp>
|
||||
#include <Fs/ProtectedPaths.hpp>
|
||||
#include <Net/Tcp.hpp>
|
||||
#include <Net/Udp.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/UserRange.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
@@ -82,6 +84,7 @@ namespace Ipc {
|
||||
|
||||
struct File : Object {
|
||||
Fs::Vfs::BackendFile backend;
|
||||
uint64_t writeCapability;
|
||||
};
|
||||
|
||||
struct UdpDgramHeader {
|
||||
@@ -178,149 +181,11 @@ namespace Ipc {
|
||||
|
||||
static void ReleaseRawObject(Object* object);
|
||||
|
||||
// MUST be a Mutex, never a Spinlock. ShootdownUserRange holds this while
|
||||
// waiting for remote CPUs to acknowledge the shootdown IPI, so a CPU that
|
||||
// is queued behind the holder has to stay interruptible long enough to
|
||||
// service that IPI itself. An interrupt-disabling Spinlock here deadlocks
|
||||
// every CPU contending for the lock, and the bounded-retry logic below
|
||||
// then reports it as a "target failed to acknowledge" Panic -- which reads
|
||||
// like a hardware fault rather than a lock-type regression.
|
||||
static kcp::Mutex g_tlbShootdownLock;
|
||||
static volatile uint64_t g_tlbShootdownSeq = 0;
|
||||
static volatile uint64_t g_tlbShootdownPml4 = 0;
|
||||
static volatile uint64_t g_tlbShootdownStartVa = 0;
|
||||
static volatile uint32_t g_tlbShootdownPages = 0;
|
||||
static volatile uint64_t g_tlbShootdownDone[Smp::MaxCPUs] = {};
|
||||
|
||||
static bool CpuCurrentlyUsesPml4(Smp::CpuData* cpu, uint64_t pml4Phys) {
|
||||
if (cpu == nullptr || pml4Phys == 0 || cpu->currentSlot < 0) return false;
|
||||
|
||||
Sched::Process* proc = Sched::GetProcessSlot(cpu->currentSlot);
|
||||
if (proc == nullptr) return false;
|
||||
if (proc->state == Sched::ProcessState::Free) return false;
|
||||
return proc->pml4Phys == pml4Phys;
|
||||
}
|
||||
|
||||
static void InvalidateLocalUserRange(uint64_t startVa, uint32_t pages) {
|
||||
if (pages == 0) return;
|
||||
|
||||
if (pages > 1024) {
|
||||
Memory::VMM::FlushTLB();
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t p = 0; p < pages; p++) {
|
||||
uint64_t va = startVa + (uint64_t)p * 0x1000ULL;
|
||||
asm volatile("invlpg (%0)" :: "r"(va) : "memory");
|
||||
}
|
||||
}
|
||||
|
||||
static void TlbShootdownIpiHandler(uint8_t, bool) {
|
||||
Smp::CpuData* cpu = Smp::GetCurrentCpuData();
|
||||
uint64_t seq = g_tlbShootdownSeq;
|
||||
uint64_t pml4 = g_tlbShootdownPml4;
|
||||
uint64_t startVa = g_tlbShootdownStartVa;
|
||||
uint32_t pages = g_tlbShootdownPages;
|
||||
|
||||
if (CpuCurrentlyUsesPml4(cpu, pml4)) {
|
||||
InvalidateLocalUserRange(startVa, pages);
|
||||
}
|
||||
|
||||
if (cpu != nullptr && cpu->cpuIndex >= 0 && cpu->cpuIndex < Smp::MaxCPUs) {
|
||||
asm volatile("" ::: "memory");
|
||||
g_tlbShootdownDone[cpu->cpuIndex] = seq;
|
||||
}
|
||||
}
|
||||
|
||||
void ShootdownUserRange(uint64_t pml4Phys, uint64_t startVa, uint32_t pages) {
|
||||
if (pml4Phys == 0 || pages == 0) return;
|
||||
|
||||
bool targets[Smp::MaxCPUs] = {};
|
||||
Smp::CpuData* currentCpu = Smp::GetCurrentCpuData();
|
||||
int currentCpuIndex = currentCpu ? currentCpu->cpuIndex : -1;
|
||||
|
||||
g_tlbShootdownLock.Acquire();
|
||||
uint64_t seq = g_tlbShootdownSeq + 1;
|
||||
g_tlbShootdownPml4 = pml4Phys;
|
||||
g_tlbShootdownStartVa = startVa;
|
||||
g_tlbShootdownPages = pages;
|
||||
asm volatile("" ::: "memory");
|
||||
g_tlbShootdownSeq = seq;
|
||||
|
||||
for (int i = 0; i < Smp::GetCpuCount(); i++) {
|
||||
Smp::CpuData* cpu = Smp::GetCpuData(i);
|
||||
if (cpu == nullptr || !cpu->started) continue;
|
||||
|
||||
if (i == currentCpuIndex) {
|
||||
if (CpuCurrentlyUsesPml4(cpu, pml4Phys)) {
|
||||
InvalidateLocalUserRange(startVa, pages);
|
||||
}
|
||||
g_tlbShootdownDone[i] = seq;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CpuCurrentlyUsesPml4(cpu, pml4Phys)) {
|
||||
g_tlbShootdownDone[i] = seq;
|
||||
continue;
|
||||
}
|
||||
|
||||
targets[i] = true;
|
||||
(void)Hal::LocalApic::SendFixedIpi(cpu->lapicId,
|
||||
Hal::IRQ_VECTOR_BASE + Hal::IRQ_TLB_SHOOTDOWN);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Smp::GetCpuCount(); i++) {
|
||||
if (!targets[i]) continue;
|
||||
uint32_t spins = 0;
|
||||
uint32_t retries = 0;
|
||||
while (g_tlbShootdownDone[i] != seq) {
|
||||
asm volatile("pause");
|
||||
if (++spins < 1000000) continue;
|
||||
|
||||
// Delivery normally completes in a handful of cycles. Retry a
|
||||
// bounded number of times in case the first IPI was lost while
|
||||
// the target changed interrupt state. Continuing without an
|
||||
// acknowledgement would let the caller free frames still
|
||||
// reachable through a remote stale TLB entry, so fail loudly
|
||||
// instead of either corrupting memory or spinning forever.
|
||||
spins = 0;
|
||||
if (++retries > 4) {
|
||||
Panic("TLB shootdown target failed to acknowledge", nullptr);
|
||||
}
|
||||
Smp::CpuData* cpu = Smp::GetCpuData(i);
|
||||
if (cpu != nullptr && cpu->started) {
|
||||
(void)Hal::LocalApic::SendFixedIpi(cpu->lapicId,
|
||||
Hal::IRQ_VECTOR_BASE + Hal::IRQ_TLB_SHOOTDOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_tlbShootdownLock.Release();
|
||||
}
|
||||
|
||||
void UnmapAndFreeUserRange(uint64_t pml4Phys, uint64_t startVa, uint64_t pages) {
|
||||
static constexpr uint32_t PagesPerChunk = 64;
|
||||
uint64_t physPages[PagesPerChunk];
|
||||
|
||||
for (uint64_t base = 0; base < pages; base += PagesPerChunk) {
|
||||
uint32_t count = (uint32_t)((pages - base > PagesPerChunk)
|
||||
? PagesPerChunk : pages - base);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
uint64_t pageVa = startVa + (base + i) * 0x1000ULL;
|
||||
physPages[i] = Memory::VMM::Paging::GetPhysAddr(pml4Phys, pageVa);
|
||||
Memory::VMM::Paging::UnmapUserIn(pml4Phys, pageVa);
|
||||
}
|
||||
|
||||
ShootdownUserRange(pml4Phys, startVa + base * 0x1000ULL, count);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (physPages[i] != 0) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(physPages[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ==========================================================================
|
||||
// Object lifetime
|
||||
// Pool allocation, refcounting and type-dispatched teardown.
|
||||
// Every object type routes through this layer.
|
||||
// ==========================================================================
|
||||
|
||||
static void InitObject(Object& object, HandleType type) {
|
||||
object.type = type;
|
||||
@@ -568,6 +433,11 @@ namespace Ipc {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Handle table
|
||||
// Per-process handle installation, rights, duplication and close.
|
||||
// ==========================================================================
|
||||
|
||||
int CurrentSlot() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return -1;
|
||||
@@ -817,6 +687,11 @@ namespace Ipc {
|
||||
return InstallHandleForSlot(slot, snapshot.object, snapshot.type, snapshot.rights);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Streams
|
||||
// Byte pipes.
|
||||
// ==========================================================================
|
||||
|
||||
Stream* CreateStream(uint32_t capacity) {
|
||||
if (capacity == 0) capacity = DefaultStreamCapacity;
|
||||
|
||||
@@ -1000,6 +875,11 @@ namespace Ipc {
|
||||
return hasData;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Mailboxes
|
||||
// Discrete message queues.
|
||||
// ==========================================================================
|
||||
|
||||
Mailbox* CreateMailbox() {
|
||||
g_mailboxPoolLock.Acquire();
|
||||
for (int i = 0; i < MaxMailboxes; i++) {
|
||||
@@ -1260,9 +1140,22 @@ namespace Ipc {
|
||||
return hasMsg;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Files
|
||||
// Write authority is re-checked against the calling process on every
|
||||
// write, so passing a writable handle to a less privileged process does
|
||||
// not transfer the ability to use it. See Fs/ProtectedPaths.cpp.
|
||||
// ==========================================================================
|
||||
|
||||
int OpenFileHandleForSlot(int slot, const char* path, bool create) {
|
||||
if (slot < 0 || slot >= Sched::MaxProcesses || path == nullptr) return -1;
|
||||
|
||||
uint64_t writeCapability = Fs::RequiredFileWriteCapability(path);
|
||||
if (create && writeCapability != 0 &&
|
||||
!Sched::HasCapability(writeCapability)) {
|
||||
return montauk::abi::SYS_ERR_PERMISSION;
|
||||
}
|
||||
|
||||
Fs::Vfs::BackendFile backend = {-1, -1, 0};
|
||||
int result = create ? Fs::Vfs::CreateBackendFile(path, backend)
|
||||
: Fs::Vfs::OpenBackendFile(path, backend);
|
||||
@@ -1273,6 +1166,7 @@ namespace Ipc {
|
||||
if (g_files[i].active || g_files[i].destroying) continue;
|
||||
InitObject(g_files[i], HandleType::File);
|
||||
g_files[i].backend = backend;
|
||||
g_files[i].writeCapability = writeCapability;
|
||||
g_filePoolLock.Release();
|
||||
|
||||
uint32_t rights = RightRead | RightWait | RightDup;
|
||||
@@ -1318,7 +1212,12 @@ namespace Ipc {
|
||||
HandleSnapshot snapshot;
|
||||
if (!snapshot.Capture(CurrentSlot(), handle)) return -1;
|
||||
if (snapshot.type != HandleType::File || (snapshot.rights & RightWrite) == 0) return -1;
|
||||
return Fs::Vfs::WriteBackendFile(((File*)snapshot.object)->backend, buffer, offset, size);
|
||||
File* file = (File*)snapshot.object;
|
||||
if (file->writeCapability != 0 &&
|
||||
!Sched::HasCapability(file->writeCapability)) {
|
||||
return montauk::abi::SYS_ERR_PERMISSION;
|
||||
}
|
||||
return Fs::Vfs::WriteBackendFile(file->backend, buffer, offset, size);
|
||||
}
|
||||
|
||||
uint64_t FileGetSizeHandle(int handle) {
|
||||
@@ -1328,6 +1227,11 @@ namespace Ipc {
|
||||
return Fs::Vfs::GetBackendFileSize(((File*)snapshot.object)->backend);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Sockets
|
||||
// TCP and UDP endpoints.
|
||||
// ==========================================================================
|
||||
|
||||
static Socket* AllocateSocketObject(int type) {
|
||||
g_socketPoolLock.Acquire();
|
||||
for (int i = 0; i < MaxSockets; i++) {
|
||||
@@ -1663,6 +1567,13 @@ namespace Ipc {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Surfaces
|
||||
// Shared pixel buffers mapped into a client address space.
|
||||
// Pages MUST be unmapped from the owner before being freed, or
|
||||
// FreeUserHalf() double-frees them on process exit.
|
||||
// ==========================================================================
|
||||
|
||||
Surface* CreateSurface(uint64_t byteSize) {
|
||||
if (byteSize == 0) byteSize = 0x1000;
|
||||
uint32_t numPages = (uint32_t)((byteSize + 0xFFFu) / 0x1000u);
|
||||
@@ -1748,7 +1659,7 @@ namespace Ipc {
|
||||
for (uint32_t p = m.numPages; p < newPages; p++) {
|
||||
Memory::VMM::Paging::UnmapUserIn(pml4, m.va + (uint64_t)p * 0x1000ULL);
|
||||
}
|
||||
ShootdownUserRange(pml4, startVa, rollbackPages);
|
||||
Memory::ShootdownUserRange(pml4, startVa, rollbackPages);
|
||||
}
|
||||
g_surfaceMapLocks[s].Release();
|
||||
}
|
||||
@@ -1900,7 +1811,7 @@ namespace Ipc {
|
||||
Memory::VMM::Paging::UnmapUserIn(pml4, va);
|
||||
}
|
||||
|
||||
ShootdownUserRange(pml4, baseVa, flushPages);
|
||||
Memory::ShootdownUserRange(pml4, baseVa, flushPages);
|
||||
m.numPages = newPages;
|
||||
}
|
||||
g_surfaceMapLocks[s].Release();
|
||||
@@ -2111,7 +2022,7 @@ namespace Ipc {
|
||||
// releasing the mapping reference can then destroy the surface
|
||||
// and recycle its frames while that sibling writes through its
|
||||
// stale TLB entry. Quiesce every CPU using this PML4 first.
|
||||
ShootdownUserRange(pml4Phys, baseVa, numPages);
|
||||
Memory::ShootdownUserRange(pml4Phys, baseVa, numPages);
|
||||
|
||||
g_surfaceMaps[slot][i].used = false;
|
||||
g_surfaceMaps[slot][i].surface = nullptr;
|
||||
@@ -2127,6 +2038,11 @@ namespace Ipc {
|
||||
return unmapped > 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Process handles
|
||||
// Wait-only references to a live process.
|
||||
// ==========================================================================
|
||||
|
||||
int OpenProcessHandle(int pid) {
|
||||
g_processPoolLock.Acquire();
|
||||
for (int i = 0; i < MaxProcessObjects; i++) {
|
||||
@@ -2183,6 +2099,11 @@ namespace Ipc {
|
||||
return exited;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Signals and waitsets
|
||||
// Readiness computation and multiplexed waiting.
|
||||
// ==========================================================================
|
||||
|
||||
static uint32_t CurrentSocketSignals(Socket* socket, uint32_t rights) {
|
||||
if (socket == nullptr) return SignalNone;
|
||||
|
||||
@@ -2513,6 +2434,10 @@ namespace Ipc {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Teardown and init
|
||||
// ==========================================================================
|
||||
|
||||
void CleanupProcessSlot(int slot, int /*pid*/, uint64_t pml4Phys) {
|
||||
if (slot < 0 || slot >= Sched::MaxProcesses) return;
|
||||
|
||||
@@ -2548,7 +2473,6 @@ namespace Ipc {
|
||||
for (int i = 0; i < Sched::MaxProcesses; i++) {
|
||||
g_processObjectsBySlot[i] = nullptr;
|
||||
}
|
||||
Hal::RegisterIrqHandler(Hal::IRQ_TLB_SHOOTDOWN, TlbShootdownIpiHandler);
|
||||
Kt::KernelLogStream(Kt::OK, "IPC") << "Initialized ("
|
||||
<< (uint64_t)MaxHandlesPerProcess << " handles/process, "
|
||||
<< (uint64_t)MaxStreams << " streams, "
|
||||
|
||||
@@ -171,11 +171,6 @@ namespace Ipc {
|
||||
int WaitsetWaitHandle(int waitsetHandle, WaitsetReady* outReady, uint64_t timeoutMs);
|
||||
|
||||
void NotifyObjectChanged(Object* object);
|
||||
// Invalidate a user range on every CPU currently running the address
|
||||
// space. Call this after removing PTEs and before releasing their frames.
|
||||
void ShootdownUserRange(uint64_t pml4Phys, uint64_t startVa, uint32_t pages);
|
||||
// Safely remove ordinary PFA-backed user mappings and release their frames.
|
||||
void UnmapAndFreeUserRange(uint64_t pml4Phys, uint64_t startVa, uint64_t pages);
|
||||
void CleanupProcessSlot(int slot, int pid, uint64_t pml4Phys);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user