/*++ Copyright (c) 1989 Microsoft Corporation Module Name: protect.c Abstract: This module contains the routines which implement the NtProtectVirtualMemory service. Author: Lou Perazzoli (loup) 18-Aug-1989 Revision History: --*/ #include "mi.h" #if DBG PEPROCESS MmWatchProcess; VOID MmFooBar(VOID); #endif // DBG HARDWARE_PTE MiFlushTbAndCapture( IN PMMPTE PtePointer, IN HARDWARE_PTE TempPte, IN PMMPFN Pfn1 ); ULONG MiSetProtectionOnTransitionPte ( IN PMMPTE PointerPte, IN ULONG ProtectionMask ); MMPTE MiCaptureSystemPte ( IN PMMPTE PointerProtoPte, IN PEPROCESS Process ); extern CCHAR MmReadWrite[32]; #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,NtProtectVirtualMemory) #pragma alloc_text(PAGE,MiProtectVirtualMemory) #pragma alloc_text(PAGE,MiSetProtectionOnSection) #pragma alloc_text(PAGE,MiGetPageProtection) #pragma alloc_text(PAGE,MiChangeNoAccessForkPte) #endif NTSTATUS NtProtectVirtualMemory( IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, IN OUT PULONG RegionSize, IN ULONG NewProtect, OUT PULONG OldProtect ) /*++ Routine Description: This routine changes the protection on a region of committed pages within the virtual address space of the subject process. Setting the protection on a ragne of pages causes the old protection to be replaced by the specified protection value. Arguments: ProcessHandle - An open handle to a process object. BaseAddress - The base address of the region of pages whose protection is to be changed. This value is rounded down to the next host page address boundary. RegionSize - A pointer to a variable that will receive the actual size in bytes of the protected region of pages. The initial value of this argument is rounded up to the next host page size boundary. NewProtect - The new protection desired for the specified region of pages. Protect Values PAGE_NOACCESS - No access to the specified region of pages is allowed. An attempt to read, write, or execute the specified region results in an access violation (i.e. a GP fault). PAGE_EXECUTE - Execute access to the specified region of pages is allowed. An attempt to read or write the specified region results in an access violation. PAGE_READONLY - Read only and execute access to the specified region of pages is allowed. An attempt to write the specified region results in an access violation. PAGE_READWRITE - Read, write, and execute access to the specified region of pages is allowed. If write access to the underlying section is allowed, then a single copy of the pages are shared. Otherwise the pages are shared read only/copy on write. PAGE_GUARD - Read, write, and execute access to the specified region of pages is allowed, however, access to the region causes a "guard region entered" condition to be raised in the subject process. If write access to the underlying section is allowed, then a single copy of the pages are shared. Otherwise the pages are shared read only/copy on write. PAGE_NOCACHE - The page should be treated as uncached. This is only valid for non-shared pages. OldProtect - A pointer to a variable that will receive the old protection of the first page within the specified region of pages. Return Value: Returns the status TBS Environment: Kernel mode. --*/ { // // note - special treatement for the following cases... // // if a page is locked in the working set (memory?) and the // protection is changed to no access, the page should be // removed from the working set... valid pages can't be no access. // // if page is going to be read only or no access? and is demand // zero, make sure it is changed to a page of zeroes. // // update the vm spec to explain locked pages are unlocked when // freed or protection is changed to no-access (this may be a nasty // problem if we don't want to do this!! // PEPROCESS Process; KPROCESSOR_MODE PreviousMode; NTSTATUS Status; ULONG Attached = FALSE; PVOID CapturedBase; ULONG CapturedRegionSize; ULONG ProtectionMask; ULONG LastProtect; PAGED_CODE(); // // Check the protection field. This could raise an exception. // try { ProtectionMask = MiMakeProtectionMask (NewProtect); } except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } PreviousMode = KeGetPreviousMode(); if (PreviousMode != KernelMode) { // // Capture the region size and base address under an exception handler. // try { ProbeForWriteUlong ((PULONG)BaseAddress); ProbeForWriteUlong (RegionSize); ProbeForWriteUlong (OldProtect); // // Capture the region size and base address. // CapturedBase = *BaseAddress; CapturedRegionSize = *RegionSize; } except (EXCEPTION_EXECUTE_HANDLER) { // // If an exception occurs during the probe or capture // of the initial values, then handle the exception and // return the exception code as the status value. // return GetExceptionCode(); } } else { // // Capture the region size and base address. // CapturedRegionSize = *RegionSize; CapturedBase = *BaseAddress; } #if DBG if (MmDebug & MM_DBG_SHOW_NT_CALLS) { if ( !MmWatchProcess ) { DbgPrint("protectvm process handle %lx base address %lx size %lx protect %lx\n", ProcessHandle, CapturedBase, CapturedRegionSize, NewProtect); } } #endif // // Make sure the specified starting and ending addresses are // within the user part of the virtual address space. // if (CapturedBase > MM_HIGHEST_USER_ADDRESS) { // // Invalid base address. // return STATUS_INVALID_PARAMETER_2; } if ((ULONG)MM_HIGHEST_USER_ADDRESS - (ULONG)CapturedBase < CapturedRegionSize) { // // Invalid region size; // return STATUS_INVALID_PARAMETER_3; } if (CapturedRegionSize == 0) { return STATUS_INVALID_PARAMETER_3; } Status = ObReferenceObjectByHandle ( ProcessHandle, PROCESS_VM_OPERATION, PsProcessType, PreviousMode, (PVOID *)&Process, NULL ); if (!NT_SUCCESS(Status)) { return Status; } // // If the specified process is not the current process, attach // to the specified process. // if (PsGetCurrentProcess() != Process) { KeAttachProcess (&Process->Pcb); Attached = TRUE; } Status = MiProtectVirtualMemory (Process, &CapturedBase, &CapturedRegionSize, NewProtect, &LastProtect); if (Attached) { KeDetachProcess(); } ObDereferenceObject (Process); // // Establish an exception handler and write the size and base // address. // try { // // Reprobe the addresses as certain architecures (intel 386 for one) // do not trap kernel writes. This is the one service which allows // the protection of the page to change between the initial probe // and the final argument update. // if (PreviousMode != KernelMode) { ProbeForWriteUlong ((PULONG)BaseAddress); ProbeForWriteUlong (RegionSize); ProbeForWriteUlong (OldProtect); } *RegionSize = CapturedRegionSize; *BaseAddress = CapturedBase; *OldProtect = LastProtect; } except (EXCEPTION_EXECUTE_HANDLER) { NOTHING; } return Status; } NTSTATUS MiProtectVirtualMemory ( IN PEPROCESS Process, IN PVOID *BaseAddress, IN PULONG RegionSize, IN ULONG NewProtect, IN PULONG LastProtect) /*++ Routine Description: This routine changes the protection on a region of committed pages within the virtual address space of the subject process. Setting the protection on a ragne of pages causes the old protection to be replaced by the specified protection value. Arguments: Process - Supplies a pointer to the current process. BaseAddress - Supplies the starting address to protect. RegionsSize - Supplies the size of the region to protect. NewProtect - Supplies the new protection to set. LastProtect - Supplies the address of a kernel owned pointer to store (without probing) the old protection into. Return Value: the status of the protect operation. Environment: Kernel mode --*/ { PMMVAD FoundVad; PVOID StartingAddress; PVOID EndingAddress; PVOID CapturedBase; ULONG CapturedRegionSize; NTSTATUS Status; ULONG Attached = FALSE; PMMPTE PointerPte; PMMPTE LastPte; PMMPTE PointerPde; PMMPTE PointerProtoPte; PMMPTE LastProtoPte; PMMPFN Pfn1; ULONG CapturedOldProtect; ULONG ProtectionMask; MMPTE TempPte; MMPTE PteContents; MMPTE PreviousPte; ULONG Locked = FALSE; PVOID Va; ULONG DoAgain; // // Get the address creation mutex to block multiple threads from // creating or deleting address space at the same time. // Get the working set mutex so PTEs can be modified. // Block APCs so an APC which takes a page // fault does not corrupt various structures. // CapturedBase = *BaseAddress; CapturedRegionSize = *RegionSize; ProtectionMask = MiMakeProtectionMask (NewProtect); LOCK_WS_AND_ADDRESS_SPACE (Process); // // Make sure the address space was not deleted, if so, return an error. // if (Process->AddressSpaceDeleted != 0) { Status = STATUS_PROCESS_IS_TERMINATING; goto ErrorFound; } EndingAddress = (PVOID)(((ULONG)CapturedBase + CapturedRegionSize - 1L) | (PAGE_SIZE - 1L)); StartingAddress = (PVOID)PAGE_ALIGN(CapturedBase); FoundVad = MiCheckForConflictingVad (StartingAddress, EndingAddress); if (FoundVad == (PMMVAD)NULL) { // // No virtual address is reserved at the specified base address, // return an error. // Status = STATUS_CONFLICTING_ADDRESSES; goto ErrorFound; } // // Ensure that the starting and ending addresses are all within // the same virtual address descriptor. // if ((StartingAddress < FoundVad->StartingVa) || (EndingAddress > FoundVad->EndingVa)) { // // Not withing the section virtual address descriptor, // return an error. // Status = STATUS_CONFLICTING_ADDRESSES; goto ErrorFound; } if (FoundVad->u.VadFlags.PhysicalMapping == 1) { // // Setting the protection of a physically mapped section is // not allowed as there is no corresponding PFN database element. // Status = STATUS_CONFLICTING_ADDRESSES; goto ErrorFound; } if (FoundVad->u.VadFlags.NoChange == 1) { // // An attempt is made at changing the protection // of a secured VAD, check to see if the address range // to change allows the change. // Status = MiCheckSecuredVad (FoundVad, CapturedBase, CapturedRegionSize, ProtectionMask); if (!NT_SUCCESS (Status)) { goto ErrorFound; } } if (FoundVad->u.VadFlags.PrivateMemory == 0) { // // For mapped sections, the NO_CACHE attribute is not allowed. // if (NewProtect & PAGE_NOCACHE) { // // Not allowed. // Status = STATUS_INVALID_PARAMETER_4; goto ErrorFound; } // // If this is a file mapping, then all pages must be // committed as there can be no sparse file maps. Images // can have non-committed pages if the alignment is greater // than the page size. // if ((FoundVad->ControlArea->u.Flags.File == 0) || (FoundVad->ControlArea->u.Flags.Image == 1)) { PointerProtoPte = MiGetProtoPteAddress (FoundVad, StartingAddress); LastProtoPte = MiGetProtoPteAddress (FoundVad, EndingAddress); // // Release the working set mutex and aquire the section // commit mutex. Check all the prototype PTEs described by // the virtual address range to ensure they are committed. // UNLOCK_WS (Process); ExAcquireFastMutex (&MmSectionCommitMutex); while (PointerProtoPte <= LastProtoPte) { // // Check to see if the prototype PTE is committed, if // not return an error. // if (PointerProtoPte->u.Long == 0) { // // Error, this prototype PTE is not committed. // ExReleaseFastMutex (&MmSectionCommitMutex); Status = STATUS_NOT_COMMITTED; goto ErrorFoundNoWs; } PointerProtoPte += 1; } // // The range is committed, release the section committment // mutex, aquire the working set mutex and update the local PTEs. // ExReleaseFastMutex (&MmSectionCommitMutex); // // Set the protection on the section pages. This could // get a quota exceeded exception. // LOCK_WS (Process); } try { Locked = MiSetProtectionOnSection ( Process, FoundVad, StartingAddress, EndingAddress, NewProtect, &CapturedOldProtect, FALSE ); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); goto ErrorFound; } } else { // // Not a section, private. // For private pages, the WRITECOPY attribute is not allowed. // if ((NewProtect & PAGE_WRITECOPY) || (NewProtect & PAGE_EXECUTE_WRITECOPY)) { // // Not allowed. // Status = STATUS_INVALID_PARAMETER_4; goto ErrorFound; } // // Ensure all of the pages are already committed as described // in the virtual address descriptor. // if ( !MiIsEntireRangeCommitted(StartingAddress, EndingAddress, FoundVad, Process)) { // // Previously reserved pages have been decommitted, or an error // occurred, release mutex and return status. // Status = STATUS_NOT_COMMITTED; goto ErrorFound; } // // The address range is committed, change the protection. // PointerPde = MiGetPdeAddress (StartingAddress); PointerPte = MiGetPteAddress (StartingAddress); LastPte = MiGetPteAddress (EndingAddress); MiMakePdeExistAndMakeValid(PointerPde, Process, FALSE); // // Capture the protection for the first page. // if (PointerPte->u.Long != 0) { CapturedOldProtect = MiGetPageProtection (PointerPte, Process); // // Make sure the Page table page is still resident. // (VOID)MiDoesPdeExistAndMakeValid(PointerPde, Process, FALSE); } else { // // Get the protection from the VAD. // CapturedOldProtect = MI_CONVERT_FROM_PTE_PROTECTION(FoundVad->u.VadFlags.Protection); } // // For all the PTEs in the specified address range, set the // protection depending on the state of the PTE. // while (PointerPte <= LastPte) { if (((ULONG)PointerPte & (PAGE_SIZE - 1)) == 0) { PointerPde = MiGetPteAddress (PointerPte); MiMakePdeExistAndMakeValid(PointerPde, Process, FALSE); } PteContents = *PointerPte; if (PteContents.u.Long == 0) { // // Increment the count of non-zero page table entires // for this page table and the number of private pages // for the process. The protection will be set as // if the PTE was demand zero. // MmWorkingSetList->UsedPageTableEntries [MiGetPteOffset(PointerPte)] += 1; } if (PteContents.u.Hard.Valid == 1) { // // Set the protection into both the PTE and the original PTE // in the PFN database. // Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber); if (Pfn1->u3.e1.PrototypePte == 1) { // // This PTE refers to a fork prototype PTE, make it // private. // MiCopyOnWrite (MiGetVirtualAddressMappedByPte (PointerPte), PointerPte); // // This may have released the working set mutex and // the page table page may no longer be in memory. // (VOID)MiDoesPdeExistAndMakeValid (PointerPde, Process, FALSE); // // Do the loop again for the same PTE. // continue; } else { // // The PTE is a private page which is valid, if the // specified protection is no-access or guard page // remove the PTE from the working set. // if ((NewProtect & PAGE_NOACCESS) || (NewProtect & PAGE_GUARD)) { // // Remove the page from the working set. // Locked = MiRemovePageFromWorkingSet (PointerPte, Pfn1, &Process->Vm); continue; } else { Pfn1->OriginalPte.u.Soft.Protection = ProtectionMask; MI_MAKE_VALID_PTE (TempPte, PointerPte->u.Hard.PageFrameNumber, ProtectionMask, PointerPte); // // Flush the TB as we have changed the protection // of a valid PTE. // PreviousPte.u.Flush = MiFlushTbAndCapture (PointerPte, TempPte.u.Flush, Pfn1); } } } else { if (PteContents.u.Soft.Prototype == 1) { // // This PTE refers to a fork prototype PTE, make the // page private. This is accomplished by releasing // the working set mutex, reading the page thereby // causing a fault, and re-executing the loop, hopefully, // this time, we'll find the page present and will // turn it into a private page. // // Note, that page a TRY is used to catch guard // page exceptions and no-access exceptions. // Va = MiGetVirtualAddressMappedByPte (PointerPte); DoAgain = TRUE; while (PteContents.u.Hard.Valid == 0) { UNLOCK_WS (Process); try { *(volatile ULONG *)Va; } except (EXCEPTION_EXECUTE_HANDLER) { if (GetExceptionCode() == STATUS_ACCESS_VIOLATION) { // // The prototype PTE must be noaccess. // DoAgain = MiChangeNoAccessForkPte (PointerPte, ProtectionMask); } else if (GetExceptionCode() == STATUS_IN_PAGE_ERROR) { // // Ignore this page and go onto the next one. // PointerPte += 1; LOCK_WS (Process); continue; } } LOCK_WS (Process); (VOID)MiDoesPdeExistAndMakeValid(PointerPde, Process, FALSE); PteContents = *(volatile MMPTE *)PointerPte; } if (DoAgain) { continue; } } else { if (PteContents.u.Soft.Transition == 1) { if (MiSetProtectionOnTransitionPte ( PointerPte, ProtectionMask)) { continue; } } else { // // Must be page file space or demand zero. // PointerPte->u.Soft.Protection = ProtectionMask; ASSERT (PointerPte->u.Long != 0); } } } PointerPte += 1; } //end while } // // Common completion code. // *RegionSize = (ULONG)EndingAddress - (ULONG)StartingAddress + 1L; *BaseAddress = StartingAddress; *LastProtect = CapturedOldProtect; if (Locked) { Status = STATUS_WAS_UNLOCKED; } else { Status = STATUS_SUCCESS; } ErrorFound: UNLOCK_WS (Process); ErrorFoundNoWs: UNLOCK_ADDRESS_SPACE (Process); return Status; } ULONG MiSetProtectionOnSection ( IN PEPROCESS Process, IN PMMVAD FoundVad, IN PVOID StartingAddress, IN PVOID EndingAddress, IN ULONG NewProtect, OUT PULONG CapturedOldProtect, IN ULONG DontCharge ) /*++ Routine Description: This routine changes the protection on a region of committed pages within the virtual address space of the subject process. Setting the protection on a ragne of pages causes the old protection to be replaced by the specified protection value. Arguments: Process - Supplies a pointer to the current process. FoundVad - Supplies a pointer to the VAD containing the range to protect. StartingAddress - Supplies the starting address to protect. EndingAddress - Supplies the ending address to protect. NewProtect - Supplies the new protection to set. CapturedOldProtect - Supplies the address of a kernel owned pointer to store (without probing) the old protection into. DontCharge - Supplies TRUE if no quota or commitment should be charged. Return Value: Returns TRUE if a locked page was removed from the working set (protection was guard page or no-access, FALSE otherwise. Exceptions raised for page file quota or commitment violations. Environment: Kernel mode, working set mutex held, address creation mutex held APCs disabled. --*/ { PMMPTE PointerPte; PMMPTE LastPte; PMMPTE PointerPde; PMMPTE PointerProtoPte; PMMPFN Pfn1; MMPTE TempPte; MMPTE PreviousPte; ULONG Locked = FALSE; ULONG ProtectionMask; ULONG ProtectionMaskNotCopy; ULONG NewProtectionMask; MMPTE PteContents; ULONG Index; PULONG Va; ULONG WriteCopy = FALSE; ULONG DoAgain; ULONG QuotaCharge = 0; PAGED_CODE(); // // Make the protection field. // if ((FoundVad->u.VadFlags.ImageMap == 1) || (FoundVad->u.VadFlags.CopyOnWrite == 1)) { if (NewProtect & PAGE_READWRITE) { NewProtect &= ~PAGE_READWRITE; NewProtect |= PAGE_WRITECOPY; } if (NewProtect & PAGE_EXECUTE_READWRITE) { NewProtect &= ~PAGE_EXECUTE_READWRITE; NewProtect |= PAGE_EXECUTE_WRITECOPY; } } ProtectionMask = MiMakeProtectionMask (NewProtect); // // Determine if copy on write is being set. // ProtectionMaskNotCopy = ProtectionMask; if ((ProtectionMask & MM_COPY_ON_WRITE_MASK) == MM_COPY_ON_WRITE_MASK) { WriteCopy = TRUE; ProtectionMaskNotCopy &= ~MM_PROTECTION_COPY_MASK; } PointerPde = MiGetPdeAddress (StartingAddress); PointerPte = MiGetPteAddress (StartingAddress); LastPte = MiGetPteAddress (EndingAddress); MiMakePdeExistAndMakeValid(PointerPde, Process, FALSE); // // Capture the protection for the first page. // if (PointerPte->u.Long != 0) { *CapturedOldProtect = MiGetPageProtection (PointerPte, Process); // // Make sure the Page table page is still resident. // (VOID)MiDoesPdeExistAndMakeValid(PointerPde, Process, FALSE); } else { // // Get the protection from the VAD, unless image file. // if (FoundVad->u.VadFlags.ImageMap == 0) { // // This is not an image file, the protection is in the VAD. // *CapturedOldProtect = MI_CONVERT_FROM_PTE_PROTECTION(FoundVad->u.VadFlags.Protection); } else { // // This is an image file, the protection is in the // prototype PTE. // PointerProtoPte = MiGetProtoPteAddress (FoundVad, MiGetVirtualAddressMappedByPte (PointerPte)); TempPte = MiCaptureSystemPte (PointerProtoPte, Process); *CapturedOldProtect = MiGetPageProtection (&TempPte, Process); // // Make sure the Page table page is still resident. // (VOID)MiDoesPdeExistAndMakeValid(PointerPde, Process, FALSE); } } // // If the page protection is being change to be copy-on-write, the // commitment and page file quota for the potentially dirty private pages // must be calculated and charged. This must be done before any // protections are changed as the changes cannot be undone. // if (WriteCopy) { // // Calculate the charges. If the page is shared and not write copy // it is counted as a charged page. // while (PointerPte <= LastPte) { if (((ULONG)PointerPte & (PAGE_SIZE - 1)) == 0) { PointerPde = MiGetPteAddress (PointerPte); while (!MiDoesPdeExistAndMakeValid(PointerPde, Process, FALSE)) { // // No PDE exists for this address. Therefore // all the PTEs are shared and not copy on write. // go to the next PDE. // PointerPde += 1; PointerProtoPte = PointerPte; PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); if (PointerPte > LastPte) { QuotaCharge += 1 + LastPte - PointerProtoPte; goto Done; } QuotaCharge += PointerPte - PointerProtoPte; } } PteContents = *PointerPte; if (PteContents.u.Long == 0) { // // The PTE has not been evalulated, assume copy on write. // QuotaCharge += 1; } else if ((PteContents.u.Hard.Valid == 1) && (PteContents.u.Hard.CopyOnWrite == 0)) { // // See if this is a prototype PTE, if so charge it. // Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber); if (Pfn1->u3.e1.PrototypePte == 1) { QuotaCharge += 1; } } else { if (PteContents.u.Soft.Prototype == 1) { // // This is a prototype PTE. Charge if it is not // in copy on write format. // if (PteContents.u.Soft.PageFileHigh == 0xFFFFF) { // // Page protection is within the PTE. // if (!MI_IS_PTE_PROTECTION_COPY_WRITE(PteContents.u.Soft.Protection)) { QuotaCharge += 1; } } else { // // The PTE references the prototype directly, therefore // it can't be copy on write. Charge. // QuotaCharge += 1; } } } PointerPte += 1; } Done: NOTHING; // // Charge for the quota. // if (!DontCharge) { MiChargePageFileQuota (QuotaCharge, Process); try { MiChargeCommitment (QuotaCharge, Process); } except (EXCEPTION_EXECUTE_HANDLER) { MiReturnPageFileQuota (QuotaCharge, Process); ExRaiseStatus (GetExceptionCode()); } // // Add the quota into the charge to the VAD. // FoundVad->u.VadFlags.CommitCharge += QuotaCharge; Process->CommitCharge += QuotaCharge; } } // // For all the PTEs in the specified address range, set the // protection depending on the state of the PTE. // // // If the PTE was copy on write (but not written) and the // new protection is NOT copy-on-write, return page file quota // and committment. // PointerPde = MiGetPdeAddress (StartingAddress); PointerPte = MiGetPteAddress (StartingAddress); MiDoesPdeExistAndMakeValid (PointerPde, Process, FALSE); QuotaCharge = 0; while (PointerPte <= LastPte) { if (((ULONG)PointerPte & (PAGE_SIZE - 1)) == 0) { PointerPde = MiGetPteAddress (PointerPte); MiMakePdeExistAndMakeValid (PointerPde, Process, FALSE); } PteContents = *PointerPte; if (PteContents.u.Long == 0) { // // The PTE is Zero, set it into prototype PTE format // with the protection in the prototype PTE. // *PointerPte = PrototypePte; PointerPte->u.Soft.Protection = ProtectionMask; // // Increment the count of non-zero page table entires // for this page table and the number of private pages // for the process. // MmWorkingSetList->UsedPageTableEntries [MiGetPteOffset(PointerPte)] += 1; } else if (PteContents.u.Hard.Valid == 1) { // // Set the protection into both the PTE and the original PTE // in the PFN database for private pages only. // NewProtectionMask = ProtectionMask; Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber); if ((NewProtect & PAGE_NOACCESS) || (NewProtect & PAGE_GUARD)) { Locked = MiRemovePageFromWorkingSet (PointerPte, Pfn1, &Process->Vm ); continue; } else { if (Pfn1->u3.e1.PrototypePte == 1) { // // The true protection may be in the WSLE, locate // the WSLE. // Va = (PULONG)MiGetVirtualAddressMappedByPte (PointerPte); Index = MiLocateWsle ((PVOID)Va, MmWorkingSetList, Pfn1->u1.WsIndex); // // Check to see if this is a prototype PTE. This // is done by comparing the PTE address in the // PFN database to the PTE address indicated by the // VAD. If they are not equal, this is a prototype // PTE. // if (Pfn1->PteAddress != MiGetProtoPteAddress (FoundVad, (PVOID)Va)) { // // This PTE refers to a fork prototype PTE, make it // private. // MiCopyOnWrite ((PVOID)Va, PointerPte); if (WriteCopy) { QuotaCharge += 1; } // // This may have released the working set mutex and // the page table page may no longer be in memory. // (VOID)MiDoesPdeExistAndMakeValid(PointerPde, Process, FALSE); // // Do the loop again. // continue; } else { // // Update the protection field in the WSLE and // the PTE. // // // If the PTE is copy on write uncharge the // previously charged quota. // if ((!WriteCopy) && (PteContents.u.Hard.CopyOnWrite == 1)) { QuotaCharge += 1; } MmWsle[Index].u1.e1.Protection = ProtectionMask; MmWsle[Index].u1.e1.SameProtectAsProto = 0; } } else { // // Page is private (copy on written), protection mask // is stored in the original pte field. // Pfn1->OriginalPte.u.Soft.Protection = ProtectionMaskNotCopy; NewProtectionMask = ProtectionMaskNotCopy; } MI_MAKE_VALID_PTE (TempPte, PteContents.u.Hard.PageFrameNumber, NewProtectionMask, PointerPte); } // // Flush the TB as we have changed the protection // of a valid PTE. // PreviousPte.u.Flush = MiFlushTbAndCapture (PointerPte, TempPte.u.Flush, Pfn1); } else { if (PteContents.u.Soft.Prototype == 1) { // // The PTE is in prototype PTE format. // // // Is it a fork prototype PTE? // Va = (PULONG)MiGetVirtualAddressMappedByPte (PointerPte); if ((PteContents.u.Soft.PageFileHigh != 0xFFFFF) && (MiPteToProto (PointerPte) != MiGetProtoPteAddress (FoundVad, (PVOID)Va))) { // // This PTE refers to a fork prototype PTE, make the // page private. This is accomplished by releasing // the working set mutex, reading the page thereby // causing a fault, and re-executing the loop, hopefully, // this time, we'll find the page present and will // turn it into a private page. // // Note, that page with prototype = 1 cannot be // no-access. // DoAgain = TRUE; while (PteContents.u.Hard.Valid == 0) { UNLOCK_WS (Process); try { *(volatile ULONG *)Va; } except (EXCEPTION_EXECUTE_HANDLER) { if (GetExceptionCode() != STATUS_GUARD_PAGE_VIOLATION) { // // The prototype PTE must be noaccess. // DoAgain = MiChangeNoAccessForkPte (PointerPte, ProtectionMask); } } LOCK_WS (Process); (VOID)MiDoesPdeExistAndMakeValid(PointerPde, Process, FALSE); PteContents = *(volatile MMPTE *)PointerPte; } if (DoAgain) { continue; } } else { // // If the new protection is not write-copy, the PTE // protection is not in the prototype PTE (can't be // write copy for sections), and the protection in // the PTE is write-copy, release the page file // quota and commitment for this page. // if ((!WriteCopy) && (PteContents.u.Soft.PageFileHigh == 0XFFFFF)) { if (MI_IS_PTE_PROTECTION_COPY_WRITE(PteContents.u.Soft.Protection)) { QuotaCharge += 1; } } // // The PTE is a prototype PTE. Make the high part // of the PTE indicate that the protection field // is in the PTE itself. // *PointerPte = PrototypePte; PointerPte->u.Soft.Protection = ProtectionMask; } } else { if (PteContents.u.Soft.Transition == 1) { // // This is a transition PTE. (Page is private) // if (MiSetProtectionOnTransitionPte ( PointerPte, ProtectionMaskNotCopy)) { continue; } } else { // // Must be page file space or demand zero. // PointerPte->u.Soft.Protection = ProtectionMaskNotCopy; } } } PointerPte += 1; } // // Return the quota charge and the commitment, if any. // if ((QuotaCharge > 0) && (!DontCharge)) { MiReturnCommitment (QuotaCharge); MiReturnPageFileQuota (QuotaCharge, Process); ASSERT (QuotaCharge <= FoundVad->u.VadFlags.CommitCharge); FoundVad->u.VadFlags.CommitCharge -= QuotaCharge; Process->CommitCharge -= QuotaCharge; } return Locked; } ULONG MiGetPageProtection ( IN PMMPTE PointerPte, IN PEPROCESS Process ) /*++ Routine Description: This routine returns the page protection of a non-zero PTE. It may release and reacquire the working set mutex. Arguments: PointerPte - Supplies a pointer to a non-zero PTE. Return Value: Returns the protection code. Environment: Kernel mode, working set and address creation mutex held. Note, that the address creation mutex does not need to be held if the working set mutex does not need to be released in the case of a prototype PTE. --*/ { MMPTE PteContents; MMPTE ProtoPteContents; PMMPFN Pfn1; PMMPTE ProtoPteAddress; PVOID Va; ULONG Index; PAGED_CODE(); PteContents = *PointerPte; if ((PteContents.u.Soft.Valid == 0) && (PteContents.u.Soft.Prototype == 1)) { // // This pte is in prototype format, the protection is // stored in the prototype PTE. // if ((PointerPte > (PMMPTE)PDE_TOP) || (PteContents.u.Soft.PageFileHigh == 0xFFFFF)) { // // The protection is within this PTE. // return MI_CONVERT_FROM_PTE_PROTECTION ( PteContents.u.Soft.Protection); } ProtoPteAddress = MiPteToProto (PointerPte); // // Capture protopte PTE contents. // ProtoPteContents = MiCaptureSystemPte (ProtoPteAddress, Process); // // The working set mutex may have been released and the // page may no longer be in prototype format, get the // new contents of the PTE and obtain the protection mask. // PteContents = MiCaptureSystemPte (PointerPte, Process); } if ((PteContents.u.Soft.Valid == 0) && (PteContents.u.Soft.Prototype == 1)) { // // Pte is still prototype, return the protection captured // from the prototype PTE. // if (ProtoPteContents.u.Hard.Valid == 1) { // // The prototype PTE is valid, get the protection from // the PFN database. // Pfn1 = MI_PFN_ELEMENT (ProtoPteContents.u.Hard.PageFrameNumber); return MI_CONVERT_FROM_PTE_PROTECTION( Pfn1->OriginalPte.u.Soft.Protection); } else { // // The prototype PTE is not valid, return the protection from the // PTE. // return MI_CONVERT_FROM_PTE_PROTECTION ( ProtoPteContents.u.Soft.Protection); } } if (PteContents.u.Hard.Valid == 1) { // // The page is valid, the protection field is either in the // PFN database origional PTE element or the WSLE. If // the page is private, get it from the PFN original PTE // element. If the page else use the WSLE. // Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber); if ((Pfn1->u3.e1.PrototypePte == 0) || (PointerPte > (PMMPTE)PDE_TOP)) { // // This is a private PTE or the PTE address is that of a // prototype PTE, hence the protection is in // the orginal PTE. // return MI_CONVERT_FROM_PTE_PROTECTION( Pfn1->OriginalPte.u.Soft.Protection); } // // The PTE was a hardware PTE, get the protection // from the WSLE. Va = (PULONG)MiGetVirtualAddressMappedByPte (PointerPte); Index = MiLocateWsle ((PVOID)Va, MmWorkingSetList, Pfn1->u1.WsIndex); return MI_CONVERT_FROM_PTE_PROTECTION (MmWsle[Index].u1.e1.Protection); } // // PTE is either demand zero or transition, in either // case protection is in PTE. // return MI_CONVERT_FROM_PTE_PROTECTION (PteContents.u.Soft.Protection); } ULONG MiChangeNoAccessForkPte ( IN PMMPTE PointerPte, IN ULONG ProtectionMask ) /*++ Routine Description: Arguments: PointerPte - Supplies a pointer to the current PTE. ProtectionMask - Supplies the protection mask to set. Return Value: FASLE if the loop should be repeated for this PTE, TRUE if protection has been set. Environment: Kernel mode, address creation mutex held, APCs disabled. --*/ { PAGED_CODE(); if (ProtectionMask == MM_NOACCESS) { // // No need to change the page protection. // return TRUE; } PointerPte->u.Proto.ReadOnly = 1; return FALSE; } HARDWARE_PTE MiFlushTbAndCapture( IN PMMPTE PointerPte, IN HARDWARE_PTE TempPte, IN PMMPFN Pfn1 ) // non pagable helper routine. { MMPTE PreviousPte; KIRQL OldIrql; // // Flush the TB as we have changed the protection // of a valid PTE. // LOCK_PFN (OldIrql); PreviousPte.u.Flush = KeFlushSingleTb ( MiGetVirtualAddressMappedByPte (PointerPte), FALSE, FALSE, (PHARDWARE_PTE)PointerPte, TempPte); ASSERT (PreviousPte.u.Hard.Valid == 1); // // A page protection is being changed, on certain // hardware the dirty bit should be ORed into the // modify bit in the PFN element. // MI_CAPTURE_DIRTY_BIT_TO_PFN (&PreviousPte, Pfn1); UNLOCK_PFN (OldIrql); return PreviousPte.u.Flush; } ULONG MiSetProtectionOnTransitionPte ( IN PMMPTE PointerPte, IN ULONG ProtectionMask ) // nonpaged helper routine. { KIRQL OldIrql; MMPTE PteContents; PMMPFN Pfn1; // // This is a transition PTE. (Page is private) // // // Need pfn mutex to ensure page doesn't become // non-transition. // LOCK_PFN (OldIrql); // // Make sure the page is still a transition page. // PteContents = *(volatile MMPTE *)PointerPte; if ((PteContents.u.Soft.Prototype == 0) && (PointerPte->u.Soft.Transition == 1)) { Pfn1 = MI_PFN_ELEMENT ( PteContents.u.Trans.PageFrameNumber); Pfn1->OriginalPte.u.Soft.Protection = ProtectionMask; PointerPte->u.Soft.Protection = ProtectionMask; UNLOCK_PFN (OldIrql); return FALSE; } // // Do this loop again for the same PTE. // UNLOCK_PFN (OldIrql); return TRUE; } MMPTE MiCaptureSystemPte ( IN PMMPTE PointerProtoPte, IN PEPROCESS Process ) // nonpagable helper routine. { MMPTE TempPte; KIRQL OldIrql; LOCK_PFN (OldIrql); MiMakeSystemAddressValidPfnWs (PointerProtoPte, Process); TempPte = *PointerProtoPte; UNLOCK_PFN (OldIrql); return TempPte; } NTSTATUS MiCheckSecuredVad ( IN PMMVAD Vad, IN PVOID Base, IN ULONG Size, IN ULONG ProtectionMask ) /*++ Routine Description: This routine checks to see if the specified VAD is secured in such a way as to conflick with the address range and protection mask specified. Arguments: Vad - Supplies a pointer to the VAD containing the address range. Base - Supplies the base of the range the protection starts at. Size - Supplies the size of the range. ProtectionMask - Supplies the protection mask being set. Return Value: Status value. Environment: Kernel mode. --*/ { PVOID End; PLIST_ENTRY Next; PMMSECURE_ENTRY Entry; NTSTATUS Status = STATUS_SUCCESS; End = (PVOID)((PCHAR)Base + Size); if (ProtectionMask < MM_SECURE_DELETE_CHECK) { if ((Vad->u.VadFlags.NoChange == 1) && (Vad->u2.VadFlags2.SecNoChange == 1) && (Vad->u.VadFlags.Protection != ProtectionMask)) { // // An attempt is made at changing the protection // of a SEC_NO_CHANGE section - return an error. // Status = STATUS_INVALID_PAGE_PROTECTION; goto done; } } else { // // Deletion - set to no-access for check. SEC_NOCHANGE allows // deletion, but does not allow page protection changes. // ProtectionMask = 0; } if (Vad->u2.VadFlags2.OneSecured) { if ((Base <= Vad->u3.Secured.EndVa) && (End >= Vad->u3.Secured.EndVa)) { // // This region conflicts, check the protections. // if (Vad->u2.VadFlags2.ReadOnly) { if (MmReadWrite[ProtectionMask] < 10) { Status = STATUS_INVALID_PAGE_PROTECTION; goto done; } } else { if (MmReadWrite[ProtectionMask] < 11) { Status = STATUS_INVALID_PAGE_PROTECTION; goto done; } } } } else if (Vad->u2.VadFlags2.MultipleSecured) { Next = Vad->u3.List.Flink; do { Entry = CONTAINING_RECORD( Next, MMSECURE_ENTRY, List); if ((Base <= Entry->EndVa) && (End >= Entry->EndVa)) { // // This region conflicts, check the protections. // if (Entry->u2.VadFlags2.ReadOnly) { if (MmReadWrite[ProtectionMask] < 10) { Status = STATUS_INVALID_PAGE_PROTECTION; goto done; } } else { if (MmReadWrite[ProtectionMask] < 11) { Status = STATUS_INVALID_PAGE_PROTECTION; goto done; } } } Next = Entry->List.Flink; } while (Entry->List.Flink != &Vad->u3.List); } done: return Status; }