feat: add permission consent prompts, drop permitted capability mask

This commit is contained in:
daniel committed 2026-09-30 08:32:48 +02:00
1 parent c54181103f
commit ae5e84592d
25 files changed
+1632 -94

No files matched your search

+101 -4
View File
@@ -7,6 +7,8 @@
#include <Memory/UserRange.hpp>
#include "Scheduler.hpp"
#include "ElfLoader.hpp"
#include "Consent.hpp"
#include <Fs/ProtectedPaths.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Memory/Paging.hpp>
#include <Memory/HHDM.hpp>
@@ -299,9 +301,10 @@ namespace Sched {
processTable[i].environment[0] = '\0';
processTable[i].environmentLength = 1;
processTable[i].user[0] = '\0';
processTable[i].permittedCaps = 0;
processTable[i].effectiveCaps = 0;
processTable[i].delegableCaps = 0;
processTable[i].consentCeiling = 0;
processTable[i].consentDenied = 0;
processTable[i].sessionId = -1;
processTable[i].cwd[0] = '\0';
processTable[i].runningOnCpu = -1;
@@ -349,6 +352,20 @@ namespace Sched {
const char* environment, uint32_t environmentLength,
const montauk::abi::SpawnCapabilities* capabilities,
const char* userOverride) {
// Grants are keyed on the image path, so they are only as trustworthy
// as the path: anything outside 0:/os and 0:/apps can be replaced by
// any process, which would then run with the grant the next time a
// privileged launcher starts it. Such a program starts with nothing
// and asks through consent instead.
montauk::abi::SpawnCapabilities clamped{0, 0};
if (capabilities != nullptr &&
(capabilities->effective | capabilities->delegable) != 0 &&
!Fs::IsSystemImage(vfsPath)) {
Kt::KernelLogStream(Kt::WARNING, "Sched") << "Not an installed program, "
<< "starting without capabilities: " << vfsPath;
capabilities = &clamped;
}
schedLock.Acquire();
int slot = -1;
@@ -558,11 +575,9 @@ namespace Sched {
// must use SYS_SPAWN_CAPS for an explicit, kernel-validated delegation.
if (parentPrimarySlot >= 0) {
if (capabilities != nullptr) {
proc.permittedCaps = capabilities->permitted;
proc.effectiveCaps = capabilities->effective;
proc.delegableCaps = capabilities->delegable;
} else {
proc.permittedCaps = 0;
proc.effectiveCaps = 0;
proc.delegableCaps = 0;
}
@@ -570,10 +585,21 @@ namespace Sched {
// The kernel-created init process is the root of the capability
// delegation tree. No userspace pathname or PID receives this
// treatment; it is reached only with no current parent process.
proc.permittedCaps = montauk::abi::CAP_ALL;
proc.effectiveCaps = montauk::abi::CAP_ALL;
proc.delegableCaps = montauk::abi::CAP_ALL;
}
// Consent can later grant the child exactly the eligible authority
// its parent could have delegated to it now.
proc.consentCeiling = (parentPrimarySlot >= 0)
? processTable[parentPrimarySlot].delegableCaps & montauk::abi::CAP_CONSENT_ELIGIBLE
: 0;
proc.consentDenied = 0;
{
int i = 0;
for (; i < (int)sizeof(proc.imagePath) - 1 && vfsPath[i]; i++)
proc.imagePath[i] = vfsPath[i];
proc.imagePath[i] = '\0';
}
// Inherit user string from parent, or default to "system" if no parent.
// An explicit override is accepted only through the validated
@@ -1638,6 +1664,10 @@ namespace Sched {
// Clean up any windows owned by this process
WinServer::CleanupProcess(exitingPid);
// Cancel this process's consent requests; if it was the consent
// agent, deny everything still waiting on it.
Consent::OnProcessExit(exitingPid);
// Release any mixer virtual streams owned by this process so the slot
// and its ring buffer don't leak when an app forgets to audio_close.
Drivers::Audio::Mixer::CleanupProcess(exitingPid);
@@ -2104,6 +2134,73 @@ namespace Sched {
return nullptr;
}
// ==== Consent support ====
static Process* FindLiveProcessLocked(int pid) {
if (pid < 0) return nullptr;
for (int i = 0; i < MaxProcesses; i++) {
Process& p = processTable[i];
if (p.primarySlot != i || p.pid != pid) continue;
if (p.state == ProcessState::Ready || p.state == ProcessState::Running ||
p.state == ProcessState::Blocked) {
return &p;
}
}
return nullptr;
}
static bool ConsentPairValidLocked(const Process* subject, const Process* agent,
uint64_t capability) {
return subject != nullptr && agent != nullptr && subject != agent &&
agent->sessionId >= 0 && subject->sessionId == agent->sessionId &&
(agent->effectiveCaps & montauk::abi::CAP_COMPOSITOR) != 0 &&
(agent->delegableCaps & capability) == capability &&
(subject->consentCeiling & capability) == capability;
}
bool ConsentSubjectEligible(int pid, int agentPid, uint64_t capability) {
schedLock.Acquire();
Process* subject = FindLiveProcessLocked(pid);
bool ok = ConsentPairValidLocked(subject, FindLiveProcessLocked(agentPid), capability) &&
(subject->consentDenied & capability) == 0;
schedLock.Release();
return ok;
}
bool ApplyConsentGrant(int pid, int agentPid, uint64_t capability) {
schedLock.Acquire();
Process* subject = FindLiveProcessLocked(pid);
bool ok = ConsentPairValidLocked(subject, FindLiveProcessLocked(agentPid), capability);
if (ok) {
// Usable, never delegable: a consented app cannot hand the
// authority on to anything it launches.
subject->effectiveCaps |= capability;
}
schedLock.Release();
return ok;
}
void ApplyConsentDenial(int pid, uint64_t capability) {
schedLock.Acquire();
if (Process* subject = FindLiveProcessLocked(pid))
subject->consentDenied |= capability;
schedLock.Release();
}
bool SnapshotConsentSubject(int pid, montauk::abi::ConsentRequest& out) {
schedLock.Acquire();
Process* subject = FindLiveProcessLocked(pid);
if (subject != nullptr) {
memcpy(out.imagePath, subject->imagePath, sizeof(out.imagePath));
out.imagePath[sizeof(out.imagePath) - 1] = '\0';
// Only an image that cannot be rewritten without CAP_SYSTEM_IMAGE
// is identified by its path well enough to remember an answer.
out.imageProtected = Fs::IsSystemImage(out.imagePath);
}
schedLock.Release();
return subject != nullptr;
}
Process* GetProcessSlot(int slot) {
if (slot < 0 || slot >= MaxProcesses) return nullptr;
return &processTable[slot];