CORE-6639 #resolve #time 1d #comment Guard pages now work ;-)
[reactos.git] / reactos / ntoskrnl / mm / ARM3 / pagfault.c
index f3f2b6c..3a5e948 100644 (file)
 
 /* GLOBALS ********************************************************************/
 
+#define HYDRA_PROCESS (PEPROCESS)1
 #if MI_TRACE_PFNS
 BOOLEAN UserPdeFault = FALSE;
 #endif
 
 /* PRIVATE FUNCTIONS **********************************************************/
 
+NTSTATUS
+NTAPI
+MiCheckForUserStackOverflow(IN PVOID Address,
+                            IN PVOID TrapInformation)
+{
+    PETHREAD CurrentThread = PsGetCurrentThread();
+    PTEB Teb = CurrentThread->Tcb.Teb;
+    PVOID StackBase, DeallocationStack, NextStackAddress;
+    ULONG GuranteedSize;
+    NTSTATUS Status;
+
+    /* Do we own the address space lock? */
+    if (CurrentThread->AddressSpaceOwner == 1)
+    {
+        /* This isn't valid */
+        DPRINT1("Process owns address space lock\n");
+        ASSERT(KeAreAllApcsDisabled() == TRUE);
+        return STATUS_GUARD_PAGE_VIOLATION;
+    }
+
+    /* Are we attached? */
+    if (KeIsAttachedProcess())
+    {
+        /* This isn't valid */
+        DPRINT1("Process is attached\n");
+        return STATUS_GUARD_PAGE_VIOLATION;
+    }
+
+    /* Read the current settings */
+    StackBase = Teb->NtTib.StackBase;
+    DeallocationStack = Teb->DeallocationStack;
+    GuranteedSize = Teb->GuaranteedStackBytes;
+    DPRINT1("Handling guard page fault with Stacks Addresses 0x%p and 0x%p, guarantee: %lx\n",
+            StackBase, DeallocationStack, GuranteedSize);
+
+    /* Guarantees make this code harder, for now, assume there aren't any */
+    ASSERT(GuranteedSize == 0);
+
+    /* So allocate only the minimum guard page size */
+    GuranteedSize = PAGE_SIZE;
+
+    /* Does this faulting stack address actually exist in the stack? */
+    if ((Address >= StackBase) || (Address < DeallocationStack))
+    {
+        /* That's odd... */
+        DPRINT1("Faulting address outside of stack bounds\n");
+        return STATUS_GUARD_PAGE_VIOLATION;
+    }
+
+    /* This is where the stack will start now */
+    NextStackAddress = (PVOID)((ULONG_PTR)PAGE_ALIGN(Address) - GuranteedSize);
+
+    /* Do we have at least one page between here and the end of the stack? */
+    if (((ULONG_PTR)NextStackAddress - PAGE_SIZE) <= (ULONG_PTR)DeallocationStack)
+    {
+        /* We don't -- Windows would try to make this guard page valid now */
+        DPRINT1("Close to our death...\n");
+        ASSERT(FALSE);
+        return STATUS_STACK_OVERFLOW;
+    }
+
+    /* Don't handle this flag yet */
+    ASSERT((PsGetCurrentProcess()->Peb->NtGlobalFlag & FLG_DISABLE_STACK_EXTENSION) == 0);
+
+    /* Update the stack limit */
+    Teb->NtTib.StackLimit = (PVOID)((ULONG_PTR)NextStackAddress + GuranteedSize);
+
+    /* Now move the guard page to the next page */
+    Status = ZwAllocateVirtualMemory(NtCurrentProcess(),
+                                     &NextStackAddress,
+                                     0,
+                                     &GuranteedSize,
+                                     MEM_COMMIT,
+                                     PAGE_READWRITE | PAGE_GUARD);
+    if ((NT_SUCCESS(Status) || (Status == STATUS_ALREADY_COMMITTED)))
+    {
+        /* We did it! */
+        DPRINT1("Guard page handled successfully for %p\n", Address);
+        return STATUS_PAGE_FAULT_GUARD_PAGE;
+    }
+
+    /* Fail, we couldn't move the guard page */
+    DPRINT1("Guard page failure: %lx\n", Status);
+    ASSERT(FALSE);
+    return STATUS_STACK_OVERFLOW;
+}
+
+NTSTATUS
+NTAPI
+MiAccessCheck(IN PMMPTE PointerPte,
+              IN BOOLEAN StoreInstruction,
+              IN KPROCESSOR_MODE PreviousMode,
+              IN ULONG ProtectionCode,
+              IN PVOID TrapFrame,
+              IN BOOLEAN LockHeld)
+{
+    MMPTE TempPte;
+
+    /* Check for invalid user-mode access */
+    if ((PreviousMode == UserMode) && (PointerPte > MiHighestUserPte))
+    {
+        return STATUS_ACCESS_VIOLATION;
+    }
+
+    /* Capture the PTE -- is it valid? */
+    TempPte = *PointerPte;
+    if (TempPte.u.Hard.Valid)
+    {
+        /* Was someone trying to write to it? */
+        if (StoreInstruction)
+        {
+            /* Is it writable?*/
+            if ((TempPte.u.Hard.Write) || (TempPte.u.Hard.CopyOnWrite))
+            {
+                /* Then there's nothing to worry about */
+                return STATUS_SUCCESS;
+            }
+
+            /* Oops! This isn't allowed */
+            return STATUS_ACCESS_VIOLATION;
+        }
+
+        /* Someone was trying to read from a valid PTE, that's fine too */
+        return STATUS_SUCCESS;
+    }
+
+    /* Convert any fault flag to 1 only */
+    if (StoreInstruction) StoreInstruction = 1;
+
+#if 0
+    /* Check if the protection on the page allows what is being attempted */
+    if ((MmReadWrite[Protection] - StoreInstruction) < 10)
+    {
+        return STATUS_ACCESS_VIOLATION;
+    }
+#endif
+
+    /* Check if this is a guard page */
+    if (ProtectionCode & MM_DECOMMIT)
+    {
+        /* Attached processes can't expand their stack */
+        if (KeIsAttachedProcess()) return STATUS_ACCESS_VIOLATION;
+
+        /* No support for transition PTEs yet */
+        ASSERT(((TempPte.u.Soft.Transition == 1) &&
+                (TempPte.u.Soft.Prototype == 0)) == FALSE);
+
+        /* Remove the guard page bit, and return a guard page violation */
+        PointerPte->u.Soft.Protection = ProtectionCode & ~MM_DECOMMIT;
+        return STATUS_GUARD_PAGE_VIOLATION;
+    }
+
+    /* Nothing to do */
+    return STATUS_SUCCESS;
+}
+
 PMMPTE
 NTAPI
 MiCheckVirtualAddress(IN PVOID VirtualAddress,
@@ -35,8 +192,70 @@ MiCheckVirtualAddress(IN PVOID VirtualAddress,
     /* No prototype/section support for now */
     *ProtoVad = NULL;
 
-    /* Check if this is a page table address */
-    if (MI_IS_PAGE_TABLE_ADDRESS(VirtualAddress))
+    /* User or kernel fault? */
+    if (VirtualAddress <= MM_HIGHEST_USER_ADDRESS)
+    {
+        /* Special case for shared data */
+        if (PAGE_ALIGN(VirtualAddress) == (PVOID)MM_SHARED_USER_DATA_VA)
+        {
+            /* It's a read-only page */
+            *ProtectCode = MM_READONLY;
+            return MmSharedUserDataPte;
+        }
+
+        /* Find the VAD, it might not exist if the address is bogus */
+        Vad = MiLocateAddress(VirtualAddress);
+        if (!Vad)
+        {
+            /* Bogus virtual address */
+            *ProtectCode = MM_NOACCESS;
+            return NULL;
+        }
+
+        /* ReactOS does not handle physical memory VADs yet */
+        ASSERT(Vad->u.VadFlags.VadType != VadDevicePhysicalMemory);
+
+        /* Check if it's a section, or just an allocation */
+        if (Vad->u.VadFlags.PrivateMemory)
+        {
+            /* ReactOS does not handle AWE VADs yet */
+            ASSERT(Vad->u.VadFlags.VadType != VadAwe);
+
+            /* This must be a TEB/PEB VAD */
+            if (Vad->u.VadFlags.MemCommit)
+            {
+                /* It's committed, so return the VAD protection */
+                *ProtectCode = (ULONG)Vad->u.VadFlags.Protection;
+            }
+            else
+            {
+                /* It has not yet been committed, so return no access */
+                *ProtectCode = MM_NOACCESS;
+            }
+
+            /* In both cases, return no PTE */
+            return NULL;
+        }
+        else
+        {
+            /* ReactOS does not supoprt these VADs yet */
+            ASSERT(Vad->u.VadFlags.VadType != VadImageMap);
+            ASSERT(Vad->u2.VadFlags2.ExtendableFile == 0);
+
+            /* Return the proto VAD */
+            *ProtoVad = Vad;
+
+            /* Get the prototype PTE for this page */
+            PointerPte = (((ULONG_PTR)VirtualAddress >> PAGE_SHIFT) - Vad->StartingVpn) + Vad->FirstPrototypePte;
+            ASSERT(PointerPte != NULL);
+            ASSERT(PointerPte <= Vad->LastContiguousPte);
+
+            /* Return the Prototype PTE and the protection for the page mapping */
+            *ProtectCode = (ULONG)Vad->u.VadFlags.Protection;
+            return PointerPte;
+        }
+    }
+    else if (MI_IS_PAGE_TABLE_ADDRESS(VirtualAddress))
     {
         /* This should never happen, as these addresses are handled by the double-maping */
         if (((PMMPTE)VirtualAddress >= MiAddressToPte(MmPagedPoolStart)) &&
@@ -51,53 +270,116 @@ MiCheckVirtualAddress(IN PVOID VirtualAddress,
         *ProtectCode = MM_READWRITE;
         return NULL;
     }
-
-    /* Should not be a session address */
-    ASSERT(MI_IS_SESSION_ADDRESS(VirtualAddress) == FALSE);
-
-    /* Special case for shared data */
-    if (PAGE_ALIGN(VirtualAddress) == (PVOID)USER_SHARED_DATA)
+    else if (MI_IS_SESSION_ADDRESS(VirtualAddress))
     {
-        /* It's a read-only page */
-        *ProtectCode = MM_READONLY;
-        return MmSharedUserDataPte;
+        /* ReactOS does not have an image list yet, so bail out to failure case */
+        ASSERT(IsListEmpty(&MmSessionSpace->ImageList));
     }
 
-    /* Find the VAD, it might not exist if the address is bogus */
-    Vad = MiLocateAddress(VirtualAddress);
-    if (!Vad)
+    /* Default case -- failure */
+    *ProtectCode = MM_NOACCESS;
+    return NULL;
+}
+
+#if (_MI_PAGING_LEVELS == 2)
+BOOLEAN
+FORCEINLINE
+MiSynchronizeSystemPde(PMMPDE PointerPde)
+{
+    MMPDE SystemPde;
+    ULONG Index;
+
+    /* Get the Index from the PDE */
+    Index = ((ULONG_PTR)PointerPde & (SYSTEM_PD_SIZE - 1)) / sizeof(MMPTE);
+
+    /* Copy the PDE from the double-mapped system page directory */
+    SystemPde = MmSystemPagePtes[Index];
+    *PointerPde = SystemPde;
+
+    /* Make sure we re-read the PDE and PTE */
+    KeMemoryBarrierWithoutFence();
+
+    /* Return, if we had success */
+    return (BOOLEAN)SystemPde.u.Hard.Valid;
+}
+
+NTSTATUS
+FASTCALL
+MiCheckPdeForSessionSpace(IN PVOID Address)
+{
+    MMPTE TempPde;
+    PMMPTE PointerPde;
+    PVOID SessionAddress;
+    ULONG Index;
+
+    /* Is this a session PTE? */
+    if (MI_IS_SESSION_PTE(Address))
     {
-        /* Bogus virtual address */
-        *ProtectCode = MM_NOACCESS;
-        return NULL;
+        /* Make sure the PDE for session space is valid */
+        PointerPde = MiAddressToPde(MmSessionSpace);
+        if (!PointerPde->u.Hard.Valid)
+        {
+            /* This means there's no valid session, bail out */
+            DbgPrint("MiCheckPdeForSessionSpace: No current session for PTE %p\n",
+                     Address);
+            DbgBreakPoint();
+            return STATUS_ACCESS_VIOLATION;
+        }
+
+        /* Now get the session-specific page table for this address */
+        SessionAddress = MiPteToAddress(Address);
+        PointerPde = MiAddressToPte(Address);
+        if (PointerPde->u.Hard.Valid) return STATUS_WAIT_1;
+
+        /* It's not valid, so find it in the page table array */
+        Index = ((ULONG_PTR)SessionAddress - (ULONG_PTR)MmSessionBase) >> 22;
+        TempPde.u.Long = MmSessionSpace->PageTables[Index].u.Long;
+        if (TempPde.u.Hard.Valid)
+        {
+            /* The copy is valid, so swap it in */
+            InterlockedExchange((PLONG)PointerPde, TempPde.u.Long);
+            return STATUS_WAIT_1;
+        }
+
+        /* We don't seem to have allocated a page table for this address yet? */
+        DbgPrint("MiCheckPdeForSessionSpace: No Session PDE for PTE %p, %p\n",
+                 PointerPde->u.Long, SessionAddress);
+        DbgBreakPoint();
+        return STATUS_ACCESS_VIOLATION;
     }
 
-    /* This must be a VM VAD */
-    ASSERT(Vad->u.VadFlags.VadType == VadNone);
+    /* Is the address also a session address? If not, we're done */
+    if (!MI_IS_SESSION_ADDRESS(Address)) return STATUS_SUCCESS;
 
-    /* Check if it's a section, or just an allocation */
-    if (Vad->u.VadFlags.PrivateMemory == TRUE)
+    /* It is, so again get the PDE for session space */
+    PointerPde = MiAddressToPde(MmSessionSpace);
+    if (!PointerPde->u.Hard.Valid)
     {
-        /* This must be a TEB/PEB VAD */
-        ASSERT(Vad->u.VadFlags.MemCommit == TRUE);
-        *ProtectCode = Vad->u.VadFlags.Protection;
-        return NULL;
+        /* This means there's no valid session, bail out */
+        DbgPrint("MiCheckPdeForSessionSpace: No current session for VA %p\n",
+                    Address);
+        DbgBreakPoint();
+        return STATUS_ACCESS_VIOLATION;
     }
-    else
-    {
-        /* Return the proto VAD */
-        ASSERT(Vad->u2.VadFlags2.ExtendableFile == 0);
-        *ProtoVad = Vad;
-
-        /* Get the prototype PTE for this page */
-        PointerPte = (((ULONG_PTR)VirtualAddress >> PAGE_SHIFT) - Vad->StartingVpn) + Vad->FirstPrototypePte;
-        ASSERT(PointerPte <= Vad->LastContiguousPte);
-        ASSERT(PointerPte != NULL);
 
-        /* Return the Prototype PTE and the protection for the page mapping */
-        *ProtectCode = Vad->u.VadFlags.Protection;
-        return PointerPte;
+    /* Now get the PDE for the address itself */
+    PointerPde = MiAddressToPde(Address);
+    if (!PointerPde->u.Hard.Valid)
+    {
+        /* Do the swap, we should be good to go */
+        Index = ((ULONG_PTR)Address - (ULONG_PTR)MmSessionBase) >> 22;
+        PointerPde->u.Long = MmSessionSpace->PageTables[Index].u.Long;
+        if (PointerPde->u.Hard.Valid) return STATUS_WAIT_1;
+
+        /* We had not allocated a page table for this session address yet, fail! */
+        DbgPrint("MiCheckPdeForSessionSpace: No Session PDE for VA %p, %p\n",
+                 PointerPde->u.Long, Address);
+        DbgBreakPoint();
+        return STATUS_ACCESS_VIOLATION;
     }
+
+    /* It's valid, so there's nothing to do */
+    return STATUS_SUCCESS;
 }
 
 NTSTATUS
@@ -107,9 +389,9 @@ MiCheckPdeForPagedPool(IN PVOID Address)
     PMMPDE PointerPde;
     NTSTATUS Status = STATUS_SUCCESS;
 
-    /* No session support in ReactOS yet */
-    ASSERT(MI_IS_SESSION_ADDRESS(Address) == FALSE);
-    ASSERT(MI_IS_SESSION_PTE(Address) == FALSE);
+    /* Check session PDE */
+    if (MI_IS_SESSION_ADDRESS(Address)) return MiCheckPdeForSessionSpace(Address);
+    if (MI_IS_SESSION_PTE(Address)) return MiCheckPdeForSessionSpace(Address);
 
     //
     // Check if this is a fault while trying to access the page table itself
@@ -143,15 +425,11 @@ MiCheckPdeForPagedPool(IN PVOID Address)
     //
     if (PointerPde->u.Hard.Valid == 0)
     {
-#ifdef _M_AMD64
-        ASSERT(FALSE);
-#else
         //
         // Copy it from our double-mapped system page directory
         //
         InterlockedExchangePte(PointerPde,
                                MmSystemPagePtes[((ULONG_PTR)PointerPde & (SYSTEM_PD_SIZE - 1)) / sizeof(MMPTE)].u.Long);
-#endif
     }
 
     //
@@ -159,6 +437,14 @@ MiCheckPdeForPagedPool(IN PVOID Address)
     //
     return Status;
 }
+#else
+NTSTATUS
+FASTCALL
+MiCheckPdeForPagedPool(IN PVOID Address)
+{
+    return STATUS_ACCESS_VIOLATION;
+}
+#endif
 
 VOID
 NTAPI
@@ -217,12 +503,13 @@ MiResolveDemandZeroFault(IN PVOID Address,
     MMPTE TempPte;
     BOOLEAN NeedZero = FALSE, HaveLock = FALSE;
     ULONG Color;
+    PMMPFN Pfn1;
     DPRINT("ARM3 Demand Zero Page Fault Handler for address: %p in process: %p\n",
             Address,
             Process);
 
     /* Must currently only be called by paging path */
-    if ((Process) && (OldIrql == MM_NOIRQL))
+    if ((Process > HYDRA_PROCESS) && (OldIrql == MM_NOIRQL))
     {
         /* Sanity check */
         ASSERT(MI_IS_PAGE_TABLE_ADDRESS(PointerPte));
@@ -242,8 +529,16 @@ MiResolveDemandZeroFault(IN PVOID Address,
         /* Check if we need a zero page */
         NeedZero = (OldIrql != MM_NOIRQL);
 
-        /* Get the next system page color */
-        Color = MI_GET_NEXT_COLOR();
+        /* Session-backed image views must be zeroed */
+        if ((Process == HYDRA_PROCESS) &&
+            ((MI_IS_SESSION_IMAGE_ADDRESS(Address)) ||
+             ((Address >= MiSessionViewStart) && (Address < MiSessionSpaceWs))))
+        {
+            NeedZero = TRUE;
+        }
+
+        /* Hardcode unknown color */
+        Color = 0xFFFFFFFF;
     }
 
     /* Check if the PFN database should be acquired */
@@ -256,59 +551,69 @@ MiResolveDemandZeroFault(IN PVOID Address,
 
     /* We either manually locked the PFN DB, or already came with it locked */
     ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
-
-    /* Do we need a zero page? */
     ASSERT(PointerPte->u.Hard.Valid == 0);
+
+    /* Assert we have enough pages */
+    ASSERT(MmAvailablePages >= 32);
+
 #if MI_TRACE_PFNS
     if (UserPdeFault) MI_SET_USAGE(MI_USAGE_PAGE_TABLE);
     if (!UserPdeFault) MI_SET_USAGE(MI_USAGE_DEMAND_ZERO);
 #endif
     if (Process) MI_SET_PROCESS2(Process->ImageFileName);
     if (!Process) MI_SET_PROCESS2("Kernel Demand 0");
-    if ((NeedZero) && (Process))
+
+    /* Do we need a zero page? */
+    if (Color != 0xFFFFFFFF)
     {
         /* Try to get one, if we couldn't grab a free page and zero it */
         PageFrameNumber = MiRemoveZeroPageSafe(Color);
-        if (PageFrameNumber)
-        {
-            /* We got a genuine zero page, stop worrying about it */
-            NeedZero = FALSE;
-        }
-        else
+        if (!PageFrameNumber)
         {
             /* We'll need a free page and zero it manually */
             PageFrameNumber = MiRemoveAnyPage(Color);
+            NeedZero = TRUE;
         }
     }
-    else if (!NeedZero)
-    {
-        /* Process or system doesn't want a zero page, grab anything */
-        PageFrameNumber = MiRemoveAnyPage(Color);
-    }
     else
     {
-        /* System wants a zero page, obtain one */
-        PageFrameNumber = MiRemoveZeroPage(Color);
+        /* Get a color, and see if we should grab a zero or non-zero page */
+        Color = MI_GET_NEXT_COLOR();
+        if (!NeedZero)
+        {
+            /* Process or system doesn't want a zero page, grab anything */
+            PageFrameNumber = MiRemoveAnyPage(Color);
+        }
+        else
+        {
+            /* System wants a zero page, obtain one */
+            PageFrameNumber = MiRemoveZeroPage(Color);
+        }
     }
 
     /* Initialize it */
     MiInitializePfn(PageFrameNumber, PointerPte, TRUE);
 
-    /* Release PFN lock if needed */
-    if (HaveLock) KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
+    /* Do we have the lock? */
+    if (HaveLock)
+    {
+        /* Release it */
+        KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
 
-    //
-    // Increment demand zero faults
-    //
+        /* Update performance counters */
+        if (Process > HYDRA_PROCESS) Process->NumberOfPrivatePages++;
+    }
+
+    /* Increment demand zero faults */
     InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount);
 
     /* Zero the page if need be */
     if (NeedZero) MiZeroPfn(PageFrameNumber);
 
-    /* Build the PTE */
+    /* Fault on user PDE, or fault on user PTE? */
     if (PointerPte <= MiHighestUserPte)
     {
-        /* For user mode */
+        /* User fault, build a user PTE */
         MI_MAKE_HARDWARE_PTE_USER(&TempPte,
                                   PointerPte,
                                   PointerPte->u.Soft.Protection,
@@ -316,7 +621,7 @@ MiResolveDemandZeroFault(IN PVOID Address,
     }
     else
     {
-        /* For kernel mode */
+        /* This is a user-mode PDE, create a kernel PTE for it */
         MI_MAKE_HARDWARE_PTE(&TempPte,
                              PointerPte,
                              PointerPte->u.Soft.Protection,
@@ -329,10 +634,21 @@ MiResolveDemandZeroFault(IN PVOID Address,
     /* Write it */
     MI_WRITE_VALID_PTE(PointerPte, TempPte);
 
+    /* Did we manually acquire the lock */
+    if (HaveLock)
+    {
+        /* Get the PFN entry */
+        Pfn1 = MI_PFN_ELEMENT(PageFrameNumber);
+
+        /* Windows does these sanity checks */
+        ASSERT(Pfn1->u1.Event == 0);
+        ASSERT(Pfn1->u3.e1.PrototypePte == 0);
+    }
+
     //
     // It's all good now
     //
-    DPRINT("Paged pool page has now been paged in\n");
+    DPRINT("Demand zero page has now been paged in\n");
     return STATUS_PAGE_FAULT_DEMAND_ZERO;
 }
 
@@ -343,12 +659,14 @@ MiCompleteProtoPteFault(IN BOOLEAN StoreInstruction,
                         IN PMMPTE PointerPte,
                         IN PMMPTE PointerProtoPte,
                         IN KIRQL OldIrql,
-                        IN PMMPFN Pfn1)
+                        IN PMMPFN* LockedProtoPfn)
 {
     MMPTE TempPte;
-    PMMPTE OriginalPte;
-    ULONG Protection;
+    PMMPTE OriginalPte, PageTablePte;
+    ULONG_PTR Protection;
     PFN_NUMBER PageFrameIndex;
+    PMMPFN Pfn1, Pfn2;
+    BOOLEAN OriginalProtection, DirtyPage;
 
     /* Must be called with an valid prototype PTE, with the PFN lock held */
     ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
@@ -361,19 +679,58 @@ MiCompleteProtoPteFault(IN BOOLEAN StoreInstruction,
     Pfn1 = MiGetPfnEntry(PageFrameIndex);
     Pfn1->u3.e1.PrototypePte = 1;
 
-    /* FIXME: Increment the share count for the page table */
+    /* Increment the share count for the page table */
+    // FIXME: This doesn't work because we seem to bump the sharecount to two, and MiDeletePte gets annoyed and ASSERTs.
+    // This could be beause MiDeletePte is now being called from strange code in Rosmm
+    PageTablePte = MiAddressToPte(PointerPte);
+    Pfn2 = MiGetPfnEntry(PageTablePte->u.Hard.PageFrameNumber);
+    //Pfn2->u2.ShareCount++;
+    DBG_UNREFERENCED_LOCAL_VARIABLE(Pfn2);
 
     /* Check where we should be getting the protection information from */
     if (PointerPte->u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED)
     {
         /* Get the protection from the PTE, there's no real Proto PTE data */
         Protection = PointerPte->u.Soft.Protection;
+
+        /* Remember that we did not use the proto protection */
+        OriginalProtection = FALSE;
     }
     else
     {
         /* Get the protection from the original PTE link */
         OriginalPte = &Pfn1->OriginalPte;
         Protection = OriginalPte->u.Soft.Protection;
+
+        /* Remember that we used the original protection */
+        OriginalProtection = TRUE;
+
+        /* Check if this was a write on a read only proto */
+        if ((StoreInstruction) && !(Protection & MM_READWRITE))
+        {
+            /* Clear the flag */
+            StoreInstruction = 0;
+        }
+    }
+
+    /* Check if this was a write on a non-COW page */
+    DirtyPage = FALSE;
+    if ((StoreInstruction) && ((Protection & MM_WRITECOPY) != MM_WRITECOPY))
+    {
+        /* Then the page should be marked dirty */
+        DirtyPage = TRUE;
+
+        /* ReactOS check */
+        ASSERT(Pfn1->OriginalPte.u.Soft.Prototype != 0);
+    }
+
+    /* Did we get a locked incoming PFN? */
+    if (*LockedProtoPfn)
+    {
+        /* Drop a reference */
+        ASSERT((*LockedProtoPfn)->u3.e2.ReferenceCount >= 1);
+        MiDereferencePfnAndDropLockCount(*LockedProtoPfn);
+        *LockedProtoPfn = NULL;
     }
 
     /* Release the PFN lock */
@@ -382,6 +739,20 @@ MiCompleteProtoPteFault(IN BOOLEAN StoreInstruction,
     /* Remove caching bits */
     Protection &= ~(MM_NOCACHE | MM_NOACCESS);
 
+    /* Setup caching */
+    if (Pfn1->u3.e1.CacheAttribute == MiWriteCombined)
+    {
+        /* Write combining, no caching */
+        MI_PAGE_DISABLE_CACHE(&TempPte);
+        MI_PAGE_WRITE_COMBINED(&TempPte);
+    }
+    else if (Pfn1->u3.e1.CacheAttribute == MiNonCached)
+    {
+        /* Write through, no caching */
+        MI_PAGE_DISABLE_CACHE(&TempPte);
+        MI_PAGE_WRITE_THROUGH(&TempPte);
+    }
+
     /* Check if this is a kernel or user address */
     if (Address < MmSystemRangeStart)
     {
@@ -394,13 +765,137 @@ MiCompleteProtoPteFault(IN BOOLEAN StoreInstruction,
         MI_MAKE_HARDWARE_PTE(&TempPte, PointerPte, Protection, PageFrameIndex);
     }
 
+    /* Set the dirty flag if needed */
+    if (DirtyPage) TempPte.u.Hard.Dirty = TRUE;
+
     /* Write the PTE */
     MI_WRITE_VALID_PTE(PointerPte, TempPte);
 
+    /* Reset the protection if needed */
+    if (OriginalProtection) Protection = MM_ZERO_ACCESS;
+
     /* Return success */
+    ASSERT(PointerPte == MiAddressToPte(Address));
     return STATUS_SUCCESS;
 }
 
+NTSTATUS
+NTAPI
+MiResolveTransitionFault(IN PVOID FaultingAddress,
+                         IN PMMPTE PointerPte,
+                         IN PEPROCESS CurrentProcess,
+                         IN KIRQL OldIrql,
+                         OUT PVOID *InPageBlock)
+{
+    PFN_NUMBER PageFrameIndex;
+    PMMPFN Pfn1;
+    MMPTE TempPte;
+    PMMPTE PointerToPteForProtoPage;
+    DPRINT1("Transition fault on 0x%p with PTE 0x%lx in process %s\n", FaultingAddress, PointerPte, CurrentProcess->ImageFileName);
+
+    /* Windowss does this check */
+    ASSERT(*InPageBlock == NULL);
+
+    /* ARM3 doesn't support this path */
+    ASSERT(OldIrql != MM_NOIRQL);
+
+    /* Capture the PTE and make sure it's in transition format */
+    TempPte = *PointerPte;
+    ASSERT((TempPte.u.Soft.Valid == 0) &&
+           (TempPte.u.Soft.Prototype == 0) &&
+           (TempPte.u.Soft.Transition == 1));
+
+    /* Get the PFN and the PFN entry */
+    PageFrameIndex = TempPte.u.Trans.PageFrameNumber;
+    DPRINT1("Transition PFN: %lx\n", PageFrameIndex);
+    Pfn1 = MiGetPfnEntry(PageFrameIndex);
+
+    /* One more transition fault! */
+    InterlockedIncrement(&KeGetCurrentPrcb()->MmTransitionCount);
+
+    /* This is from ARM3 -- Windows normally handles this here */
+    ASSERT(Pfn1->u4.InPageError == 0);
+
+    /* Not supported in ARM3 */
+    ASSERT(Pfn1->u3.e1.ReadInProgress == 0);
+
+    /* Windows checks there's some free pages and this isn't an in-page error */
+    ASSERT(MmAvailablePages > 0);
+    ASSERT(Pfn1->u4.InPageError == 0);
+
+    /* ReactOS checks for this */
+    ASSERT(MmAvailablePages > 32);
+
+    /* Was this a transition page in the valid list, or free/zero list? */
+    if (Pfn1->u3.e1.PageLocation == ActiveAndValid)
+    {
+        /* All Windows does here is a bunch of sanity checks */
+        DPRINT1("Transition in active list\n");
+        ASSERT((Pfn1->PteAddress >= MiAddressToPte(MmPagedPoolStart)) &&
+               (Pfn1->PteAddress <= MiAddressToPte(MmPagedPoolEnd)));
+        ASSERT(Pfn1->u2.ShareCount != 0);
+        ASSERT(Pfn1->u3.e2.ReferenceCount != 0);
+    }
+    else
+    {
+        /* Otherwise, the page is removed from its list */
+        DPRINT1("Transition page in free/zero list\n");
+        MiUnlinkPageFromList(Pfn1);
+        MiReferenceUnusedPageAndBumpLockCount(Pfn1);
+    }
+
+    /* At this point, there should no longer be any in-page errors */
+    ASSERT(Pfn1->u4.InPageError == 0);
+
+    /* Check if this was a PFN with no more share references */
+    if (Pfn1->u2.ShareCount == 0) MiDropLockCount(Pfn1);
+
+    /* Bump the share count and make the page valid */
+    Pfn1->u2.ShareCount++;
+    Pfn1->u3.e1.PageLocation = ActiveAndValid;
+
+    /* Prototype PTEs are in paged pool, which itself might be in transition */
+    if (FaultingAddress >= MmSystemRangeStart)
+    {
+        /* Check if this is a paged pool PTE in transition state */
+        PointerToPteForProtoPage = MiAddressToPte(PointerPte);
+        TempPte = *PointerToPteForProtoPage;
+        if ((TempPte.u.Hard.Valid == 0) && (TempPte.u.Soft.Transition == 1))
+        {
+            /* This isn't yet supported */
+            DPRINT1("Double transition fault not yet supported\n");
+            ASSERT(FALSE);
+        }
+    }
+
+    /* Build the transition PTE -- maybe a macro? */
+    ASSERT(PointerPte->u.Hard.Valid == 0);
+    ASSERT(PointerPte->u.Trans.Prototype == 0);
+    ASSERT(PointerPte->u.Trans.Transition == 1);
+    TempPte.u.Long = (PointerPte->u.Long & ~0xFFF) |
+                     (MmProtectToPteMask[PointerPte->u.Trans.Protection]) |
+                     MiDetermineUserGlobalPteMask(PointerPte);
+
+    /* Is the PTE writeable? */
+    if (((Pfn1->u3.e1.Modified) && (TempPte.u.Hard.Write)) &&
+        (TempPte.u.Hard.CopyOnWrite == 0))
+    {
+        /* Make it dirty */
+        TempPte.u.Hard.Dirty = TRUE;
+    }
+    else
+    {
+        /* Make it clean */
+        TempPte.u.Hard.Dirty = FALSE;
+    }
+
+    /* Write the valid PTE */
+    MI_WRITE_VALID_PTE(PointerPte, TempPte);
+
+    /* Return success */
+    return STATUS_PAGE_FAULT_TRANSITION;
+}
+
 NTSTATUS
 NTAPI
 MiResolveProtoPteFault(IN BOOLEAN StoreInstruction,
@@ -414,10 +909,11 @@ MiResolveProtoPteFault(IN BOOLEAN StoreInstruction,
                        IN KIRQL OldIrql,
                        IN PVOID TrapInformation)
 {
-    MMPTE TempPte;
+    MMPTE TempPte, PteContents;
     PMMPFN Pfn1;
     PFN_NUMBER PageFrameIndex;
     NTSTATUS Status;
+    PVOID InPageBlock = NULL;
 
     /* Must be called with an invalid, prototype PTE, with the PFN lock held */
     ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
@@ -442,7 +938,7 @@ MiResolveProtoPteFault(IN BOOLEAN StoreInstruction,
                                        PointerPte,
                                        PointerProtoPte,
                                        OldIrql,
-                                       NULL);
+                                       OutPfn);
     }
 
     /* Make sure there's some protection mask */
@@ -454,25 +950,78 @@ MiResolveProtoPteFault(IN BOOLEAN StoreInstruction,
         return STATUS_ACCESS_VIOLATION;
     }
 
-    /* This is the only thing we support right now */
-    ASSERT(TempPte.u.Soft.PageFileHigh == 0);
-    ASSERT(TempPte.u.Proto.ReadOnly == 0);
-    ASSERT(PointerPte > MiHighestUserPte);
-    ASSERT(TempPte.u.Soft.Prototype == 0);
-    ASSERT(TempPte.u.Soft.Transition == 0);
-
-    /* Resolve the demand zero fault */
-    Status = MiResolveDemandZeroFault(Address, PointerProtoPte, Process, OldIrql);
-    ASSERT(NT_SUCCESS(Status));
+    /* Check for access rights on the PTE proper */
+    PteContents = *PointerPte;
+    if (PteContents.u.Soft.PageFileHigh != MI_PTE_LOOKUP_NEEDED)
+    {
+        if (!PteContents.u.Proto.ReadOnly)
+        {
+            /* Check for page acess in software */
+            Status = MiAccessCheck(PointerProtoPte,
+                                   StoreInstruction,
+                                   KernelMode,
+                                   TempPte.u.Soft.Protection,
+                                   TrapInformation,
+                                   TRUE);
+            ASSERT(Status == STATUS_SUCCESS);
 
-    /* Complete the prototype PTE fault -- this will release the PFN lock */
-    ASSERT(PointerPte->u.Hard.Valid == 0);
-    return MiCompleteProtoPteFault(StoreInstruction,
-                                   Address,
+            /* Check for copy on write page */
+            if ((TempPte.u.Soft.Protection & MM_WRITECOPY) == MM_WRITECOPY)
+            {
+                /* Not yet supported */
+                ASSERT(FALSE);
+            }
+        }
+    }
+    else
+    {
+        /* Check for copy on write page */
+        if ((PteContents.u.Soft.Protection & MM_WRITECOPY) == MM_WRITECOPY)
+        {
+            /* Not yet supported */
+            ASSERT(FALSE);
+        }
+    }
+
+    /* Check for clone PTEs */
+    if (PointerPte <= MiHighestUserPte) ASSERT(Process->CloneRoot == NULL);
+
+    /* We don't support mapped files yet */
+    ASSERT(TempPte.u.Soft.Prototype == 0);
+
+    /* We might however have transition PTEs */
+    if (TempPte.u.Soft.Transition == 1)
+    {
+        /* Resolve the transition fault */
+        ASSERT(OldIrql != MM_NOIRQL);
+        Status = MiResolveTransitionFault(Address,
+                                          PointerProtoPte,
+                                          Process,
+                                          OldIrql,
+                                          &InPageBlock);
+        ASSERT(NT_SUCCESS(Status));
+    }
+    else
+    {
+        /* We also don't support paged out pages */
+        ASSERT(TempPte.u.Soft.PageFileHigh == 0);
+
+        /* Resolve the demand zero fault */
+        Status = MiResolveDemandZeroFault(Address,
+                                          PointerProtoPte,
+                                          Process,
+                                          OldIrql);
+        ASSERT(NT_SUCCESS(Status));
+    }
+
+    /* Complete the prototype PTE fault -- this will release the PFN lock */
+    ASSERT(PointerPte->u.Hard.Valid == 0);
+    return MiCompleteProtoPteFault(StoreInstruction,
+                                   Address,
                                    PointerPte,
                                    PointerProtoPte,
                                    OldIrql,
-                                   NULL);
+                                   OutPfn);
 }
 
 NTSTATUS
@@ -484,16 +1033,22 @@ MiDispatchFault(IN BOOLEAN StoreInstruction,
                 IN BOOLEAN Recursive,
                 IN PEPROCESS Process,
                 IN PVOID TrapInformation,
-                IN PVOID Vad)
+                IN PMMVAD Vad)
 {
     MMPTE TempPte;
     KIRQL OldIrql, LockIrql;
     NTSTATUS Status;
     PMMPTE SuperProtoPte;
+    PMMPFN Pfn1, OutPfn = NULL;
+    PFN_NUMBER PageFrameIndex;
+    PFN_COUNT PteCount, ProcessedPtes;
     DPRINT("ARM3 Page Fault Dispatcher for address: %p in process: %p\n",
              Address,
              Process);
 
+    /* Make sure the addresses are ok */
+    ASSERT(PointerPte == MiAddressToPte(Address));
+
     //
     // Make sure APCs are off and we're not at dispatch
     //
@@ -522,76 +1077,219 @@ MiDispatchFault(IN BOOLEAN StoreInstruction,
             /* Has the PTE been made valid yet? */
             if (!SuperProtoPte->u.Hard.Valid)
             {
-                UNIMPLEMENTED;
-                while (TRUE);
+                ASSERT(FALSE);
             }
-            else
+            else if (PointerPte->u.Hard.Valid == 1)
             {
-                /* Resolve the fault -- this will release the PFN lock */
-                ASSERT(PointerPte->u.Hard.Valid == 0);
-                Status = MiResolveProtoPteFault(StoreInstruction,
-                                                Address,
-                                                PointerPte,
-                                                PointerProtoPte,
-                                                NULL,
-                                                NULL,
-                                                NULL,
-                                                Process,
-                                                LockIrql,
-                                                TrapInformation);
-                ASSERT(Status == STATUS_SUCCESS);
-
-                /* Complete this as a transition fault */
-                ASSERT(OldIrql == KeGetCurrentIrql());
-                ASSERT(OldIrql <= APC_LEVEL);
-                ASSERT(KeAreAllApcsDisabled() == TRUE);
-                return Status;
+                ASSERT(FALSE);
             }
+
+            /* Resolve the fault -- this will release the PFN lock */
+            Status = MiResolveProtoPteFault(StoreInstruction,
+                                            Address,
+                                            PointerPte,
+                                            PointerProtoPte,
+                                            &OutPfn,
+                                            NULL,
+                                            NULL,
+                                            Process,
+                                            LockIrql,
+                                            TrapInformation);
+            ASSERT(Status == STATUS_SUCCESS);
+
+            /* Complete this as a transition fault */
+            ASSERT(OldIrql == KeGetCurrentIrql());
+            ASSERT(OldIrql <= APC_LEVEL);
+            ASSERT(KeAreAllApcsDisabled() == TRUE);
+            return Status;
         }
         else
         {
-            /* We currently only handle very limited paths */
-            ASSERT(PointerPte->u.Soft.Prototype == 1);
+            /* We only handle the lookup path */
             ASSERT(PointerPte->u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED);
 
+            /* Is there a non-image VAD? */
+            if ((Vad) &&
+                (Vad->u.VadFlags.VadType != VadImageMap) &&
+                !(Vad->u2.VadFlags2.ExtendableFile))
+            {
+                /* One day, ReactOS will cluster faults */
+                ASSERT(Address <= MM_HIGHEST_USER_ADDRESS);
+                DPRINT("Should cluster fault, but won't\n");
+            }
+
+            /* Only one PTE to handle for now */
+            PteCount = 1;
+            ProcessedPtes = 0;
+
             /* Lock the PFN database */
             LockIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
 
-            /* For our current usage, this should be true */
+            /* We only handle the valid path */
             ASSERT(SuperProtoPte->u.Hard.Valid == 1);
-            ASSERT(TempPte.u.Hard.Valid == 0);
+
+            /* Capture the PTE */
+            TempPte = *PointerProtoPte;
+
+            /* Loop to handle future case of clustered faults */
+            while (TRUE)
+            {
+                /* For our current usage, this should be true */
+                if (TempPte.u.Hard.Valid == 1)
+                {
+                    /* Bump the share count on the PTE */
+                    PageFrameIndex = PFN_FROM_PTE(&TempPte);
+                    Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
+                    Pfn1->u2.ShareCount++;
+                }
+                else if ((TempPte.u.Soft.Prototype == 0) &&
+                         (TempPte.u.Soft.Transition == 1))
+                {
+                    /* This is a standby page, bring it back from the cache */
+                    PageFrameIndex = TempPte.u.Trans.PageFrameNumber;
+                    DPRINT("oooh, shiny, a soft fault! 0x%lx\n", PageFrameIndex);
+                    Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
+                    ASSERT(Pfn1->u3.e1.PageLocation != ActiveAndValid);
+
+                    /* Should not yet happen in ReactOS */
+                    ASSERT(Pfn1->u3.e1.ReadInProgress == 0);
+                    ASSERT(Pfn1->u4.InPageError == 0);
+
+                    /* Get the page */
+                    MiUnlinkPageFromList(Pfn1);
+
+                    /* Bump its reference count */
+                    ASSERT(Pfn1->u2.ShareCount == 0);
+                    InterlockedIncrement16((PSHORT)&Pfn1->u3.e2.ReferenceCount);
+                    Pfn1->u2.ShareCount++;
+
+                    /* Make it valid again */
+                    /* This looks like another macro.... */
+                    Pfn1->u3.e1.PageLocation = ActiveAndValid;
+                    ASSERT(PointerProtoPte->u.Hard.Valid == 0);
+                    ASSERT(PointerProtoPte->u.Trans.Prototype == 0);
+                    ASSERT(PointerProtoPte->u.Trans.Transition == 1);
+                    TempPte.u.Long = (PointerProtoPte->u.Long & ~0xFFF) |
+                                     MmProtectToPteMask[PointerProtoPte->u.Trans.Protection];
+                    TempPte.u.Hard.Valid = 1;
+                    TempPte.u.Hard.Accessed = 1;
+
+                    /* Is the PTE writeable? */
+                    if (((Pfn1->u3.e1.Modified) && (TempPte.u.Hard.Write)) &&
+                        (TempPte.u.Hard.CopyOnWrite == 0))
+                    {
+                        /* Make it dirty */
+                        TempPte.u.Hard.Dirty = TRUE;
+                    }
+                    else
+                    {
+                        /* Make it clean */
+                        TempPte.u.Hard.Dirty = FALSE;
+                    }
+
+                    /* Write the valid PTE */
+                    MI_WRITE_VALID_PTE(PointerProtoPte, TempPte);
+                    ASSERT(PointerPte->u.Hard.Valid == 0);
+                }
+                else
+                {
+                    /* Page is invalid, get out of the loop */
+                    break;
+                }
+
+                /* One more done, was it the last? */
+                if (++ProcessedPtes == PteCount)
+                {
+                    /* Complete the fault */
+                    MiCompleteProtoPteFault(StoreInstruction,
+                                            Address,
+                                            PointerPte,
+                                            PointerProtoPte,
+                                            LockIrql,
+                                            &OutPfn);
+
+                    /* THIS RELEASES THE PFN LOCK! */
+                    break;
+                }
+
+                /* No clustered faults yet */
+                ASSERT(FALSE);
+            }
+
+            /* Did we resolve the fault? */
+            if (ProcessedPtes)
+            {
+                /* Bump the transition count */
+                InterlockedExchangeAddSizeT(&KeGetCurrentPrcb()->MmTransitionCount, ProcessedPtes);
+                ProcessedPtes--;
+
+                /* Loop all the processing we did */
+                ASSERT(ProcessedPtes == 0);
+
+                /* Complete this as a transition fault */
+                ASSERT(OldIrql == KeGetCurrentIrql());
+                ASSERT(OldIrql <= APC_LEVEL);
+                ASSERT(KeAreAllApcsDisabled() == TRUE);
+                return STATUS_PAGE_FAULT_TRANSITION;
+            }
+
+            /* We did not -- PFN lock is still held, prepare to resolve prototype PTE fault */
+            OutPfn = MI_PFN_ELEMENT(SuperProtoPte->u.Hard.PageFrameNumber);
+            MiReferenceUsedPageAndBumpLockCount(OutPfn);
+            ASSERT(OutPfn->u3.e2.ReferenceCount > 1);
+            ASSERT(PointerPte->u.Hard.Valid == 0);
 
             /* Resolve the fault -- this will release the PFN lock */
             Status = MiResolveProtoPteFault(StoreInstruction,
                                             Address,
                                             PointerPte,
                                             PointerProtoPte,
-                                            NULL,
+                                            &OutPfn,
                                             NULL,
                                             NULL,
                                             Process,
                                             LockIrql,
                                             TrapInformation);
-            ASSERT(Status == STATUS_SUCCESS);
+            //ASSERT(Status != STATUS_ISSUE_PAGING_IO);
+            //ASSERT(Status != STATUS_REFAULT);
+            //ASSERT(Status != STATUS_PTE_CHANGED);
+
+            /* Did the routine clean out the PFN or should we? */
+            if (OutPfn)
+            {
+                /* We had a locked PFN, so acquire the PFN lock to dereference it */
+                ASSERT(PointerProtoPte != NULL);
+                OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
+
+                /* Dereference the locked PFN */
+                MiDereferencePfnAndDropLockCount(OutPfn);
+                ASSERT(OutPfn->u3.e2.ReferenceCount >= 1);
+
+                /* And now release the lock */
+                KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
+            }
 
             /* Complete this as a transition fault */
             ASSERT(OldIrql == KeGetCurrentIrql());
             ASSERT(OldIrql <= APC_LEVEL);
             ASSERT(KeAreAllApcsDisabled() == TRUE);
-            return STATUS_PAGE_FAULT_TRANSITION;
+            return Status;
         }
     }
 
     //
-    // The PTE must be invalid, but not totally blank
+    // The PTE must be invalid but not completely empty. It must also not be a
+    // prototype PTE as that scenario should've been handled above. These are
+    // all Windows checks
     //
     ASSERT(TempPte.u.Hard.Valid == 0);
+    ASSERT(TempPte.u.Soft.Prototype == 0);
     ASSERT(TempPte.u.Long != 0);
 
     //
-    // No prototype, transition or page file software PTEs in ARM3 yet
+    // No transition or page file software PTEs in ARM3 yet, so this must be a
+    // demand zero page. These are all ReactOS checks
     //
-    ASSERT(TempPte.u.Soft.Prototype == 0);
     ASSERT(TempPte.u.Soft.Transition == 0);
     ASSERT(TempPte.u.Soft.PageFileHigh == 0);
 
@@ -628,8 +1326,15 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
                  IN PVOID TrapInformation)
 {
     KIRQL OldIrql = KeGetCurrentIrql(), LockIrql;
-    PMMPTE PointerPte, ProtoPte = NULL;
-    PMMPDE PointerPde;
+    PMMPTE ProtoPte = NULL;
+    PMMPTE PointerPte = MiAddressToPte(Address);
+    PMMPDE PointerPde = MiAddressToPde(Address);
+#if (_MI_PAGING_LEVELS >= 3)
+    PMMPDE PointerPpe = MiAddressToPpe(Address);
+#if (_MI_PAGING_LEVELS == 4)
+    PMMPDE PointerPxe = MiAddressToPxe(Address);
+#endif
+#endif
     MMPTE TempPte;
     PETHREAD CurrentThread;
     PEPROCESS CurrentProcess;
@@ -639,156 +1344,280 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
     PMMVAD Vad;
     PFN_NUMBER PageFrameIndex;
     ULONG Color;
+    BOOLEAN IsSessionAddress;
+    PMMPFN Pfn1;
     DPRINT("ARM3 FAULT AT: %p\n", Address);
 
-    //
-    // Get the PTE and PDE
-    //
-    PointerPte = MiAddressToPte(Address);
-    PointerPde = MiAddressToPde(Address);
+    /* Check for page fault on high IRQL */
+    if (OldIrql > APC_LEVEL)
+    {
+#if (_MI_PAGING_LEVELS < 3)
+        /* Could be a page table for paged pool, which we'll allow */
+        if (MI_IS_SYSTEM_PAGE_TABLE_ADDRESS(Address)) MiSynchronizeSystemPde((PMMPDE)PointerPte);
+        MiCheckPdeForPagedPool(Address);
+#endif
+        /* Check if any of the top-level pages are invalid */
+        if (
+#if (_MI_PAGING_LEVELS == 4)
+            (PointerPxe->u.Hard.Valid == 0) ||
+#endif
 #if (_MI_PAGING_LEVELS >= 3)
-    /* We need the PPE and PXE addresses */
-    ASSERT(FALSE);
+            (PointerPpe->u.Hard.Valid == 0) ||
+#endif
+            (PointerPde->u.Hard.Valid == 0))
+        {
+            /* This fault is not valid, printf out some debugging help */
+            DbgPrint("MM:***PAGE FAULT AT IRQL > 1  Va %p, IRQL %lx\n",
+                     Address,
+                     OldIrql);
+            if (TrapInformation)
+            {
+                PKTRAP_FRAME TrapFrame = TrapInformation;
+#ifdef _M_IX86
+                DbgPrint("MM:***EIP %p, EFL %p\n", TrapFrame->Eip, TrapFrame->EFlags);
+                DbgPrint("MM:***EAX %p, ECX %p EDX %p\n", TrapFrame->Eax, TrapFrame->Ecx, TrapFrame->Edx);
+                DbgPrint("MM:***EBX %p, ESI %p EDI %p\n", TrapFrame->Ebx, TrapFrame->Esi, TrapFrame->Edi);
+#elif defined(_M_AMD64)
+                DbgPrint("MM:***RIP %p, EFL %p\n", TrapFrame->Rip, TrapFrame->EFlags);
+                DbgPrint("MM:***RAX %p, RCX %p RDX %p\n", TrapFrame->Rax, TrapFrame->Rcx, TrapFrame->Rdx);
+                DbgPrint("MM:***RBX %p, RSI %p RDI %p\n", TrapFrame->Rbx, TrapFrame->Rsi, TrapFrame->Rdi);
 #endif
+            }
 
-    //
-    // Check for dispatch-level snafu
-    //
-    if (OldIrql > APC_LEVEL)
-    {
-        //
-        // There are some special cases where this is okay, but not in ARM3 yet
-        //
-        DbgPrint("MM:***PAGE FAULT AT IRQL > 1  Va %p, IRQL %lx\n",
-                 Address,
-                 OldIrql);
-        ASSERT(OldIrql <= APC_LEVEL);
+            /* Tell the trap handler to fail */
+            return STATUS_IN_PAGE_ERROR | 0x10000000;
+        }
+
+        /* Not yet implemented in ReactOS */
+        ASSERT(MI_IS_PAGE_LARGE(PointerPde) == FALSE);
+        ASSERT(((StoreInstruction) && (PointerPte->u.Hard.CopyOnWrite)) == FALSE);
+
+        /* Check if this was a write */
+        if (StoreInstruction)
+        {
+            /* Was it to a read-only page? */
+            Pfn1 = MI_PFN_ELEMENT(PointerPte->u.Hard.PageFrameNumber);
+            if (!(PointerPte->u.Long & PTE_READWRITE) &&
+                !(Pfn1->OriginalPte.u.Soft.Protection & MM_READWRITE))
+            {
+                /* Crash with distinguished bugcheck code */
+                KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY,
+                             (ULONG_PTR)Address,
+                             PointerPte->u.Long,
+                             (ULONG_PTR)TrapInformation,
+                             10);
+            }
+        }
+
+        /* Nothing is actually wrong */
+        DPRINT1("Fault at IRQL1 is ok\n");
+        return STATUS_SUCCESS;
     }
 
-    //
-    // Check for kernel fault
-    //
-    while (Address >= MmSystemRangeStart)
+    /* Check for kernel fault address */
+    if (Address >= MmSystemRangeStart)
     {
-        //
-        // What are you even DOING here?
-        //
+        /* Bail out, if the fault came from user mode */
         if (Mode == UserMode) return STATUS_ACCESS_VIOLATION;
 
-#if (_MI_PAGING_LEVELS >= 3)
-        /* Need to check PXE and PDE validity */
-        ASSERT(FALSE);
+#if (_MI_PAGING_LEVELS == 4)
+        /* AMD64 system, check if PXE is invalid */
+        if (PointerPxe->u.Hard.Valid == 0)
+        {
+            KeBugCheckEx(PAGE_FAULT_IN_NONPAGED_AREA,
+                         (ULONG_PTR)Address,
+                         StoreInstruction,
+                         (ULONG_PTR)TrapInformation,
+                         7);
+        }
 #endif
-
-        //
-        // Is the PDE valid?
-        //
-        if (PointerPde->u.Hard.Valid == 0)
+#if (_MI_PAGING_LEVELS == 4)
+        /* PAE/AMD64 system, check if PPE is invalid */
+        if (PointerPpe->u.Hard.Valid == 0)
         {
-            //
-            // Debug spew (eww!)
-            //
-            DPRINT("Invalid PDE\n");
+            KeBugCheckEx(PAGE_FAULT_IN_NONPAGED_AREA,
+                         (ULONG_PTR)Address,
+                         StoreInstruction,
+                         (ULONG_PTR)TrapInformation,
+                         5);
+        }
+#endif
 #if (_MI_PAGING_LEVELS == 2)
-            //
-            // Handle mapping in "Special" PDE directoreis
-            //
-            MiCheckPdeForPagedPool(Address);
+        if (MI_IS_SYSTEM_PAGE_TABLE_ADDRESS(Address)) MiSynchronizeSystemPde((PMMPDE)PointerPte);
+        MiCheckPdeForPagedPool(Address);
 #endif
-            //
-            // Now we SHOULD be good
-            //
-            if (PointerPde->u.Hard.Valid == 0)
-            {
-                //
-                // FIXFIX: Do the S-LIST hack
-                //
 
-                //
-                // Kill the system
-                //
-                KeBugCheckEx(PAGE_FAULT_IN_NONPAGED_AREA,
-                             (ULONG_PTR)Address,
-                             StoreInstruction,
-                             (ULONG_PTR)TrapInformation,
-                             2);
-            }
+        /* Check if the PDE is invalid */
+        if (PointerPde->u.Hard.Valid == 0)
+        {
+            /* PDE (still) not valid, kill the system */
+            KeBugCheckEx(PAGE_FAULT_IN_NONPAGED_AREA,
+                         (ULONG_PTR)Address,
+                         StoreInstruction,
+                         (ULONG_PTR)TrapInformation,
+                         2);
         }
 
-        //
-        // The PDE is valid, so read the PTE
-        //
+        /* Not handling session faults yet */
+        IsSessionAddress = MI_IS_SESSION_ADDRESS(Address);
+
+        /* The PDE is valid, so read the PTE */
         TempPte = *PointerPte;
         if (TempPte.u.Hard.Valid == 1)
         {
-            //
-            // Only two things can go wrong here:
-            // Executing NX page (we couldn't care less)
-            // Writing to a read-only page (the stuff ARM3 works with is write,
-            // so again, moot point).
-            //
-            if (StoreInstruction)
+            /* Check if this was system space or session space */
+            if (!IsSessionAddress)
             {
-                DPRINT1("Should NEVER happen on ARM3!!!\n");
-                return STATUS_ACCESS_VIOLATION;
+                /* Check if the PTE is still valid under PFN lock */
+                OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
+                TempPte = *PointerPte;
+                if (TempPte.u.Hard.Valid)
+                {
+                    /* Check if this was a write */
+                    if (StoreInstruction)
+                    {
+                        /* Was it to a read-only page? */
+                        Pfn1 = MI_PFN_ELEMENT(PointerPte->u.Hard.PageFrameNumber);
+                        if (!(PointerPte->u.Long & PTE_READWRITE) &&
+                            !(Pfn1->OriginalPte.u.Soft.Protection & MM_READWRITE))
+                        {
+                            /* Crash with distinguished bugcheck code */
+                            KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY,
+                                         (ULONG_PTR)Address,
+                                         PointerPte->u.Long,
+                                         (ULONG_PTR)TrapInformation,
+                                         11);
+                        }
+                    }
+                }
+
+                /* Release PFN lock and return all good */
+                KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
+                return STATUS_SUCCESS;
             }
-
-            //
-            // Otherwise, the PDE was probably invalid, and all is good now
-            //
-            return STATUS_SUCCESS;
         }
 
-        //
-        // Check for a fault on the page table or hyperspace itself
-        //
-        if (MI_IS_PAGE_TABLE_OR_HYPER_ADDRESS(Address))
+        /* Check if this was a session PTE that needs to remap the session PDE */
+        if (MI_IS_SESSION_PTE(Address))
         {
-#if (_MI_PAGING_LEVELS == 2)
-            /* Could be paged pool access from a new process -- synchronize the page directories */
-            if (MiCheckPdeForPagedPool(Address) == STATUS_WAIT_1)
+            /* Do the remapping */
+            Status = MiCheckPdeForSessionSpace(Address);
+            if (!NT_SUCCESS(Status))
             {
-                DPRINT1("PAGE TABLES FAULTED IN!\n");
-                return STATUS_SUCCESS;
+                /* It failed, this address is invalid */
+                KeBugCheckEx(PAGE_FAULT_IN_NONPAGED_AREA,
+                             (ULONG_PTR)Address,
+                             StoreInstruction,
+                             (ULONG_PTR)TrapInformation,
+                             6);
             }
+        }
+
+        /* Check for a fault on the page table or hyperspace */
+        if (MI_IS_PAGE_TABLE_OR_HYPER_ADDRESS(Address))
+        {
+#if (_MI_PAGING_LEVELS < 3)
+            /* Windows does this check but I don't understand why -- it's done above! */
+            ASSERT(MiCheckPdeForPagedPool(Address) != STATUS_WAIT_1);
 #endif
-            /* Otherwise this could be a commit of a virtual address */
-            break;
+            /* Handle this as a user mode fault */
+            goto UserFault;
         }
 
-        /* In this path, we are using the system working set */
+        /* Get the current thread */
         CurrentThread = PsGetCurrentThread();
-        WorkingSet = &MmSystemCacheWs;
 
-        /* Acquire it */
+        /* What kind of address is this */
+        if (!IsSessionAddress)
+        {
+            /* Use the system working set */
+            WorkingSet = &MmSystemCacheWs;
+            CurrentProcess = NULL;
+
+            /* Make sure we don't have a recursive working set lock */
+            if ((CurrentThread->OwnsProcessWorkingSetExclusive) ||
+                (CurrentThread->OwnsProcessWorkingSetShared) ||
+                (CurrentThread->OwnsSystemWorkingSetExclusive) ||
+                (CurrentThread->OwnsSystemWorkingSetShared) ||
+                (CurrentThread->OwnsSessionWorkingSetExclusive) ||
+                (CurrentThread->OwnsSessionWorkingSetShared))
+            {
+                /* Fail */
+                return STATUS_IN_PAGE_ERROR | 0x10000000;
+            }
+        }
+        else
+        {
+            /* Use the session process and working set */
+            CurrentProcess = HYDRA_PROCESS;
+            WorkingSet = &MmSessionSpace->GlobalVirtualAddress->Vm;
+
+            /* Make sure we don't have a recursive working set lock */
+            if ((CurrentThread->OwnsSessionWorkingSetExclusive) ||
+                (CurrentThread->OwnsSessionWorkingSetShared))
+            {
+                /* Fail */
+                return STATUS_IN_PAGE_ERROR | 0x10000000;
+            }
+        }
+
+        /* Acquire the working set lock */
         KeRaiseIrql(APC_LEVEL, &LockIrql);
         MiLockWorkingSet(CurrentThread, WorkingSet);
 
-        //
-        // Re-read PTE now that the IRQL has been raised
-        //
+        /* Re-read PTE now that we own the lock */
         TempPte = *PointerPte;
         if (TempPte.u.Hard.Valid == 1)
         {
-            //
-            // Only two things can go wrong here:
-            // Executing NX page (we couldn't care less)
-            // Writing to a read-only page (the stuff ARM3 works with is write,
-            // so again, moot point.
-            //
+            /* Check if this was a write */
             if (StoreInstruction)
             {
-                DPRINT1("Should NEVER happen on ARM3!!!\n");
-                return STATUS_ACCESS_VIOLATION;
+                /* Was it to a read-only page that is not copy on write? */
+                Pfn1 = MI_PFN_ELEMENT(PointerPte->u.Hard.PageFrameNumber);
+                if (!(TempPte.u.Long & PTE_READWRITE) &&
+                    !(Pfn1->OriginalPte.u.Soft.Protection & MM_READWRITE) &&
+                    !(TempPte.u.Hard.CopyOnWrite))
+                {
+                    /* Case not yet handled */
+                    ASSERT(!IsSessionAddress);
+
+                    /* Crash with distinguished bugcheck code */
+                    KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY,
+                                 (ULONG_PTR)Address,
+                                 TempPte.u.Long,
+                                 (ULONG_PTR)TrapInformation,
+                                 12);
+                }
+            }
+
+            /* Check for read-only write in session space */
+            if ((IsSessionAddress) &&
+                (StoreInstruction) &&
+                !(TempPte.u.Hard.Write))
+            {
+                /* Sanity check */
+                ASSERT(MI_IS_SESSION_IMAGE_ADDRESS(Address));
+
+                /* Was this COW? */
+                if (TempPte.u.Hard.CopyOnWrite == 0)
+                {
+                    /* Then this is not allowed */
+                    KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY,
+                                 (ULONG_PTR)Address,
+                                 (ULONG_PTR)TempPte.u.Long,
+                                 (ULONG_PTR)TrapInformation,
+                                 13);
+                }
+
+                /* Otherwise, handle COW */
+                ASSERT(FALSE);
             }
 
             /* Release the working set */
             MiUnlockWorkingSet(CurrentThread, WorkingSet);
             KeLowerIrql(LockIrql);
 
-            //
-            // Otherwise, the PDE was probably invalid, and all is good now
-            //
+            /* Otherwise, the PDE was probably invalid, and all is good now */
             return STATUS_SUCCESS;
         }
 
@@ -813,18 +1642,27 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
 
             /* Get the prototype PTE! */
             ProtoPte = MiProtoPteToPte(&TempPte);
+
+            /* Do we need to locate the prototype PTE in session space? */
+            if ((IsSessionAddress) &&
+                (TempPte.u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED))
+            {
+                /* Yep, go find it as well as the VAD for it */
+                ProtoPte = MiCheckVirtualAddress(Address,
+                                                 &ProtectionCode,
+                                                 &Vad);
+                ASSERT(ProtoPte != NULL);
+            }
         }
         else
         {
-            //
-            // We don't implement transition PTEs
-            //
+            /* We don't implement transition PTEs */
             ASSERT(TempPte.u.Soft.Transition == 0);
 
             /* Check for no-access PTE */
             if (TempPte.u.Soft.Protection == MM_NOACCESS)
             {
-                /* Bad boy, bad boy, whatcha gonna do, whatcha gonna do when ARM3 comes for you! */
+                /* Bugcheck the system! */
                 KeBugCheckEx(PAGE_FAULT_IN_NONPAGED_AREA,
                              (ULONG_PTR)Address,
                              StoreInstruction,
@@ -834,12 +1672,16 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
         }
 
         /* Check for demand page */
-        if ((StoreInstruction) && !(ProtoPte) && !(TempPte.u.Hard.Valid))
+        if ((StoreInstruction) &&
+            !(ProtoPte) &&
+            !(IsSessionAddress) &&
+            !(TempPte.u.Hard.Valid))
         {
             /* Get the protection code */
+            ASSERT(TempPte.u.Soft.Transition == 0);
             if (!(TempPte.u.Soft.Protection & MM_READWRITE))
             {
-                /* Bad boy, bad boy, whatcha gonna do, whatcha gonna do when ARM3 comes for you! */
+                /* Bugcheck the system! */
                 KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY,
                              (ULONG_PTR)Address,
                              TempPte.u.Long,
@@ -848,15 +1690,13 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
             }
         }
 
-        //
-        // Now do the real fault handling
-        //
+        /* Now do the real fault handling */
         Status = MiDispatchFault(StoreInstruction,
                                  Address,
                                  PointerPte,
                                  ProtoPte,
                                  FALSE,
-                                 NULL,
+                                 CurrentProcess,
                                  TrapInformation,
                                  NULL);
 
@@ -865,48 +1705,91 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
         MiUnlockWorkingSet(CurrentThread, WorkingSet);
         KeLowerIrql(LockIrql);
 
-        //
-        // We are done!
-        //
+        /* We are done! */
         DPRINT("Fault resolved with status: %lx\n", Status);
         return Status;
     }
 
     /* This is a user fault */
+UserFault:
     CurrentThread = PsGetCurrentThread();
-    CurrentProcess = PsGetCurrentProcess();
+    CurrentProcess = (PEPROCESS)CurrentThread->Tcb.ApcState.Process;
 
     /* Lock the working set */
     MiLockProcessWorkingSet(CurrentProcess, CurrentThread);
 
+#if (_MI_PAGING_LEVELS == 4)
+// Note to Timo: You should call MiCheckVirtualAddress and also check if it's zero pte
+// also this is missing the page count increment
+    /* Check if the PXE is valid */
+    if (PointerPxe->u.Hard.Valid == 0)
+    {
+        /* Right now, we only handle scenarios where the PXE is totally empty */
+        ASSERT(PointerPxe->u.Long == 0);
+#if 0
+        /* Resolve a demand zero fault */
+        Status = MiResolveDemandZeroFault(PointerPpe,
+                                          MM_READWRITE,
+                                          CurrentProcess,
+                                          MM_NOIRQL);
+#endif
+        /* We should come back with a valid PXE */
+        ASSERT(PointerPxe->u.Hard.Valid == 1);
+    }
+#endif
+
 #if (_MI_PAGING_LEVELS >= 3)
-    /* Need to check/handle PPE and PXE validity too */
-    ASSERT(FALSE);
+// Note to Timo: You should call MiCheckVirtualAddress and also check if it's zero pte
+// also this is missing the page count increment
+    /* Check if the PPE is valid */
+    if (PointerPpe->u.Hard.Valid == 0)
+    {
+        /* Right now, we only handle scenarios where the PPE is totally empty */
+        ASSERT(PointerPpe->u.Long == 0);
+#if 0
+        /* Resolve a demand zero fault */
+        Status = MiResolveDemandZeroFault(PointerPde,
+                                          MM_READWRITE,
+                                          CurrentProcess,
+                                          MM_NOIRQL);
+#endif
+        /* We should come back with a valid PPE */
+        ASSERT(PointerPpe->u.Hard.Valid == 1);
+    }
 #endif
 
-    /* First things first, is the PDE valid? */
-    ASSERT(PointerPde->u.Hard.LargePage == 0);
+    /* Check if the PDE is valid */
     if (PointerPde->u.Hard.Valid == 0)
     {
         /* Right now, we only handle scenarios where the PDE is totally empty */
         ASSERT(PointerPde->u.Long == 0);
 
-        /* Check if this address range belongs to a valid allocation (VAD) */
-        MiCheckVirtualAddress(Address, &ProtectionCode, &Vad);
-
-        /* Right now, we expect a valid protection mask on the VAD */
-        ASSERT(ProtectionCode != MM_NOACCESS);
-
-        /* Make the PDE demand-zero */
-        MI_WRITE_INVALID_PDE(PointerPde, DemandZeroPde);
-
         /* And go dispatch the fault on the PDE. This should handle the demand-zero */
 #if MI_TRACE_PFNS
         UserPdeFault = TRUE;
 #endif
+        MiCheckVirtualAddress(Address, &ProtectionCode, &Vad);
+        if (ProtectionCode == MM_NOACCESS)
+        {
+#if (_MI_PAGING_LEVELS == 2)
+            /* Could be a page table for paged pool */
+            MiCheckPdeForPagedPool(Address);
+#endif
+            /* Has the code above changed anything -- is this now a valid PTE? */
+            Status = (PointerPde->u.Hard.Valid == 1) ? STATUS_SUCCESS : STATUS_ACCESS_VIOLATION;
+
+            /* Either this was a bogus VA or we've fixed up a paged pool PDE */
+            MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
+            return Status;
+        }
+
+        /* Write a demand-zero PDE */
+        MI_WRITE_INVALID_PTE(PointerPde, DemandZeroPde);
+
+        /* Dispatch the fault */
         Status = MiDispatchFault(TRUE,
                                  PointerPte,
-                                 (PMMPTE)PointerPde,
+                                 PointerPde,
                                  NULL,
                                  FALSE,
                                  PsGetCurrentProcess(),
@@ -917,12 +1800,13 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
 #endif
         /* We should come back with APCs enabled, and with a valid PDE */
         ASSERT(KeAreAllApcsDisabled() == TRUE);
-#if (_MI_PAGING_LEVELS >= 3)
-        /* Need to check/handle PPE and PXE validity too */
-        ASSERT(FALSE);
-#endif
         ASSERT(PointerPde->u.Hard.Valid == 1);
     }
+    else
+    {
+        /* Not yet implemented in ReactOS */
+        ASSERT(MI_IS_PAGE_LARGE(PointerPde) == FALSE);
+    }
 
     /* Now capture the PTE. Ignore virtual faults for now */
     TempPte = *PointerPte;
@@ -942,156 +1826,342 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
         return STATUS_PAGE_FAULT_DEMAND_ZERO;
     }
 
-    /* Get protection and check if it's a prototype PTE */
-    ProtectionCode = TempPte.u.Soft.Protection;
-    ASSERT(TempPte.u.Soft.Prototype == 0);
-
-    /* Check for non-demand zero PTE */
-    if (TempPte.u.Long != 0)
+    /* Check for zero PTE */
+    if (TempPte.u.Long == 0)
     {
-        /* This is a page fault, check for valid protection */
-        ASSERT(ProtectionCode != 0x100);
+        /* Check if this address range belongs to a valid allocation (VAD) */
+        ProtoPte = MiCheckVirtualAddress(Address, &ProtectionCode, &Vad);
+        if (ProtectionCode == MM_NOACCESS)
+        {
+#if (_MI_PAGING_LEVELS == 2)
+            /* Could be a page table for paged pool */
+            MiCheckPdeForPagedPool(Address);
+#endif
+            /* Has the code above changed anything -- is this now a valid PTE? */
+            Status = (PointerPte->u.Hard.Valid == 1) ? STATUS_SUCCESS : STATUS_ACCESS_VIOLATION;
 
-        /* FIXME: Run MiAccessCheck */
+            /* Either this was a bogus VA or we've fixed up a paged pool PDE */
+            MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
+            return Status;
+        }
 
-        /* Dispatch the fault */
-        Status = MiDispatchFault(StoreInstruction,
-                                 Address,
-                                 PointerPte,
-                                 NULL,
-                                 FALSE,
-                                 PsGetCurrentProcess(),
-                                 TrapInformation,
-                                 NULL);
+        /*
+         * Check if this is a real user-mode address or actually a kernel-mode
+         * page table for a user mode address
+         */
+        if (Address <= MM_HIGHEST_USER_ADDRESS)
+        {
+            /* Add an additional page table reference */
+            MiIncrementPageTableReferences(Address);
+        }
 
-        /* Return the status */
-        ASSERT(NT_SUCCESS(Status));
-        ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
-        MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
-        return Status;
-    }
+        /* Is this a guard page? */
+        if (ProtectionCode & MM_DECOMMIT)
+        {
+            /* Remove the bit */
+            PointerPte->u.Soft.Protection = ProtectionCode & ~MM_DECOMMIT;
 
-    /* Check if this address range belongs to a valid allocation (VAD) */
-    ASSERT(TempPte.u.Long == 0);
-    ProtoPte = MiCheckVirtualAddress(Address, &ProtectionCode, &Vad);
-    if (ProtectionCode == MM_NOACCESS)
-    {
-        /* This is a bogus VA */
-        Status = STATUS_ACCESS_VIOLATION;
+            /* Not supported */
+            ASSERT(ProtoPte == NULL);
+            ASSERT(CurrentThread->ApcNeeded == 0);
 
-        /* Could be a not-yet-mapped paged pool page table */
-#if (_MI_PAGING_LEVELS == 2)
-        MiCheckPdeForPagedPool(Address);
-#endif
-        /* See if that fixed it */
-        if (PointerPte->u.Hard.Valid == 1) Status = STATUS_SUCCESS;
+            /* Drop the working set lock */
+            MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
+            ASSERT(KeGetCurrentIrql() == OldIrql);
 
-        /* Return the status */
-        MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
-        return Status;
-    }
+            /* Handle stack expansion */
+            return MiCheckForUserStackOverflow(Address, TrapInformation);
+        }
 
-    /* Is this a user address? */
-    if (Address <= MM_HIGHEST_USER_ADDRESS)
-    {
-        /* Add an additional page table reference */
-        MmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)]++;
-        ASSERT(MmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)] <= PTE_COUNT);
-    }
+        /* Did we get a prototype PTE back? */
+        if (!ProtoPte)
+        {
+            /* Is this PTE actually part of the PDE-PTE self-mapping directory? */
+            if (PointerPde == MiAddressToPde(PTE_BASE))
+            {
+                /* Then it's really a demand-zero PDE (on behalf of user-mode) */
+                MI_WRITE_INVALID_PTE(PointerPte, DemandZeroPde);
+            }
+            else
+            {
+                /* No, create a new PTE. First, write the protection */
+                PointerPte->u.Soft.Protection = ProtectionCode;
+            }
 
-    /* Did we get a prototype PTE back? */
-    if (!ProtoPte)
-    {
-        /* No, create a new PTE. First, write the protection */
-        PointerPte->u.Soft.Protection = ProtectionCode;
+            /* Lock the PFN database since we're going to grab a page */
+            OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
 
-        /* Lock the PFN database since we're going to grab a page */
-        OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
+            /* Make sure we have enough pages */
+            ASSERT(MmAvailablePages >= 32);
 
-        /* Try to get a zero page */
-        MI_SET_USAGE(MI_USAGE_PEB_TEB);
-        MI_SET_PROCESS2(CurrentProcess->ImageFileName);
-        Color = MI_GET_NEXT_PROCESS_COLOR(CurrentProcess);
-        PageFrameIndex = MiRemoveZeroPageSafe(Color);
-        if (!PageFrameIndex)
-        {
-            /* Grab a page out of there. Later we should grab a colored zero page */
-            PageFrameIndex = MiRemoveAnyPage(Color);
-            ASSERT(PageFrameIndex);
+            /* Try to get a zero page */
+            MI_SET_USAGE(MI_USAGE_PEB_TEB);
+            MI_SET_PROCESS2(CurrentProcess->ImageFileName);
+            Color = MI_GET_NEXT_PROCESS_COLOR(CurrentProcess);
+            PageFrameIndex = MiRemoveZeroPageSafe(Color);
+            if (!PageFrameIndex)
+            {
+                /* Grab a page out of there. Later we should grab a colored zero page */
+                PageFrameIndex = MiRemoveAnyPage(Color);
+                ASSERT(PageFrameIndex);
+
+                /* Release the lock since we need to do some zeroing */
+                KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
+
+                /* Zero out the page, since it's for user-mode */
+                MiZeroPfn(PageFrameIndex);
 
-            /* Release the lock since we need to do some zeroing */
+                /* Grab the lock again so we can initialize the PFN entry */
+                OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
+            }
+
+            /* Initialize the PFN entry now */
+            MiInitializePfn(PageFrameIndex, PointerPte, 1);
+
+            /* And we're done with the lock */
             KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
 
-            /* Zero out the page, since it's for user-mode */
-            MiZeroPfn(PageFrameIndex);
+            /* Increment the count of pages in the process */
+            CurrentProcess->NumberOfPrivatePages++;
 
-            /* Grab the lock again so we can initialize the PFN entry */
-            OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
-        }
+            /* One more demand-zero fault */
+            InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount);
 
-        /* Initialize the PFN entry now */
-        MiInitializePfn(PageFrameIndex, PointerPte, 1);
+            /* Fault on user PDE, or fault on user PTE? */
+            if (PointerPte <= MiHighestUserPte)
+            {
+                /* User fault, build a user PTE */
+                MI_MAKE_HARDWARE_PTE_USER(&TempPte,
+                                          PointerPte,
+                                          PointerPte->u.Soft.Protection,
+                                          PageFrameIndex);
+            }
+            else
+            {
+                /* This is a user-mode PDE, create a kernel PTE for it */
+                MI_MAKE_HARDWARE_PTE(&TempPte,
+                                     PointerPte,
+                                     PointerPte->u.Soft.Protection,
+                                     PageFrameIndex);
+            }
 
-        /* And we're done with the lock */
-        KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
+            /* Write the dirty bit for writeable pages */
+            if (MI_IS_PAGE_WRITEABLE(&TempPte)) MI_MAKE_DIRTY_PAGE(&TempPte);
 
-        /* One more demand-zero fault */
-        InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount);
+            /* And now write down the PTE, making the address valid */
+            MI_WRITE_VALID_PTE(PointerPte, TempPte);
+            Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
+            ASSERT(Pfn1->u1.Event == NULL);
 
-        /* Was the fault on an actual user page, or a kernel page for the user? */
-        if (PointerPte <= MiHighestUserPte)
+            /* Demand zero */
+            ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
+            MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
+            return STATUS_PAGE_FAULT_DEMAND_ZERO;
+        }
+
+        /* We should have a valid protection here */
+        ASSERT(ProtectionCode != 0x100);
+
+        /* Write the prototype PTE */
+        TempPte = PrototypePte;
+        TempPte.u.Soft.Protection = ProtectionCode;
+        MI_WRITE_INVALID_PTE(PointerPte, TempPte);
+    }
+    else
+    {
+        /* Get the protection code and check if this is a proto PTE */
+        ProtectionCode = TempPte.u.Soft.Protection;
+        if (TempPte.u.Soft.Prototype)
         {
-            /* User fault, build a user PTE */
-            MI_MAKE_HARDWARE_PTE_USER(&TempPte,
-                                      PointerPte,
-                                      PointerPte->u.Soft.Protection,
-                                      PageFrameIndex);
+            /* Do we need to go find the real PTE? */
+            if (TempPte.u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED)
+            {
+                /* Get the prototype pte and VAD for it */
+                ProtoPte = MiCheckVirtualAddress(Address,
+                                                 &ProtectionCode,
+                                                 &Vad);
+                if (!ProtoPte)
+                {
+                    ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
+                    MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
+                    return STATUS_ACCESS_VIOLATION;
+                }
+            }
+            else
+            {
+                /* Get the prototype PTE! */
+                ProtoPte = MiProtoPteToPte(&TempPte);
+
+                /* Is it read-only */
+                if (TempPte.u.Proto.ReadOnly)
+                {
+                    /* Set read-only code */
+                    ProtectionCode = MM_READONLY;
+                }
+                else
+                {
+                    /* Set unknown protection */
+                    ProtectionCode = 0x100;
+                    ASSERT(CurrentProcess->CloneRoot != NULL);
+                }
+            }
         }
-        else
+    }
+
+    /* Do we have a valid protection code? */
+    if (ProtectionCode != 0x100)
+    {
+        /* Run a software access check first, including to detect guard pages */
+        Status = MiAccessCheck(PointerPte,
+                               StoreInstruction,
+                               Mode,
+                               ProtectionCode,
+                               TrapInformation,
+                               FALSE);
+        if (Status != STATUS_SUCCESS)
         {
-            /* Session, kernel, or user PTE, figure it out and build it */
-            MI_MAKE_HARDWARE_PTE(&TempPte,
-                                 PointerPte,
-                                 PointerPte->u.Soft.Protection,
-                                 PageFrameIndex);
+            /* Not supported */
+            ASSERT(CurrentThread->ApcNeeded == 0);
+
+            /* Drop the working set lock */
+            MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
+            ASSERT(KeGetCurrentIrql() == OldIrql);
+
+            /* Did we hit a guard page? */
+            if (Status == STATUS_GUARD_PAGE_VIOLATION)
+            {
+                /* Handle stack expansion */
+                return MiCheckForUserStackOverflow(Address, TrapInformation);
+            }
+
+            /* Otherwise, fail back to the caller directly */
+            return Status;
         }
+    }
 
-        /* Write the dirty bit for writeable pages */
-        if (MI_IS_PAGE_WRITEABLE(&TempPte)) MI_MAKE_DIRTY_PAGE(&TempPte);
+    /* Dispatch the fault */
+    Status = MiDispatchFault(StoreInstruction,
+                             Address,
+                             PointerPte,
+                             ProtoPte,
+                             FALSE,
+                             CurrentProcess,
+                             TrapInformation,
+                             Vad);
+
+    /* Return the status */
+    ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
+    MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
+    return Status;
+}
 
