/*++ Copyright (c) 1989 Microsoft Corporation Module Name: acceschk.c Abstract: This module contains the access check routines for memory management. Author: Lou Perazzoli (loup) 10-Apr-1989 Landy Wang (landyw) 02-June-1997 Revision History: --*/ #include "mi.h" #if defined(_WIN64) #include "wow64t.h" #pragma alloc_text(PAGE, MiCheckForUserStackOverflow) #if PAGE_SIZE != PAGE_SIZE_X86NT #define EMULATE_USERMODE_STACK_4K 1 #endif #endif // // MmReadWrite yields 0 if no-access, 10 if read-only, 11 if read-write. // It is indexed by a page protection. The value of this array is added // to the !WriteOperation value. If the value is 10 or less an access // violation is issued (read-only - write_operation) = 9, // (read_only - read_operation) = 10, etc. // CCHAR MmReadWrite[32] = {1, 10, 10, 10, 11, 11, 11, 11, 1, 10, 10, 10, 11, 11, 11, 11, 1, 10, 10, 10, 11, 11, 11, 11, 1, 10, 10, 10, 11, 11, 11, 11 }; NTSTATUS MiAccessCheck ( IN PMMPTE PointerPte, IN ULONG_PTR WriteOperation, IN KPROCESSOR_MODE PreviousMode, IN ULONG Protection, IN BOOLEAN CallerHoldsPfnLock ) /*++ Routine Description: Arguments: PointerPte - Supplies the pointer to the PTE which caused the page fault. WriteOperation - Supplies nonzero if the operation is a write, 0 if the operation is a read. PreviousMode - Supplies the previous mode, one of UserMode or KernelMode. Protection - Supplies the protection mask to check. CallerHoldsPfnLock - Supplies TRUE if the PFN lock is held, FALSE otherwise. Return Value: Returns TRUE if access to the page is allowed, FALSE otherwise. Environment: Kernel mode, APCs disabled. --*/ { MMPTE PteContents; KIRQL OldIrql; PMMPFN Pfn1; // // Check to see if the owner bit allows access to the previous mode. // Access is not allowed if the owner is kernel and the previous // mode is user. Access is also disallowed if the write operation // is true and the write field in the PTE is false. // // // If both an access violation and a guard page violation could // occur for the page, the access violation must be returned. // if (PreviousMode == UserMode) { if (PointerPte > MiHighestUserPte) { return STATUS_ACCESS_VIOLATION; } } PteContents = *PointerPte; if (PteContents.u.Hard.Valid == 1) { // // Valid pages cannot be guard page violations. // if (WriteOperation != 0) { if ((PteContents.u.Hard.Write == 1) || (PteContents.u.Hard.CopyOnWrite == 1)) { return STATUS_SUCCESS; } return STATUS_ACCESS_VIOLATION; } return STATUS_SUCCESS; } if (WriteOperation != 0) { WriteOperation = 1; } if ((MmReadWrite[Protection] - (CCHAR)WriteOperation) < 10) { return STATUS_ACCESS_VIOLATION; } // // Check for a guard page fault. // if (Protection & MM_GUARD_PAGE) { // // If this thread is attached to a different process, // return an access violation rather than a guard // page exception. This prevents problems with unwanted // stack expansion and unexpected guard page behavior // from debuggers. // if (KeIsAttachedProcess()) { return STATUS_ACCESS_VIOLATION; } // // Check to see if this is a transition PTE. If so, the // PFN database original contents field needs to be updated. // if ((PteContents.u.Soft.Transition == 1) && (PteContents.u.Soft.Prototype == 0)) { // // Acquire the PFN lock and check to see if the // PTE is still in the transition state. If so, // update the original PTE in the PFN database. // SATISFY_OVERZEALOUS_COMPILER (OldIrql = PASSIVE_LEVEL); if (CallerHoldsPfnLock == FALSE) { LOCK_PFN (OldIrql); } PteContents = *PointerPte; if ((PteContents.u.Soft.Transition == 1) && (PteContents.u.Soft.Prototype == 0)) { // // Still in transition, update the PFN database. // Pfn1 = MI_PFN_ELEMENT (PteContents.u.Trans.PageFrameNumber); // // Note that forked processes using guard pages only take the // guard page fault when the first thread in either process // access the address. This seems to be the best behavior we // can provide users of this API as we must allow the first // thread to make forward progress and the guard attribute is // stored in the shared fork prototype PTE. // if (PteContents.u.Soft.Protection == MM_NOACCESS) { ASSERT ((Pfn1->u3.e1.PrototypePte == 1) && (MiLocateCloneAddress (PsGetCurrentProcess (), Pfn1->PteAddress) != NULL)); if (CallerHoldsPfnLock == FALSE) { UNLOCK_PFN (OldIrql); } return STATUS_ACCESS_VIOLATION; } ASSERT ((Pfn1->u3.e1.PrototypePte == 0) || (MiLocateCloneAddress (PsGetCurrentProcess (), Pfn1->PteAddress) != NULL)); Pfn1->OriginalPte.u.Soft.Protection = Protection & ~MM_GUARD_PAGE; } if (CallerHoldsPfnLock == FALSE) { UNLOCK_PFN (OldIrql); } } PointerPte->u.Soft.Protection = Protection & ~MM_GUARD_PAGE; return STATUS_GUARD_PAGE_VIOLATION; } return STATUS_SUCCESS; } NTSTATUS FASTCALL MiCheckForUserStackOverflow ( IN PVOID FaultingAddress ) /*++ Routine Description: This routine checks to see if the faulting address is within the stack limits and if so tries to create another guard page on the stack. A stack overflow is returned if the creation of a new guard page fails or if the stack is in the following form: stack +----------------+ growth | | StackBase | +----------------+ v | | | allocated | | | | ... | | | +----------------+ | old guard page | <- faulting address is in this page. +----------------+ | | +----------------+ | | last page of stack (always no access) +----------------+ In this case, the page before the last page is committed, but not as a guard page and a STACK_OVERFLOW condition is returned. Arguments: FaultingAddress - Supplies the virtual address of the page which was a guard page. Return Value: NTSTATUS. Environment: Kernel mode. No mutexes held. --*/ { PTEB Teb; PPEB Peb; ULONG_PTR NextPage; SIZE_T RegionSize; NTSTATUS status; PVOID DeallocationStack; PVOID *StackLimit; PVOID StackBase; PETHREAD Thread; ULONG_PTR PageSize; PEPROCESS Process; ULONG OldProtection; ULONG ExecuteFlags; ULONG ProtectionFlags; LOGICAL RevertExecuteFlag; ULONG StackProtection; #if defined (_IA64_) PVOID DeallocationBStore; #endif #if defined(_WIN64) PTEB32 Teb32; Teb32 = NULL; #endif // // Make sure we are not recursing with the address space mutex held. // Thread = PsGetCurrentThread (); if (Thread->AddressSpaceOwner == 1) { ASSERT (KeAreAllApcsDisabled () == TRUE); return STATUS_GUARD_PAGE_VIOLATION; } // // If this thread is attached to a different process, // return an access violation rather than a guard // page exception. This prevents problems with unwanted // stack expansion and unexpected guard page behavior // from debuggers. // // Note we must bail when attached because we reference fields in our // TEB below which have no relationship to the virtual address space // of the process we are attached to. Rather than introduce random // behavior into applications, it's better to consistently return an AV // for this scenario (one thread trying to grow another's stack). // if (KeIsAttachedProcess()) { return STATUS_GUARD_PAGE_VIOLATION; } Process = NULL; // // Initialize default protections early so that they can be used on // all code paths. // ProtectionFlags = PAGE_READWRITE | PAGE_GUARD; RevertExecuteFlag = FALSE; StackProtection = PAGE_READWRITE; Teb = Thread->Tcb.Teb; // // Create an exception handler as the TEB is within the user's // address space. // try { StackBase = Teb->NtTib.StackBase; #if defined (_IA64_) DeallocationBStore = Teb->DeallocationBStore; if ((FaultingAddress >= StackBase) && (FaultingAddress < DeallocationBStore)) { // // Check to see if the faulting address is within // the bstore limits and if so try to create another guard // page in the bstore. // // // +----------------+ // | | last page of stack (always no access) // +----------------+ // | | // | | // | | // +----------------+ // | old guard page | <- faulting address is in this page. // +----------------+ // bstore | | // growth | ...... | // | | // ^ | allocated | // | | | StackBase // +----------------+ // // NextPage = (ULONG_PTR)PAGE_ALIGN(FaultingAddress) + PAGE_SIZE; RegionSize = PAGE_SIZE; if ((NextPage + PAGE_SIZE) >= (ULONG_PTR)PAGE_ALIGN(DeallocationBStore)) { // // There is no more room for expansion, attempt to // commit the page before the last page of the stack. // NextPage = (ULONG_PTR)PAGE_ALIGN(DeallocationBStore) - PAGE_SIZE; status = ZwAllocateVirtualMemory (NtCurrentProcess(), (PVOID *)&NextPage, 0, &RegionSize, MEM_COMMIT, PAGE_READWRITE); if (NT_SUCCESS(status)) { Teb->BStoreLimit = (PVOID) NextPage; } return STATUS_STACK_OVERFLOW; } Teb->BStoreLimit = (PVOID) NextPage; goto AllocateTheGuard; } #endif DeallocationStack = Teb->DeallocationStack; StackLimit = &Teb->NtTib.StackLimit; // // The faulting address must be below the stack base and // above the stack limit. // if ((FaultingAddress >= StackBase) || (FaultingAddress < DeallocationStack)) { // // Not within the native stack. // #if defined (_WIN64) // // Also check for the 32-bit stack if this is a Wow64 process. // Process = PsGetCurrentProcessByThread (Thread); if (Process->Wow64Process != NULL) { Teb32 = (PTEB32) Teb->NtTib.ExceptionList; if (Teb32 != NULL) { ProbeForReadSmallStructure (Teb32, sizeof(TEB32), sizeof(ULONG)); StackBase = (PVOID) (ULONG_PTR) Teb32->NtTib.StackBase; DeallocationStack = (PVOID) (ULONG_PTR) Teb32->DeallocationStack; if ((FaultingAddress >= StackBase) || (FaultingAddress < DeallocationStack)) { // // Not within the stack. // return STATUS_GUARD_PAGE_VIOLATION; } StackLimit = (PVOID *)&Teb32->NtTib.StackLimit; goto ExtendTheStack; } } #endif // // Not within the stack. // return STATUS_GUARD_PAGE_VIOLATION; } #if defined (_WIN64) ExtendTheStack: #endif // // If the image was marked for no stack extensions, // return stack overflow immediately. // Process = PsGetCurrentProcessByThread (Thread); Peb = Process->Peb; if (Peb->NtGlobalFlag & FLG_DISABLE_STACK_EXTENSION) { return STATUS_STACK_OVERFLOW; } // // Add execute permission if necessary. We do not need to change // anything for the old guard page because either it is the first // guard page of the current thread and it will get correct // protection during user mode thread initialization (see // LdrpInitialize in base\ntdll\ldrinit.c) or it is a // guard page created by this function during stack growth // and in this case it gets correct protection. // #if defined(_WIN64) if (Teb32 != NULL) { ASSERT (Process->Wow64Process != NULL); ExecuteFlags = ((PPEB32)(Process->Wow64Process->Wow64))->ExecuteOptions; } else { #endif ExecuteFlags = Peb->ExecuteOptions; #if defined(_WIN64) } #endif if (ExecuteFlags & (MEM_EXECUTE_OPTION_STACK | MEM_EXECUTE_OPTION_DATA)) { if (ExecuteFlags & MEM_EXECUTE_OPTION_STACK) { StackProtection = PAGE_EXECUTE_READWRITE; ProtectionFlags = PAGE_EXECUTE_READWRITE | PAGE_GUARD; } else { // // The stack must be made non-executable. The // ZwAllocateVirtualMemory call below will make it // executable because this process is marked as wanting // executable data and ZwAllocate cannot tell this is // really a stack allocation. // ASSERT (ExecuteFlags & MEM_EXECUTE_OPTION_DATA); RevertExecuteFlag = TRUE; } } // // This address is within the current stack, if there is ample // room for another guard page then attempt to commit it. // #if EMULATE_USERMODE_STACK_4K if (Teb32 != NULL) { NextPage = (ULONG_PTR) PAGE_4K_ALIGN (FaultingAddress) - PAGE_4K; DeallocationStack = PAGE_4K_ALIGN (DeallocationStack); PageSize = PAGE_4K; RegionSize = PAGE_4K; // // Don't set the guard bit in the native PTE - just set // it in the AltPte. // ProtectionFlags &= ~PAGE_GUARD; } else #endif { NextPage = (ULONG_PTR)PAGE_ALIGN (FaultingAddress) - PAGE_SIZE; DeallocationStack = PAGE_ALIGN (DeallocationStack); PageSize = PAGE_SIZE; RegionSize = PAGE_SIZE; } if ((NextPage - PageSize) <= (ULONG_PTR)DeallocationStack) { // // There is no more room for expansion, attempt to // commit the page before the last page of the stack. // NextPage = (ULONG_PTR)DeallocationStack + PageSize; status = ZwAllocateVirtualMemory (NtCurrentProcess(), (PVOID *)&NextPage, 0, &RegionSize, MEM_COMMIT, StackProtection); if (NT_SUCCESS(status)) { #if defined(_WIN64) if (Teb32 != NULL) { *(PULONG) StackLimit = (ULONG) NextPage; } else #endif *StackLimit = (PVOID) NextPage; // // Revert the EXECUTE bit if we get it by default // but it is not desired. // if (RevertExecuteFlag) { status = ZwProtectVirtualMemory (NtCurrentProcess(), (PVOID *)&NextPage, &RegionSize, StackProtection, &OldProtection); ASSERT (StackProtection & PAGE_READWRITE); } } return STATUS_STACK_OVERFLOW; } #if defined(_WIN64) if (Teb32 != NULL) { // // Update the 32-bit stack limit. // *(PULONG) StackLimit = (ULONG) (NextPage + PageSize); } else #endif *StackLimit = (PVOID)(NextPage + PAGE_SIZE); } except (EXCEPTION_EXECUTE_HANDLER) { // // An exception has occurred during the referencing of the // TEB or TIB, just return a guard page violation and // don't deal with the stack overflow. // return STATUS_GUARD_PAGE_VIOLATION; } #if defined (_IA64_) AllocateTheGuard: #endif // // Set the guard page. For Wow64 processes the protection // will not contain the PAGE_GUARD bit. This is ok since in these // cases we will set the bit for the top emulated 4K page. // status = ZwAllocateVirtualMemory (NtCurrentProcess(), (PVOID *)&NextPage, 0, &RegionSize, MEM_COMMIT, ProtectionFlags); if (NT_SUCCESS(status) || (status == STATUS_ALREADY_COMMITTED)) { // // Revert the EXECUTE bit with an extra protect() call // if we get it by default but it is not desired. // if (RevertExecuteFlag) { if (ProtectionFlags & PAGE_GUARD) { ProtectionFlags = PAGE_READWRITE | PAGE_GUARD; } else { ProtectionFlags = PAGE_READWRITE; } status = ZwProtectVirtualMemory (NtCurrentProcess(), (PVOID *)&NextPage, &RegionSize, ProtectionFlags, &OldProtection); } #if EMULATE_USERMODE_STACK_4K if (Teb32 != NULL) { LOCK_ADDRESS_SPACE (Process); MiProtectFor4kPage ((PVOID)NextPage, RegionSize, (MM_READWRITE | MM_GUARD_PAGE), ALT_CHANGE, Process); UNLOCK_ADDRESS_SPACE (Process); } #endif // // The guard page is now committed or stack space is // already present, return success. // return STATUS_PAGE_FAULT_GUARD_PAGE; } return STATUS_STACK_OVERFLOW; }