From 45f75d5d320caf7ab1113f0a2cf7c853e9ad4216 Mon Sep 17 00:00:00 2001 From: Timo Kreuzer Date: Sat, 26 Jun 2021 18:49:47 +0200 Subject: [PATCH] [NTOS:KE/x64] Handle user faults in KiGeneralProtectionFaultHandler --- ntoskrnl/ke/amd64/except.c | 190 ++++++++++++++++++++++++++++++++++--- ntoskrnl/ke/amd64/trap.S | 16 +++- sdk/lib/rtl/amd64/unwind.c | 7 +- 3 files changed, 198 insertions(+), 15 deletions(-) diff --git a/ntoskrnl/ke/amd64/except.c b/ntoskrnl/ke/amd64/except.c index dfaee9ed3cc..a04d3038bd9 100644 --- a/ntoskrnl/ke/amd64/except.c +++ b/ntoskrnl/ke/amd64/except.c @@ -425,6 +425,184 @@ KiNpxNotAvailableFaultHandler( return -1; } +static +BOOLEAN +KiIsPrivilegedInstruction(PUCHAR Ip, BOOLEAN Wow64) +{ + ULONG i; + + /* Handle prefixes */ + for (i = 0; i < 15; i++) + { + if (!Wow64) + { + /* Check for REX prefix */ + if ((Ip[0] >= 0x40) && (Ip[0] <= 0x4F)) + { + Ip++; + continue; + } + } + + switch (Ip[0]) + { + /* Check prefixes */ + case 0x26: // ES + case 0x2E: // CS / null + case 0x36: // SS + case 0x3E: // DS + case 0x64: // FS + case 0x65: // GS + case 0x66: // OP + case 0x67: // ADDR + case 0xF0: // LOCK + case 0xF2: // REP + case 0xF3: // REP INS/OUTS + Ip++; + continue; + } + + break; + } + + if (i == 15) + { + /* Too many prefixes. Should only happen, when the code was concurrently modified. */ + return FALSE; + } + + switch (Ip[0]) + { + case 0xF4: // HLT + case 0xFA: // CLI + case 0xFB: // STI + return TRUE; + + case 0x0F: + { + switch (Ip[1]) + { + case 0x06: // CLTS + case 0x07: // SYSRET + case 0x08: // INVD + case 0x09: // WBINVD + case 0x20: // MOV CR, XXX + case 0x21: // MOV DR, XXX + case 0x22: // MOV XXX, CR + case 0x23: // MOV YYY, DR + case 0x30: // WRMSR + case 0x32: // RDMSR + case 0x33: // RDPMC + case 0x35: // SYSEXIT + case 0x78: // VMREAD + case 0x79: // VMWRITE + return TRUE; + + case 0x00: + { + /* Check MODRM Reg field */ + switch ((Ip[2] >> 3) & 0x7) + { + case 2: // LLDT + case 3: // LTR + return TRUE; + } + break; + } + + case 0x01: + { + switch (Ip[2]) + { + case 0xC1: // VMCALL + case 0xC2: // VMLAUNCH + case 0xC3: // VMRESUME + case 0xC4: // VMXOFF + case 0xC8: // MONITOR + case 0xC9: // MWAIT + case 0xD1: // XSETBV + case 0xF8: // SWAPGS + return TRUE; + } + + /* Check MODRM Reg field */ + switch ((Ip[2] >> 3) & 0x7) + { + case 2: // LGDT + case 3: // LIDT + case 6: // LMSW + case 7: // INVLPG / SWAPGS / RDTSCP + return TRUE; + } + break; + } + + case 0x38: + { + switch (Ip[2]) + { + case 0x80: // INVEPT + case 0x81: // INVVPID + return TRUE; + } + break; + } + + case 0xC7: + { + /* Check MODRM Reg field */ + switch ((Ip[2] >> 3) & 0x7) + { + case 0x06: // VMPTRLD, VMCLEAR, VMXON + case 0x07: // VMPTRST + return TRUE; + } + break; + } + } + + break; + } + } + + return FALSE; +} + +static +NTSTATUS +KiGeneralProtectionFaultUserMode( + _In_ PKTRAP_FRAME TrapFrame) +{ + BOOLEAN Wow64 = TrapFrame->SegCs == KGDT64_R3_CMCODE; + PUCHAR InstructionPointer; + NTSTATUS Status; + + /* We need to decode the instruction at RIP */ + InstructionPointer = (PUCHAR)TrapFrame->Rip; + + _SEH2_TRY + { + /* Probe the instruction address */ + ProbeForRead(InstructionPointer, 64, 1); + + /* Check if it's a privileged instruction */ + if (KiIsPrivilegedInstruction(InstructionPointer, Wow64)) + { + Status = STATUS_PRIVILEGED_INSTRUCTION; + } + else + { + Status = STATUS_ACCESS_VIOLATION; + } + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + Status = _SEH2_GetExceptionCode(); + } + _SEH2_END + + return Status; +} NTSTATUS NTAPI @@ -436,8 +614,7 @@ KiGeneralProtectionFaultHandler( /* Check for user-mode GPF */ if (TrapFrame->SegCs & 3) { - UNIMPLEMENTED; - ASSERT(FALSE); + return KiGeneralProtectionFaultUserMode(TrapFrame); } /* Check for lazy segment load */ @@ -454,15 +631,6 @@ KiGeneralProtectionFaultHandler( return STATUS_SUCCESS; } - /* Check for nested exception */ - if ((TrapFrame->Rip >= (ULONG64)KiGeneralProtectionFaultHandler) && - (TrapFrame->Rip < (ULONG64)KiGeneralProtectionFaultHandler)) - { - /* Not implemented */ - UNIMPLEMENTED; - ASSERT(FALSE); - } - /* Get Instruction Pointer */ Instructions = (PUCHAR)TrapFrame->Rip; diff --git a/ntoskrnl/ke/amd64/trap.S b/ntoskrnl/ke/amd64/trap.S index 1598cef290c..644b4c2032d 100644 --- a/ntoskrnl/ke/amd64/trap.S +++ b/ntoskrnl/ke/amd64/trap.S @@ -285,7 +285,7 @@ KiInvalidOpcodeKernel: /* Kernel mode fault */ /* Dispatch the exception */ - DispatchException STATUS_ILLEGAL_INSTRUCTION, 3, 0, 0, 0 + DispatchException STATUS_ILLEGAL_INSTRUCTION, 0, 0, 0, 0 /* Return */ ExitTrap TF_SAVE_ALL @@ -385,8 +385,18 @@ FUNC KiGeneralProtectionFault test eax, eax jge KiGpfExit - /* Dispatch the exception */ - DispatchException eax, 3, 0, 0, 0 + /* Check for access violation */ + cmp eax, STATUS_ACCESS_VIOLATION + je DispatchAccessViolation + + /* Dispatch privileged instruction fault */ + DispatchException eax, 0, 0, 0, 0 + jmp KiGpfFatal + +DispatchAccessViolation: + + /* Dispatch access violation */ + DispatchException eax, 2, 0, -1, 0 KiGpfFatal: diff --git a/sdk/lib/rtl/amd64/unwind.c b/sdk/lib/rtl/amd64/unwind.c index 49e3740bec4..619d31b6bb9 100644 --- a/sdk/lib/rtl/amd64/unwind.c +++ b/sdk/lib/rtl/amd64/unwind.c @@ -701,9 +701,15 @@ RtlpUnwindInternal( Note: this can happen after the first frame as the result of an exception */ UnwindContext.Rip = *(DWORD64*)UnwindContext.Rsp; UnwindContext.Rsp += sizeof(DWORD64); + + /* Copy the context back for the next iteration */ + *ContextRecord = UnwindContext; continue; } + /* Save Rip before the virtual unwind */ + DispatcherContext.ControlPc = UnwindContext.Rip; + /* Do a virtual unwind to get the next frame */ ExceptionRoutine = RtlVirtualUnwind(HandlerType, ImageBase, @@ -749,7 +755,6 @@ RtlpUnwindInternal( sizeof(DispatcherContext)); /* Set up the variable fields of the dispatcher context */ - DispatcherContext.ControlPc = ContextRecord->Rip; DispatcherContext.ImageBase = ImageBase; DispatcherContext.FunctionEntry = FunctionEntry; DispatcherContext.LanguageHandler = ExceptionRoutine;