-        /* And now write down the PTE, making the address valid */
-        MI_WRITE_VALID_PTE(PointerPte, TempPte);
+NTSTATUS
+NTAPI
+MmGetExecuteOptions(IN PULONG ExecuteOptions)
+{
+    PKPROCESS CurrentProcess = &PsGetCurrentProcess()->Pcb;
+    ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
 
-        /* Demand zero */
-        Status = STATUS_PAGE_FAULT_DEMAND_ZERO;
+    *ExecuteOptions = 0;
+
+    if (CurrentProcess->Flags.ExecuteDisable)
+    {
+        *ExecuteOptions |= MEM_EXECUTE_OPTION_DISABLE;
     }
-    else
+
+    if (CurrentProcess->Flags.ExecuteEnable)
     {
-        /* No guard page support yet */
-        ASSERT((ProtectionCode & MM_DECOMMIT) == 0);
-        ASSERT(ProtectionCode != 0x100);
+        *ExecuteOptions |= MEM_EXECUTE_OPTION_ENABLE;
+    }
 
-        /* Write the prototype PTE */
-        TempPte = PrototypePte;
-        TempPte.u.Soft.Protection = ProtectionCode;
-        MI_WRITE_INVALID_PTE(PointerPte, TempPte);
+    if (CurrentProcess->Flags.DisableThunkEmulation)
+    {
+        *ExecuteOptions |= MEM_EXECUTE_OPTION_DISABLE_THUNK_EMULATION;
+    }
 
-        /* Handle the fault */
-        Status = MiDispatchFault(StoreInstruction,
-                                 Address,
-                                 PointerPte,
-                                 ProtoPte,
-                                 FALSE,
-                                 CurrentProcess,
-                                 TrapInformation,
-                                 Vad);
-        ASSERT(Status == STATUS_PAGE_FAULT_TRANSITION);
-        ASSERT(PointerPte->u.Hard.Valid == 1);
-        ASSERT(PointerPte->u.Hard.PageFrameNumber != 0);
+    if (CurrentProcess->Flags.Permanent)
+    {
+        *ExecuteOptions |= MEM_EXECUTE_OPTION_PERMANENT;
     }
 
-    /* Release the working set */
-    MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
+    if (CurrentProcess->Flags.ExecuteDispatchEnable)
+    {
+        *ExecuteOptions |= MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE;
+    }
+
+    if (CurrentProcess->Flags.ImageDispatchEnable)
+    {
+        *ExecuteOptions |= MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE;
+    }
+
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS
+NTAPI
+MmSetExecuteOptions(IN ULONG ExecuteOptions)
+{
+    PKPROCESS CurrentProcess = &PsGetCurrentProcess()->Pcb;
+    KLOCK_QUEUE_HANDLE ProcessLock;
+    NTSTATUS Status = STATUS_ACCESS_DENIED;
+    ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
+
+    /* Only accept valid flags */
+    if (ExecuteOptions & ~MEM_EXECUTE_OPTION_VALID_FLAGS)
+    {
+        /* Fail */
+        DPRINT1("Invalid no-execute options\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    /* Change the NX state in the process lock */
+    KiAcquireProcessLock(CurrentProcess, &ProcessLock);
+
+    /* Don't change anything if the permanent flag was set */
+    if (!CurrentProcess->Flags.Permanent)
+    {
+        /* Start by assuming it's not disabled */
+        CurrentProcess->Flags.ExecuteDisable = FALSE;
+
+        /* Now process each flag and turn the equivalent bit on */
+        if (ExecuteOptions & MEM_EXECUTE_OPTION_DISABLE)
+        {
+            CurrentProcess->Flags.ExecuteDisable = TRUE;
+        }
+        if (ExecuteOptions & MEM_EXECUTE_OPTION_ENABLE)
+        {
+            CurrentProcess->Flags.ExecuteEnable = TRUE;
+        }
+        if (ExecuteOptions & MEM_EXECUTE_OPTION_DISABLE_THUNK_EMULATION)
+        {
+            CurrentProcess->Flags.DisableThunkEmulation = TRUE;
+        }
+        if (ExecuteOptions & MEM_EXECUTE_OPTION_PERMANENT)
+        {
+            CurrentProcess->Flags.Permanent = TRUE;
+        }
+        if (ExecuteOptions & MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE)
+        {
+            CurrentProcess->Flags.ExecuteDispatchEnable = TRUE;
+        }
+        if (ExecuteOptions & MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE)
+        {
+            CurrentProcess->Flags.ImageDispatchEnable = TRUE;
+        }
+
+        /* These are turned on by default if no-execution is also eanbled */
+        if (CurrentProcess->Flags.ExecuteEnable)
+        {
+            CurrentProcess->Flags.ExecuteDispatchEnable = TRUE;
+            CurrentProcess->Flags.ImageDispatchEnable = TRUE;
+        }
+
+        /* All good */
+        Status = STATUS_SUCCESS;
+    }
+
+    /* Release the lock and return status */
+    KiReleaseProcessLock(&ProcessLock);
     return Status;
 }