reactos/ntoskrnl/ke/amd64/traphandler.c
Timo Kreuzer 91948dea80 [NTOS:KE/x64] Fix handling of PCR::UserRsp
This is a temporary helper for the system call entry point to store the user mode stack, before switching to the kernel mode stack. Initially it was copied to the trap frame inside KiSystemCallHandler. This has been moved to the system call entry point, but some remnants remained. The problem is that KiSystemCallHandler can be called twice in a system call (when the call is the first GUI call and the stack needs to be extended). In that scenario, when the thread was preempted, a new value could be saved in the PCR before running KiSystemCallHandler again, and then overwriting the proper value with a bogus one from a different thread. This rarely seemed to happen on UP, but happens a lot with SMP.
2024-12-16 16:18:45 +02:00

280 lines
7.5 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL - See COPYING in the top level directory
* PURPOSE: x64 trap handlers
* PROGRAMMERS: Timo Kreuzer (timo.kreuzer@reactos.org)
*/
/* INCLUDES ******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
VOID
KiRetireDpcListInDpcStack(
PKPRCB Prcb,
PVOID DpcStack);
NTSTATUS
KiConvertToGuiThread(
VOID);
_Requires_lock_not_held_(Prcb->PrcbLock)
VOID
NTAPI
KiDpcInterruptHandler(VOID)
{
PKPRCB Prcb = KeGetCurrentPrcb();
PKTHREAD NewThread, OldThread;
KIRQL OldIrql;
/* Raise to DISPATCH_LEVEL */
OldIrql = KfRaiseIrql(DISPATCH_LEVEL);
/* Send an EOI */
KiSendEOI();
/* Check for pending timers, pending DPCs, or pending ready threads */
if ((Prcb->DpcData[0].DpcQueueDepth) ||
(Prcb->TimerRequest) ||
(Prcb->DeferredReadyListHead.Next))
{
/* Retire DPCs while under the DPC stack */
KiRetireDpcListInDpcStack(Prcb, Prcb->DpcStack);
}
/* Enable interrupts */
_enable();
/* Check for quantum end */
if (Prcb->QuantumEnd)
{
/* Handle quantum end */
Prcb->QuantumEnd = FALSE;
KiQuantumEnd();
}
else if (Prcb->NextThread)
{
/* Acquire the PRCB lock */
KiAcquirePrcbLock(Prcb);
/* Capture current thread data */
OldThread = Prcb->CurrentThread;
NewThread = Prcb->NextThread;
/* Set new thread data */
Prcb->NextThread = NULL;
Prcb->CurrentThread = NewThread;
/* The thread is now running */
NewThread->State = Running;
OldThread->WaitReason = WrDispatchInt;
/* Make the old thread ready */
KxQueueReadyThread(OldThread, Prcb);
/* Swap to the new thread */
KiSwapContext(APC_LEVEL, OldThread);
}
/* Disable interrupts and go back to old irql */
_disable();
KeLowerIrql(OldIrql);
}
VOID
KiNmiInterruptHandler(
_In_ PKTRAP_FRAME TrapFrame,
_In_ PKEXCEPTION_FRAME ExceptionFrame)
{
BOOLEAN ManualSwapGs = FALSE;
/* Check if the NMI came from kernel mode */
if ((TrapFrame->SegCs & MODE_MASK) == 0)
{
/* Check if GS base is already kernel mode. This is needed, because
we might be interrupted during an interrupt/exception from user-mode
before the swapgs instruction. */
if ((LONG64)__readmsr(MSR_GS_BASE) >= 0)
{
/* Swap GS to kernel */
__swapgs();
ManualSwapGs = TRUE;
}
}
/* Check if this is a freeze */
if (KiProcessorFreezeHandler(TrapFrame, ExceptionFrame))
{
/* NMI was handled */
goto Exit;
}
/* Handle the NMI */
KiHandleNmi();
Exit:
/* Check if we need to swap GS back */
if (ManualSwapGs)
{
/* Swap GS back to user */
__swapgs();
}
}
#define MAX_SYSCALL_PARAMS 16
NTSTATUS
NtSyscallFailure(void)
{
/* This is the failure function */
return (NTSTATUS)KeGetCurrentThread()->TrapFrame->Rax;
}
PVOID
KiSystemCallHandler(
VOID)
{
PKTRAP_FRAME TrapFrame;
PKSERVICE_TABLE_DESCRIPTOR DescriptorTable;
PKTHREAD Thread;
PULONG64 KernelParams, UserParams;
ULONG ServiceNumber, TableIndex, Count;
ULONG64 UserRsp;
/* Get a pointer to the trap frame */
TrapFrame = (PKTRAP_FRAME)((PULONG64)_AddressOfReturnAddress() + 1 + MAX_SYSCALL_PARAMS);
/* Increase system call count */
__addgsdword(FIELD_OFFSET(KIPCR, Prcb.KeSystemCalls), 1);
/* Get the current thread */
Thread = KeGetCurrentThread();
/* Set previous mode */
Thread->PreviousMode = TrapFrame->PreviousMode = UserMode;
/* We don't have an exception frame yet */
TrapFrame->ExceptionFrame = 0;
/* Get the user Stack pointer */
UserRsp = TrapFrame->Rsp;
/* Enable interrupts */
_enable();
/* If the usermode rsp was not a usermode address, prepare an exception */
if (UserRsp > MmUserProbeAddress) UserRsp = MmUserProbeAddress;
/* Get the address of the usermode and kernelmode parameters */
UserParams = (PULONG64)UserRsp + 1;
KernelParams = (PULONG64)TrapFrame - MAX_SYSCALL_PARAMS;
/* Get the system call number from the trap frame and decode it */
ServiceNumber = (ULONG)TrapFrame->Rax;
TableIndex = (ServiceNumber >> TABLE_OFFSET_BITS) & ((1 << TABLE_NUMBER_BITS) - 1);
ServiceNumber &= SERVICE_NUMBER_MASK;
/* Check for win32k system calls */
if (TableIndex == WIN32K_SERVICE_INDEX)
{
ULONG GdiBatchCount;
/* Read the GDI batch count from the TEB */
_SEH2_TRY
{
GdiBatchCount = NtCurrentTeb()->GdiBatchCount;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
GdiBatchCount = 0;
}
_SEH2_END;
/* Flush batch, if there are entries */
if (GdiBatchCount != 0)
{
KeGdiFlushUserBatch();
}
}
/* Get descriptor table */
DescriptorTable = &((PKSERVICE_TABLE_DESCRIPTOR)Thread->ServiceTable)[TableIndex];
/* Validate the system call number */
if (ServiceNumber >= DescriptorTable->Limit)
{
/* Check if this is a GUI call and this is not a GUI thread yet */
if ((TableIndex == WIN32K_SERVICE_INDEX) &&
(Thread->ServiceTable == KeServiceDescriptorTable))
{
/* Convert this thread to a GUI thread.
It is invalid to change the stack in the middle of a C function,
therefore we return KiConvertToGuiThread to the system call entry
point, which then calls the asm function KiConvertToGuiThread,
which allocates a new stack, switches to it, calls
PsConvertToGuiThread and resumes in the middle of
KiSystemCallEntry64 to restart the system call handling.
If converting fails, the system call returns a failure code. */
return (PVOID)KiConvertToGuiThread;
}
/* Fail the call */
TrapFrame->Rax = STATUS_INVALID_SYSTEM_SERVICE;
return (PVOID)NtSyscallFailure;
}
/* Get stack bytes and calculate argument count */
Count = DescriptorTable->Number[ServiceNumber] / 8;
_SEH2_TRY
{
switch (Count)
{
case 16: KernelParams[15] = UserParams[15];
case 15: KernelParams[14] = UserParams[14];
case 14: KernelParams[13] = UserParams[13];
case 13: KernelParams[12] = UserParams[12];
case 12: KernelParams[11] = UserParams[11];
case 11: KernelParams[10] = UserParams[10];
case 10: KernelParams[9] = UserParams[9];
case 9: KernelParams[8] = UserParams[8];
case 8: KernelParams[7] = UserParams[7];
case 7: KernelParams[6] = UserParams[6];
case 6: KernelParams[5] = UserParams[5];
case 5: KernelParams[4] = UserParams[4];
case 4:
case 3:
case 2:
case 1:
case 0:
break;
default:
ASSERT(FALSE);
break;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
TrapFrame->Rax = _SEH2_GetExceptionCode();
return (PVOID)NtSyscallFailure;
}
_SEH2_END;
return (PVOID)DescriptorTable->Base[ServiceNumber];
}
// FIXME: we need to
VOID
KiSystemService(IN PKTHREAD Thread,
IN PKTRAP_FRAME TrapFrame,
IN ULONG Instruction)
{
UNIMPLEMENTED;
__debugbreak();
}