[NTOSKRNL] Improve S-List-Fault detection in KiTrap0EHandler to handle usermode fault...
[reactos.git] / reactos / ntoskrnl / ke / i386 / traphdlr.c
index e6e4452..0a8cf6f 100644 (file)
@@ -15,6 +15,8 @@
 VOID __cdecl KiFastCallEntry(VOID);
 VOID __cdecl KiFastCallEntryWithSingleStep(VOID);
 
+extern PVOID KeUserPopEntrySListFault;
+extern PVOID KeUserPopEntrySListResume;
 extern PVOID FrRestore;
 VOID FASTCALL Ke386LoadFpuState(IN PFX_SAVE_AREA SaveArea);
 
@@ -529,7 +531,7 @@ KiTrap02(VOID)
     TrapFrame.Edi = Tss->Edi;
     TrapFrame.SegFs = Tss->Fs;
     TrapFrame.ExceptionList = PCR->NtTib.ExceptionList;
-    TrapFrame.PreviousPreviousMode = -1;
+    TrapFrame.PreviousPreviousMode = (ULONG)-1;
     TrapFrame.Eax = Tss->Eax;
     TrapFrame.Ecx = Tss->Ecx;
     TrapFrame.Edx = Tss->Edx;
@@ -1197,6 +1199,8 @@ FASTCALL
 KiTrap0EHandler(IN PKTRAP_FRAME TrapFrame)
 {
     PKTHREAD Thread;
+    BOOLEAN Present;
+    BOOLEAN StoreInstruction;
     ULONG_PTR Cr2;
     NTSTATUS Status;
 
@@ -1219,25 +1223,65 @@ KiTrap0EHandler(IN PKTRAP_FRAME TrapFrame)
     /* Save CR2 */
     Cr2 = __readcr2();
 
-    /* Enable interupts */
+    /* Enable interrupts */
     _enable();
 
+    /* Interpret the error code */
+    Present = (TrapFrame->ErrCode & 1) != 0;
+    StoreInstruction = (TrapFrame->ErrCode & 2) != 0;
+
     /* Check if we came in with interrupts disabled */
     if (!(TrapFrame->EFlags & EFLAGS_INTERRUPT_MASK))
     {
         /* This is completely illegal, bugcheck the system */
         KeBugCheckWithTf(IRQL_NOT_LESS_OR_EQUAL,
                          Cr2,
-                         -1,
-                         TrapFrame->ErrCode & 2 ? TRUE : FALSE,
+                         (ULONG_PTR)-1,
+                         StoreInstruction,
                          TrapFrame->Eip,
                          TrapFrame);
     }
 
-    /* Check for S-LIST fault in kernel mode */
-    if (TrapFrame->Eip == (ULONG_PTR)ExpInterlockedPopEntrySListFault)
+    /* Check for S-List fault
+
+       Explanation: An S-List fault can occur due to a race condition between 2
+       threads simultaneously trying to pop an element from the S-List. After
+       thread 1 has read the pointer to the top element on the S-List it is
+       preempted and thread 2 calls InterlockedPopEntrySlist on the same S-List,
+       removing the top element and freeing it's memory. After that thread 1
+       resumes and tries to read the address of the Next pointer from the top
+       element, which it assumes will be the next top element.
+       But since that memory has been freed, we get a page fault. To handle this
+       race condition, we let thread 1 repeat the operation.
+       We do NOT invoke the page fault handler in this case, since we do not
+       want to trigger any side effects, like paging or a guard page fault.
+
+       Sequence of operations:
+
+           Thread 1 : mov eax, [ebp] <= eax now points to the first element
+           Thread 1 : mov edx, [ebp + 4] <= edx is loaded with Depth and Sequence
+            *** preempted ***
+           Thread 2 : calls InterlockedPopEntrySlist, changing the top element
+           Thread 2 : frees the memory of the element that was popped
+            *** preempted ***
+           Thread 1 : checks if eax is NULL
+           Thread 1 : InterlockedPopEntrySListFault: mov ebx, [eax] <= faults
+
+        To be sure that we are dealing with exactly the case described above, we
+        check whether the ListHeader has changed. If Thread 2 only popped one
+        entry, the Next field in the S-List-header has changed.
+        If after thread 1 has faulted, thread 2 allocates a new element, by
+        chance getting the same address as the previously freed element and
+        pushes it on the list again, we will see the same top element, but the
+        Sequence member of the S-List header has changed. Therefore we check
+        both fields to make sure we catch any concurrent modification of the
+        S-List-header.
+    */
+    if ((TrapFrame->Eip == (ULONG_PTR)ExpInterlockedPopEntrySListFault) ||
+        (TrapFrame->Eip == (ULONG_PTR)KeUserPopEntrySListFault))
     {
-        PSLIST_HEADER SListHeader;
+        ULARGE_INTEGER SListHeader;
+        PVOID ResumeAddress;
 
         /* Sanity check that the assembly is correct:
            This must be mov ebx, [eax]
@@ -1249,33 +1293,53 @@ KiTrap0EHandler(IN PKTRAP_FRAME TrapFrame)
                (((UCHAR*)TrapFrame->Eip)[4] == 0x4D) &&
                (((UCHAR*)TrapFrame->Eip)[5] == 0x00));
 
-        /* Get the pointer to the SLIST_HEADER */
-        SListHeader = (PSLIST_HEADER)TrapFrame->Ebp;
+        /* Check if this is a user fault */
+        if (TrapFrame->Eip == (ULONG_PTR)KeUserPopEntrySListFault)
+        {
+            /* EBP points to the S-List-header. Copy it inside SEH, to protect
+               against a bogus pointer from user mode */
+            _SEH2_TRY
+            {
+                ProbeForRead((PVOID)TrapFrame->Ebp,
+                             sizeof(ULARGE_INTEGER),
+                             TYPE_ALIGNMENT(SLIST_HEADER));
+                SListHeader = *(PULARGE_INTEGER)TrapFrame->Ebp;
+            }
+            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+            {
+                /* The S-List pointer is not valid! */
+                goto NotSListFault;
+            }
+            _SEH2_END;
+            ResumeAddress = KeUserPopEntrySListResume;
+        }
+        else
+        {
+            SListHeader = *(PULARGE_INTEGER)TrapFrame->Ebp;
+            ResumeAddress = ExpInterlockedPopEntrySListResume;
+        }
 
-        /* Check if the Next member of the SLIST_HEADER was changed */
-        if (SListHeader->Next.Next != (PSLIST_ENTRY)TrapFrame->Eax)
+        /* Check if either the Next pointer or the Sequence member in the
+           S-List-header has changed. If any of these has changed, we restart
+           the operation. Otherwise we only have a bogus pointer and let the
+           page fault handler deal with it. */
+        if ((SListHeader.LowPart != TrapFrame->Eax) ||
+            (SListHeader.HighPart != TrapFrame->Edx))
         {
+            DPRINT1("*** Got an S-List-Fault ***\n");
+            KeGetCurrentThread()->SListFaultCount++;
+
             /* Restart the operation */
-            TrapFrame->Eip = (ULONG_PTR)ExpInterlockedPopEntrySListResume;
+            TrapFrame->Eip = (ULONG_PTR)ResumeAddress;
 
             /* Continue execution */
             KiEoiHelper(TrapFrame);
         }
-        else
-        {
-#if 0
-            /* Do what windows does and issue an invalid access violation */
-            KiDispatchException2Args(KI_EXCEPTION_ACCESS_VIOLATION,
-                                     TrapFrame->Eip,
-                                     TrapFrame->ErrCode & 2 ? TRUE : FALSE,
-                                     Cr2,
-                                     TrapFrame);
-#endif
-        }
     }
+NotSListFault:
 
     /* Call the access fault handler */
-    Status = MmAccessFault(TrapFrame->ErrCode & 1,
+    Status = MmAccessFault(Present,
                            (PVOID)Cr2,
                            KiUserTrap(TrapFrame),
                            TrapFrame);
@@ -1298,8 +1362,20 @@ KiTrap0EHandler(IN PKTRAP_FRAME TrapFrame)
         UNIMPLEMENTED_FATAL();
     }
 #endif
+
     /* Check for VDM trap */
-    ASSERT((KiVdmTrap(TrapFrame)) == FALSE);
+    if (KiVdmTrap(TrapFrame))
+    {
+        DPRINT1("VDM PAGE FAULT at %lx:%lx for address %lx\n",
+                TrapFrame->SegCs, TrapFrame->Eip, Cr2);
+        if (VdmDispatchPageFault(TrapFrame))
+        {
+            /* Return and end VDM execution */
+            DPRINT1("VDM page fault with status 0x%lx resolved\n", Status);
+            KiEoiHelper(TrapFrame);
+        }
+        DPRINT1("VDM page fault with status 0x%lx NOT resolved\n", Status);
+    }
 
     /* Either kernel or user trap (non VDM) so dispatch exception */
     if (Status == STATUS_ACCESS_VIOLATION)
@@ -1307,7 +1383,7 @@ KiTrap0EHandler(IN PKTRAP_FRAME TrapFrame)
         /* This status code is repurposed so we can recognize it later */
         KiDispatchException2Args(KI_EXCEPTION_ACCESS_VIOLATION,
                                  TrapFrame->Eip,
-                                 TrapFrame->ErrCode & 2 ? TRUE : FALSE,
+                                 StoreInstruction,
                                  Cr2,
                                  TrapFrame);
     }
