From 4edc8183311170f4b035bfc166a248f452df7deb Mon Sep 17 00:00:00 2001 From: Timo Kreuzer Date: Tue, 7 Nov 2023 00:51:26 +0200 Subject: [PATCH] [NTOS:KE] Use callout stacks for user callback stack expansion (optional) This is a Windows 7+ feature, which allows deeper recursion of user mode callbacks for win32k. It is required for some modern applications, like latest versions of Chrome. --- ntoskrnl/include/internal/ke.h | 10 ++++ ntoskrnl/ke/amd64/usercall.c | 102 ++++++++++++++++++++++++++++++++- ntoskrnl/ke/i386/usercall.c | 8 +++ ntoskrnl/ntos.cmake | 2 +- ntoskrnl/ps/kill.c | 16 +++++- sdk/cmake/config.cmake | 8 +++ 6 files changed, 140 insertions(+), 6 deletions(-) diff --git a/ntoskrnl/include/internal/ke.h b/ntoskrnl/include/internal/ke.h index 576db504672..db48c349704 100644 --- a/ntoskrnl/include/internal/ke.h +++ b/ntoskrnl/include/internal/ke.h @@ -1084,6 +1084,16 @@ NTAPI KiGetStackPointer( VOID); +VOID +NTAPI +KiReapCallbackStacks( + _In_ PKTHREAD Thread); + +VOID +NTAPI +KiRemoveThreadCalloutStack( + _In_ PKTHREAD Thread); + #ifdef __cplusplus } // extern "C" diff --git a/ntoskrnl/ke/amd64/usercall.c b/ntoskrnl/ke/amd64/usercall.c index bab35e9e40e..209d1213589 100644 --- a/ntoskrnl/ke/amd64/usercall.c +++ b/ntoskrnl/ke/amd64/usercall.c @@ -8,6 +8,7 @@ /* INCLUDES ******************************************************************/ #include +#include #define NDEBUG #include @@ -146,7 +147,6 @@ KiUserModeCallout( KTRAP_FRAME CallbackTrapFrame; PKIPCR Pcr; ULONG_PTR InitialStack; - NTSTATUS Status; /* Get the current thread */ CurrentThread = KeGetCurrentThread(); @@ -165,7 +165,7 @@ KiUserModeCallout( if ((InitialStack - KERNEL_STACK_SIZE) < CurrentThread->StackLimit) { /* We don't, we'll have to grow our stack */ - Status = MmGrowKernelStack((PVOID)InitialStack); + NTSTATUS Status = MmGrowKernelStack((PVOID)InitialStack); /* Quit if we failed */ if (!NT_SUCCESS(Status)) return Status; @@ -194,7 +194,7 @@ KiUserModeCallout( /* Get PCR */ Pcr = (PKIPCR)KeGetPcr(); - /* Set user-mode dispatcher address as EIP */ + /* Set user-mode dispatcher address as RIP */ Pcr->TssBase->Rsp0 = InitialStack; Pcr->Prcb.RspBase = InitialStack; CallbackTrapFrame.Rip = (ULONG_PTR)KeUserCallbackDispatcher; @@ -230,6 +230,98 @@ KiSetupUserCalloutFrame( #endif } +#if ENABLE_CALLBACK_STACKS + +typedef struct _KUSER_CALLOUT_PARAMETER +{ + PVOID *Result; + PULONG ResultLength; + NTSTATUS CallbackStatus; +} KUSER_CALLOUT_PARAMETER, *PKUSER_CALLOUT_PARAMETER; + +VOID +NTAPI +KiCallUserModeHelper( + _In_ PVOID Parameter) +{ + PKUSER_CALLOUT_PARAMETER CalloutParameter = (PKUSER_CALLOUT_PARAMETER)Parameter; + NTSTATUS Status; + + /* Call the callback */ + Status = KiCallUserMode(CalloutParameter->Result, + CalloutParameter->ResultLength); + + /* Store the callback status */ + CalloutParameter->CallbackStatus = Status; +} + +NTSTATUS +NTAPI +KiCallUserModeEx( + _Out_ PVOID *Result, + _Out_ PULONG ResultLength) +{ + KUSER_CALLOUT_PARAMETER CalloutParameter; + NTSTATUS Status; + +#if (NTDDI_VERSION >= NTDDI_WIN8) + Thread->CallbackNestingLevel++; +#endif // (NTDDI_VERSION >= NTDDI_WIN8) + + /* Setup the callout parameter */ + CalloutParameter.Result = Result; + CalloutParameter.ResultLength = ResultLength; + + /* Call the callback and make sure the stack is large enough */ + Status = KeExpandKernelStackAndCallout(KiCallUserModeHelper, + &CalloutParameter, + KERNEL_STACK_SIZE); + if (NT_SUCCESS(Status)) + { + /* Return the callback status */ + Status = CalloutParameter.CallbackStatus; + } + +#if (NTDDI_VERSION >= NTDDI_WIN8) + Thread->CallbackNestingLevel--; +#endif // (NTDDI_VERSION >= NTDDI_WIN8) + + /* Return the status */ + return Status; +} + +VOID +NTAPI +KiReapCallbackStacks( + _In_ PKTHREAD Thread) +{ + ASSERT(Thread != KeGetCurrentThread()); + + /* Loop while we have a callback stack */ + while (Thread->CallbackStack) + { + /* Get the current callout frame */ + PKCALLOUT_FRAME CalloutFrame = Thread->CallbackStack; + + /* Restore the previous callback stack */ + Thread->CallbackStack = (PVOID)CalloutFrame->CallbackStack; + + /* Check whether the saved trapframe is outside of the current stack */ + if (((ULONG_PTR)CalloutFrame->TrapFrame < (ULONG_PTR)Thread->StackLimit) || + ((ULONG_PTR)CalloutFrame->TrapFrame > (ULONG_PTR)Thread->StackBase)) + { + /* We were on a callout stack, remove it */ + KiRemoveThreadCalloutStack(Thread); + } + +#if (NTDDI_VERSION >= NTDDI_WIN8) + Thread->CallbackNestingLevel--; +#endif // (NTDDI_VERSION >= NTDDI_WIN8) + } +} + +#endif // ENABLE_CALLBACK_STACK + NTSTATUS NTAPI KeUserModeCallback( @@ -291,7 +383,11 @@ KeUserModeCallback( /* Jump to user mode */ *UserStackPointer = (ULONG_PTR)CalloutFrame; +#ifdef USE_CALLOUT_STACK + CallbackStatus = KiCallUserModeEx(Result, ResultLength); +#else CallbackStatus = KiCallUserMode(Result, ResultLength); +#endif if (CallbackStatus != STATUS_CALLBACK_POP_STACK) { #ifdef _M_IX86 diff --git a/ntoskrnl/ke/i386/usercall.c b/ntoskrnl/ke/i386/usercall.c index da99b0e1ce8..4f5eda01828 100644 --- a/ntoskrnl/ke/i386/usercall.c +++ b/ntoskrnl/ke/i386/usercall.c @@ -358,6 +358,14 @@ KiUserModeCallout(PKCALLOUT_FRAME CalloutFrame) KiServiceExit(CallbackTrapFrame, 0); } +VOID +NTAPI +KiReapCallbackStacks( + _In_ PKTHREAD Thread) +{ + __debugbreak(); +} + /*++ * @name NtCallbackReturn * diff --git a/ntoskrnl/ntos.cmake b/ntoskrnl/ntos.cmake index 87be101bbe7..a629a36c50b 100644 --- a/ntoskrnl/ntos.cmake +++ b/ntoskrnl/ntos.cmake @@ -295,7 +295,7 @@ list(APPEND SOURCE ${REACTOS_SOURCE_DIR}/ntoskrnl/wmi/wmi.c ${REACTOS_SOURCE_DIR}/ntoskrnl/wmi/wmidrv.c) -if(DLL_EXPORT_VERSION GREATER_EQUAL 0x600) +if(DLL_EXPORT_VERSION GREATER_EQUAL 0x600 OR ENABLE_CALLBACK_STACKS) list(APPEND SOURCE ${REACTOS_SOURCE_DIR}/ntoskrnl/ke/callout.c ) diff --git a/ntoskrnl/ps/kill.c b/ntoskrnl/ps/kill.c index 4a746492486..b295a6c808d 100644 --- a/ntoskrnl/ps/kill.c +++ b/ntoskrnl/ps/kill.c @@ -184,8 +184,14 @@ PspReapRoutine(IN PVOID Context) /* Get the first Thread Entry */ Thread = CONTAINING_RECORD(NextEntry, ETHREAD, ReaperLink); + /* Reap callback stacks */ + if (Thread->Tcb.CalloutActive) + { + KiReapCallbackStacks(&Thread->Tcb); + } + /* Delete this entry's kernel stack */ - MmDeleteKernelStack((PVOID)Thread->Tcb.StackBase, + MmDeleteKernelStack(Thread->Tcb.StackBase, Thread->Tcb.LargeStack); Thread->Tcb.InitialStack = NULL; @@ -400,8 +406,14 @@ PspDeleteThread(IN PVOID ObjectBody) /* Check if we have a stack */ if (Thread->Tcb.InitialStack) { + /* Reap callback stacks */ + if (Thread->Tcb.CalloutActive) + { + KiReapCallbackStacks(&Thread->Tcb); + } + /* Release it */ - MmDeleteKernelStack((PVOID)Thread->Tcb.StackBase, + MmDeleteKernelStack(Thread->Tcb.StackBase, Thread->Tcb.LargeStack); } diff --git a/sdk/cmake/config.cmake b/sdk/cmake/config.cmake index 77b26a0383e..8a995349db2 100644 --- a/sdk/cmake/config.cmake +++ b/sdk/cmake/config.cmake @@ -117,3 +117,11 @@ set(USE_DUMMY_PSEH FALSE CACHE BOOL set(DLL_EXPORT_VERSION "0x502" CACHE STRING "The NT version the user mode DLLs target.") + +if(ARCH STREQUAL "amd64") +set(ENABLE_CALLBACK_STACKS TRUE CACHE BOOL +"Whether to enable the use of callout stacks in user mode callbacks.") +else() +set(ENABLE_CALLBACK_STACKS FALSE CACHE BOOL +"Whether to enable the use of callout stacks in user mode callbacks.") +endif()