// Kernel capability handles. This file owns process-local handle identity, grant delegation, and revocation. #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct CAPABILITY_GRANT { volatile int32_t RefCount; volatile int32_t 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; int32_t refs = NtlAtomicIncrement32(&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) { int32_t refs = NtlAtomicDecrement32(&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 bool KiGrantIsActive(const CAPABILITY_GRANT* grant) { const CAPABILITY_GRANT* current = grant; uint32_t depth = 0; while (current) { if (NtlAtomicLoad32(¤t->Revoked) != 0) return false; current = current->Parent; if (++depth > KE_CAPABILITY_MAX_DEPTH) return false; } return true; } // 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; 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 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 ObjectTypeServiceRegistration: return management | SYS_RIGHT_QUERY | SYS_SERVICE_REGISTRATION_RIGHT_REGISTER | SYS_SERVICE_REGISTRATION_RIGHT_UNREGISTER; 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); 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; } NtlAtomicExchange32(&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; KiHandleClosed(object); KiDereferenceGrant(entry->Grant); MmFreePool(entry); } } }