@@ -1317,7 +1393,7 @@ KiTrap0EHandler(IN PKTRAP_FRAME TrapFrame)
         /* These faults only have two parameters */
         KiDispatchException2Args(Status,
                                  TrapFrame->Eip,
-                                 TrapFrame->ErrCode & 2 ? TRUE : FALSE,
+                                 StoreInstruction,
                                  Cr2,
                                  TrapFrame);
     }
@@ -1327,7 +1403,7 @@ KiTrap0EHandler(IN PKTRAP_FRAME TrapFrame)
                                      0,
                                      TrapFrame->Eip,
                                      3,
-                                     TrapFrame->ErrCode & 2 ? TRUE : FALSE,
+                                     StoreInstruction,
                                      Cr2,
                                      Status,
                                      TrapFrame);
@@ -1623,7 +1699,8 @@ KiSystemServiceHandler(IN PKTRAP_FRAME TrapFrame,
 {
     PKTHREAD Thread;
     PKSERVICE_TABLE_DESCRIPTOR DescriptorTable;
-    ULONG Id, Offset, StackBytes, Result;
+    ULONG Id, Offset, StackBytes;
+    NTSTATUS Status;
     PVOID Handler;
     ULONG SystemCallNumber = TrapFrame->Eax;
 
@@ -1681,18 +1758,18 @@ KiSystemServiceHandler(IN PKTRAP_FRAME TrapFrame,
         if (!(Offset & SERVICE_TABLE_TEST))
         {
             /* Fail the call */
-            Result = STATUS_INVALID_SYSTEM_SERVICE;
+            Status = STATUS_INVALID_SYSTEM_SERVICE;
             goto ExitCall;
         }
 
         /* Convert us to a GUI thread -- must wrap in ASM to get new EBP */
-        Result = KiConvertToGuiThread();
+        Status = KiConvertToGuiThread();
 
         /* Reload trap frame and descriptor table pointer from new stack */
         TrapFrame = *(volatile PVOID*)&Thread->TrapFrame;
         DescriptorTable = (PVOID)(*(volatile ULONG_PTR*)&Thread->ServiceTable + Offset);
 
-        if (!NT_SUCCESS(Result))
+        if (!NT_SUCCESS(Status))
         {
             /* Set the last error and fail */
             goto ExitCall;
@@ -1702,7 +1779,7 @@ KiSystemServiceHandler(IN PKTRAP_FRAME TrapFrame,
         if (Id >= DescriptorTable->Limit)
         {
             /* Fail the call */
-            Result = STATUS_INVALID_SYSTEM_SERVICE;
+            Status = STATUS_INVALID_SYSTEM_SERVICE;
             goto ExitCall;
         }
     }
@@ -1735,10 +1812,10 @@ KiSystemServiceHandler(IN PKTRAP_FRAME TrapFrame,
 
     /* Get the handler and make the system call */
     Handler = (PVOID)DescriptorTable->Base[Id];
-    Result = KiSystemCallTrampoline(Handler, Arguments, StackBytes);
+    Status = KiSystemCallTrampoline(Handler, Arguments, StackBytes);
 
     /* Call post-service debug hook */
-    Result = KiDbgPostServiceHook(SystemCallNumber, Result);
+    Status = KiDbgPostServiceHook(SystemCallNumber, Status);
 
     /* Make sure we're exiting correctly */
     KiExitSystemCallDebugChecks(Id, TrapFrame);
@@ -1748,14 +1825,14 @@ ExitCall:
     Thread->TrapFrame = (PKTRAP_FRAME)TrapFrame->Edx;
 
     /* Exit from system call */
-    KiServiceExit(TrapFrame, Result);
+    KiServiceExit(TrapFrame, Status);
 }
 
 VOID
 FASTCALL
 KiCheckForSListAddress(IN PKTRAP_FRAME TrapFrame)
 {
-    UNIMPLEMENTED;   
+    UNIMPLEMENTED;
 }
 
 /*