/*++ Copyright (c) 1999 Microsoft Corporation Module Name: altperm.c Abstract: This module contains the routines to support 4K pages on IA64. An alternate set of permissions is kept that are on 4K boundaries. Permissions are kept for all memory, not just split pages and the information is updated on any call to NtVirtualProtect() and NtAllocateVirtualMemory(). Author: Koichi Yamada 18-Aug-1998 Landy Wang (landyw) 02-June-1997 Revision History: --*/ #include "mi.h" #if defined(_MIALT4K_) ULONG MiFindProtectionForNativePte ( PVOID VirtualAddress ); VOID MiFillZeroFor4kPage ( IN PVOID BaseAddress, IN PEPROCESS Process ); VOID MiResetAccessBitForNativePtes ( IN PVOID StartVirtual, IN PVOID EndVirtual, IN PEPROCESS Process ); LOGICAL MiIsSplitPage ( IN PVOID Virtual ); VOID MiCopyOnWriteFor4kPage ( PVOID VirtualAddress ); VOID MiCheckDemandZeroCopyOnWriteFor4kPage ( PVOID VirtualAddress, PEPROCESS Process ); VOID MiCheckVirtualAddressFor4kPage ( PVOID VirtualAddress, PEPROCESS Process ); LOGICAL MiIsNativeGuardPage ( IN PVOID VirtualAddress ); VOID MiSetNativePteProtection ( IN PVOID VirtualAddress, IN ULONGLONG NewPteProtection, IN LOGICAL PageIsSplit, IN PEPROCESS CurrentProcess ); extern PMMPTE MmPteHit; NTSTATUS MmX86Fault ( IN ULONG_PTR FaultStatus, IN PVOID VirtualAddress, IN KPROCESSOR_MODE PreviousMode, IN PVOID TrapInformation ) /*++ Routine Description: This function is called by the kernel on data or instruction access faults if CurrentProcess->Wow64Process is non-NULL and the faulting address is within the first 2GB. This routine determines the type of fault by checking the alternate 4Kb granular page table and calls MmAccessFault() if necessary to handle the page fault or the write fault. Arguments: FaultStatus - Supplies fault status information bits. VirtualAddress - Supplies the virtual address which caused the fault. PreviousMode - Supplies the mode (kernel or user) in which the fault occurred. TrapInformation - Opaque information about the trap, interpreted by the kernel, not Mm. Needed to allow fast interlocked access to operate correctly. Return Value: Returns the status of the fault handling operation. Can be one of: - Success. - Access Violation. - Guard Page Violation. - In-page Error. Environment: Kernel mode, APCs disabled. --*/ { ULONG i; PMMPTE PointerAltPte; PMMPTE PointerAltPteForNativePage; MMPTE AltPteContents; PMMPTE PointerPte; PMMPTE PointerPde; ULONGLONG NewPteProtection; LOGICAL FillZero; LOGICAL PageIsSplit; LOGICAL SharedPageFault; LOGICAL NativeGuardPage; PEPROCESS CurrentProcess; PWOW64_PROCESS Wow64Process; KIRQL PreviousIrql; KIRQL OldIrql; NTSTATUS status; ULONG OriginalProtection; ULONGLONG ProtectionMaskOriginal; PMMPTE ProtoPte; PMMPFN Pfn1; PVOID OriginalVirtualAddress; ASSERT (VirtualAddress < (PVOID)MM_MAX_WOW64_ADDRESS); PreviousIrql = KeGetCurrentIrql (); if (PreviousIrql > APC_LEVEL) { return MmAccessFault (FaultStatus, VirtualAddress, PreviousMode, TrapInformation); } NewPteProtection = 0; FillZero = FALSE; PageIsSplit = FALSE; SharedPageFault = FALSE; NativeGuardPage = FALSE; PointerAltPteForNativePage = NULL; OriginalVirtualAddress = VirtualAddress; CurrentProcess = PsGetCurrentProcess (); Wow64Process = CurrentProcess->Wow64Process; PointerPte = MiGetPteAddress (VirtualAddress); PointerAltPte = MiGetAltPteAddress (VirtualAddress); #if DBG if (PointerPte == MmPteHit) { DbgPrint ("MM: PTE hit at %p\n", MmPteHit); DbgBreakPoint (); } #endif // // Acquire the alternate table mutex, also blocking APCs. // LOCK_ALTERNATE_TABLE (Wow64Process); // // If a fork operation is in progress and the faulting thread // is not the thread performing the fork operation, block until // the fork is completed. // if (CurrentProcess->ForkInProgress != NULL) { UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); KeLowerIrql (PreviousIrql); LOCK_WS (CurrentProcess); if (MiWaitForForkToComplete (CurrentProcess, FALSE) == FALSE) { ASSERT (FALSE); } UNLOCK_WS (CurrentProcess); return STATUS_SUCCESS; } // // Check to see if the protection is registered in the alternate entry. // if (MI_CHECK_BIT (Wow64Process->AltPermBitmap, MI_VA_TO_VPN(VirtualAddress)) == 0) { MiCheckVirtualAddressFor4kPage (VirtualAddress, CurrentProcess); } // // Read the alternate PTE contents. // AltPteContents = *PointerAltPte; // // Check to see if the alternate entry is no access. // if (AltPteContents.u.Alt.NoAccess != 0) { // // This 4KB page is no access. // status = STATUS_ACCESS_VIOLATION; #if DBG if (MmDebug & MM_DBG_STOP_ON_ACCVIO) { DbgPrint ("MM:access violation - %p\n",OriginalVirtualAddress); MiFormatPte (PointerPte); DbgBreakPoint (); } #endif goto return_status; } // // Check to see if the alternate entry is empty or if anyone has made any // commitments for the shared pages. // if ((AltPteContents.u.Long == 0) || ((AltPteContents.u.Alt.Commit == 0) && (AltPteContents.u.Alt.Private == 0))) { // // If empty, get the protection information and fill the entry. // LOCK_WS (CurrentProcess); ProtoPte = MiCheckVirtualAddress (VirtualAddress, &OriginalProtection); if (ProtoPte != NULL) { if (OriginalProtection == MM_UNKNOWN_PROTECTION) { if (!MI_IS_PHYSICAL_ADDRESS(ProtoPte)) { PointerPde = MiGetPteAddress (ProtoPte); LOCK_PFN (OldIrql); if (PointerPde->u.Hard.Valid == 0) { MiMakeSystemAddressValidPfn (ProtoPte); } Pfn1 = MI_PFN_ELEMENT (PointerPde->u.Hard.PageFrameNumber); MI_ADD_LOCKED_PAGE_CHARGE(Pfn1, 28); Pfn1->u3.e2.ReferenceCount += 1; ASSERT (Pfn1->u3.e2.ReferenceCount > 1); UNLOCK_PFN (OldIrql); } else { Pfn1 = NULL; } OriginalProtection = MiMakeProtectionMask (MiGetPageProtection (ProtoPte, CurrentProcess, FALSE)); // // Unlock the page containing the prototype PTEs. // if (Pfn1 != NULL) { ASSERT (!MI_IS_PHYSICAL_ADDRESS(ProtoPte)); LOCK_PFN (OldIrql); ASSERT (Pfn1->u3.e2.ReferenceCount > 1); MI_REMOVE_LOCKED_PAGE_CHARGE(Pfn1, 29); Pfn1->u3.e2.ReferenceCount -= 1; UNLOCK_PFN (OldIrql); } } UNLOCK_WS (CurrentProcess); if (OriginalProtection == MM_INVALID_PROTECTION) { status = STATUS_ACCESS_VIOLATION; #if DBG if (MmDebug & MM_DBG_STOP_ON_ACCVIO) { DbgPrint ("MM:access violation - %p\n",OriginalVirtualAddress); MiFormatPte (PointerPte); DbgBreakPoint (); } #endif goto return_status; } if (OriginalProtection != MM_NOACCESS) { ProtectionMaskOriginal = MiMakeProtectionAteMask (OriginalProtection); SharedPageFault = TRUE; ProtectionMaskOriginal |= MM_ATE_COMMIT; AltPteContents.u.Long = ProtectionMaskOriginal; AltPteContents.u.Alt.Protection = OriginalProtection; // // Atomically update the PTE. // PointerAltPte->u.Long = AltPteContents.u.Long; } } else { UNLOCK_WS (CurrentProcess); } } if (AltPteContents.u.Alt.Commit == 0) { // // If the page is not committed, return an STATUS_ACCESS_VIOLATION. // status = STATUS_ACCESS_VIOLATION; #if DBG if (MmDebug & MM_DBG_STOP_ON_ACCVIO) { DbgPrint ("MM:access violation - %p\n",OriginalVirtualAddress); MiFormatPte (PointerPte); DbgBreakPoint (); } #endif goto return_status; } // // Check whether the faulting page is split into 4k pages. // PageIsSplit = MiIsSplitPage (VirtualAddress); // // Get the real protection for the native PTE. // NewPteProtection = MiFindProtectionForNativePte (VirtualAddress); // // Set the Protection for the native PTE // MiSetNativePteProtection (VirtualAddress, NewPteProtection, PageIsSplit, CurrentProcess); // // Check the indirect PTE reference case. If so, set the protection for // the indirect PTE too. // if (AltPteContents.u.Alt.PteIndirect != 0) { PointerPte = (PMMPTE)(AltPteContents.u.Alt.PteOffset + PTE_UBASE); VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte); NewPteProtection = AltPteContents.u.Long & ALT_PROTECTION_MASK; if (AltPteContents.u.Alt.CopyOnWrite != 0) { NewPteProtection |= MM_PTE_COPY_ON_WRITE_MASK; } MiSetNativePteProtection (VirtualAddress, NewPteProtection, FALSE, CurrentProcess); } // // Since we release the AltTable lock before calling MmAccessFault, // there is a chance that two threads may execute concurrently inside // MmAccessFault, which would yield bad results since the initial native // PTE for the page has only READ protection on it. So if two threads // fault on the same address, one of them will execute through all of // this routine, however the other one will just return STATUS_SUCCESS // which will cause another fault to happen in which the protections // will be fixed on the native page. // // Note that in addition to the dual thread case there is also the case // of a single thread which also has an overlapped I/O pending (for example) // which can trigger an APC completion memory copy to the same page. // Protect against this by remaining at APC_LEVEL until clearing the // inpage in progress in the alternate PTE. // if (MI_ALT_PTE_IN_PAGE_IN_PROGRESS (PointerAltPte) != 0) { // // Release the Alt PTE lock // UNLOCK_ALTERNATE_TABLE (Wow64Process); // // Flush the TB as MiSetNativePteProtection may have edited the PTE. // KiFlushSingleTb (TRUE, OriginalVirtualAddress); // // Delay execution so that if this is a high priority thread, // it won't starve the other thread (that's doing the actual inpage) // as it may be running at a lower priority. // KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmShortTime); return STATUS_SUCCESS; } // // The faulting 4kb page must be a valid page, but we need to resolve it // on a case by case basis. // ASSERT (AltPteContents.u.Long != 0); ASSERT (AltPteContents.u.Alt.Commit != 0); if (AltPteContents.u.Alt.Accessed == 0) { // // When PointerAte->u.Hard.Accessed is zero, there are 4 possibilities: // // 1. Lowest Protection // 2. 4kb Demand Zero // 3. GUARD page fault // 4. This 4kb page is no access, but the other 4K page(s) within // the native page has accessible permissions. // if (AltPteContents.u.Alt.FillZero != 0) { // // Schedule it later. // FillZero = TRUE; } if ((AltPteContents.u.Alt.Protection & MM_GUARD_PAGE) != 0) { goto CheckGuardPage; } if (FillZero == FALSE) { // // This 4kb page has permission set to no access. // status = STATUS_ACCESS_VIOLATION; #if DBG if (MmDebug & MM_DBG_STOP_ON_ACCVIO) { DbgPrint ("MM:access violation - %p\n",OriginalVirtualAddress); MiFormatPte (PointerPte); DbgBreakPoint (); } #endif goto return_status; } } if (MI_FAULT_STATUS_INDICATES_EXECUTION(FaultStatus)) { // // Execute permission is already given to IA32 by setting it in // MI_MAKE_VALID_PTE(). // } else if (MI_FAULT_STATUS_INDICATES_WRITE(FaultStatus)) { // // Check to see if this is a copy-on-write page. // if (AltPteContents.u.Alt.CopyOnWrite != 0) { // // Let MmAccessFault() perform the copy-on-write. // status = MmAccessFault (FaultStatus, VirtualAddress, PreviousMode, TrapInformation); if (NT_SUCCESS(status)) { MiCopyOnWriteFor4kPage (OriginalVirtualAddress); } goto return_status; } if (AltPteContents.u.Hard.Write == 0) { status = STATUS_ACCESS_VIOLATION; #if DBG if (MmDebug & MM_DBG_STOP_ON_ACCVIO) { DbgPrint ("MM:access violation - %p\n",OriginalVirtualAddress); MiFormatPte (PointerPte); DbgBreakPoint (); } #endif goto return_status; } } CheckGuardPage: // // Indicate that we have begun updating the PTE for this page. // Subsequent faults on this native page will be restarted. // This should happen only if the PTE isn't valid. // PointerAltPteForNativePage = MiGetAltPteAddress (PAGE_ALIGN (VirtualAddress)); for (i = 0; i < SPLITS_PER_PAGE; i += 1) { PointerAltPteForNativePage->u.Alt.InPageInProgress = TRUE; PointerAltPteForNativePage += 1; } // // Let MmAccessFault() perform an inpage, dirty-bit setting, etc. // // Release the alternate table mutex but stay at APC_LEVEL to prevent an // incoming APC that references the same page from deadlocking this thread. // It is safe to drop below APC_LEVEL only after the in progress bit in // the alternate PTE has been cleared. // Wow64Process->AlternateTableAcquiredUnsafe = MI_MUTEX_ACQUIRED_UNSAFE; UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); status = MmAccessFault (FaultStatus, VirtualAddress, PreviousMode, TrapInformation); LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); for (i = 0; i < SPLITS_PER_PAGE; i += 1) { PointerAltPteForNativePage -= 1; PointerAltPteForNativePage->u.Alt.InPageInProgress = FALSE; } AltPteContents = *PointerAltPte; if ((AltPteContents.u.Alt.Protection & MM_GUARD_PAGE) != 0) { AltPteContents = *PointerAltPte; AltPteContents.u.Alt.Protection &= ~MM_GUARD_PAGE; AltPteContents.u.Alt.Accessed = 1; PointerAltPte->u.Long = AltPteContents.u.Long; if ((status != STATUS_PAGE_FAULT_GUARD_PAGE) && (status != STATUS_STACK_OVERFLOW)) { UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); KeLowerIrql (PreviousIrql); status = MiCheckForUserStackOverflow (VirtualAddress); KeRaiseIrql (APC_LEVEL, &PreviousIrql); LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); } } else if (status == STATUS_GUARD_PAGE_VIOLATION) { // // Native PTE has the guard bit set, but the AltPte // doesn't have it. // NativeGuardPage = MiIsNativeGuardPage (VirtualAddress); if (NativeGuardPage == TRUE) { status = STATUS_SUCCESS; } } else if ((SharedPageFault == TRUE) && (status == STATUS_ACCESS_VIOLATION)) { PointerAltPte->u.Alt.Commit = 0; } return_status: KiFlushSingleTb (TRUE, OriginalVirtualAddress); if (FillZero == TRUE) { MiFillZeroFor4kPage (VirtualAddress, CurrentProcess); } UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); KeLowerIrql (PreviousIrql); return status; } ULONG MiFindProtectionForNativePte ( IN PVOID VirtualAddress ) /*++ Routine Description: This function finds the protection for the native PTE. Arguments: VirtualAddress - Supplies a virtual address to be examined for the protection of the PTE. Return Value: The protection code. Environment: --*/ { ULONG i; ULONG ProtectionCode; PMMPTE PointerAltPte; MMPTE AltPteContents; ProtectionCode = 0; PointerAltPte = MiGetAltPteAddress (PAGE_ALIGN(VirtualAddress)); for (i = 0; i < SPLITS_PER_PAGE; i += 1) { AltPteContents.u.Long = PointerAltPte->u.Long; if (AltPteContents.u.Alt.PteIndirect == 0) { ProtectionCode |= (PointerAltPte->u.Long & ALT_PROTECTION_MASK); } PointerAltPte += 1; } return ProtectionCode; } // // Define and initialize the protection conversion table for the // Alternate Permision Table Entries. // ULONGLONG MmProtectToAteMask[32] = { MM_PTE_NOACCESS | MM_ATE_NOACCESS, MM_PTE_EXECUTE_READ | MM_PTE_ACCESS_MASK, MM_PTE_EXECUTE_READ | MM_PTE_ACCESS_MASK, MM_PTE_EXECUTE_READ | MM_PTE_ACCESS_MASK, MM_PTE_EXECUTE_READWRITE | MM_PTE_ACCESS_MASK, MM_PTE_EXECUTE_READ | MM_PTE_ACCESS_MASK | MM_ATE_COPY_ON_WRITE, MM_PTE_EXECUTE_READWRITE | MM_PTE_ACCESS_MASK, MM_PTE_EXECUTE_READ | MM_PTE_ACCESS_MASK | MM_ATE_COPY_ON_WRITE, MM_PTE_NOACCESS | MM_ATE_NOACCESS, MM_PTE_EXECUTE_READ | MM_PTE_ACCESS_MASK, MM_PTE_EXECUTE_READ | MM_PTE_ACCESS_MASK, MM_PTE_EXECUTE_READ | MM_PTE_ACCESS_MASK, MM_PTE_EXECUTE_READWRITE | MM_PTE_ACCESS_MASK, MM_PTE_EXECUTE_READ | MM_PTE_ACCESS_MASK | MM_ATE_COPY_ON_WRITE, MM_PTE_EXECUTE_READWRITE | MM_PTE_ACCESS_MASK, MM_PTE_EXECUTE_READ | MM_PTE_ACCESS_MASK | MM_ATE_COPY_ON_WRITE, MM_PTE_NOACCESS | MM_ATE_NOACCESS, MM_PTE_EXECUTE_READ, MM_PTE_EXECUTE_READ, MM_PTE_EXECUTE_READ, MM_PTE_EXECUTE_READWRITE, MM_PTE_EXECUTE_READ | MM_ATE_COPY_ON_WRITE, MM_PTE_EXECUTE_READWRITE, MM_PTE_EXECUTE_READ | MM_ATE_COPY_ON_WRITE, MM_PTE_NOACCESS | MM_ATE_NOACCESS, MM_PTE_EXECUTE_READ, MM_PTE_EXECUTE_READ, MM_PTE_EXECUTE_READ, MM_PTE_EXECUTE_READWRITE, MM_PTE_EXECUTE_READ | MM_ATE_COPY_ON_WRITE, MM_PTE_EXECUTE_READWRITE, MM_PTE_EXECUTE_READ | MM_ATE_COPY_ON_WRITE }; #define MiMakeProtectionAteMask(NewProtect) MmProtectToAteMask[NewProtect] VOID MiProtectFor4kPage ( IN PVOID Base, IN SIZE_T Size, IN ULONG NewProtect, IN ULONG Flags, IN PEPROCESS Process ) /*++ Routine Description: This routine sets the permissions on the alternate bitmap (based on 4K page sizes). The base and size are assumed to be aligned for 4K pages already. Arguments: Base - Supplies the base address (assumed to be 4K aligned already). Size - Supplies the size to be protected (assumed to be 4K aligned already). NewProtect - Supplies the protection for the new pages. Flags - Supplies the alternate table entry request flags. Process - Supplies a pointer to the process in which to create the protections on the alternate table. Return Value: None. Environment: Kernel mode. Address creation mutex held at APC_LEVEL. --*/ { ULONG i; RTL_BITMAP BitMap; ULONG NumberOfPtes; ULONG Starting4KVpn; PVOID Starting4KAddress; PVOID Ending4KAddress; PVOID VirtualAddress; ULONG NewProtectNotCopy; ULONGLONG ProtectionMask; ULONGLONG ProtectionMaskNotCopy; PMMPTE StartAltPte; PMMPTE EndAltPte; PMMPTE StartAltPte0; PMMPTE EndAltPte0; PWOW64_PROCESS Wow64Process; PVOID Virtual[MM_MAXIMUM_FLUSH_COUNT]; MMPTE AltPteContents; MMPTE TempAltPte; Starting4KAddress = Base; Ending4KAddress = (PCHAR)Base + Size - 1; // // If the addresses are not WOW64 then nothing needs to be done here. // if ((Starting4KAddress >= (PVOID)MM_MAX_WOW64_ADDRESS) || (Ending4KAddress >= (PVOID)MM_MAX_WOW64_ADDRESS)) { return; } // // Set up the protection to be used for this range of addresses. // ProtectionMask = MiMakeProtectionAteMask (NewProtect); if ((NewProtect & MM_COPY_ON_WRITE_MASK) == MM_COPY_ON_WRITE_MASK) { NewProtectNotCopy = NewProtect & ~MM_PROTECTION_COPY_MASK; ProtectionMaskNotCopy = MiMakeProtectionAteMask (NewProtectNotCopy); } else { NewProtectNotCopy = NewProtect; ProtectionMaskNotCopy = ProtectionMask; } if (Flags & ALT_COMMIT) { ProtectionMask |= MM_ATE_COMMIT; ProtectionMaskNotCopy |= MM_ATE_COMMIT; } // // Get the entry in the table for each of these addresses. // StartAltPte = MiGetAltPteAddress (Starting4KAddress); EndAltPte = MiGetAltPteAddress (Ending4KAddress); NumberOfPtes = (ULONG) ADDRESS_AND_SIZE_TO_SPAN_PAGES (Starting4KAddress, (ULONG_PTR)Ending4KAddress - (ULONG_PTR)Starting4KAddress); ASSERT (NumberOfPtes != 0); // // Ensure the proper native TB entries get flushed. // if (NumberOfPtes < MM_MAXIMUM_FLUSH_COUNT) { VirtualAddress = PAGE_ALIGN (Starting4KAddress); for (i = 0; i < NumberOfPtes; i += 1) { Virtual[i] = (PVOID)VirtualAddress; VirtualAddress = (PVOID)((ULONG_PTR)VirtualAddress + PAGE_SIZE); } } StartAltPte0 = MiGetAltPteAddress (PAGE_ALIGN (Starting4KAddress)); EndAltPte0 = MiGetAltPteAddress ((ULONG_PTR)PAGE_ALIGN(Ending4KAddress)+PAGE_SIZE-1); Wow64Process = Process->Wow64Process; Starting4KVpn = (ULONG) MI_VA_TO_VPN (Starting4KAddress); // // Acquire the mutex guarding the alternate page table. // LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); if (!(Flags & ALT_ALLOCATE) && (MI_CHECK_BIT(Wow64Process->AltPermBitmap, Starting4KVpn) == 0)) { UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); return; } // // Change all of the protections. // while (StartAltPte <= EndAltPte) { AltPteContents.u.Long = StartAltPte->u.Long; TempAltPte.u.Long = ProtectionMask; TempAltPte.u.Alt.Protection = NewProtect; if (!(Flags & ALT_ALLOCATE)) { if (AltPteContents.u.Alt.Private != 0) { // // If it is already private, don't make it writecopy. // TempAltPte.u.Long = ProtectionMaskNotCopy; TempAltPte.u.Alt.Protection = NewProtectNotCopy; // // Private is sticky bit // TempAltPte.u.Alt.Private = 1; } if (AltPteContents.u.Alt.FillZero != 0) { TempAltPte.u.Alt.Accessed = 0; TempAltPte.u.Alt.FillZero = 1; } // // Leave the other sticky attribute bits // TempAltPte.u.Alt.Lock = AltPteContents.u.Alt.Lock; TempAltPte.u.Alt.PteIndirect = AltPteContents.u.Alt.PteIndirect; TempAltPte.u.Alt.PteOffset = AltPteContents.u.Alt.PteOffset; } if (Flags & ALT_CHANGE) { // // If it is a change request, make commit sticky. // TempAltPte.u.Alt.Commit = AltPteContents.u.Alt.Commit; } // // Atomic PTE update. // StartAltPte->u.Long = TempAltPte.u.Long; StartAltPte += 1; } if (Flags & ALT_ALLOCATE) { // // Fill the empty Alt PTE as NoAccess ATE at the end. // while (EndAltPte <= EndAltPte0) { if (EndAltPte->u.Long == 0) { TempAltPte.u.Long = EndAltPte->u.Long; TempAltPte.u.Alt.NoAccess = 1; // // Atomic PTE update. // EndAltPte->u.Long = TempAltPte.u.Long; } EndAltPte += 1; } // // Update the permission bitmap. // // Initialize the bitmap inline for speed. // BitMap.SizeOfBitMap = MM_MAX_WOW64_ADDRESS >> PTI_SHIFT; BitMap.Buffer = Wow64Process->AltPermBitmap; RtlSetBits (&BitMap, Starting4KVpn, NumberOfPtes); } MiResetAccessBitForNativePtes (Starting4KAddress, Ending4KAddress, Process); if (NumberOfPtes < MM_MAXIMUM_FLUSH_COUNT) { KeFlushMultipleTb (NumberOfPtes, &Virtual[0], TRUE, TRUE, NULL, ZeroPte.u.Flush); } else { KeFlushEntireTb (TRUE, TRUE); } UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); } VOID MiProtectMapFileFor4kPage ( IN PVOID Base, IN SIZE_T Size, IN ULONG NewProtect, IN SIZE_T CommitSize, IN PMMPTE PointerPte, IN PMMPTE LastPte, IN PEPROCESS Process ) /*++ Routine Description: This routine sets the permissions on the alternate bitmap (based on 4K page sizes). The base and size are assumed to be aligned for 4K pages already. Arguments: Base - Supplies the base address (assumed to be 4K aligned already). Size - Supplies the size to be protected (assumed to be 4K aligned already). NewProtect - Supplies the protection for the new pages. CommitSize - Supplies the commit size. PointerPte - Supplies the starting PTE. LastPte - Supplies the last PTE. Process - Supplies a pointer to the process in which to create the protections on the alternate table. Return Value: None. Environment: Kernel mode. Address creation mutex held at APC_LEVEL. --*/ { RTL_BITMAP BitMap; PVOID Starting4KAddress; PVOID Ending4KAddress; ULONGLONG ProtectionMask; PMMPTE StartAltPte; PMMPTE EndAltPte; PMMPTE EndAltPte0; PWOW64_PROCESS Wow64Process; MMPTE TempAltPte; PMMPTE LastCommitPte; Wow64Process = Process->Wow64Process; Starting4KAddress = Base; Ending4KAddress = (PCHAR)Base + Size - 1; // // If the addresses are not WOW64 then nothing needs to be done here. // if ((Starting4KAddress >= (PVOID)MM_MAX_WOW64_ADDRESS) || (Ending4KAddress >= (PVOID)MM_MAX_WOW64_ADDRESS)) { return; } // // Set up the protection to be used for this range of addresses. // ProtectionMask = MiMakeProtectionAteMask (NewProtect); // // Get the entry in the table for each of these addresses. // StartAltPte = MiGetAltPteAddress (Starting4KAddress); EndAltPte = MiGetAltPteAddress (Ending4KAddress); EndAltPte0 = MiGetAltPteAddress((ULONG_PTR)PAGE_ALIGN(Ending4KAddress)+PAGE_SIZE-1); LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); ExAcquireFastMutexUnsafe (&MmSectionCommitMutex); // // And then change all of the protections. // LastCommitPte = PointerPte + BYTES_TO_PAGES(CommitSize); TempAltPte.u.Long = ProtectionMask; TempAltPte.u.Alt.Protection = NewProtect; while (StartAltPte <= EndAltPte) { if (PointerPte < LastCommitPte) { TempAltPte.u.Alt.Commit = 1; } else if ((PointerPte <= LastPte) && (PointerPte->u.Long != 0)) { TempAltPte.u.Alt.Commit = 1; } else { TempAltPte.u.Alt.Commit = 0; } // // Atomic PTE update. // StartAltPte->u.Long = TempAltPte.u.Long; StartAltPte += 1; if (((ULONG_PTR)StartAltPte & ((SPLITS_PER_PAGE * sizeof(MMPTE))-1)) == 0) { PointerPte += 1; } } ExReleaseFastMutexUnsafe (&MmSectionCommitMutex); // // Fill the empty Alt PTE as NoAccess ATE at the end. // while (EndAltPte <= EndAltPte0) { if (EndAltPte->u.Long == 0) { TempAltPte.u.Long = EndAltPte->u.Long; TempAltPte.u.Alt.NoAccess = 1; // // Atomic PTE size update. // EndAltPte->u.Long = TempAltPte.u.Long; } EndAltPte += 1; } // // Initialize the bitmap inline for speed. // BitMap.SizeOfBitMap = MM_MAX_WOW64_ADDRESS >> PTI_SHIFT; BitMap.Buffer = Wow64Process->AltPermBitmap; RtlSetBits (&BitMap, (ULONG) MI_VA_TO_VPN (Base), (ULONG) (MI_VA_TO_VPN (Ending4KAddress) - MI_VA_TO_VPN (Base) + 1)); UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); } VOID MiProtectImageFileFor4kPage ( IN PVOID Base, IN SIZE_T Size, IN PMMPTE PointerPte, IN PEPROCESS Process ) /*++ Environment: Kernel mode. Address creation mutex held at APC_LEVEL. --*/ { RTL_BITMAP BitMap; PVOID Starting4KAddress; PVOID Ending4KAddress; ULONGLONG ProtectionMask; PMMPTE StartAltPte; PMMPTE EndAltPte; PMMPTE EndAltPte0; PWOW64_PROCESS Wow64Process; MMPTE TempAltPte; MMPTE TempPte; ULONG NewProtect; KIRQL OldIrql; Wow64Process = Process->Wow64Process; Starting4KAddress = Base; Ending4KAddress = (PCHAR)Base + Size - 1; // // If the addresses are not WOW64 then nothing needs to be done here. // if ((Starting4KAddress >= (PVOID)MM_MAX_WOW64_ADDRESS) || (Ending4KAddress >= (PVOID)MM_MAX_WOW64_ADDRESS)) { return; } // // Get the entry in the table for each of these addresses. // StartAltPte = MiGetAltPteAddress (Starting4KAddress); EndAltPte = MiGetAltPteAddress (Ending4KAddress); EndAltPte0 = MiGetAltPteAddress((ULONG_PTR)PAGE_ALIGN(Ending4KAddress)+PAGE_SIZE-1); LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); // // And then change all of the protections. // while (StartAltPte <= EndAltPte) { // // Get the original protection information from the prototype PTEs. // LOCK_WS_UNSAFE (Process); LOCK_PFN (OldIrql); MiMakeSystemAddressValidPfnWs (PointerPte, Process); TempPte = *PointerPte; UNLOCK_PFN (OldIrql); NewProtect = MiMakeProtectionMask(MiGetPageProtection(&TempPte, Process, TRUE)); ASSERT (NewProtect != MM_INVALID_PROTECTION); UNLOCK_WS_UNSAFE (Process); // // If demand-zero and copy-on-write, remove copy-on-write. // if ((!IS_PTE_NOT_DEMAND_ZERO(TempPte)) && (TempPte.u.Soft.Protection & MM_COPY_ON_WRITE_MASK)) { NewProtect = NewProtect & ~MM_PROTECTION_COPY_MASK; } ProtectionMask = MiMakeProtectionAteMask (NewProtect); ProtectionMask |= MM_ATE_COMMIT; TempAltPte.u.Long = ProtectionMask; TempAltPte.u.Alt.Protection = NewProtect; if ((NewProtect & MM_PROTECTION_COPY_MASK) == 0) { // // If the copy-on-write is removed, make it private. // TempAltPte.u.Alt.Private = 1; } // // Atomic PTE update. // MiFillMemoryPte (StartAltPte, SPLITS_PER_PAGE * sizeof(MMPTE), TempAltPte.u.Long); StartAltPte += SPLITS_PER_PAGE; PointerPte += 1; } // // Fill the empty Alt PTE as NoAccess ATE at the end. // while (EndAltPte <= EndAltPte0) { if (EndAltPte->u.Long == 0) { TempAltPte.u.Long = EndAltPte->u.Long; TempAltPte.u.Alt.NoAccess = 1; // // Atomic PTE size update. // EndAltPte->u.Long = TempAltPte.u.Long; } EndAltPte += 1; } // // Initialize the bitmap inline for speed. // BitMap.SizeOfBitMap = MM_MAX_WOW64_ADDRESS >> PTI_SHIFT; BitMap.Buffer = Wow64Process->AltPermBitmap; RtlSetBits (&BitMap, (ULONG) MI_VA_TO_VPN (Base), (ULONG) (MI_VA_TO_VPN (Ending4KAddress) - MI_VA_TO_VPN (Base) + 1)); UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); } VOID MiReleaseFor4kPage ( IN PVOID StartVirtual, IN PVOID EndVirtual, IN PEPROCESS Process ) /*++ Routine Description: This function releases a region of pages within the virtual address space of the subject process. Arguments: StartVirtual - Supplies the start address of the region of pages to be released. EndVirtual - Supplies the end address of the region of pages to be released. Process - Supplies a pointer to the process in which to release the region of pages. Return Value: None. Environment: Kernel mode. Address creation mutex held at APC_LEVEL. --*/ { RTL_BITMAP BitMap; PMMPTE StartAltPte; PMMPTE EndAltPte; MMPTE TempAltPte; ULONG_PTR VirtualAddress; PVOID OriginalStartVa, OriginalEndVa; ULONG i; PWOW64_PROCESS Wow64Process; PVOID Virtual[MM_MAXIMUM_FLUSH_COUNT]; ULONG FlushCount; PFN_NUMBER NumberOfAltPtes; ULONG NumberOfPtes; ASSERT(StartVirtual <= EndVirtual); FlushCount = 0; OriginalStartVa = StartVirtual; OriginalEndVa = EndVirtual; Wow64Process = Process->Wow64Process; StartAltPte = MiGetAltPteAddress (StartVirtual); EndAltPte = MiGetAltPteAddress (EndVirtual); NumberOfAltPtes = EndAltPte - StartAltPte + 1; TempAltPte.u.Long = 0; TempAltPte.u.Alt.NoAccess = 1; TempAltPte.u.Alt.FillZero = 1; StartVirtual = PAGE_ALIGN(StartVirtual); VirtualAddress = (ULONG_PTR)StartVirtual; NumberOfPtes = (ULONG) ADDRESS_AND_SIZE_TO_SPAN_PAGES (StartVirtual, (ULONG_PTR)EndVirtual - (ULONG_PTR)StartVirtual); ASSERT (NumberOfPtes != 0); // // Ensure the proper native TB entries get flushed. // if (NumberOfPtes < MM_MAXIMUM_FLUSH_COUNT) { for (i = 0; i < NumberOfPtes; i += 1) { Virtual[i] = (PVOID)VirtualAddress; VirtualAddress += PAGE_SIZE; } VirtualAddress = (ULONG_PTR)StartVirtual; } LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); MiFillMemoryPte (StartAltPte, NumberOfAltPtes * sizeof(MMPTE), TempAltPte.u.Long); StartAltPte += NumberOfAltPtes; while (VirtualAddress <= (ULONG_PTR)EndVirtual) { StartAltPte = MiGetAltPteAddress(VirtualAddress); TempAltPte = *StartAltPte; i = 0; // // Note that this check must be made as the ATE fill above may not // have begun on a native page boundary and this scan always does. // while (TempAltPte.u.Long == StartAltPte->u.Long) { i += 1; if (i == SPLITS_PER_PAGE) { while (i != 0) { StartAltPte->u.Long = 0; StartAltPte -= 1; i -= 1; } break; } StartAltPte += 1; } VirtualAddress += PAGE_SIZE; } MiResetAccessBitForNativePtes (StartVirtual, EndVirtual, Process); // // Mark the native released pages as non-split so they re-synced // at MmX86Fault() time. NOTE: StartVirtual should be aligned on // the native page size before executing this code. // if (BYTE_OFFSET (OriginalStartVa) != 0) { if (MiArePreceding4kPagesAllocated (OriginalStartVa) != FALSE) { StartVirtual = PAGE_ALIGN ((ULONG_PTR)StartVirtual + PAGE_SIZE); } } EndVirtual = (PVOID) ((ULONG_PTR)EndVirtual | (PAGE_SIZE - 1)); if (BYTE_OFFSET (OriginalEndVa) != (PAGE_SIZE - 1)) { if (MiAreFollowing4kPagesAllocated (OriginalEndVa) != FALSE) { EndVirtual = (PVOID) ((ULONG_PTR)EndVirtual - PAGE_SIZE); } } if (StartVirtual < EndVirtual) { // // Initialize the bitmap inline for speed. // BitMap.SizeOfBitMap = MM_MAX_WOW64_ADDRESS >> PTI_SHIFT; BitMap.Buffer = Wow64Process->AltPermBitmap; RtlClearBits (&BitMap, (ULONG) MI_VA_TO_VPN (StartVirtual), (ULONG) (MI_VA_TO_VPN (EndVirtual) - MI_VA_TO_VPN (StartVirtual) + 1)); } if (NumberOfPtes < MM_MAXIMUM_FLUSH_COUNT) { KeFlushMultipleTb (FlushCount, &Virtual[0], TRUE, TRUE, NULL, ZeroPte.u.Flush); } else { KeFlushEntireTb (TRUE, TRUE); } UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); } VOID MiDecommitFor4kPage ( IN PVOID StartVirtual, IN PVOID EndVirtual, IN PEPROCESS Process ) /*++ Routine Description: This function decommits a region of pages within the virtual address space of a subject process. Arguments: StartVirtual - Supplies the start address of the region of pages to be decommitted. EndVirtual - Supplies the end address of the region of the pages to be decommitted. Process - Supplies a pointer to the process in which to decommit a a region of pages. Return Value: None. Environment: Address space mutex held at APC_LEVEL. --*/ { PMMPTE StartAltPte; PMMPTE EndAltPte; MMPTE TempAltPte; ULONG_PTR VirtualAddress; PWOW64_PROCESS Wow64Process; PVOID Virtual[MM_MAXIMUM_FLUSH_COUNT]; ULONG i; ULONG NumberOfPtes; Wow64Process = Process->Wow64Process; ASSERT(StartVirtual <= EndVirtual); StartAltPte = MiGetAltPteAddress (StartVirtual); EndAltPte = MiGetAltPteAddress (EndVirtual); NumberOfPtes = (ULONG) ADDRESS_AND_SIZE_TO_SPAN_PAGES (StartVirtual, (ULONG_PTR)EndVirtual - (ULONG_PTR)StartVirtual); ASSERT (NumberOfPtes != 0); // // Ensure the proper native TB entries get flushed. // if (NumberOfPtes < MM_MAXIMUM_FLUSH_COUNT) { VirtualAddress = (ULONG_PTR)StartVirtual; for (i = 0; i < NumberOfPtes; i += 1) { Virtual[i] = (PVOID)VirtualAddress; VirtualAddress += PAGE_SIZE; } } LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); while (StartAltPte <= EndAltPte) { TempAltPte.u.Long = StartAltPte->u.Long; TempAltPte.u.Alt.Commit = 0; TempAltPte.u.Alt.Accessed = 0; TempAltPte.u.Alt.FillZero = 1; // // Atomic PTE update. // StartAltPte->u.Long = TempAltPte.u.Long; StartAltPte += 1; } // // Flush the TB. // MiResetAccessBitForNativePtes (StartVirtual, EndVirtual, Process); if (NumberOfPtes < MM_MAXIMUM_FLUSH_COUNT) { KeFlushMultipleTb (NumberOfPtes, &Virtual[0], TRUE, TRUE, NULL, ZeroPte.u.Flush); } else { KeFlushEntireTb (TRUE, TRUE); } UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); } VOID MiDeleteFor4kPage ( IN PVOID StartVirtual, IN PVOID EndVirtual, IN PEPROCESS Process ) /*++ Routine Description: This function deletes a region of pages within the virtual address space of the subject process. Arguments: StartVirtual - Supplies the start address of the region of pages to be deleted. EndVirtual - Supplies the end address of the region of pages to be deleted. Process - Supplies a pointer to the process in which to delete the region of pages. Return Value: None. Environment: Kernel mode. Address creation mutex held at APC_LEVEL. --*/ { ULONG i; ULONG NumberOfPtes; RTL_BITMAP BitMap; PMMPTE EndAltPte; PMMPTE StartAltPte; ULONG_PTR VirtualAddress; PWOW64_PROCESS Wow64Process; PFN_NUMBER NumberOfAltPtes; PVOID Virtual[MM_MAXIMUM_FLUSH_COUNT]; Wow64Process = Process->Wow64Process; ASSERT(StartVirtual <= EndVirtual); StartAltPte = MiGetAltPteAddress (StartVirtual); EndAltPte = MiGetAltPteAddress (EndVirtual); NumberOfAltPtes = EndAltPte - StartAltPte + 1; VirtualAddress = (ULONG_PTR)StartVirtual; NumberOfPtes = (ULONG) ADDRESS_AND_SIZE_TO_SPAN_PAGES (StartVirtual, (ULONG_PTR)EndVirtual - (ULONG_PTR)StartVirtual); ASSERT (NumberOfPtes != 0); // // Ensure the proper native TB entries get flushed. // if (NumberOfPtes < MM_MAXIMUM_FLUSH_COUNT) { VirtualAddress = (ULONG_PTR)StartVirtual; for (i = 0; i < NumberOfPtes; i += 1) { Virtual[i] = (PVOID)VirtualAddress; VirtualAddress += PAGE_SIZE; } } LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); MiFillMemoryPte (StartAltPte, NumberOfAltPtes * sizeof(MMPTE), ZeroPte.u.Long); // // StartVirtual and EndVirtual are already aligned to the native // PAGE_SIZE so no need to readjust them before removing the split markers. // // Initialize the bitmap inline for speed. // BitMap.SizeOfBitMap = MM_MAX_WOW64_ADDRESS >> PTI_SHIFT; BitMap.Buffer = Wow64Process->AltPermBitmap; RtlClearBits (&BitMap, (ULONG) MI_VA_TO_VPN (StartVirtual), (ULONG) (MI_VA_TO_VPN (EndVirtual) - MI_VA_TO_VPN (StartVirtual) + 1)); MiResetAccessBitForNativePtes (StartVirtual, EndVirtual, Process); if (NumberOfPtes < MM_MAXIMUM_FLUSH_COUNT) { KeFlushMultipleTb (NumberOfPtes, &Virtual[0], TRUE, TRUE, NULL, ZeroPte.u.Flush); } else { KeFlushEntireTb (TRUE, TRUE); } UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); } LOGICAL MiIsSplitPage ( IN PVOID Virtual ) { PMMPTE AltPte; MMPTE PteContents; ULONG i; Virtual = PAGE_ALIGN(Virtual); AltPte = MiGetAltPteAddress(Virtual); PteContents = *AltPte; for (i = 0; i < SPLITS_PER_PAGE; i += 1) { if ((AltPte->u.Long != 0) && ((AltPte->u.Alt.Commit == 0) || (AltPte->u.Alt.Accessed == 0) || (AltPte->u.Alt.CopyOnWrite != 0) || (AltPte->u.Alt.PteIndirect != 0) || (AltPte->u.Alt.FillZero != 0))) { // // If it is a NoAccess, FillZero or Guard page, CopyOnWrite, // mark it as a split page. // return TRUE; } if (PteContents.u.Long != AltPte->u.Long) { // // If the next 4kb page is different from the 1st 4k page // the page is split. // return TRUE; } AltPte += 1; } return FALSE; } LOGICAL MiArePreceding4kPagesAllocated ( IN PVOID VirtualAddress ) /*++ Routine Description: This function checks to see if the specified virtual address contains any preceding 4k allocations within the native page. Arguments: VirtualAddress - Supplies the virtual address to check. Return Value: TRUE if the address has preceding 4k pages, FALSE if not. Environment: Kernel mode, address creation mutex held, APCs disabled. --*/ { PMMPTE AltPte; PMMPTE AltPteEnd; ASSERT (BYTE_OFFSET (VirtualAddress) != 0); AltPte = MiGetAltPteAddress (PAGE_ALIGN(VirtualAddress)); AltPteEnd = MiGetAltPteAddress (VirtualAddress); // // No need to hold the AltPte mutex as the address space mutex // is held which prevents allocation or deletion of the AltPte entries // inside the table. // while (AltPte != AltPteEnd) { if ((AltPte->u.Long == 0) || ((AltPte->u.Alt.NoAccess == 1) && (AltPte->u.Alt.Protection != MM_NOACCESS))) { // // The page's alternate PTE hasn't been allocated yet to the process // or it's marked no access. // NOTHING; } else { return TRUE; } AltPte += 1; } return FALSE; } LOGICAL MiAreFollowing4kPagesAllocated ( IN PVOID VirtualAddress ) /*++ Routine Description: This function checks to see if the specified virtual address contains any following 4k allocations within the native page. Arguments: VirtualAddress - Supplies the virtual address to check. Return Value: TRUE if the address has following 4k pages, FALSE if not. Environment: Kernel mode, address creation mutex held, APCs disabled. --*/ { PMMPTE AltPte; PMMPTE AltPteEnd; ASSERT (BYTE_OFFSET (VirtualAddress) != 0); AltPteEnd = MiGetAltPteAddress (PAGE_ALIGN ((ULONG_PTR)VirtualAddress + PAGE_SIZE)); AltPte = MiGetAltPteAddress (VirtualAddress) + 1; ASSERT (AltPte < AltPteEnd); // // No need to hold the AltPte mutex as the address space mutex // is held which prevents allocation or deletion of the AltPte entries // inside the table. // while (AltPte != AltPteEnd) { if ((AltPte->u.Long == 0) || ((AltPte->u.Alt.NoAccess == 1) && (AltPte->u.Alt.Protection != MM_NOACCESS))) { // // The page's alternate PTE hasn't been allocated yet to the process // or it's marked no access. // NOTHING; } else { return TRUE; } AltPte += 1; } return FALSE; } VOID MiResetAccessBitForNativePtes ( IN PVOID StartVirtual, IN PVOID EndVirtual, IN PEPROCESS Process ) /*++ Routine Description: This function resets the access bit of the native PTEs if the bitmap indicates it is a split page. Arguments: StartVirtual - Supplies the start address of the region of pages to be inspected. EndVirtual - Supplies the end address of the region of the pages to be inspected. Bitmap - Supplies the pointer to the process. Return Value: None. Environment: Alternate table mutex held at APC_LEVEL. --*/ { PMMPTE PointerPte; PMMPTE PointerPde; PMMPTE PointerPpe; LOGICAL FirstTime; ULONG Waited; PWOW64_PROCESS Wow64Process; Wow64Process = Process->Wow64Process; StartVirtual = PAGE_ALIGN(StartVirtual); PointerPte = MiGetPteAddress (StartVirtual); FirstTime = TRUE; LOCK_WS_UNSAFE (Process); while (StartVirtual <= EndVirtual) { if ((FirstTime == TRUE) || MiIsPteOnPdeBoundary (PointerPte)) { PointerPde = MiGetPteAddress (PointerPte); PointerPpe = MiGetPdeAddress (PointerPte); if (MiDoesPpeExistAndMakeValid (PointerPpe, Process, FALSE, &Waited) == FALSE) { // // This page directory parent entry is empty, // go to the next one. // PointerPpe += 1; PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe); PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); StartVirtual = MiGetVirtualAddressMappedByPte (PointerPte); continue; } if (MiDoesPdeExistAndMakeValid (PointerPde, Process, FALSE, &Waited) == FALSE) { // // This page directory entry is empty, // go to the next one. // PointerPde += 1; PointerPte = MiGetVirtualAddressMappedByPte(PointerPde); StartVirtual = MiGetVirtualAddressMappedByPte(PointerPte); continue; } FirstTime = FALSE; } if ((MI_CHECK_BIT(Wow64Process->AltPermBitmap, MI_VA_TO_VPN(StartVirtual))) && ((PointerPte->u.Hard.Valid != 0) && (PointerPte->u.Hard.Accessed != 0))) { PointerPte->u.Hard.Accessed = 0; } PointerPte += 1; StartVirtual = (PVOID)((ULONG_PTR)StartVirtual + PAGE_SIZE); } UNLOCK_WS_UNSAFE (Process); } VOID MiQueryRegionFor4kPage ( IN PVOID BaseAddress, IN PVOID EndAddress, IN OUT PSIZE_T RegionSize, IN OUT PULONG RegionState, IN OUT PULONG RegionProtect, IN PEPROCESS Process ) /*++ Routine Description: This function checks the size of the region which has the same memory state. Arguments: BaseAddress - Supplies the base address of the region of pages to be queried. EndAddress - Supplies the end of address of the region of pages to be queried. RegionSize - Supplies the original region size. Returns a region size for 4k pages if different. RegionState - Supplies the original region state. Returns a region state for 4k pages if different. RegionProtect - Supplies the original protection. Returns a protection for 4k pages if different. Process - Supplies a pointer to the process to be queried. Return Value: Returns the size of the region. Environment: Kernel mode. Address creation mutex held at APC_LEVEL. --*/ { PMMPTE AltPte; MMPTE AltContents; PVOID Va; PWOW64_PROCESS Wow64Process; // // If above the Wow64 max address, just return. // if (((UINT_PTR) BaseAddress >= MM_MAX_WOW64_ADDRESS) || ((UINT_PTR) EndAddress >= MM_MAX_WOW64_ADDRESS)) { return; } AltPte = MiGetAltPteAddress (BaseAddress); Wow64Process = Process->Wow64Process; LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); if (MI_CHECK_BIT (Wow64Process->AltPermBitmap, MI_VA_TO_VPN(BaseAddress)) == 0) { UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); return; } AltContents.u.Long = AltPte->u.Long; if (AltContents.u.Long == 0) { UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); return; } *RegionProtect = MI_CONVERT_FROM_PTE_PROTECTION(AltContents.u.Alt.Protection); if (AltContents.u.Alt.Commit != 0) { *RegionState = MEM_COMMIT; } else { if ((AltPte->u.Long == 0) || ((AltPte->u.Alt.NoAccess == 1) && (AltPte->u.Alt.Protection != MM_NOACCESS))) { *RegionState = MEM_FREE; *RegionProtect = PAGE_NOACCESS; } else { *RegionState = MEM_RESERVE; *RegionProtect = 0; } } Va = BaseAddress; while ((ULONG_PTR)Va < (ULONG_PTR)EndAddress) { Va = (PVOID)((ULONG_PTR)Va + PAGE_4K); AltPte += 1; if ((AltPte->u.Alt.Protection != AltContents.u.Alt.Protection) || (AltPte->u.Alt.Commit != AltContents.u.Alt.Commit)) { // // The state for this address does not match, calculate // size and return. // break; } } UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); *RegionSize = (SIZE_T)((ULONG_PTR)Va - (ULONG_PTR)BaseAddress); } ULONG MiQueryProtectionFor4kPage ( IN PVOID BaseAddress, IN PEPROCESS Process ) /*++ Routine Description: This function queries the protection for a specified 4k page. Arguments: BaseAddress - Supplies a base address of the 4k page. Process - Supplies a pointer to the relevant process. Return Value: Returns the protection of the 4k page. Environment: Kernel mode. Address creation mutex held at APC_LEVEL. --*/ { ULONG Protection; PMMPTE PointerAltPte; PWOW64_PROCESS Wow64Process; Wow64Process = Process->Wow64Process; PointerAltPte = MiGetAltPteAddress (BaseAddress); Protection = 0; LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); if (MI_CHECK_BIT(Wow64Process->AltPermBitmap, MI_VA_TO_VPN(BaseAddress)) != 0) { Protection = (ULONG)PointerAltPte->u.Alt.Protection; } UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); return Protection; } // // Note 1 is added to the charge to account for the page table page. // #define MI_ALTERNATE_PAGE_TABLE_CHARGE ((((MM_MAX_WOW64_ADDRESS >> PAGE_4K_SHIFT) * sizeof (MMPTE)) >> PAGE_SHIFT) + 1) NTSTATUS MiInitializeAlternateTable ( IN PEPROCESS Process ) /*++ Routine Description: This function initializes the alternate table for the specified process. Arguments: Process - Supplies a pointer to the process to initialize the alternate table for. Return Value: NTSTATUS. Environment: --*/ { PULONG AltTablePointer; PWOW64_PROCESS Wow64Process; // // Charge commitment now for the alternate PTE table pages as they will // need to be dynamically created later at fault time. // if (MiChargeCommitment (MI_ALTERNATE_PAGE_TABLE_CHARGE, NULL) == FALSE) { return STATUS_COMMITMENT_LIMIT; } AltTablePointer = (PULONG)ExAllocatePoolWithTag (NonPagedPool, (MM_MAX_WOW64_ADDRESS >> PTI_SHIFT)/8, 'AlmM'); if (AltTablePointer == NULL) { MiReturnCommitment (MI_ALTERNATE_PAGE_TABLE_CHARGE); return STATUS_NO_MEMORY; } RtlZeroMemory (AltTablePointer, (MM_MAX_WOW64_ADDRESS >> PTI_SHIFT)/8); Wow64Process = Process->Wow64Process; Wow64Process->AltPermBitmap = AltTablePointer; ExInitializeFastMutex (&Wow64Process->AlternateTableLock); return STATUS_SUCCESS; } VOID MiDuplicateAlternateTable ( IN PEPROCESS CurrentProcess, IN PEPROCESS ProcessToInitialize ) /*++ Routine Description: This function duplicates the alternate table bitmap and the alternate PTEs themselves for the specified process. Arguments: Process - Supplies a pointer to the process whose alternate information should be copied. ProcessToInitialize - Supplies a pointer to the target process who should receive the new alternate information. Return Value: None. Environment: Kernel mode, APCs disabled, working set and address space mutex and ForkInProgress flag held. --*/ { PVOID Source; KAPC_STATE ApcState; PMMPTE PointerPte; PMMPTE PointerAltPte; PMMPTE PointerPde; ULONG_PTR Va; ULONG i; ULONG j; ULONG Waited; KIRQL OldIrql; // // It's not necessary to acquire the alternate table mutex since both the // address space and ForkInProgress resources are held on entry. // RtlCopyMemory (ProcessToInitialize->Wow64Process->AltPermBitmap, CurrentProcess->Wow64Process->AltPermBitmap, (MM_MAX_WOW64_ADDRESS >> PTI_SHIFT)/8); // // Since the PPE for the Alternate Table is shared with hyperspace, // we can assume it is always present without performing // MiDoesPpeExistAndMakeValid(). // PointerPde = MiGetPdeAddress (ALT4KB_PERMISSION_TABLE_START); PointerPte = MiGetPteAddress (ALT4KB_PERMISSION_TABLE_START); Va = (ULONG_PTR)ALT4KB_PERMISSION_TABLE_START; LOCK_PFN (OldIrql); while (Va < (ULONG_PTR) ALT4KB_PERMISSION_TABLE_END) { while (MiDoesPdeExistAndMakeValid (PointerPde, CurrentProcess, TRUE, &Waited) == FALSE) { // // This page directory entry is empty, go to the next one. // PointerPde += 1; PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); Va = (ULONG_PTR)MiGetVirtualAddressMappedByPte (PointerPte); if (Va > (ULONG_PTR) ALT4KB_PERMISSION_TABLE_END) { UNLOCK_PFN (OldIrql); return; } } // // Duplicate any addresses that exist in the parent, bringing them // in from disk or materializing them as necessary. Note the // KSEG address is used for each parent address to avoid allocating // a system PTE for this mapping as this routine cannot fail (the // overall fork is too far along to tolerate a failure). // for (i = 0; i < PTE_PER_PAGE; i += 1) { if (PointerPte->u.Long != 0) { if (MiDoesPdeExistAndMakeValid (PointerPte, CurrentProcess, TRUE, &Waited) == TRUE) { UNLOCK_PFN (OldIrql); ASSERT (PointerPte->u.Hard.Valid == 1); Source = KSEG_ADDRESS (PointerPte->u.Hard.PageFrameNumber); KeStackAttachProcess (&ProcessToInitialize->Pcb, &ApcState); RtlCopyMemory ((PVOID)Va, Source, PAGE_SIZE); // // Eliminate any bits that should NOT be copied. // PointerAltPte = (PMMPTE) Va; for (j = 0; j < PTE_PER_PAGE; j += 1) { if (PointerAltPte->u.Alt.InPageInProgress == 1) { PointerAltPte->u.Alt.InPageInProgress = 0; } PointerAltPte += 1; } KeUnstackDetachProcess (&ApcState); LOCK_PFN (OldIrql); } } Va += PAGE_SIZE; PointerPte += 1; } PointerPde += 1; PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); Va = (ULONG_PTR)MiGetVirtualAddressMappedByPte (PointerPte); } UNLOCK_PFN (OldIrql); // // Initialize the child's 32-bit PEB to be the same as the parent's. // ProcessToInitialize->Wow64Process->Wow64 = CurrentProcess->Wow64Process->Wow64; return; } VOID MiDeleteAlternateTable ( IN PEPROCESS Process ) /*++ Routine Description: This function deletes the alternate table for the specified process. Arguments: Process - Supplies a pointer to the process to delete the alternate table for. Return Value: None. Environment: Kernel mode, APCs disabled, working set mutex held. --*/ { PMMPTE PointerPte; PMMPTE PointerPde; ULONG_PTR Va; ULONG_PTR TempVa; ULONG i; ULONG Waited; MMPTE_FLUSH_LIST PteFlushList; PWOW64_PROCESS Wow64Process; KIRQL OldIrql; Wow64Process = Process->Wow64Process; if (Wow64Process->AltPermBitmap == NULL) { // // This is only NULL (and Wow64Process non-NULL) if a memory allocation // failed during process creation. // return; } // // Since the PPE for the Alternate Table is shared with hyperspace, // we can assume it is always present without performing // MiDoesPpeExistAndMakeValid(). // PointerPde = MiGetPdeAddress (ALT4KB_PERMISSION_TABLE_START); PointerPte = MiGetPteAddress (ALT4KB_PERMISSION_TABLE_START); Va = (ULONG_PTR)ALT4KB_PERMISSION_TABLE_START; PteFlushList.Count = 0; LOCK_PFN (OldIrql); while (Va < (ULONG_PTR) ALT4KB_PERMISSION_TABLE_END) { while (MiDoesPdeExistAndMakeValid (PointerPde, Process, TRUE, &Waited) == FALSE) { // // This page directory entry is empty, go to the next one. // PointerPde += 1; PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); Va = (ULONG_PTR)MiGetVirtualAddressMappedByPte (PointerPte); if (Va > (ULONG_PTR) ALT4KB_PERMISSION_TABLE_END) { goto delete_end; } } // // Delete the PTE entries mapping the Alternate Table. // TempVa = Va; for (i = 0; i < PTE_PER_PAGE; i += 1) { if (PointerPte->u.Long != 0) { if (IS_PTE_NOT_DEMAND_ZERO (*PointerPte)) { MiDeletePte (PointerPte, (PVOID)TempVa, TRUE, Process, NULL, &PteFlushList); } else { *PointerPte = ZeroPte; } } TempVa += PAGE_4K; PointerPte += 1; } // // Delete the PDE entry mapping the Alternate Table. // TempVa = (ULONG_PTR)MiGetVirtualAddressMappedByPte(PointerPde); MiDeletePte (PointerPde, (PVOID)TempVa, TRUE, Process, NULL, &PteFlushList); MiFlushPteList (&PteFlushList, FALSE, ZeroPte); PointerPde += 1; PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); Va = (ULONG_PTR)MiGetVirtualAddressMappedByPte (PointerPte); } delete_end: MiFlushPteList (&PteFlushList, FALSE, ZeroPte); UNLOCK_PFN (OldIrql); ExFreePool (Wow64Process->AltPermBitmap); Wow64Process->AltPermBitmap = NULL; MiReturnCommitment (MI_ALTERNATE_PAGE_TABLE_CHARGE); return; } VOID MiFillZeroFor4kPage ( IN PVOID VirtualAddress, IN PEPROCESS Process ) /*++ Routine Description: This function zeroes the specified 4k page. Arguments: VirtualAddress - Supplies the base address of the 4k page. Process - Supplies the relevant process. Return Value: None. Environment: Alternate PTE mutex held. --*/ { PVOID BaseAddress; PMMPTE PointerAltPte; PMMPTE PointerPde; PMMPTE PointerPpe; PMMPTE PointerPte; MMPTE TempAltContents; MMPTE PteContents; ULONG Waited; PWOW64_PROCESS Wow64Process; Wow64Process = Process->Wow64Process; PointerAltPte = MiGetAltPteAddress (VirtualAddress); PointerPte = MiGetPteAddress(VirtualAddress); PointerPde = MiGetPdeAddress(VirtualAddress); PointerPpe = MiGetPpeAddress(VirtualAddress); do { if (PointerAltPte->u.Alt.FillZero == 0) { // // Another thread has already completed the zero operation. // return; } // // Make the PPE and PDE valid as well as the // page table for the original PTE. This guarantees // TB forward progress for the TB indirect fault. // LOCK_WS_UNSAFE (Process); if (MiDoesPpeExistAndMakeValid (PointerPpe, Process, FALSE, &Waited) == FALSE) { PteContents.u.Long = 0; } else if (MiDoesPdeExistAndMakeValid (PointerPde, Process, FALSE, &Waited) == FALSE) { PteContents.u.Long = 0; } else { // // Now it is safe to read PointerPte. // PteContents = *PointerPte; } // // The alternate PTE may have been trimmed during the period prior to // the working set mutex being acquired or when it was released prior // to being reacquired. // if (MmIsAddressValid (PointerAltPte) == TRUE) { break; } UNLOCK_WS_UNSAFE (Process); } while (TRUE); TempAltContents.u.Long = PointerAltPte->u.Long; if (PteContents.u.Hard.Valid != 0) { BaseAddress = KSEG_ADDRESS(PteContents.u.Hard.PageFrameNumber); BaseAddress = (PVOID)((ULONG_PTR)BaseAddress + ((ULONG_PTR)PAGE_4K_ALIGN(VirtualAddress) & (PAGE_SIZE-1))); RtlZeroMemory(BaseAddress, PAGE_4K); UNLOCK_WS_UNSAFE (Process); TempAltContents.u.Alt.FillZero = 0; TempAltContents.u.Alt.Accessed = 1; } else { UNLOCK_WS_UNSAFE (Process); TempAltContents.u.Alt.Accessed = 0; } PointerAltPte->u.Long = TempAltContents.u.Long; } VOID MiRemoveAliasedVadsApc ( IN PKAPC Apc, OUT PKNORMAL_ROUTINE *NormalRoutine, IN OUT PVOID NormalContext, IN OUT PVOID *SystemArgument1, IN OUT PVOID *SystemArgument2 ) { ULONG i; PALIAS_VAD_INFO2 AliasBase; PEPROCESS Process; PALIAS_VAD_INFO AliasInformation; UNREFERENCED_PARAMETER (Apc); UNREFERENCED_PARAMETER (NormalContext); UNREFERENCED_PARAMETER (SystemArgument2); Process = PsGetCurrentProcess (); AliasInformation = (PALIAS_VAD_INFO) *SystemArgument1; AliasBase = (PALIAS_VAD_INFO2)(AliasInformation + 1); LOCK_ADDRESS_SPACE (Process); for (i = 0; i < AliasInformation->NumberOfEntries; i += 1) { ASSERT (AliasBase->BaseAddress < _2gb); MiUnsecureVirtualMemory (AliasBase->SecureHandle, TRUE); MiUnmapViewOfSection (Process, (PVOID) (ULONG_PTR)AliasBase->BaseAddress, TRUE); AliasBase += 1; } UNLOCK_ADDRESS_SPACE (Process); ExFreePool (AliasInformation); // // Clear the normal routine so this routine doesn't get called again // for the same request. // *NormalRoutine = NULL; } VOID MiRemoveAliasedVads ( IN PEPROCESS Process, IN PMMVAD Vad ) /*++ Routine Description: This function removes all aliased VADs spawned earlier from the argument VAD. Arguments: Process - Supplies the EPROCESS pointer to the current process. Vad - Supplies a pointer to the VAD describing the range being removed. Return Value: None. Environment: Kernel mode, address creation and working set mutexes held, APCs disabled. --*/ { PALIAS_VAD_INFO AliasInformation; ASSERT (Process->Wow64Process != NULL); AliasInformation = ((PMMVAD_LONG)Vad)->AliasInformation; ASSERT (AliasInformation != NULL); if ((Process->Flags & PS_PROCESS_FLAGS_VM_DELETED) == 0) { // // This process is still alive so queue an APC to delete each aliased // VAD. This is because the deletion must also get rid of page table // commitment which requires that it search (and modify) VAD trees, // etc - but the address space mutex is already held and the caller // is not generally prepared for all this to change at this point. // KeInitializeApc (&AliasInformation->Apc, &PsGetCurrentThread()->Tcb, OriginalApcEnvironment, (PKKERNEL_ROUTINE) MiRemoveAliasedVadsApc, NULL, (PKNORMAL_ROUTINE) MiRemoveAliasedVadsApc, KernelMode, (PVOID) AliasInformation); KeInsertQueueApc (&AliasInformation->Apc, AliasInformation, NULL, 0); } else { // // This process is exiting so all the VADs are being rundown anyway. // Just free the pool and let normal rundown handle the aliases. // ExFreePool (AliasInformation); } } PVOID MiDuplicateAliasVadList ( IN PMMVAD Vad ) { SIZE_T AliasInfoSize; PALIAS_VAD_INFO AliasInfo; PALIAS_VAD_INFO NewAliasInfo; AliasInfo = ((PMMVAD_LONG)Vad)->AliasInformation; ASSERT (AliasInfo != NULL); AliasInfoSize = sizeof (ALIAS_VAD_INFO) + AliasInfo->MaximumEntries * sizeof (ALIAS_VAD_INFO2); NewAliasInfo = ExAllocatePoolWithTag (NonPagedPool, AliasInfoSize, 'AdaV'); if (NewAliasInfo != NULL) { RtlCopyMemory (NewAliasInfo, AliasInfo, AliasInfoSize); } return NewAliasInfo; } #define ALIAS_VAD_INCREMENT 4 NTSTATUS MiSetCopyPagesFor4kPage ( IN PEPROCESS Process, IN PMMVAD Vad, IN OUT PVOID StartingAddress, IN OUT PVOID EndingAddress, IN ULONG NewProtection, OUT PMMVAD *CallerNewVad ) /*++ Routine Description: This function creates another map for the existing mapped view space and gives it copy-on-write protection. This is called when SetProtectionOnSection() tries to change the protection from non-copy-on-write to copy-on-write. Since a large native page cannot be split to shared and copy-on-write 4kb pages, references to the copy-on-write page(s) need to be fixed to reference the new mapped view space and this is done through the smart TB handler and the alternate page table entries. Arguments: Process - Supplies the EPROCESS pointer to the current process. Vad - Supplies a pointer to the VAD describing the range to protect. StartingAddress - Supplies a pointer to the starting address to protect. EndingAddress - Supplies a pointer to the ending address to the protect. NewProtect - Supplies the new protection to set. CallerNewVad - Returns the new VAD the caller should use for this range. Return Value: NTSTATUS. Environment: Kernel mode, address creation mutex held, APCs disabled. --*/ { PALIAS_VAD_INFO2 AliasBase; HANDLE Handle; PMMVAD_LONG NewVad; SIZE_T AliasInfoSize; PALIAS_VAD_INFO AliasInfo; PALIAS_VAD_INFO NewAliasInfo; LARGE_INTEGER SectionOffset; SIZE_T CapturedViewSize; PVOID CapturedBase; PVOID Va; PVOID VaEnd; PVOID Alias; PMMPTE PointerPte; PMMPTE AltPte; MMPTE AltPteContents; LOGICAL AliasReferenced; SECTION Section; PCONTROL_AREA ControlArea; NTSTATUS status; PWOW64_PROCESS Wow64Process; ULONGLONG ProtectionMask; ULONGLONG ProtectionMaskNotCopy; ULONG NewProtectNotCopy; AliasReferenced = FALSE; StartingAddress = PAGE_ALIGN(StartingAddress); EndingAddress = (PVOID)((ULONG_PTR)PAGE_ALIGN(EndingAddress) + PAGE_SIZE - 1); SectionOffset.QuadPart = (ULONG_PTR)MI_64K_ALIGN((ULONG_PTR)StartingAddress - (ULONG_PTR)(Vad->StartingVpn << PAGE_SHIFT)); CapturedBase = NULL; Va = MI_VPN_TO_VA (Vad->StartingVpn); VaEnd = MI_VPN_TO_VA_ENDING (Vad->EndingVpn); CapturedViewSize = (ULONG_PTR)VaEnd - (ULONG_PTR)Va + 1L; ControlArea = Vad->ControlArea; RtlZeroMemory ((PVOID)&Section, sizeof(Section)); status = MiMapViewOfDataSection (ControlArea, Process, &CapturedBase, &SectionOffset, &CapturedViewSize, &Section, ViewShare, (ULONG)Vad->u.VadFlags.Protection, 0, 0, 0); if (!NT_SUCCESS (status)) { return status; } Handle = MiSecureVirtualMemory (CapturedBase, CapturedViewSize, PAGE_READONLY, TRUE); if (Handle == NULL) { MiUnmapViewOfSection (Process, CapturedBase, FALSE); return STATUS_INSUFFICIENT_RESOURCES; } // // If the original VAD is a short or regular VAD, it needs to be // reallocated as a large VAD. Note that a short VAD that was // previously converted to a long VAD here will still be marked // as private memory, thus to handle this case the NoChange bit // must also be tested. // if (((Vad->u.VadFlags.PrivateMemory) && (Vad->u.VadFlags.NoChange == 0)) || (Vad->u2.VadFlags2.LongVad == 0)) { if (Vad->u.VadFlags.PrivateMemory == 0) { ASSERT (Vad->u2.VadFlags2.OneSecured == 0); ASSERT (Vad->u2.VadFlags2.MultipleSecured == 0); } AliasInfoSize = sizeof (ALIAS_VAD_INFO) + ALIAS_VAD_INCREMENT * sizeof (ALIAS_VAD_INFO2); AliasInfo = ExAllocatePoolWithTag (NonPagedPool, AliasInfoSize, 'AdaV'); if (AliasInfo == NULL) { MiUnsecureVirtualMemory (Handle, TRUE); MiUnmapViewOfSection (Process, CapturedBase, TRUE); return STATUS_INSUFFICIENT_RESOURCES; } AliasInfo->NumberOfEntries = 0; AliasInfo->MaximumEntries = ALIAS_VAD_INCREMENT; NewVad = ExAllocatePoolWithTag (NonPagedPool, sizeof(MMVAD_LONG), 'ldaV'); if (NewVad == NULL) { ExFreePool (AliasInfo); MiUnsecureVirtualMemory (Handle, TRUE); MiUnmapViewOfSection (Process, CapturedBase, TRUE); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory (NewVad, sizeof(MMVAD_LONG)); if (Vad->u.VadFlags.PrivateMemory) { RtlCopyMemory (NewVad, Vad, sizeof(MMVAD_SHORT)); } else { RtlCopyMemory (NewVad, Vad, sizeof(MMVAD)); } NewVad->u2.VadFlags2.LongVad = 1; NewVad->AliasInformation = AliasInfo; // // Replace the current VAD with this expanded VAD. // LOCK_WS_UNSAFE (Process); if (Vad->Parent) { if (Vad->Parent->RightChild == Vad) { Vad->Parent->RightChild = (PMMVAD) NewVad; } else { ASSERT (Vad->Parent->LeftChild == Vad); Vad->Parent->LeftChild = (PMMVAD) NewVad; } } else { Process->VadRoot = NewVad; } if (Vad->LeftChild) { Vad->LeftChild->Parent = (PMMVAD) NewVad; } if (Vad->RightChild) { Vad->RightChild->Parent = (PMMVAD) NewVad; } if (Process->VadHint == Vad) { Process->VadHint = (PMMVAD) NewVad; } if (Process->VadFreeHint == Vad) { Process->VadFreeHint = (PMMVAD) NewVad; } if ((Vad->u.VadFlags.PhysicalMapping == 1) || (Vad->u.VadFlags.WriteWatch == 1)) { MiPhysicalViewAdjuster (Process, Vad, (PMMVAD) NewVad); } UNLOCK_WS_UNSAFE (Process); ExFreePool (Vad); Vad = (PMMVAD) NewVad; } else { AliasInfo = (PALIAS_VAD_INFO) ((PMMVAD_LONG)Vad)->AliasInformation; if (AliasInfo == NULL) { AliasInfoSize = sizeof (ALIAS_VAD_INFO) + ALIAS_VAD_INCREMENT * sizeof (ALIAS_VAD_INFO2); } else if (AliasInfo->NumberOfEntries >= AliasInfo->MaximumEntries) { AliasInfoSize = sizeof (ALIAS_VAD_INFO) + (AliasInfo->MaximumEntries + ALIAS_VAD_INCREMENT) * sizeof (ALIAS_VAD_INFO2); } else { AliasInfoSize = 0; } if (AliasInfoSize != 0) { NewAliasInfo = ExAllocatePoolWithTag (NonPagedPool, AliasInfoSize, 'AdaV'); if (NewAliasInfo == NULL) { MiUnsecureVirtualMemory (Handle, TRUE); MiUnmapViewOfSection (Process, CapturedBase, TRUE); return STATUS_INSUFFICIENT_RESOURCES; } if (AliasInfo != NULL) { RtlCopyMemory (NewAliasInfo, AliasInfo, AliasInfoSize - ALIAS_VAD_INCREMENT * sizeof (ALIAS_VAD_INFO2)); NewAliasInfo->MaximumEntries += ALIAS_VAD_INCREMENT; ExFreePool (AliasInfo); } else { NewAliasInfo->NumberOfEntries = 0; NewAliasInfo->MaximumEntries = ALIAS_VAD_INCREMENT; } AliasInfo = NewAliasInfo; } } *CallerNewVad = Vad; Va = StartingAddress; VaEnd = EndingAddress; Alias = (PVOID)((ULONG_PTR)CapturedBase + ((ULONG_PTR)StartingAddress & (X64K - 1))); ProtectionMask = MiMakeProtectionAteMask (NewProtection); NewProtectNotCopy = NewProtection & ~MM_PROTECTION_COPY_MASK; ProtectionMaskNotCopy = MiMakeProtectionAteMask (NewProtectNotCopy); Wow64Process = Process->Wow64Process; AltPte = MiGetAltPteAddress (Va); LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); while (Va <= VaEnd) { PointerPte = MiGetPteAddress (Alias); AltPteContents.u.Long = AltPte->u.Long; // // If this address is NOT copy-on-write, AND it is not already // redirected through an indirect entry, then redirect it now to // the alias VAD which points at the original section. // if ((AltPteContents.u.Alt.CopyOnWrite == 0) && (AltPteContents.u.Alt.PteIndirect == 0)) { AltPteContents.u.Alt.PteOffset = (ULONG_PTR)PointerPte - PTE_UBASE; AltPteContents.u.Alt.PteIndirect = 1; AltPte->u.Long = AltPteContents.u.Long; AliasReferenced = TRUE; } Va = (PVOID)((ULONG_PTR)Va + PAGE_4K); Alias = (PVOID)((ULONG_PTR)Alias + PAGE_4K); AltPte += 1; } UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); ASSERT (AliasInfo->NumberOfEntries < AliasInfo->MaximumEntries); if (AliasReferenced == TRUE) { // // The alias view of the shared section was referenced so chain it so // the alias view can be : // // a) easily duplicated if the process subsequently forks. // // AND // // b) deleted when/if the original VAD is deleted later. // AliasBase = (PALIAS_VAD_INFO2)(AliasInfo + 1); AliasBase += AliasInfo->NumberOfEntries; ASSERT (CapturedBase < (PVOID)(ULONG_PTR)_2gb); AliasBase->BaseAddress = (ULONG)(ULONG_PTR)CapturedBase; AliasBase->SecureHandle = Handle; AliasInfo->NumberOfEntries += 1; } else { // // The alias view of the shared section wasn't referenced, delete it. // MiUnsecureVirtualMemory (Handle, TRUE); MiUnmapViewOfSection (Process, CapturedBase, TRUE); } PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_WOW64_SPLIT_PAGES); return STATUS_SUCCESS; } VOID MiLockFor4kPage ( IN PVOID CapturedBase, IN SIZE_T CapturedRegionSize, IN PEPROCESS Process ) /*++ Routine Description: This function adds the page locked attribute to the alternate table entries. Arguments: CapturedBase - Supplies the base address to be locked. CapturedRegionSize - Supplies the size of the region to be locked. Process - Supplies a pointer to the process object. Return Value: None. Environment: Kernel mode, address creation mutex held. --*/ { PWOW64_PROCESS Wow64Process; PVOID EndingAddress; PMMPTE StartAltPte; PMMPTE EndAltPte; Wow64Process = Process->Wow64Process; EndingAddress = (PVOID)((ULONG_PTR)CapturedBase + CapturedRegionSize - 1); StartAltPte = MiGetAltPteAddress(CapturedBase); EndAltPte = MiGetAltPteAddress(EndingAddress); LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); while (StartAltPte <= EndAltPte) { StartAltPte->u.Alt.Lock = 1; StartAltPte += 1; } UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); } NTSTATUS MiUnlockFor4kPage ( IN PVOID CapturedBase, IN SIZE_T CapturedRegionSize, IN PEPROCESS Process ) /*++ Routine Description: This function removes the page locked attribute from the alternate table entries. Arguments: CapturedBase - Supplies the base address to be unlocked. CapturedRegionSize - Supplies the size of the region to be unlocked. Process - Supplies a pointer to the process object. Return Value: NTSTATUS. Environment: Kernel mode, address creation and working set mutexes held. Note this routine releases and reacquires the working set mutex !!! --*/ { PMMPTE StartAltPte; PMMPTE EndAltPte; PWOW64_PROCESS Wow64Process; PVOID EndingAddress; NTSTATUS Status; UNLOCK_WS_UNSAFE (Process); Status = STATUS_SUCCESS; Wow64Process = Process->Wow64Process; EndingAddress = (PVOID)((ULONG_PTR)CapturedBase + CapturedRegionSize - 1); StartAltPte = MiGetAltPteAddress (CapturedBase); EndAltPte = MiGetAltPteAddress (EndingAddress); LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); while (StartAltPte <= EndAltPte) { if (StartAltPte->u.Alt.Lock == 0) { Status = STATUS_NOT_LOCKED; goto StatusReturn; } StartAltPte += 1; } StartAltPte = MiGetAltPteAddress (CapturedBase); while (StartAltPte <= EndAltPte) { StartAltPte->u.Alt.Lock = 0; StartAltPte += 1; } StatusReturn: UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); LOCK_WS_UNSAFE (Process); return Status; } LOGICAL MiShouldBeUnlockedFor4kPage ( IN PVOID VirtualAddress, IN PEPROCESS Process ) /*++ Routine Description: This function examines whether the pape should be unlocked. Arguments: VirtualAddress - Supplies the virtual address to be examined. Process - Supplies a pointer to the process object. Return Value: None. Environment: Kernel mode, address creation and working set mutexes held. Note this routine releases and reacquires the working set mutex !!! --*/ { PMMPTE StartAltPte; PMMPTE EndAltPte; PWOW64_PROCESS Wow64Process; PVOID VirtualAligned; PVOID EndingAddress; LOGICAL PageUnlocked; UNLOCK_WS_UNSAFE (Process); PageUnlocked = TRUE; Wow64Process = Process->Wow64Process; VirtualAligned = PAGE_ALIGN(VirtualAddress); EndingAddress = (PVOID)((ULONG_PTR)VirtualAligned + PAGE_SIZE - 1); StartAltPte = MiGetAltPteAddress (VirtualAligned); EndAltPte = MiGetAltPteAddress (EndingAddress); LOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); while (StartAltPte <= EndAltPte) { if (StartAltPte->u.Alt.Lock != 0) { PageUnlocked = FALSE; } StartAltPte += 1; } UNLOCK_ALTERNATE_TABLE_UNSAFE (Wow64Process); LOCK_WS_UNSAFE (Process); return PageUnlocked; } VOID MiCopyOnWriteFor4kPage ( IN PVOID VirtualAddress ) /*++ Routine Description: This function changes the protection of the alternate pages for a copy on write native page. Arguments: VirtualAddress - Supplies the virtual address which caused the copy-on-write fault. Return Value: None. Environment: Kernel mode, alternate table mutex held. --*/ { PMMPTE PointerAltPte; MMPTE TempAltPte; ULONG i; PointerAltPte = MiGetAltPteAddress (PAGE_ALIGN(VirtualAddress)); for (i = 0; i < SPLITS_PER_PAGE; i += 1) { TempAltPte.u.Long = PointerAltPte->u.Long; if ((TempAltPte.u.Alt.Commit != 0) && (TempAltPte.u.Alt.CopyOnWrite != 0)) { TempAltPte.u.Alt.CopyOnWrite = 0; TempAltPte.u.Alt.Private = 1; TempAltPte.u.Hard.Write = 1; TempAltPte.u.Alt.Protection = MI_MAKE_PROTECT_NOT_WRITE_COPY(PointerAltPte->u.Alt.Protection); PointerAltPte->u.Long = TempAltPte.u.Long; } // // Atomically update the PTE. // PointerAltPte += 1; } } ULONG MiMakeProtectForNativePage ( IN PVOID VirtualAddress, IN ULONG NewProtect, IN PEPROCESS Process ) /*++ Routine Description: This function makes a page protection mask for native pages. Arguments: VirtualAddress - Supplies the virtual address for the protection mask. NewProtect - Supplies the original protection. Process - Supplies a pointer to the process object. Return Value: None. Environment: Kernel mode. --*/ { PWOW64_PROCESS Wow64Process; Wow64Process = Process->Wow64Process; if (MI_CHECK_BIT(Wow64Process->AltPermBitmap, MI_VA_TO_VPN(VirtualAddress)) != 0) { if (NewProtect & PAGE_NOACCESS) { NewProtect &= ~PAGE_NOACCESS; NewProtect |= PAGE_EXECUTE_READWRITE; } if (NewProtect & PAGE_READONLY) { NewProtect &= ~PAGE_READONLY; NewProtect |= PAGE_EXECUTE_READWRITE; } if (NewProtect & PAGE_EXECUTE) { NewProtect &= ~PAGE_EXECUTE; NewProtect |= PAGE_EXECUTE_READWRITE; } if (NewProtect & PAGE_EXECUTE_READ) { NewProtect &= ~PAGE_EXECUTE_READ; NewProtect |= PAGE_EXECUTE_READWRITE; } // // Remove PAGE_GUARD as it is emulated by the Alternate Table. // if (NewProtect & PAGE_GUARD) { NewProtect &= ~PAGE_GUARD; } } return NewProtect; } VOID MiCheckVirtualAddressFor4kPage ( IN PVOID VirtualAddress, IN PEPROCESS Process ) /*++ Routine Description: This function checks the virtual address to see if it is a 4K page. Arguments: VirtualAddress - Supplies the virtual address to check. Process - Supplies a pointer to the process object. Return Value: None. Environment: Kernel mode, alternate table mutex held. --*/ { PMMPTE ProtoPte; PMMPTE PointerAltPte; PMMPTE PointerPde; MMPTE AltPteContents; ULONG OriginalProtection; ULONGLONG ProtectionMaskOriginal; PWOW64_PROCESS Wow64Process; KIRQL OldIrql; PMMPFN Pfn1; ULONG i; ULONG_PTR StartVpn; Wow64Process = Process->Wow64Process; LOCK_WS_UNSAFE (Process); ProtoPte = MiCheckVirtualAddress (VirtualAddress, &OriginalProtection); if (OriginalProtection == MM_UNKNOWN_PROTECTION) { if (!MI_IS_PHYSICAL_ADDRESS(ProtoPte)) { PointerPde = MiGetPteAddress (ProtoPte); LOCK_PFN (OldIrql); if (PointerPde->u.Hard.Valid == 0) { MiMakeSystemAddressValidPfn (ProtoPte); } Pfn1 = MI_PFN_ELEMENT (PointerPde->u.Hard.PageFrameNumber); MI_ADD_LOCKED_PAGE_CHARGE(Pfn1, 32); Pfn1->u3.e2.ReferenceCount += 1; ASSERT (Pfn1->u3.e2.ReferenceCount > 1); UNLOCK_PFN (OldIrql); } else { Pfn1 = NULL; } OriginalProtection = MiMakeProtectionMask(MiGetPageProtection(ProtoPte, Process, FALSE)); // // Unlock the page containing the prototype PTEs. // if (Pfn1 != NULL) { ASSERT (!MI_IS_PHYSICAL_ADDRESS(ProtoPte)); LOCK_PFN (OldIrql); ASSERT (Pfn1->u3.e2.ReferenceCount > 1); MI_REMOVE_LOCKED_PAGE_CHARGE(Pfn1, 33); Pfn1->u3.e2.ReferenceCount -= 1; UNLOCK_PFN (OldIrql); } if (OriginalProtection == MM_INVALID_PROTECTION) { UNLOCK_WS_UNSAFE (Process); return; } } UNLOCK_WS_UNSAFE (Process); ProtectionMaskOriginal = MiMakeProtectionAteMask (OriginalProtection); ProtectionMaskOriginal |= MM_ATE_COMMIT; PointerAltPte = MiGetAltPteAddress (PAGE_ALIGN(VirtualAddress)); AltPteContents.u.Long = ProtectionMaskOriginal; AltPteContents.u.Alt.Protection = OriginalProtection; for (i = 0; i < SPLITS_PER_PAGE; i += 1) { // // Update the alternate PTEs. // PointerAltPte->u.Long = AltPteContents.u.Long; PointerAltPte += 1; } // // Update the bitmap. // StartVpn = MI_VA_TO_VPN (VirtualAddress); MI_SET_BIT (Wow64Process->AltPermBitmap, StartVpn); } LOGICAL MiIsNativeGuardPage ( IN PVOID VirtualAddress ) /*++ Routine Description: This function checks to see if any of the AltPtes has the Guard bit set. Arguments: VirtualAddress - Supplies the virtual address to check. Return Value: None. Environment: Kernel mode, alternate table mutex held. --*/ { ULONG i; PMMPTE PointerAltPte; PointerAltPte = MiGetAltPteAddress (PAGE_ALIGN(VirtualAddress)); for (i = 0; i < SPLITS_PER_PAGE; i += 1) { if ((PointerAltPte->u.Alt.Protection & MM_GUARD_PAGE) != 0) { return TRUE; } PointerAltPte += 1; } return FALSE; } VOID MiSetNativePteProtection ( PVOID VirtualAddress, ULONGLONG NewPteProtection, LOGICAL PageIsSplit, PEPROCESS CurrentProcess ) { MMPTE PteContents; MMPTE TempPte; PMMPTE PointerPte; PMMPTE PointerPde; PMMPTE PointerPpe; ULONG Waited; PointerPte = MiGetPteAddress (VirtualAddress); PointerPde = MiGetPdeAddress (VirtualAddress); PointerPpe = MiGetPpeAddress (VirtualAddress); // // Block APCs and acquire the working set lock. // LOCK_WS (CurrentProcess); // // Make the PPE and PDE exist and valid. // if (MiDoesPpeExistAndMakeValid (PointerPpe, CurrentProcess, FALSE, &Waited) == FALSE) { UNLOCK_WS (CurrentProcess); return; } if (MiDoesPdeExistAndMakeValid (PointerPde, CurrentProcess, FALSE, &Waited) == FALSE) { UNLOCK_WS (CurrentProcess); return; } // // Now it is safe to read PointerPte. // PteContents = *PointerPte; // // Check to see if the protection for the native page should be set // and if the access bit of the PTE should be set. // if (PteContents.u.Hard.Valid != 0) { TempPte = PteContents; // // Perform PTE protection mask corrections. // TempPte.u.Long |= NewPteProtection; if (PteContents.u.Hard.Accessed == 0) { TempPte.u.Hard.Accessed = 1; if (PageIsSplit == TRUE) { TempPte.u.Hard.Cache = MM_PTE_CACHE_RESERVED; } } MI_WRITE_VALID_PTE_NEW_PROTECTION(PointerPte, TempPte); } UNLOCK_WS (CurrentProcess); } #endif