767 lines
30 KiB
C
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*)¤t->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 | SYS_PROCESS_RIGHT_SECURITY_CONTEXT;
|
|
case ObjectTypeThread:
|
|
return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_THREAD_RIGHT_RESUME |
|
|
SYS_THREAD_RIGHT_SET_INFORMATION | SYS_THREAD_RIGHT_NOTIFY;
|
|
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 | SYS_PORT_RIGHT_OPEN_PEER_PROCESS;
|
|
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);
|
|
}
|
|
}
|
|
}
|