Files
nt-lite/code/kernel/ke_handle.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

767 lines
30 KiB
C

// Kernel capability handles. This file owns process-local handle identity, grant delegation, and revocation.
#include <string.h>
#include <intrin.h>
#include <kernel/ke_handle.h>
#include <kernel/ke_process.h>
#include <kernel/ke_named_queue.h>
#include <kernel/ke_debug.h>
#include <kernel/io_file.h>
#include <kernel/ke_audio.h>
#include <kernel/mm_pool.h>
#include <ntlite/syscall.h>
#include <ntlite/security.h>
#include <ntlite/port.h>
typedef struct CAPABILITY_GRANT {
volatile long RefCount;
volatile long Revoked;
uint32_t Depth;
SYS_RIGHTS MaximumAccess;
OBJECT* Target;
struct CAPABILITY_GRANT* Parent;
} CAPABILITY_GRANT;
typedef struct KE_HANDLE_ENTRY {
struct KE_HANDLE_ENTRY* Next;
SYS_HANDLE Handle;
SYS_RIGHTS Access;
CAPABILITY_GRANT* Grant;
} KE_HANDLE_ENTRY;
typedef struct KE_CAPTURED_HANDLE {
CAPABILITY_GRANT* Grant;
SYS_RIGHTS Access;
} KE_CAPTURED_HANDLE;
_Static_assert((KE_HANDLE_BUCKET_COUNT & (KE_HANDLE_BUCKET_COUNT - 1u)) == 0,
"handle bucket count must be a power of two");
// Returns the process-local hash bucket for one opaque handle value.
static uint32_t KiHandleBucket(SYS_HANDLE handle) {
return (handle ^ (handle >> 16) ^ (handle >> 8)) & (KE_HANDLE_BUCKET_COUNT - 1u);
}
// Returns one entry while the handle-table lock is held.
static KE_HANDLE_ENTRY* KiFindHandleLocked(KE_HANDLE_TABLE* table, SYS_HANDLE handle, KE_HANDLE_ENTRY*** linkOut) {
if (linkOut) *linkOut = (KE_HANDLE_ENTRY**)0;
if (!table || handle == SYS_HANDLE_INVALID) return (KE_HANDLE_ENTRY*)0;
uint32_t bucket = KiHandleBucket(handle);
KE_HANDLE_ENTRY** link = &table->Buckets[bucket];
while (*link) {
if ((*link)->Handle == handle) {
if (linkOut) *linkOut = link;
return *link;
}
link = &(*link)->Next;
}
return (KE_HANDLE_ENTRY*)0;
}
// Acquires one grant reference while its owner still stabilizes the pointer.
static void KiReferenceGrant(CAPABILITY_GRANT* grant) {
if (!grant) return;
long refs = _InterlockedIncrement(&grant->RefCount);
if (refs <= 1) {
KePanic(KE_PANIC_CODE_GENERIC,
"capability: invalid grant reference grant=0x%016llX refs=%ld\n",
(uint64_t)(uintptr_t)grant,
refs);
}
}
// Releases one grant and recursively drops its immutable parent chain.
static void KiDereferenceGrant(CAPABILITY_GRANT* grant) {
while (grant) {
long refs = _InterlockedDecrement(&grant->RefCount);
if (refs > 0) return;
if (refs < 0) {
KePanic(KE_PANIC_CODE_GENERIC,
"capability: grant reference underflow grant=0x%016llX refs=%ld\n",
(uint64_t)(uintptr_t)grant,
refs);
}
CAPABILITY_GRANT* parent = grant->Parent;
if (!parent && grant->Target)
ObDereferenceObject(grant->Target);
MmFreePool(grant);
grant = parent;
}
}
// Returns whether a grant and every ancestor remain unrevoked.
static int KiGrantIsActive(const CAPABILITY_GRANT* grant) {
const CAPABILITY_GRANT* current = grant;
uint32_t depth = 0;
while (current) {
if (_InterlockedCompareExchange((volatile long*)&current->Revoked, 0, 0) != 0)
return 0;
current = current->Parent;
if (++depth > KE_CAPABILITY_MAX_DEPTH)
return 0;
}
return 1;
}
// Creates a root grant and takes the target object's lifetime reference.
static KSTATUS KiCreateRootGrant(OBJECT* object, SYS_RIGHTS maximumAccess, CAPABILITY_GRANT** grantOut) {
if (!object || !grantOut || maximumAccess == 0) return NTLSTATUS_INVALID_PARAMETER;
*grantOut = (CAPABILITY_GRANT*)0;
CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)MmAllocatePoolTag(sizeof(CAPABILITY_GRANT),
MM_POOL_TAG('c', 'a', 'p', 'g'));
if (!grant) return NTLSTATUS_NO_MEMORY;
memset(grant, 0, sizeof(*grant));
if (!ObTryReferenceObject(object)) {
MmFreePool(grant);
return NTLSTATUS_OBJECT_DEAD;
}
grant->RefCount = 1;
grant->Depth = 0;
grant->MaximumAccess = maximumAccess;
grant->Target = object;
grant->Parent = (CAPABILITY_GRANT*)0;
*grantOut = grant;
return NTLSTATUS_SUCCESS;
}
// Creates one attenuated child grant and retains its parent.
static KSTATUS KiCreateChildGrant(CAPABILITY_GRANT* parent,
SYS_RIGHTS maximumAccess,
CAPABILITY_GRANT** grantOut) {
if (!parent || !grantOut || maximumAccess == 0) return NTLSTATUS_INVALID_PARAMETER;
*grantOut = (CAPABILITY_GRANT*)0;
if (parent->Depth + 1u >= KE_CAPABILITY_MAX_DEPTH)
return NTLSTATUS_LIMIT_REACHED;
if ((maximumAccess & ~parent->MaximumAccess) != 0)
return NTLSTATUS_ACCESS_DENIED;
if (!KiGrantIsActive(parent))
return NTLSTATUS_CAPABILITY_REVOKED;
CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)MmAllocatePoolTag(sizeof(CAPABILITY_GRANT),
MM_POOL_TAG('c', 'a', 'p', 'g'));
if (!grant) return NTLSTATUS_NO_MEMORY;
memset(grant, 0, sizeof(*grant));
KiReferenceGrant(parent);
grant->RefCount = 1;
grant->Depth = parent->Depth + 1u;
grant->MaximumAccess = maximumAccess;
grant->Target = parent->Target;
grant->Parent = parent;
if (!KiGrantIsActive(parent)) {
KiDereferenceGrant(grant);
return NTLSTATUS_CAPABILITY_REVOKED;
}
*grantOut = grant;
return NTLSTATUS_SUCCESS;
}
// Records type-specific ownership after a process handle becomes visible.
static void KiHandleOpened(OBJECT* object) {
if (object && object->Type == ObjectTypeAudioStream)
KeAudioStreamHandleOpened((KE_AUDIO_STREAM*)object);
}
// Releases type-specific ownership before a process handle disappears.
static void KiHandleClosed(OBJECT* object) {
if (object && object->Type == ObjectTypeAudioStream)
KeAudioStreamHandleClosed((KE_AUDIO_STREAM*)object);
}
// Inserts one retained grant into a process and consumes that grant reference on success.
static KSTATUS KiInsertGrantHandle(PROCESS* process,
CAPABILITY_GRANT* grant,
SYS_RIGHTS access,
SYS_HANDLE* handleOut) {
if (!process || !grant || !handleOut || access == 0) return NTLSTATUS_INVALID_PARAMETER;
if ((access & ~grant->MaximumAccess) != 0) return NTLSTATUS_ACCESS_DENIED;
if (!KiGrantIsActive(grant)) return NTLSTATUS_CAPABILITY_REVOKED;
KE_HANDLE_ENTRY* entry = (KE_HANDLE_ENTRY*)MmAllocatePoolTag(sizeof(KE_HANDLE_ENTRY),
MM_POOL_TAG('h', 'n', 'd', 'l'));
if (!entry) return NTLSTATUS_NO_MEMORY;
memset(entry, 0, sizeof(*entry));
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
if (table->Rundown) {
KeReleaseSpinlock(&table->Lock, flags);
MmFreePool(entry);
return NTLSTATUS_OBJECT_DEAD;
}
SYS_HANDLE handle = table->NextHandle;
if (handle == SYS_HANDLE_INVALID) {
KeReleaseSpinlock(&table->Lock, flags);
MmFreePool(entry);
return NTLSTATUS_ID_EXHAUSTED;
}
table->NextHandle = handle + 1u;
entry->Handle = handle;
entry->Access = access;
entry->Grant = grant;
uint32_t bucket = KiHandleBucket(handle);
entry->Next = table->Buckets[bucket];
table->Buckets[bucket] = entry;
table->Count++;
KiHandleOpened(grant->Target);
KeReleaseSpinlock(&table->Lock, flags);
*handleOut = handle;
return NTLSTATUS_SUCCESS;
}
// Initializes an empty process-local handle namespace.
void KeInitializeHandleTable(KE_HANDLE_TABLE* table) {
if (!table) return;
memset(table, 0, sizeof(*table));
table->NextHandle = 1u;
table->Lock = (SPINLOCK)SPINLOCK_INIT;
}
// Returns the object-type-specific rights mask accepted by the handle core.
SYS_RIGHTS KeGetValidHandleRights(OBJECT_TYPE type) {
const SYS_RIGHTS management = SYS_RIGHT_MANAGE;
switch (type) {
case ObjectTypeProcess:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_PROCESS_RIGHT_TERMINATE | SYS_PROCESS_RIGHT_INSERT_HANDLE |
SYS_PROCESS_RIGHT_BUILD;
case ObjectTypeThread:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_THREAD_RIGHT_RESUME |
SYS_THREAD_RIGHT_SET_INFORMATION;
case ObjectTypeMutex:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_MUTEX_RIGHT_RELEASE;
case ObjectTypeSemaphore:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_SEMAPHORE_RIGHT_SIGNAL;
case ObjectTypeEvent:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_EVENT_RIGHT_SIGNAL;
case ObjectTypeQueue:
return management | SYS_RIGHT_QUERY | SYS_QUEUE_RIGHT_SEND |
SYS_QUEUE_RIGHT_RECEIVE | SYS_QUEUE_RIGHT_PUBLISH;
case ObjectTypeFile:
return management | SYS_RIGHT_QUERY | SYS_FILE_RIGHT_READ_DATA |
SYS_FILE_RIGHT_WRITE_DATA | SYS_FILE_RIGHT_LOCK |
SYS_FILE_RIGHT_MAP_READ | SYS_FILE_RIGHT_MAP_WRITE;
case ObjectTypeDir:
return management | SYS_RIGHT_QUERY | SYS_DIR_RIGHT_ENUMERATE | SYS_DIR_RIGHT_OPEN |
SYS_DIR_RIGHT_MODIFY;
case ObjectTypeAsyncIo:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_ASYNC_IO_RIGHT_CANCEL;
case ObjectTypeSocket:
return management | SYS_RIGHT_QUERY | SYS_SOCKET_RIGHT_BIND |
SYS_SOCKET_RIGHT_CONNECT | SYS_SOCKET_RIGHT_LISTEN |
SYS_SOCKET_RIGHT_ACCEPT | SYS_SOCKET_RIGHT_SEND |
SYS_SOCKET_RIGHT_RECEIVE | SYS_SOCKET_RIGHT_SHUTDOWN |
SYS_SOCKET_RIGHT_POLL | SYS_SOCKET_RIGHT_SET_OPTION;
case ObjectTypePipeEndpoint:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_PIPE_RIGHT_READ | SYS_PIPE_RIGHT_WRITE | SYS_PIPE_RIGHT_POLL;
case ObjectTypeFileMapping:
return management | SYS_RIGHT_QUERY | SYS_FILE_MAPPING_RIGHT_MAP;
case ObjectTypeSurface:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_SURFACE_RIGHT_MAP_READ | SYS_SURFACE_RIGHT_MAP_WRITE |
SYS_SURFACE_RIGHT_COMMIT;
case ObjectTypeInputQueue:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_INPUT_QUEUE_RIGHT_READ;
case ObjectTypeTimer:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_TIMER_RIGHT_SET;
case ObjectTypeDisplayClock:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT;
case ObjectTypeAudioStream:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_AUDIO_STREAM_RIGHT_WRITE | SYS_AUDIO_STREAM_RIGHT_CONTROL;
case ObjectTypeSecurityContext:
return management | SYS_RIGHT_QUERY |
SYS_SECURITY_CONTEXT_RIGHT_DERIVE | SYS_SECURITY_CONTEXT_RIGHT_ASSIGN;
case ObjectTypePort:
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_PORT_RIGHT_CONNECT | SYS_PORT_RIGHT_ACCEPT |
SYS_PORT_RIGHT_SEND | SYS_PORT_RIGHT_RECEIVE |
SYS_PORT_RIGHT_CALL | SYS_PORT_RIGHT_REPLY |
SYS_PORT_RIGHT_DISCONNECT;
case ObjectTypeVolume:
return management | SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY;
case ObjectTypeVolumeManager:
return management | SYS_RIGHT_QUERY |
SYS_VOLUME_MANAGER_RIGHT_ENUMERATE | SYS_VOLUME_MANAGER_RIGHT_OPEN |
SYS_VOLUME_MANAGER_RIGHT_WAIT;
case ObjectTypeInputManager:
return management | SYS_RIGHT_QUERY | SYS_INPUT_MANAGER_RIGHT_READ |
SYS_INPUT_MANAGER_RIGHT_CONFIGURE;
case ObjectTypeDisplayManager:
return management | SYS_RIGHT_QUERY | SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE |
SYS_DISPLAY_MANAGER_RIGHT_MODESET | SYS_DISPLAY_MANAGER_RIGHT_PRESENT |
SYS_DISPLAY_MANAGER_RIGHT_CURSOR | SYS_DISPLAY_MANAGER_RIGHT_CONSOLE;
case ObjectTypeDevice:
case ObjectTypeDriver:
case ObjectTypeUsbDevice:
case ObjectTypeUsbPipe:
case ObjectTypeImageSection:
case ObjectTypeAudioEndpoint:
return management | SYS_RIGHT_QUERY;
default:
return 0;
}
}
// Creates one root capability handle for an object.
KSTATUS KeCreateHandle(PROCESS* process, OBJECT* object, SYS_RIGHTS access, SYS_HANDLE* handleOut) {
if (!process || !object || !handleOut || access == 0) return NTLSTATUS_INVALID_PARAMETER;
SYS_RIGHTS valid = KeGetValidHandleRights(object->Type);
if (!valid || (access & ~valid) != 0) return NTLSTATUS_INVALID_PARAMETER;
CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)0;
KSTATUS status = KiCreateRootGrant(object, access, &grant);
if (!KSUCCESS(status)) return status;
status = KiInsertGrantHandle(process, grant, access, handleOut);
if (!KSUCCESS(status)) KiDereferenceGrant(grant);
return status;
}
// References the target object after validating type, revocation, and rights.
KSTATUS KeReferenceHandle(PROCESS* process,
SYS_HANDLE handle,
OBJECT_TYPE expectedType,
SYS_RIGHTS requiredRights,
OBJECT** objectOut) {
if (!process || !objectOut) return NTLSTATUS_INVALID_PARAMETER;
*objectOut = (OBJECT*)0;
if (handle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, handle, (KE_HANDLE_ENTRY***)0);
if (!entry) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
if (requiredRights && (entry->Access & requiredRights) != requiredRights) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
if (!KiGrantIsActive(entry->Grant)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_CAPABILITY_REVOKED;
}
OBJECT* object = entry->Grant->Target;
if (!object || (expectedType != 0 && object->Type != expectedType)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
if (!ObTryReferenceObject(object)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_OBJECT_DEAD;
}
KeReleaseSpinlock(&table->Lock, flags);
*objectOut = object;
return NTLSTATUS_SUCCESS;
}
// Closes one process-local handle without reusing its numeric identity.
KSTATUS KeCloseHandle(PROCESS* process, SYS_HANDLE handle) {
if (!process || handle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY** link = (KE_HANDLE_ENTRY**)0;
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, handle, &link);
if (!entry || !link) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
*link = entry->Next;
if (table->Count == 0) {
KeReleaseSpinlock(&table->Lock, flags);
KePanic(KE_PANIC_CODE_GENERIC, "capability: handle count underflow pid=%u\n", process->ProcessId);
}
table->Count--;
KeReleaseSpinlock(&table->Lock, flags);
KSTATUS closeStatus = NTLSTATUS_SUCCESS;
OBJECT* object = entry->Grant->Target;
if (object && object->Type == ObjectTypeFile)
closeStatus = IoFlushFile((FILE_OBJECT*)object);
KeNamedQueueUnpublishHandle(process, handle, object);
KiHandleClosed(object);
KiDereferenceGrant(entry->Grant);
MmFreePool(entry);
return closeStatus;
}
// Duplicates one alias of the same capability grant into another process.
KSTATUS KeDuplicateHandle(PROCESS* sourceProcess,
SYS_HANDLE sourceHandle,
PROCESS* targetProcess,
SYS_RIGHTS desiredRights,
uint32_t duplicateFlags,
SYS_HANDLE* targetHandleOut) {
if (!sourceProcess || !targetProcess || !targetHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
if (sourceHandle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE;
const uint32_t validFlags = SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS;
if ((duplicateFlags & ~validFlags) != 0) return NTLSTATUS_INVALID_PARAMETER;
if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) && desiredRights != 0)
return NTLSTATUS_INVALID_PARAMETER;
if (!(duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) && desiredRights == 0)
return NTLSTATUS_INVALID_PARAMETER;
CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)0;
SYS_RIGHTS sourceAccess = 0;
KE_HANDLE_TABLE* table = &sourceProcess->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, sourceHandle, (KE_HANDLE_ENTRY***)0);
if (!entry) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
if (!KiGrantIsActive(entry->Grant)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_CAPABILITY_REVOKED;
}
SYS_RIGHTS requiredManagement = SYS_RIGHT_DUPLICATE;
if (sourceProcess != targetProcess)
requiredManagement |= SYS_RIGHT_TRANSFER;
if ((entry->Access & requiredManagement) != requiredManagement) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
sourceAccess = entry->Access;
SYS_RIGHTS validRights = KeGetValidHandleRights(entry->Grant->Target->Type);
if (!(duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) &&
(desiredRights & ~validRights) != 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_PARAMETER;
}
if (!(duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) &&
(desiredRights & ~sourceAccess) != 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
grant = entry->Grant;
KiReferenceGrant(grant);
KeReleaseSpinlock(&table->Lock, flags);
SYS_RIGHTS targetAccess = (duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS)
? sourceAccess : desiredRights;
KSTATUS status = KiInsertGrantHandle(targetProcess, grant, targetAccess, targetHandleOut);
if (!KSUCCESS(status)) KiDereferenceGrant(grant);
return status;
}
// Derives one child grant whose maximum rights are an attenuated subset.
KSTATUS KeDeriveHandle(PROCESS* process,
SYS_HANDLE sourceHandle,
SYS_RIGHTS desiredRights,
SYS_HANDLE* derivedHandleOut) {
if (!process || !derivedHandleOut || desiredRights == 0)
return NTLSTATUS_INVALID_PARAMETER;
if (sourceHandle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE;
CAPABILITY_GRANT* parent = (CAPABILITY_GRANT*)0;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, sourceHandle, (KE_HANDLE_ENTRY***)0);
if (!entry) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
if (!KiGrantIsActive(entry->Grant)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_CAPABILITY_REVOKED;
}
if ((entry->Access & SYS_RIGHT_DERIVE) == 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
SYS_RIGHTS validRights = KeGetValidHandleRights(entry->Grant->Target->Type);
if ((desiredRights & ~validRights) != 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_PARAMETER;
}
if ((desiredRights & ~entry->Access) != 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
parent = entry->Grant;
KiReferenceGrant(parent);
KeReleaseSpinlock(&table->Lock, flags);
CAPABILITY_GRANT* child = (CAPABILITY_GRANT*)0;
KSTATUS status = KiCreateChildGrant(parent, desiredRights, &child);
KiDereferenceGrant(parent);
if (!KSUCCESS(status)) return status;
status = KiInsertGrantHandle(process, child, desiredRights, derivedHandleOut);
if (!KSUCCESS(status)) KiDereferenceGrant(child);
return status;
}
// Revokes one grant and all grants derived from it.
KSTATUS KeRevokeHandle(PROCESS* process, SYS_HANDLE handle) {
if (!process) return NTLSTATUS_INVALID_PARAMETER;
if (handle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, handle, (KE_HANDLE_ENTRY***)0);
if (!entry) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_INVALID_HANDLE;
}
if ((entry->Access & SYS_RIGHT_REVOKE) == 0) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
if (!KiGrantIsActive(entry->Grant)) {
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_CAPABILITY_REVOKED;
}
_InterlockedExchange(&entry->Grant->Revoked, 1);
KeReleaseSpinlock(&table->Lock, flags);
return NTLSTATUS_SUCCESS;
}
// Captures one capability so a later Port delivery does not depend on the source handle staying open.
KSTATUS KeCaptureHandleForTransfer(PROCESS* sourceProcess,
SYS_HANDLE sourceHandle,
SYS_RIGHTS desiredRights,
uint32_t flags,
KE_CAPTURED_HANDLE** capturedOut) {
if (!sourceProcess || !capturedOut)
return NTLSTATUS_INVALID_PARAMETER;
*capturedOut = (KE_CAPTURED_HANDLE*)0;
if (sourceHandle == SYS_HANDLE_INVALID)
return NTLSTATUS_INVALID_HANDLE;
const uint32_t validFlags = KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS |
KE_HANDLE_TRANSFER_FLAG_DERIVE;
if ((flags & ~validFlags) != 0)
return NTLSTATUS_INVALID_PARAMETER;
if ((flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS) &&
(flags & KE_HANDLE_TRANSFER_FLAG_DERIVE))
return NTLSTATUS_INVALID_PARAMETER;
if ((flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS) && desiredRights != 0)
return NTLSTATUS_INVALID_PARAMETER;
if (!(flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS) && desiredRights == 0)
return NTLSTATUS_INVALID_PARAMETER;
CAPABILITY_GRANT* sourceGrant = (CAPABILITY_GRANT*)0;
SYS_RIGHTS sourceAccess = 0;
KE_HANDLE_TABLE* table = &sourceProcess->HandleTable;
uintptr_t lockFlags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, sourceHandle, (KE_HANDLE_ENTRY***)0);
if (!entry) {
KeReleaseSpinlock(&table->Lock, lockFlags);
return NTLSTATUS_INVALID_HANDLE;
}
if (!KiGrantIsActive(entry->Grant)) {
KeReleaseSpinlock(&table->Lock, lockFlags);
return NTLSTATUS_CAPABILITY_REVOKED;
}
SYS_RIGHTS required = SYS_RIGHT_TRANSFER;
if (flags & KE_HANDLE_TRANSFER_FLAG_DERIVE)
required |= SYS_RIGHT_DERIVE;
if ((entry->Access & required) != required) {
KeReleaseSpinlock(&table->Lock, lockFlags);
return NTLSTATUS_ACCESS_DENIED;
}
sourceAccess = entry->Access;
SYS_RIGHTS targetAccess = (flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS)
? sourceAccess : desiredRights;
SYS_RIGHTS validRights = KeGetValidHandleRights(entry->Grant->Target->Type);
if ((targetAccess & ~validRights) != 0) {
KeReleaseSpinlock(&table->Lock, lockFlags);
return NTLSTATUS_INVALID_PARAMETER;
}
if ((targetAccess & ~sourceAccess) != 0) {
KeReleaseSpinlock(&table->Lock, lockFlags);
return NTLSTATUS_ACCESS_DENIED;
}
sourceGrant = entry->Grant;
KiReferenceGrant(sourceGrant);
KeReleaseSpinlock(&table->Lock, lockFlags);
CAPABILITY_GRANT* capturedGrant = sourceGrant;
if (flags & KE_HANDLE_TRANSFER_FLAG_DERIVE) {
KSTATUS status = KiCreateChildGrant(sourceGrant, targetAccess, &capturedGrant);
KiDereferenceGrant(sourceGrant);
if (!KSUCCESS(status))
return status;
}
KE_CAPTURED_HANDLE* captured = (KE_CAPTURED_HANDLE*)MmAllocatePoolTag(sizeof(*captured),
MM_POOL_TAG('c', 'a', 'p', 't'));
if (!captured) {
KiDereferenceGrant(capturedGrant);
return NTLSTATUS_NO_MEMORY;
}
captured->Grant = capturedGrant;
captured->Access = targetAccess;
*capturedOut = captured;
return NTLSTATUS_SUCCESS;
}
// Releases one captured capability that did not become a target-process handle.
void KeReleaseCapturedHandle(KE_CAPTURED_HANDLE* captured) {
if (!captured)
return;
if (captured->Grant)
KiDereferenceGrant(captured->Grant);
MmFreePool(captured);
}
// Materializes a complete transfer set atomically with respect to target-process handle visibility.
KSTATUS KeMaterializeCapturedHandles(PROCESS* targetProcess,
KE_CAPTURED_HANDLE** captured,
uint32_t count,
SYS_HANDLE* handlesOut) {
if (!targetProcess || (count && (!captured || !handlesOut)))
return NTLSTATUS_INVALID_PARAMETER;
if (count == 0)
return NTLSTATUS_SUCCESS;
KE_HANDLE_ENTRY** entries = (KE_HANDLE_ENTRY**)MmAllocatePoolTag(sizeof(KE_HANDLE_ENTRY*) * count,
MM_POOL_TAG('h', 'b', 'a', 't'));
if (!entries)
return NTLSTATUS_NO_MEMORY;
memset(entries, 0, sizeof(KE_HANDLE_ENTRY*) * count);
KSTATUS status = NTLSTATUS_SUCCESS;
for (uint32_t i = 0; i < count; ++i) {
if (!captured[i] || !captured[i]->Grant || captured[i]->Access == 0) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
if (!KiGrantIsActive(captured[i]->Grant)) {
status = NTLSTATUS_CAPABILITY_REVOKED;
break;
}
entries[i] = (KE_HANDLE_ENTRY*)MmAllocatePoolTag(sizeof(KE_HANDLE_ENTRY),
MM_POOL_TAG('h', 'n', 'd', 'l'));
if (!entries[i]) {
status = NTLSTATUS_NO_MEMORY;
break;
}
memset(entries[i], 0, sizeof(*entries[i]));
}
if (!KSUCCESS(status)) {
for (uint32_t i = 0; i < count; ++i)
if (entries[i]) MmFreePool(entries[i]);
MmFreePool(entries);
return status;
}
KE_HANDLE_TABLE* table = &targetProcess->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
if (table->Rundown) {
status = NTLSTATUS_OBJECT_DEAD;
} else if (table->NextHandle == SYS_HANDLE_INVALID ||
(uint64_t)table->NextHandle + (uint64_t)count - 1ull > UINT32_MAX) {
status = NTLSTATUS_ID_EXHAUSTED;
} else {
for (uint32_t i = 0; i < count; ++i) {
if (!KiGrantIsActive(captured[i]->Grant)) {
status = NTLSTATUS_CAPABILITY_REVOKED;
break;
}
}
}
if (KSUCCESS(status)) {
for (uint32_t i = 0; i < count; ++i) {
SYS_HANDLE handle = table->NextHandle++;
entries[i]->Handle = handle;
entries[i]->Access = captured[i]->Access;
entries[i]->Grant = captured[i]->Grant;
uint32_t bucket = KiHandleBucket(handle);
entries[i]->Next = table->Buckets[bucket];
table->Buckets[bucket] = entries[i];
table->Count++;
KiHandleOpened(captured[i]->Grant->Target);
handlesOut[i] = handle;
captured[i]->Grant = (CAPABILITY_GRANT*)0;
}
}
KeReleaseSpinlock(&table->Lock, flags);
if (!KSUCCESS(status)) {
for (uint32_t i = 0; i < count; ++i)
MmFreePool(entries[i]);
MmFreePool(entries);
return status;
}
for (uint32_t i = 0; i < count; ++i) {
MmFreePool(captured[i]);
captured[i] = (KE_CAPTURED_HANDLE*)0;
}
MmFreePool(entries);
return NTLSTATUS_SUCCESS;
}
// Returns the number of currently live handles in one process.
uint32_t KeGetHandleCount(PROCESS* process) {
if (!process) return 0;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
uint32_t count = table->Count;
KeReleaseSpinlock(&table->Lock, flags);
return count;
}
// Releases every handle and capability grant owned by one process.
void KeRundownHandleTable(PROCESS* process) {
if (!process) return;
KE_HANDLE_TABLE* table = &process->HandleTable;
uintptr_t rundownFlags = KeAcquireSpinlock(&table->Lock);
table->Rundown = 1u;
KeReleaseSpinlock(&table->Lock, rundownFlags);
for (uint32_t bucket = 0; bucket < KE_HANDLE_BUCKET_COUNT; ++bucket) {
while (true) {
uintptr_t flags = KeAcquireSpinlock(&table->Lock);
KE_HANDLE_ENTRY* entry = table->Buckets[bucket];
if (entry) {
table->Buckets[bucket] = entry->Next;
if (table->Count == 0) {
KeReleaseSpinlock(&table->Lock, flags);
KePanic(KE_PANIC_CODE_GENERIC,
"capability: rundown count underflow pid=%u\n",
process->ProcessId);
}
table->Count--;
}
KeReleaseSpinlock(&table->Lock, flags);
if (!entry) break;
OBJECT* object = entry->Grant->Target;
KeNamedQueueUnpublishHandle(process, entry->Handle, object);
KiHandleClosed(object);
KiDereferenceGrant(entry->Grant);
MmFreePool(entry);
}
}
}