// Kernel APC infrastructure. This file owns APC queuing, delivery, and user-mode return transitions. #include #include #include #include #include #include #include #include #include #include #define KI_USER_CS 0x1BULL #define KI_USER_SS 0x23ULL #define KI_RFLAGS_FIXED 0x2ULL #define KI_RFLAGS_IOPL 0x3000ULL #define KI_RFLAGS_NT 0x4000ULL #define KI_RFLAGS_VM 0x20000ULL static volatile long long gUserApcReturnTokenSeed; // Returns whether an address is in the canonical user half accepted by IRET. static bool KiUserAddressValid(uint64_t address) { return address >= USER_BASE && address < USER_LIMIT; } // Returns whether a saved user RFLAGS value can be restored safely. static bool KiUserRflagsValid(uint64_t rflags) { if ((rflags & KI_RFLAGS_FIXED) == 0) return false; if ((rflags & (KI_RFLAGS_IOPL | KI_RFLAGS_NT | KI_RFLAGS_VM)) != 0) return false; return true; } // Validates the kernel-owned state that will be restored after a user APC. static KSTATUS KiValidateUserApcResumeState(uint64_t rip, uint64_t rsp, uint64_t rflags, uint64_t cs, uint64_t ss) { if (!KiUserAddressValid(rip) || !KiUserAddressValid(rsp)) return STATUS_ACCESS_DENIED; if (cs != KI_USER_CS || ss != KI_USER_SS) return STATUS_ACCESS_DENIED; if (!KiUserRflagsValid(rflags)) return STATUS_ACCESS_DENIED; return STATUS_SUCCESS; } // Allocates a nonzero token for one active user APC return frame. static uint64_t KiNextUserApcReturnToken(THREAD* thread, uintptr_t frameAddress) { uint64_t seed = (uint64_t)_InterlockedIncrement64(&gUserApcReturnTokenSeed); uint64_t token = seed ^ (uint64_t)(uintptr_t)thread ^ (uint64_t)frameAddress ^ SYS_USER_APC_FRAME_MAGIC; if (token == 0) token = SYS_USER_APC_FRAME_MAGIC; return token; } // Initializes apc State. void KiInitializeApcState(THREAD* thread) { if (!thread) return; LIST_INIT(&thread->ApcQueue); thread->ApcLock = (SPINLOCK)SPINLOCK_INIT; thread->KernelApcPending = 0; thread->KernelApcDisable = 0; thread->ApcDeliveryActive = 0; LIST_INIT(&thread->UserApcQueue); thread->UserApcLock = (SPINLOCK)SPINLOCK_INIT; thread->UserApcPending = 0; thread->UserApcDispatcher = 0; thread->UserApcDeliverable = 0; thread->UserApcDeliveryActive = 0; thread->UserApcFrameUserAddress = 0; thread->UserApcReturnToken = 0; thread->UserApcSavedRip = 0; thread->UserApcSavedRsp = 0; thread->UserApcSavedRflags = 0; thread->UserApcSavedRax = 0; } __declspec(dllexport) void KeInitializeApc(KAPC* apc, THREAD* thread, PKKERNEL_ROUTINE kernelRoutine, PKNORMAL_ROUTINE normalRoutine, void* normalContext) { if (!apc) return; LIST_INIT(&apc->Link); apc->Thread = thread; apc->KernelRoutine = kernelRoutine; apc->NormalRoutine = normalRoutine; apc->RundownRoutine = (PKRUNDOWN_ROUTINE)0; apc->NormalContext = normalContext; apc->SystemArgument1 = (void*)0; apc->SystemArgument2 = (void*)0; apc->Inserted = 0; } __declspec(dllexport) void KeSetApcRundownRoutine(KAPC* apc, PKRUNDOWN_ROUTINE rundownRoutine) { if (!apc) return; apc->RundownRoutine = rundownRoutine; } __declspec(dllexport) bool KeInsertQueueApc(KAPC* apc, void* systemArgument1, void* systemArgument2) { if (!apc || !apc->Thread) return false; THREAD* thread = apc->Thread; if (thread->State == ThreadDead) return false; if (_InterlockedCompareExchange(&apc->Inserted, 1, 0) != 0) return false; apc->SystemArgument1 = systemArgument1; apc->SystemArgument2 = systemArgument2; uintptr_t flags = KeAcquireSpinlock(&thread->ApcLock); ListInsertTail(&thread->ApcQueue, &apc->Link); thread->KernelApcPending = 1; uint8_t wakeAlertable = (thread->State == ThreadWaiting && thread->AlertableWait && thread->KernelApcDisable == 0); KeReleaseSpinlock(&thread->ApcLock, flags); // If the target opted into an alertable wait, make the APC itself the // wait completion. The target thread will deliver the APC before // KeWaitForEvent(..., true) returns STATUS_KERNEL_APC. if (wakeAlertable) KiWakeThreadForKernelApc(thread); // If the caller queued an APC to itself at PASSIVE_LEVEL, try to deliver // immediately. Cross-thread APCs run the next time the target thread // returns to PASSIVE_LEVEL. if (thread == KeGetCurrentThread()) KiDeliverKernelApcs(); return true; } // Implements the enter Critical Region operation. __declspec(dllexport) void KeEnterCriticalRegion(void) { THREAD* thread = KeGetCurrentThread(); if (!thread) return; uintptr_t flags = KeAcquireSpinlock(&thread->ApcLock); thread->KernelApcDisable++; KeReleaseSpinlock(&thread->ApcLock, flags); } // Implements the leave Critical Region operation. __declspec(dllexport) void KeLeaveCriticalRegion(void) { THREAD* thread = KeGetCurrentThread(); if (!thread) return; uintptr_t flags = KeAcquireSpinlock(&thread->ApcLock); if (thread->KernelApcDisable > 0) thread->KernelApcDisable--; int deliver = (thread->KernelApcDisable == 0 && thread->KernelApcPending); KeReleaseSpinlock(&thread->ApcLock, flags); if (deliver) KiDeliverKernelApcs(); } // Implements the are Kernel Apcs Disabled operation. __declspec(dllexport) bool KeAreKernelApcsDisabled(void) { THREAD* thread = KeGetCurrentThread(); if (!thread) return true; return thread->KernelApcDisable != 0; } // Implements the pop Kernel Apc operation. static KAPC* KiPopKernelApc(THREAD* thread) { uintptr_t flags = KeAcquireSpinlock(&thread->ApcLock); LIST_ENTRY* node = ListPopHead(&thread->ApcQueue); if (LIST_IS_EMPTY(&thread->ApcQueue)) thread->KernelApcPending = 0; KeReleaseSpinlock(&thread->ApcLock, flags); if (!node) return (KAPC*)0; return CONTAINING_RECORD(node, KAPC, Link); } // Implements the deliver Kernel Apcs operation. void KiDeliverKernelApcs(void) { THREAD* thread = KeGetCurrentThread(); if (!thread) return; if (KeGetCurrentIrql() != PASSIVE_LEVEL) return; if (thread->ApcDeliveryActive || thread->KernelApcDisable != 0 || !thread->KernelApcPending) return; thread->ApcDeliveryActive = 1; for (;;) { if (KeGetCurrentIrql() != PASSIVE_LEVEL || thread->KernelApcDisable != 0) break; KAPC* apc = KiPopKernelApc(thread); if (!apc) break; apc->Inserted = 0; PKNORMAL_ROUTINE normalRoutine = apc->NormalRoutine; void* normalContext = apc->NormalContext; void* arg1 = apc->SystemArgument1; void* arg2 = apc->SystemArgument2; if (apc->KernelRoutine) apc->KernelRoutine(apc, (void**)&normalRoutine, &normalContext, &arg1, &arg2); // After the kernel routine has had a chance to adjust the callout, // stop touching APC storage before invoking the normal routine. The // normal routine is allowed to free the APC's containing allocation. apc->SystemArgument1 = (void*)0; apc->SystemArgument2 = (void*)0; if (normalRoutine && KeGetCurrentIrql() == PASSIVE_LEVEL) normalRoutine(normalContext, arg1, arg2); } thread->ApcDeliveryActive = 0; } // Updates user Apc Dispatcher. __declspec(dllexport) KSTATUS KeSetUserApcDispatcher(void* dispatcher) { THREAD* thread = KeGetCurrentThread(); if (!thread) return STATUS_FAILURE; if (!dispatcher) return STATUS_INVALID_PARAMETER; if (!KiUserAddressValid((uint64_t)(uintptr_t)dispatcher)) return STATUS_ACCESS_DENIED; KSTATUS s = MmProbeUserRead(dispatcher, 1); if (!KSUCCESS(s)) return s; thread->UserApcDispatcher = (uintptr_t)dispatcher; return STATUS_SUCCESS; } __declspec(dllexport) KSTATUS KeQueueUserApc(THREAD* thread, void* routine, void* normalContext, void* systemArgument1, void* systemArgument2) { if (!thread || !routine) return STATUS_INVALID_PARAMETER; if (!KiUserAddressValid((uint64_t)(uintptr_t)routine)) return STATUS_ACCESS_DENIED; if (thread->State == ThreadDead) return STATUS_OBJECT_DEAD; KUSER_APC* apc = (KUSER_APC*)MmAllocatePoolTag(sizeof(KUSER_APC), MM_POOL_TAG('a', 'p', 'c', ' ')); if (!apc) return STATUS_NO_MEMORY; memset(apc, 0, sizeof(*apc)); LIST_INIT(&apc->Link); apc->Routine = routine; apc->NormalContext = normalContext; apc->SystemArgument1 = systemArgument1; apc->SystemArgument2 = systemArgument2; uintptr_t flags = KeAcquireSpinlock(&thread->UserApcLock); ListInsertTail(&thread->UserApcQueue, &apc->Link); thread->UserApcPending = 1; uint8_t wakeAlertable = (thread->State == ThreadWaiting && thread->AlertableWait && thread->UserApcDispatcher != 0); KeReleaseSpinlock(&thread->UserApcLock, flags); if (wakeAlertable) KiWakeThreadForUserApc(thread); return STATUS_SUCCESS; } // Implements the pop User Apc operation. static KUSER_APC* KiPopUserApc(THREAD* thread) { uintptr_t flags = KeAcquireSpinlock(&thread->UserApcLock); LIST_ENTRY* node = ListPopHead(&thread->UserApcQueue); if (LIST_IS_EMPTY(&thread->UserApcQueue)) thread->UserApcPending = 0; KeReleaseSpinlock(&thread->UserApcLock, flags); if (!node) return (KUSER_APC*)0; return CONTAINING_RECORD(node, KUSER_APC, Link); } // Implements the check For User Apc Delivery operation. void KiCheckForUserApcDelivery(KTRAP_FRAME* frame) { if (!frame || !KeTrapFrameFromUser(frame)) return; THREAD* thread = KeGetCurrentThread(); if (!thread) return; if (!thread->UserApcDeliverable || thread->UserApcDeliveryActive || !thread->UserApcPending || !thread->UserApcDispatcher) return; if (KeGetCurrentIrql() != PASSIVE_LEVEL) return; KUSER_APC* apc = KiPopUserApc(thread); if (!apc) return; KSTATUS s = KiValidateUserApcResumeState(frame->Rip, frame->Rsp, frame->Rflags, frame->Cs, frame->Ss); if (!KSUCCESS(s)) { DbgPrint("USER/APC: rejected saved return state rip=0x%016llX rsp=0x%016llX rflags=0x%016llX cs=0x%016llX ss=0x%016llX status=%u\n", frame->Rip, frame->Rsp, frame->Rflags, frame->Cs, frame->Ss, s); uintptr_t flags = KeAcquireSpinlock(&thread->UserApcLock); ListInsertTail(&thread->UserApcQueue, &apc->Link); thread->UserApcPending = 1; KeReleaseSpinlock(&thread->UserApcLock, flags); return; } uintptr_t oldRsp = (uintptr_t)frame->Rsp; uintptr_t newRsp = oldRsp - sizeof(SYS_USER_APC_FRAME_K); newRsp &= ~(uintptr_t)0xFULL; newRsp -= 8; // Make injected dispatcher entry look like a normal call target. SYS_USER_APC_FRAME_K userFrame; memset(&userFrame, 0, sizeof(userFrame)); userFrame.Magic = SYS_USER_APC_FRAME_MAGIC; userFrame.ReturnToken = KiNextUserApcReturnToken(thread, newRsp); userFrame.Routine = (uint64_t)(uintptr_t)apc->Routine; userFrame.NormalContext = (uint64_t)(uintptr_t)apc->NormalContext; userFrame.SystemArgument1 = (uint64_t)(uintptr_t)apc->SystemArgument1; userFrame.SystemArgument2 = (uint64_t)(uintptr_t)apc->SystemArgument2; s = CopyToUser((void*)newRsp, &userFrame, sizeof(userFrame)); if (!KSUCCESS(s)) { uintptr_t flags = KeAcquireSpinlock(&thread->UserApcLock); ListInsertTail(&thread->UserApcQueue, &apc->Link); thread->UserApcPending = 1; KeReleaseSpinlock(&thread->UserApcLock, flags); return; } thread->UserApcFrameUserAddress = newRsp; thread->UserApcReturnToken = userFrame.ReturnToken; thread->UserApcSavedRip = frame->Rip; thread->UserApcSavedRsp = frame->Rsp; thread->UserApcSavedRflags = frame->Rflags; thread->UserApcSavedRax = STATUS_USER_APC; frame->Rip = thread->UserApcDispatcher; frame->Rsp = newRsp; frame->Rax = STATUS_USER_APC; thread->UserApcDeliverable = 0; thread->UserApcDeliveryActive = 1; MmFreePool(apc); } // Implements the rundown Apc State operation. void KiRundownApcState(THREAD* thread) { if (!thread) return; for (;;) { uintptr_t flags = KeAcquireSpinlock(&thread->ApcLock); LIST_ENTRY* node = ListPopHead(&thread->ApcQueue); if (LIST_IS_EMPTY(&thread->ApcQueue)) thread->KernelApcPending = 0; KeReleaseSpinlock(&thread->ApcLock, flags); if (!node) break; KAPC* apc = CONTAINING_RECORD(node, KAPC, Link); apc->Inserted = 0; if (apc->RundownRoutine) apc->RundownRoutine(apc); } for (;;) { uintptr_t flags = KeAcquireSpinlock(&thread->UserApcLock); LIST_ENTRY* node = ListPopHead(&thread->UserApcQueue); if (LIST_IS_EMPTY(&thread->UserApcQueue)) thread->UserApcPending = 0; KeReleaseSpinlock(&thread->UserApcLock, flags); if (!node) break; KUSER_APC* apc = CONTAINING_RECORD(node, KUSER_APC, Link); MmFreePool(apc); } thread->UserApcDispatcher = 0; thread->UserApcDeliverable = 0; thread->UserApcDeliveryActive = 0; thread->UserApcFrameUserAddress = 0; thread->UserApcReturnToken = 0; thread->UserApcSavedRip = 0; thread->UserApcSavedRsp = 0; thread->UserApcSavedRflags = 0; thread->UserApcSavedRax = 0; } // Implements the user Apc Return operation. __declspec(dllexport) uint64_t KeUserApcReturn(const SYS_USER_APC_FRAME_K* userFrame) { THREAD* thread = KeGetCurrentThread(); if (!thread) return STATUS_FAILURE; if (!thread->UserApcDeliveryActive || !userFrame) return STATUS_INVALID_PARAMETER; SYS_USER_APC_FRAME_K frameCopy; KSTATUS s = CopyFromUser(&frameCopy, userFrame, sizeof(frameCopy)); if (!KSUCCESS(s)) return s; if (frameCopy.Magic != SYS_USER_APC_FRAME_MAGIC || frameCopy.ReturnToken != thread->UserApcReturnToken || (uintptr_t)userFrame != thread->UserApcFrameUserAddress) { DbgPrint("USER/APC: rejected return frame user=0x%016llX active=0x%016llX token=0x%016llX expected=0x%016llX\n", (uint64_t)(uintptr_t)userFrame, (uint64_t)thread->UserApcFrameUserAddress, frameCopy.ReturnToken, thread->UserApcReturnToken); return STATUS_INVALID_PARAMETER; } KTRAP_FRAME* frame = KeGetCurrentTrapFrame(); if (!frame || !KeTrapFrameFromUser(frame) || frame->Kind != KTRAP_KIND_SYSCALL) return STATUS_FAILURE; s = KiValidateUserApcResumeState(thread->UserApcSavedRip, thread->UserApcSavedRsp, thread->UserApcSavedRflags, frame->Cs, frame->Ss); if (!KSUCCESS(s)) { DbgPrint("USER/APC: rejected kernel-owned resume state rip=0x%016llX rsp=0x%016llX rflags=0x%016llX status=%u\n", thread->UserApcSavedRip, thread->UserApcSavedRsp, thread->UserApcSavedRflags, s); return s; } uint64_t savedRax = thread->UserApcSavedRax; frame->Rip = thread->UserApcSavedRip; frame->Rsp = thread->UserApcSavedRsp; frame->Rflags = thread->UserApcSavedRflags; frame->Rax = savedRax; thread->UserApcDeliveryActive = 0; thread->UserApcDeliverable = 0; thread->UserApcFrameUserAddress = 0; thread->UserApcReturnToken = 0; thread->UserApcSavedRip = 0; thread->UserApcSavedRsp = 0; thread->UserApcSavedRflags = 0; thread->UserApcSavedRax = 0; return savedRax; }