reactos/ntoskrnl/mm/ARM3/virtual.c
Tuur Martens 10126e7710 [NTOS:MM] Fix VADs being inserted even though the quota would exceed
Since we were charging the pool quota after the VAD insertion,
if the quota charge failed, the VAD would still have been inserted.
This commit attempts to resolve this issue by charging quota
before inserting the VAD thus allowing the quota charge to fail early.

Addendum to 884356a0. CORE-18028
2022-07-06 18:48:32 +02:00

5775 lines
174 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: BSD - See COPYING.ARM in the top level directory
* FILE: ntoskrnl/mm/ARM3/virtual.c
* PURPOSE: ARM Memory Manager Virtual Memory Management
* PROGRAMMERS: ReactOS Portable Systems Group
*/
/* INCLUDES *******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
#define MODULE_INVOLVED_IN_ARM3
#include <mm/ARM3/miarm.h>
#define MI_MAPPED_COPY_PAGES 14
#define MI_POOL_COPY_BYTES 512
#define MI_MAX_TRANSFER_SIZE 64 * 1024
NTSTATUS NTAPI
MiProtectVirtualMemory(IN PEPROCESS Process,
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T NumberOfBytesToProtect,
IN ULONG NewAccessProtection,
OUT PULONG OldAccessProtection OPTIONAL);
VOID
NTAPI
MiFlushTbAndCapture(IN PMMVAD FoundVad,
IN PMMPTE PointerPte,
IN ULONG ProtectionMask,
IN PMMPFN Pfn1,
IN BOOLEAN CaptureDirtyBit);
/* PRIVATE FUNCTIONS **********************************************************/
ULONG
NTAPI
MiCalculatePageCommitment(IN ULONG_PTR StartingAddress,
IN ULONG_PTR EndingAddress,
IN PMMVAD Vad,
IN PEPROCESS Process)
{
PMMPTE PointerPte, LastPte;
PMMPDE PointerPde;
BOOLEAN OnPdeBoundary = TRUE;
#if _MI_PAGING_LEVELS >= 3
PMMPPE PointerPpe;
BOOLEAN OnPpeBoundary = TRUE;
#if _MI_PAGING_LEVELS == 4
PMMPXE PointerPxe;
BOOLEAN OnPxeBoundary = TRUE;
#endif
#endif
/* Make sure this all makes sense */
ASSERT(PsGetCurrentThread()->OwnsProcessWorkingSetExclusive || PsGetCurrentThread()->OwnsProcessWorkingSetShared);
ASSERT(EndingAddress >= StartingAddress);
PointerPte = MiAddressToPte(StartingAddress);
LastPte = MiAddressToPte(EndingAddress);
/*
* In case this is a committed VAD, assume the whole range is committed
* and count the individually decommitted pages.
* In case it is not, assume the range is not committed and count the individually committed pages.
*/
ULONG_PTR CommittedPages = Vad->u.VadFlags.MemCommit ? BYTES_TO_PAGES(EndingAddress - StartingAddress) : 0;
while (PointerPte <= LastPte)
{
#if _MI_PAGING_LEVELS == 4
/* Check if PXE was ever paged in. */
if (OnPxeBoundary)
{
PointerPxe = MiPteToPxe(PointerPte);
/* Check that this loop is sane */
ASSERT(OnPpeBoundary);
ASSERT(OnPdeBoundary);
if (PointerPxe->u.Long == 0)
{
PointerPxe++;
PointerPte = MiPxeToPte(PointerPde);
continue;
}
if (PointerPxe->u.Hard.Valid == 0)
MiMakeSystemAddressValid(MiPteToPpe(PointerPte), Process);
}
ASSERT(PointerPxe->u.Hard.Valid == 1);
#endif
#if _MI_PAGING_LEVELS >= 3
/* Now PPE */
if (OnPpeBoundary)
{
PointerPpe = MiPteToPpe(PointerPte);
/* Sanity again */
ASSERT(OnPdeBoundary);
if (PointerPpe->u.Long == 0)
{
PointerPpe++;
PointerPte = MiPpeToPte(PointerPpe);
#if _MI_PAGING_LEVELS == 4
OnPxeBoundary = MiIsPteOnPxeBoundary(PointerPte);
#endif
continue;
}
if (PointerPpe->u.Hard.Valid == 0)
MiMakeSystemAddressValid(MiPteToPde(PointerPte), Process);
}
ASSERT(PointerPpe->u.Hard.Valid == 1);
#endif
/* Last level is the PDE */
if (OnPdeBoundary)
{
PointerPde = MiPteToPde(PointerPte);
if (PointerPde->u.Long == 0)
{
PointerPde++;
PointerPte = MiPdeToPte(PointerPde);
#if _MI_PAGING_LEVELS >= 3
OnPpeBoundary = MiIsPteOnPpeBoundary(PointerPte);
#if _MI_PAGING_LEVELS == 4
OnPxeBoundary = MiIsPteOnPxeBoundary(PointerPte);
#endif
#endif
continue;
}
if (PointerPde->u.Hard.Valid == 0)
MiMakeSystemAddressValid(PointerPte, Process);
}
ASSERT(PointerPde->u.Hard.Valid == 1);
/* Is this PTE demand zero? */
if (PointerPte->u.Long != 0)
{
/* It isn't -- is it a decommited, invalid, or faulted PTE? */
if ((PointerPte->u.Hard.Valid == 0) &&
(PointerPte->u.Soft.Protection == MM_DECOMMIT) &&
((PointerPte->u.Soft.Prototype == 0) ||
(PointerPte->u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED)))
{
/* It is, so remove it from the count of committed pages if we have to */
if (Vad->u.VadFlags.MemCommit)
CommittedPages--;
}
else if (!Vad->u.VadFlags.MemCommit)
{
/* It is a valid, non-decommited, non-paged out PTE. Count it in. */
CommittedPages++;
}
}
/* Move to the next PTE */
PointerPte++;
/* Manage page tables */
OnPdeBoundary = MiIsPteOnPdeBoundary(PointerPte);
#if _MI_PAGING_LEVELS >= 3
OnPpeBoundary = MiIsPteOnPpeBoundary(PointerPte);
#if _MI_PAGING_LEVELS == 4
OnPxeBoundary = MiIsPteOnPxeBoundary(PointerPte);
#endif
#endif
}
/* Make sure we didn't mess this up */
ASSERT(CommittedPages <= BYTES_TO_PAGES(EndingAddress - StartingAddress));
return CommittedPages;
}
ULONG
NTAPI
MiMakeSystemAddressValid(IN PVOID PageTableVirtualAddress,
IN PEPROCESS CurrentProcess)
{
NTSTATUS Status;
BOOLEAN WsShared = FALSE, WsSafe = FALSE, LockChange = FALSE;
PETHREAD CurrentThread = PsGetCurrentThread();
/* Must be a non-pool page table, since those are double-mapped already */
ASSERT(PageTableVirtualAddress > MM_HIGHEST_USER_ADDRESS);
ASSERT((PageTableVirtualAddress < MmPagedPoolStart) ||
(PageTableVirtualAddress > MmPagedPoolEnd));
/* Working set lock or PFN lock should be held */
ASSERT(KeAreAllApcsDisabled() == TRUE);
/* Check if the page table is valid */
while (!MmIsAddressValid(PageTableVirtualAddress))
{
/* Release the working set lock */
MiUnlockProcessWorkingSetForFault(CurrentProcess,
CurrentThread,
&WsSafe,
&WsShared);
/* Fault it in */
Status = MmAccessFault(FALSE, PageTableVirtualAddress, KernelMode, NULL);
if (!NT_SUCCESS(Status))
{
/* This should not fail */
KeBugCheckEx(KERNEL_DATA_INPAGE_ERROR,
1,
Status,
(ULONG_PTR)CurrentProcess,
(ULONG_PTR)PageTableVirtualAddress);
}
/* Lock the working set again */
MiLockProcessWorkingSetForFault(CurrentProcess,
CurrentThread,
WsSafe,
WsShared);
/* This flag will be useful later when we do better locking */
LockChange = TRUE;
}
/* Let caller know what the lock state is */
return LockChange;
}
ULONG
NTAPI
MiMakeSystemAddressValidPfn(IN PVOID VirtualAddress,
IN KIRQL OldIrql)
{
NTSTATUS Status;
BOOLEAN LockChange = FALSE;
/* Must be e kernel address */
ASSERT(VirtualAddress > MM_HIGHEST_USER_ADDRESS);
/* Check if the page is valid */
while (!MmIsAddressValid(VirtualAddress))
{
/* Release the PFN database */
MiReleasePfnLock(OldIrql);
/* Fault it in */
Status = MmAccessFault(FALSE, VirtualAddress, KernelMode, NULL);
if (!NT_SUCCESS(Status))
{
/* This should not fail */
KeBugCheckEx(KERNEL_DATA_INPAGE_ERROR,
3,
Status,
0,
(ULONG_PTR)VirtualAddress);
}
/* This flag will be useful later when we do better locking */
LockChange = TRUE;
/* Lock the PFN database */
OldIrql = MiAcquirePfnLock();
}
/* Let caller know what the lock state is */
return LockChange;
}
PFN_COUNT
NTAPI
MiDeleteSystemPageableVm(IN PMMPTE PointerPte,
IN PFN_NUMBER PageCount,
IN ULONG Flags,
OUT PPFN_NUMBER ValidPages)
{
PFN_COUNT ActualPages = 0;
PETHREAD CurrentThread = PsGetCurrentThread();
PMMPFN Pfn1, Pfn2;
PFN_NUMBER PageFrameIndex, PageTableIndex;
KIRQL OldIrql;
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
/* Lock the system working set */
MiLockWorkingSet(CurrentThread, &MmSystemCacheWs);
/* Loop all pages */
while (PageCount)
{
/* Make sure there's some data about the page */
if (PointerPte->u.Long)
{
/* Normally this is one possibility -- freeing a valid page */
if (PointerPte->u.Hard.Valid)
{
/* Get the page PFN */
PageFrameIndex = PFN_FROM_PTE(PointerPte);
Pfn1 = MiGetPfnEntry(PageFrameIndex);
/* Should not have any working set data yet */
ASSERT(Pfn1->u1.WsIndex == 0);
/* Actual valid, legitimate, pages */
if (ValidPages) (*ValidPages)++;
/* Get the page table entry */
PageTableIndex = Pfn1->u4.PteFrame;
Pfn2 = MiGetPfnEntry(PageTableIndex);
/* Lock the PFN database */
OldIrql = MiAcquirePfnLock();
/* Delete it the page */
MI_SET_PFN_DELETED(Pfn1);
MiDecrementShareCount(Pfn1, PageFrameIndex);
/* Decrement the page table too */
MiDecrementShareCount(Pfn2, PageTableIndex);
/* Release the PFN database */
MiReleasePfnLock(OldIrql);
/* Destroy the PTE */
MI_ERASE_PTE(PointerPte);
}
else
{
/* As always, only handle current ARM3 scenarios */
ASSERT(PointerPte->u.Soft.Prototype == 0);
ASSERT(PointerPte->u.Soft.Transition == 0);
/*
* The only other ARM3 possibility is a demand zero page, which would
* mean freeing some of the paged pool pages that haven't even been
* touched yet, as part of a larger allocation.
*
* Right now, we shouldn't expect any page file information in the PTE
*/
ASSERT(PointerPte->u.Soft.PageFileHigh == 0);
/* Destroy the PTE */
MI_ERASE_PTE(PointerPte);
}
/* Actual legitimate pages */
ActualPages++;
}
/* Keep going */
PointerPte++;
PageCount--;
}
/* Release the working set */
MiUnlockWorkingSet(CurrentThread, &MmSystemCacheWs);
/* Flush the entire TLB */
KeFlushEntireTb(TRUE, TRUE);
/* Done */
return ActualPages;
}
VOID
NTAPI
MiDeletePte(IN PMMPTE PointerPte,
IN PVOID VirtualAddress,
IN PEPROCESS CurrentProcess,
IN PMMPTE PrototypePte)
{
PMMPFN Pfn1;
MMPTE TempPte;
PFN_NUMBER PageFrameIndex;
PMMPDE PointerPde;
/* PFN lock must be held */
MI_ASSERT_PFN_LOCK_HELD();
/* WorkingSet must be exclusively locked */
ASSERT(MM_ANY_WS_LOCK_HELD_EXCLUSIVE(PsGetCurrentThread()));
/* This must be current process. */
ASSERT(CurrentProcess == PsGetCurrentProcess());
/* Capture the PTE */
TempPte = *PointerPte;
/* See if the PTE is valid */
if (TempPte.u.Hard.Valid == 0)
{
/* Prototype and paged out PTEs not supported yet */
ASSERT(TempPte.u.Soft.Prototype == 0);
ASSERT((TempPte.u.Soft.PageFileHigh == 0) || (TempPte.u.Soft.Transition == 1));
if (TempPte.u.Soft.Transition)
{
/* Get the PFN entry */
PageFrameIndex = PFN_FROM_PTE(&TempPte);
Pfn1 = MiGetPfnEntry(PageFrameIndex);
DPRINT("Pte %p is transitional!\n", PointerPte);
/* Make sure the saved PTE address is valid */
ASSERT((PMMPTE)((ULONG_PTR)Pfn1->PteAddress & ~0x1) == PointerPte);
/* Destroy the PTE */
MI_ERASE_PTE(PointerPte);
/* Drop the reference on the page table. */
MiDecrementShareCount(MiGetPfnEntry(Pfn1->u4.PteFrame), Pfn1->u4.PteFrame);
/* In case of shared page, the prototype PTE must be in transition, not the process one */
ASSERT(Pfn1->u3.e1.PrototypePte == 0);
/* Delete the PFN */
MI_SET_PFN_DELETED(Pfn1);
/* It must be either free (refcount == 0) or being written (refcount == 1) */
ASSERT(Pfn1->u3.e2.ReferenceCount == Pfn1->u3.e1.WriteInProgress);
/* See if we must free it ourselves, or if it will be freed once I/O is over */
if (Pfn1->u3.e2.ReferenceCount == 0)
{
/* And it should be in standby or modified list */
ASSERT((Pfn1->u3.e1.PageLocation == ModifiedPageList) || (Pfn1->u3.e1.PageLocation == StandbyPageList));
/* Unlink it and set its reference count to one */
MiUnlinkPageFromList(Pfn1);
Pfn1->u3.e2.ReferenceCount++;
/* This will put it back in free list and clean properly up */
MiDecrementReferenceCount(Pfn1, PageFrameIndex);
}
return;
}
}
/* Get the PFN entry */
PageFrameIndex = PFN_FROM_PTE(&TempPte);
Pfn1 = MiGetPfnEntry(PageFrameIndex);
/* Check if this is a valid, prototype PTE */
if (Pfn1->u3.e1.PrototypePte == 1)
{
/* Get the PDE and make sure it's faulted in */
PointerPde = MiPteToPde(PointerPte);
if (PointerPde->u.Hard.Valid == 0)
{
#if (_MI_PAGING_LEVELS == 2)
/* Could be paged pool access from a new process -- synchronize the page directories */
if (!NT_SUCCESS(MiCheckPdeForPagedPool(VirtualAddress)))
{
#endif
/* The PDE must be valid at this point */
KeBugCheckEx(MEMORY_MANAGEMENT,
0x61940,
(ULONG_PTR)PointerPte,
PointerPte->u.Long,
(ULONG_PTR)VirtualAddress);
}
#if (_MI_PAGING_LEVELS == 2)
}
#endif
/* Drop the share count on the page table */
PointerPde = MiPteToPde(PointerPte);
MiDecrementShareCount(MiGetPfnEntry(PointerPde->u.Hard.PageFrameNumber),
PointerPde->u.Hard.PageFrameNumber);
/* Drop the share count */
MiDecrementShareCount(Pfn1, PageFrameIndex);
/* Either a fork, or this is the shared user data page */
if ((PointerPte <= MiHighestUserPte) && (PrototypePte != Pfn1->PteAddress))
{
/* If it's not the shared user page, then crash, since there's no fork() yet */
if ((PAGE_ALIGN(VirtualAddress) != (PVOID)USER_SHARED_DATA) ||
(MmHighestUserAddress <= (PVOID)USER_SHARED_DATA))
{
/* Must be some sort of memory corruption */
KeBugCheckEx(MEMORY_MANAGEMENT,
0x400,
(ULONG_PTR)PointerPte,
(ULONG_PTR)PrototypePte,
(ULONG_PTR)Pfn1->PteAddress);
}
}
/* Erase it */
MI_ERASE_PTE(PointerPte);
}
else
{
/* Make sure the saved PTE address is valid */
if ((PMMPTE)((ULONG_PTR)Pfn1->PteAddress & ~0x1) != PointerPte)
{
/* The PFN entry is illegal, or invalid */
KeBugCheckEx(MEMORY_MANAGEMENT,
0x401,
(ULONG_PTR)PointerPte,
PointerPte->u.Long,
(ULONG_PTR)Pfn1->PteAddress);
}
/* Erase the PTE */
MI_ERASE_PTE(PointerPte);
/* There should only be 1 shared reference count */
ASSERT(Pfn1->u2.ShareCount == 1);
/* Drop the reference on the page table. */
MiDecrementShareCount(MiGetPfnEntry(Pfn1->u4.PteFrame), Pfn1->u4.PteFrame);
/* Mark the PFN for deletion and dereference what should be the last ref */
MI_SET_PFN_DELETED(Pfn1);
MiDecrementShareCount(Pfn1, PageFrameIndex);
/* We should eventually do this */
//CurrentProcess->NumberOfPrivatePages--;
}
/* Flush the TLB */
KeFlushCurrentTb();
}
VOID
NTAPI
MiDeleteVirtualAddresses(IN ULONG_PTR Va,
IN ULONG_PTR EndingAddress,
IN PMMVAD Vad)
{
PMMPTE PointerPte, PrototypePte, LastPrototypePte;
PMMPDE PointerPde;
#if (_MI_PAGING_LEVELS >= 3)
PMMPPE PointerPpe;
#endif
#if (_MI_PAGING_LEVELS >= 4)
PMMPPE PointerPxe;
#endif
MMPTE TempPte;
PEPROCESS CurrentProcess;
KIRQL OldIrql;
BOOLEAN AddressGap = FALSE;
PSUBSECTION Subsection;
/* Get out if this is a fake VAD, RosMm will free the marea pages */
if ((Vad) && (Vad->u.VadFlags.Spare == 1)) return;
/* Get the current process */
CurrentProcess = PsGetCurrentProcess();
/* Check if this is a section VAD or a VM VAD */
if (!(Vad) || (Vad->u.VadFlags.PrivateMemory) || !(Vad->FirstPrototypePte))
{
/* Don't worry about prototypes */
PrototypePte = LastPrototypePte = NULL;
}
else
{
/* Get the prototype PTE */
PrototypePte = Vad->FirstPrototypePte;
LastPrototypePte = Vad->FirstPrototypePte + 1;
}
/* In all cases, we don't support fork() yet */
ASSERT(CurrentProcess->CloneRoot == NULL);
/* Loop the PTE for each VA (EndingAddress is inclusive!) */
while (Va <= EndingAddress)
{
#if (_MI_PAGING_LEVELS >= 4)
/* Get the PXE and check if it's valid */
PointerPxe = MiAddressToPxe((PVOID)Va);
if (!PointerPxe->u.Hard.Valid)
{
/* Check for unmapped range and skip it */
if (!PointerPxe->u.Long)
{
/* There are gaps in the address space */
AddressGap = TRUE;
/* Update Va and continue looping */
Va = (ULONG_PTR)MiPxeToAddress(PointerPxe + 1);
continue;
}
/* Make the PXE valid */
MiMakeSystemAddressValid(MiPteToAddress(PointerPxe), CurrentProcess);
}
#endif
#if (_MI_PAGING_LEVELS >= 3)
/* Get the PPE and check if it's valid */
PointerPpe = MiAddressToPpe((PVOID)Va);
if (!PointerPpe->u.Hard.Valid)
{
/* Check for unmapped range and skip it */
if (!PointerPpe->u.Long)
{
/* There are gaps in the address space */
AddressGap = TRUE;
/* Update Va and continue looping */
Va = (ULONG_PTR)MiPpeToAddress(PointerPpe + 1);
continue;
}
/* Make the PPE valid */
MiMakeSystemAddressValid(MiPteToAddress(PointerPpe), CurrentProcess);
}
#endif
/* Skip invalid PDEs */
PointerPde = MiAddressToPde((PVOID)Va);
if (!PointerPde->u.Long)
{
/* There are gaps in the address space */
AddressGap = TRUE;
/* Check if all the PDEs are invalid, so there's nothing to free */
Va = (ULONG_PTR)MiPdeToAddress(PointerPde + 1);
continue;
}
/* Now check if the PDE is mapped in */
if (!PointerPde->u.Hard.Valid)
{
/* It isn't, so map it in */
PointerPte = MiPteToAddress(PointerPde);
MiMakeSystemAddressValid(PointerPte, CurrentProcess);
}
/* Now we should have a valid PDE, mapped in, and still have some VA */
ASSERT(PointerPde->u.Hard.Valid == 1);
ASSERT(Va <= EndingAddress);
/* Check if this is a section VAD with gaps in it */
if ((AddressGap) && (LastPrototypePte))
{
/* We need to skip to the next correct prototype PTE */
PrototypePte = MI_GET_PROTOTYPE_PTE_FOR_VPN(Vad, Va >> PAGE_SHIFT);
/* And we need the subsection to skip to the next last prototype PTE */
Subsection = MiLocateSubsection(Vad, Va >> PAGE_SHIFT);
if (Subsection)
{
/* Found it! */
LastPrototypePte = &Subsection->SubsectionBase[Subsection->PtesInSubsection];
}
else
{
/* No more subsections, we are done with prototype PTEs */
PrototypePte = NULL;
}
}
/* Lock the PFN Database while we delete the PTEs */
OldIrql = MiAcquirePfnLock();
PointerPte = MiAddressToPte(Va);
do
{
/* Making sure the PDE is still valid */
ASSERT(PointerPde->u.Hard.Valid == 1);
/* Capture the PDE and make sure it exists */
TempPte = *PointerPte;
if (TempPte.u.Long)
{
/* Check if the PTE is actually mapped in */
if (MI_IS_MAPPED_PTE(&TempPte))
{
/* Are we dealing with section VAD? */
if ((LastPrototypePte) && (PrototypePte > LastPrototypePte))
{
/* We need to skip to the next correct prototype PTE */
PrototypePte = MI_GET_PROTOTYPE_PTE_FOR_VPN(Vad, Va >> PAGE_SHIFT);
/* And we need the subsection to skip to the next last prototype PTE */
Subsection = MiLocateSubsection(Vad, Va >> PAGE_SHIFT);
if (Subsection)
{
/* Found it! */
LastPrototypePte = &Subsection->SubsectionBase[Subsection->PtesInSubsection];
}
else
{
/* No more subsections, we are done with prototype PTEs */
PrototypePte = NULL;
}
}
/* Check for prototype PTE */
if ((TempPte.u.Hard.Valid == 0) &&
(TempPte.u.Soft.Prototype == 1))
{
/* Just nuke it */
MI_ERASE_PTE(PointerPte);
}
else
{
/* Delete the PTE proper */
MiDeletePte(PointerPte,
(PVOID)Va,
CurrentProcess,
PrototypePte);
}
}
else
{
/* The PTE was never mapped, just nuke it here */
MI_ERASE_PTE(PointerPte);
}
if (MiDecrementPageTableReferences((PVOID)Va) == 0)
{
ASSERT(PointerPde->u.Long != 0);
/* Delete the PDE proper */
MiDeletePde(PointerPde, CurrentProcess);
/* Continue with the next PDE */
Va = (ULONG_PTR)MiPdeToAddress(PointerPde + 1);
/* Use this to detect address gaps */
PointerPte++;
break;
}
}
/* Update the address and PTE for it */
Va += PAGE_SIZE;
PointerPte++;
PrototypePte++;
} while ((Va & (PDE_MAPPED_VA - 1)) && (Va <= EndingAddress));
/* Release the lock */
MiReleasePfnLock(OldIrql);
if (Va > EndingAddress) return;
/* Check if we exited the loop regularly */
AddressGap = (PointerPte != MiAddressToPte(Va));
}
}
LONG
MiGetExceptionInfo(IN PEXCEPTION_POINTERS ExceptionInfo,
OUT PBOOLEAN HaveBadAddress,
OUT PULONG_PTR BadAddress)
{
PEXCEPTION_RECORD ExceptionRecord;
PAGED_CODE();
//
// Assume default
//
*HaveBadAddress = FALSE;
//
// Get the exception record
//
ExceptionRecord = ExceptionInfo->ExceptionRecord;
//
// Look at the exception code
//
if ((ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) ||
(ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) ||
(ExceptionRecord->ExceptionCode == STATUS_IN_PAGE_ERROR))
{
//
// We can tell the address if we have more than one parameter
//
if (ExceptionRecord->NumberParameters > 1)
{
//
// Return the address
//
*HaveBadAddress = TRUE;
*BadAddress = ExceptionRecord->ExceptionInformation[1];
}
}
//
// Continue executing the next handler
//
return EXCEPTION_EXECUTE_HANDLER;
}
NTSTATUS
NTAPI
MiDoMappedCopy(IN PEPROCESS SourceProcess,
IN PVOID SourceAddress,
IN PEPROCESS TargetProcess,
OUT PVOID TargetAddress,
IN SIZE_T BufferSize,
IN KPROCESSOR_MODE PreviousMode,
OUT PSIZE_T ReturnSize)
{
PFN_NUMBER MdlBuffer[(sizeof(MDL) / sizeof(PFN_NUMBER)) + MI_MAPPED_COPY_PAGES + 1];
PMDL Mdl = (PMDL)MdlBuffer;
SIZE_T TotalSize, CurrentSize, RemainingSize;
volatile BOOLEAN FailedInProbe = FALSE;
volatile BOOLEAN PagesLocked = FALSE;
PVOID CurrentAddress = SourceAddress, CurrentTargetAddress = TargetAddress;
volatile PVOID MdlAddress = NULL;
KAPC_STATE ApcState;
BOOLEAN HaveBadAddress;
ULONG_PTR BadAddress;
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
//
// Calculate the maximum amount of data to move
//
TotalSize = MI_MAPPED_COPY_PAGES * PAGE_SIZE;
if (BufferSize <= TotalSize) TotalSize = BufferSize;
CurrentSize = TotalSize;
RemainingSize = BufferSize;
//
// Loop as long as there is still data
//
while (RemainingSize > 0)
{
//
// Check if this transfer will finish everything off
//
if (RemainingSize < CurrentSize) CurrentSize = RemainingSize;
//
// Attach to the source address space
//
KeStackAttachProcess(&SourceProcess->Pcb, &ApcState);
//
// Check state for this pass
//
ASSERT(MdlAddress == NULL);
ASSERT(PagesLocked == FALSE);
ASSERT(FailedInProbe == FALSE);
//
// Protect user-mode copy
//
_SEH2_TRY
{
//
// If this is our first time, probe the buffer
//
if ((CurrentAddress == SourceAddress) && (PreviousMode != KernelMode))
{
//
// Catch a failure here
//
FailedInProbe = TRUE;
//
// Do the probe
//
ProbeForRead(SourceAddress, BufferSize, sizeof(CHAR));
//
// Passed
//
FailedInProbe = FALSE;
}
//
// Initialize and probe and lock the MDL
//
MmInitializeMdl(Mdl, CurrentAddress, CurrentSize);
MmProbeAndLockPages(Mdl, PreviousMode, IoReadAccess);
PagesLocked = TRUE;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END
/* Detach from source process */
KeUnstackDetachProcess(&ApcState);
if (Status != STATUS_SUCCESS)
{
goto Exit;
}
//
// Now map the pages
//
MdlAddress = MmMapLockedPagesSpecifyCache(Mdl,
KernelMode,
MmCached,
NULL,
FALSE,
HighPagePriority);
if (!MdlAddress)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
//
// Grab to the target process
//
KeStackAttachProcess(&TargetProcess->Pcb, &ApcState);
_SEH2_TRY
{
//
// Check if this is our first time through
//
if ((CurrentTargetAddress == TargetAddress) && (PreviousMode != KernelMode))
{
//
// Catch a failure here
//
FailedInProbe = TRUE;
//
// Do the probe
//
ProbeForWrite(TargetAddress, BufferSize, sizeof(CHAR));
//
// Passed
//
FailedInProbe = FALSE;
}
//
// Now do the actual move
//
RtlCopyMemory(CurrentTargetAddress, MdlAddress, CurrentSize);
}
_SEH2_EXCEPT(MiGetExceptionInfo(_SEH2_GetExceptionInformation(),
&HaveBadAddress,
&BadAddress))
{
*ReturnSize = BufferSize - RemainingSize;
//
// Check if we failed during the probe
//
if (FailedInProbe)
{
//
// Exit
//
Status = _SEH2_GetExceptionCode();
}
else
{
//
// Othewise we failed during the move.
// Check if we know exactly where we stopped copying
//
if (HaveBadAddress)
{
//
// Return the exact number of bytes copied
//
*ReturnSize = BadAddress - (ULONG_PTR)SourceAddress;
}
//
// Return partial copy
//
Status = STATUS_PARTIAL_COPY;
}
}
_SEH2_END;
/* Detach from target process */
KeUnstackDetachProcess(&ApcState);
//
// Check for SEH status
//
if (Status != STATUS_SUCCESS)
{
goto Exit;
}
//
// Unmap and unlock
//
MmUnmapLockedPages(MdlAddress, Mdl);
MdlAddress = NULL;
MmUnlockPages(Mdl);
PagesLocked = FALSE;
//
// Update location and size
//
RemainingSize -= CurrentSize;
CurrentAddress = (PVOID)((ULONG_PTR)CurrentAddress + CurrentSize);
CurrentTargetAddress = (PVOID)((ULONG_PTR)CurrentTargetAddress + CurrentSize);
}
Exit:
if (MdlAddress != NULL)
MmUnmapLockedPages(MdlAddress, Mdl);
if (PagesLocked)
MmUnlockPages(Mdl);
//
// All bytes read
//
if (Status == STATUS_SUCCESS)
*ReturnSize = BufferSize;
return Status;
}
NTSTATUS
NTAPI
MiDoPoolCopy(IN PEPROCESS SourceProcess,
IN PVOID SourceAddress,
IN PEPROCESS TargetProcess,
OUT PVOID TargetAddress,
IN SIZE_T BufferSize,
IN KPROCESSOR_MODE PreviousMode,
OUT PSIZE_T ReturnSize)
{
UCHAR StackBuffer[MI_POOL_COPY_BYTES];
SIZE_T TotalSize, CurrentSize, RemainingSize;
volatile BOOLEAN FailedInProbe = FALSE, HavePoolAddress = FALSE;
PVOID CurrentAddress = SourceAddress, CurrentTargetAddress = TargetAddress;
PVOID PoolAddress;
KAPC_STATE ApcState;
BOOLEAN HaveBadAddress;
ULONG_PTR BadAddress;
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
DPRINT("Copying %Iu bytes from process %p (address %p) to process %p (Address %p)\n",
BufferSize, SourceProcess, SourceAddress, TargetProcess, TargetAddress);
//
// Calculate the maximum amount of data to move
//
TotalSize = MI_MAX_TRANSFER_SIZE;
if (BufferSize <= MI_MAX_TRANSFER_SIZE) TotalSize = BufferSize;
CurrentSize = TotalSize;
RemainingSize = BufferSize;
//
// Check if we can use the stack
//
if (BufferSize <= MI_POOL_COPY_BYTES)
{
//
// Use it
//
PoolAddress = (PVOID)StackBuffer;
}
else
{
//
// Allocate pool
//
PoolAddress = ExAllocatePoolWithTag(NonPagedPool, TotalSize, 'VmRw');
if (!PoolAddress) ASSERT(FALSE);
HavePoolAddress = TRUE;
}
//
// Loop as long as there is still data
//
while (RemainingSize > 0)
{
//
// Check if this transfer will finish everything off
//
if (RemainingSize < CurrentSize) CurrentSize = RemainingSize;
//
// Attach to the source address space
//
KeStackAttachProcess(&SourceProcess->Pcb, &ApcState);
/* Check that state is sane */
ASSERT(FailedInProbe == FALSE);
ASSERT(Status == STATUS_SUCCESS);
//
// Protect user-mode copy
//
_SEH2_TRY
{
//
// If this is our first time, probe the buffer
//
if ((CurrentAddress == SourceAddress) && (PreviousMode != KernelMode))
{
//
// Catch a failure here
//
FailedInProbe = TRUE;
//
// Do the probe
//
ProbeForRead(SourceAddress, BufferSize, sizeof(CHAR));
//
// Passed
//
FailedInProbe = FALSE;
}
//
// Do the copy
//
RtlCopyMemory(PoolAddress, CurrentAddress, CurrentSize);
}
_SEH2_EXCEPT(MiGetExceptionInfo(_SEH2_GetExceptionInformation(),
&HaveBadAddress,
&BadAddress))
{
*ReturnSize = BufferSize - RemainingSize;
//
// Check if we failed during the probe
//
if (FailedInProbe)
{
//
// Exit
//
Status = _SEH2_GetExceptionCode();
}
else
{
//
// We failed during the move.
// Check if we know exactly where we stopped copying
//
if (HaveBadAddress)
{
//
// Return the exact number of bytes copied
//
*ReturnSize = BadAddress - (ULONG_PTR)SourceAddress;
}
//
// Return partial copy
//
Status = STATUS_PARTIAL_COPY;
}
}
_SEH2_END
/* Let go of the source */
KeUnstackDetachProcess(&ApcState);
if (Status != STATUS_SUCCESS)
{
goto Exit;
}
/* Grab the target process */
KeStackAttachProcess(&TargetProcess->Pcb, &ApcState);
_SEH2_TRY
{
//
// Check if this is our first time through
//
if ((CurrentTargetAddress == TargetAddress) && (PreviousMode != KernelMode))
{
//
// Catch a failure here
//
FailedInProbe = TRUE;
//
// Do the probe
//
ProbeForWrite(TargetAddress, BufferSize, sizeof(CHAR));
//
// Passed
//
FailedInProbe = FALSE;
}
//
// Now do the actual move
//
RtlCopyMemory(CurrentTargetAddress, PoolAddress, CurrentSize);
}
_SEH2_EXCEPT(MiGetExceptionInfo(_SEH2_GetExceptionInformation(),
&HaveBadAddress,
&BadAddress))
{
*ReturnSize = BufferSize - RemainingSize;
//
// Check if we failed during the probe
//
if (FailedInProbe)
{
//
// Exit
//
Status = _SEH2_GetExceptionCode();
}
else
{
//
// Otherwise we failed during the move.
// Check if we know exactly where we stopped copying
//
if (HaveBadAddress)
{
//
// Return the exact number of bytes copied
//
*ReturnSize = BadAddress - (ULONG_PTR)SourceAddress;
}
//
// Return partial copy
//
Status = STATUS_PARTIAL_COPY;
}
}
_SEH2_END;
//
// Detach from target
//
KeUnstackDetachProcess(&ApcState);
//
// Check for SEH status
//
if (Status != STATUS_SUCCESS)
{
goto Exit;
}
//
// Update location and size
//
RemainingSize -= CurrentSize;
CurrentAddress = (PVOID)((ULONG_PTR)CurrentAddress + CurrentSize);
CurrentTargetAddress = (PVOID)((ULONG_PTR)CurrentTargetAddress +
CurrentSize);
}
Exit:
//
// Check if we had allocated pool
//
if (HavePoolAddress)
ExFreePoolWithTag(PoolAddress, 'VmRw');
//
// All bytes read
//
if (Status == STATUS_SUCCESS)
*ReturnSize = BufferSize;
return Status;
}
NTSTATUS
NTAPI
MmCopyVirtualMemory(IN PEPROCESS SourceProcess,
IN PVOID SourceAddress,
IN PEPROCESS TargetProcess,
OUT PVOID TargetAddress,
IN SIZE_T BufferSize,
IN KPROCESSOR_MODE PreviousMode,
OUT PSIZE_T ReturnSize)
{
NTSTATUS Status;
PEPROCESS Process = SourceProcess;
//
// Don't accept zero-sized buffers
//
if (!BufferSize) return STATUS_SUCCESS;
//
// If we are copying from ourselves, lock the target instead
//
if (SourceProcess == PsGetCurrentProcess()) Process = TargetProcess;
//
// Acquire rundown protection
//
if (!ExAcquireRundownProtection(&Process->RundownProtect))
{
//
// Fail
//
return STATUS_PROCESS_IS_TERMINATING;
}
//
// See if we should use the pool copy
//
if (BufferSize > MI_POOL_COPY_BYTES)
{
//
// Use MDL-copy
//
Status = MiDoMappedCopy(SourceProcess,
SourceAddress,
TargetProcess,
TargetAddress,
BufferSize,
PreviousMode,
ReturnSize);
}
else
{
//
// Do pool copy
//
Status = MiDoPoolCopy(SourceProcess,
SourceAddress,
TargetProcess,
TargetAddress,
BufferSize,
PreviousMode,
ReturnSize);
}
//
// Release the lock
//
ExReleaseRundownProtection(&Process->RundownProtect);
return Status;
}
NTSTATUS
NTAPI
MmFlushVirtualMemory(IN PEPROCESS Process,
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T RegionSize,
OUT PIO_STATUS_BLOCK IoStatusBlock)
{
PAGED_CODE();
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
ULONG
NTAPI
MiGetPageProtection(IN PMMPTE PointerPte)
{
MMPTE TempPte;
PMMPFN Pfn;
PEPROCESS CurrentProcess;
PETHREAD CurrentThread;
BOOLEAN WsSafe, WsShared;
ULONG Protect;
KIRQL OldIrql;
PAGED_CODE();
/* Copy this PTE's contents */
TempPte = *PointerPte;
/* Assure it's not totally zero */
ASSERT(TempPte.u.Long);
/* Check for a special prototype format */
if ((TempPte.u.Soft.Valid == 0) &&
(TempPte.u.Soft.Prototype == 1))
{
/* Check if the prototype PTE is not yet pointing to a PTE */
if (TempPte.u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED)
{
/* The prototype PTE contains the protection */
return MmProtectToValue[TempPte.u.Soft.Protection];
}
/* Get a pointer to the underlying shared PTE */
PointerPte = MiProtoPteToPte(&TempPte);
/* Since the PTE we want to read can be paged out at any time, we need
to release the working set lock first, so that it can be paged in */
CurrentThread = PsGetCurrentThread();
CurrentProcess = PsGetCurrentProcess();
MiUnlockProcessWorkingSetForFault(CurrentProcess,
CurrentThread,
&WsSafe,
&WsShared);
/* Now read the PTE value */
TempPte = *PointerPte;
/* Check if that one is invalid */
if (!TempPte.u.Hard.Valid)
{
/* We get the protection directly from this PTE */
Protect = MmProtectToValue[TempPte.u.Soft.Protection];
}
else
{
/* The PTE is valid, so we might need to get the protection from
the PFN. Lock the PFN database */
OldIrql = MiAcquirePfnLock();
/* Check if the PDE is still valid */
if (MiAddressToPte(PointerPte)->u.Hard.Valid == 0)
{
/* It's not, make it valid */
MiMakeSystemAddressValidPfn(PointerPte, OldIrql);
}
/* Now it's safe to read the PTE value again */
TempPte = *PointerPte;
ASSERT(TempPte.u.Long != 0);
/* Check again if the PTE is invalid */
if (!TempPte.u.Hard.Valid)
{
/* The PTE is not valid, so we can use it's protection field */
Protect = MmProtectToValue[TempPte.u.Soft.Protection];
}
else
{
/* The PTE is valid, so we can find the protection in the
OriginalPte field of the PFN */
Pfn = MI_PFN_ELEMENT(TempPte.u.Hard.PageFrameNumber);
Protect = MmProtectToValue[Pfn->OriginalPte.u.Soft.Protection];
}
/* Release the PFN database */
MiReleasePfnLock(OldIrql);
}
/* Lock the working set again */
MiLockProcessWorkingSetForFault(CurrentProcess,
CurrentThread,
WsSafe,
WsShared);
return Protect;
}
/* In the easy case of transition or demand zero PTE just return its protection */
if (!TempPte.u.Hard.Valid) return MmProtectToValue[TempPte.u.Soft.Protection];
/* If we get here, the PTE is valid, so look up the page in PFN database */
Pfn = MiGetPfnEntry(TempPte.u.Hard.PageFrameNumber);
if (!Pfn->u3.e1.PrototypePte)
{
/* Return protection of the original pte */
ASSERT(Pfn->u4.AweAllocation == 0);
return MmProtectToValue[Pfn->OriginalPte.u.Soft.Protection];
}
/* This is software PTE */
DPRINT("Prototype PTE: %lx %p\n", TempPte.u.Hard.PageFrameNumber, Pfn);
DPRINT("VA: %p\n", MiPteToAddress(&TempPte));
DPRINT("Mask: %lx\n", TempPte.u.Soft.Protection);
DPRINT("Mask2: %lx\n", Pfn->OriginalPte.u.Soft.Protection);
return MmProtectToValue[TempPte.u.Soft.Protection];
}
ULONG
NTAPI
MiQueryAddressState(IN PVOID Va,
IN PMMVAD Vad,
IN PEPROCESS TargetProcess,
OUT PULONG ReturnedProtect,
OUT PVOID *NextVa)
{
PMMPTE PointerPte, ProtoPte;
PMMPDE PointerPde;
#if (_MI_PAGING_LEVELS >= 3)
PMMPPE PointerPpe;
#endif
#if (_MI_PAGING_LEVELS >= 4)
PMMPXE PointerPxe;
#endif
MMPTE TempPte, TempProtoPte;
BOOLEAN DemandZeroPte = TRUE, ValidPte = FALSE;
ULONG State = MEM_RESERVE, Protect = 0;
ASSERT((Vad->StartingVpn <= ((ULONG_PTR)Va >> PAGE_SHIFT)) &&
(Vad->EndingVpn >= ((ULONG_PTR)Va >> PAGE_SHIFT)));
/* Only normal VADs supported */
ASSERT(Vad->u.VadFlags.VadType == VadNone);
/* Get the PDE and PTE for the address */
PointerPde = MiAddressToPde(Va);
PointerPte = MiAddressToPte(Va);
#if (_MI_PAGING_LEVELS >= 3)
PointerPpe = MiAddressToPpe(Va);
#endif
#if (_MI_PAGING_LEVELS >= 4)
PointerPxe = MiAddressToPxe(Va);
#endif
/* Return the next range */
*NextVa = (PVOID)((ULONG_PTR)Va + PAGE_SIZE);
do
{
#if (_MI_PAGING_LEVELS >= 4)
/* Does the PXE exist? */
if (PointerPxe->u.Long == 0)
{
/* It does not, next range starts at the next PXE */
*NextVa = MiPxeToAddress(PointerPxe + 1);
break;
}
/* Is the PXE valid? */
if (PointerPxe->u.Hard.Valid == 0)
{
/* Is isn't, fault it in (make the PPE accessible) */
MiMakeSystemAddressValid(PointerPpe, TargetProcess);
}
#endif
#if (_MI_PAGING_LEVELS >= 3)
/* Does the PPE exist? */
if (PointerPpe->u.Long == 0)
{
/* It does not, next range starts at the next PPE */
*NextVa = MiPpeToAddress(PointerPpe + 1);
break;
}
/* Is the PPE valid? */
if (PointerPpe->u.Hard.Valid == 0)
{
/* Is isn't, fault it in (make the PDE accessible) */
MiMakeSystemAddressValid(PointerPde, TargetProcess);
}
#endif
/* Does the PDE exist? */
if (PointerPde->u.Long == 0)
{
/* It does not, next range starts at the next PDE */
*NextVa = MiPdeToAddress(PointerPde + 1);
break;
}
/* Is the PDE valid? */
if (PointerPde->u.Hard.Valid == 0)
{
/* Is isn't, fault it in (make the PTE accessible) */
MiMakeSystemAddressValid(PointerPte, TargetProcess);
}
/* We have a PTE that we can access now! */
ValidPte = TRUE;
} while (FALSE);
/* Is it safe to try reading the PTE? */
if (ValidPte)
{
/* FIXME: watch out for large pages */
ASSERT(PointerPde->u.Hard.LargePage == FALSE);
/* Capture the PTE */
TempPte = *PointerPte;
if (TempPte.u.Long != 0)
{
/* The PTE is valid, so it's not zeroed out */
DemandZeroPte = FALSE;
/* Is it a decommited, invalid, or faulted PTE? */
if ((TempPte.u.Soft.Protection == MM_DECOMMIT) &&
(TempPte.u.Hard.Valid == 0) &&
((TempPte.u.Soft.Prototype == 0) ||
(TempPte.u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED)))
{
/* Otherwise our defaults should hold */
ASSERT(Protect == 0);
ASSERT(State == MEM_RESERVE);
}
else
{
/* This means it's committed */
State = MEM_COMMIT;
/* We don't support these */
ASSERT(Vad->u.VadFlags.VadType != VadDevicePhysicalMemory);
ASSERT(Vad->u.VadFlags.VadType != VadRotatePhysical);
ASSERT(Vad->u.VadFlags.VadType != VadAwe);
/* Get protection state of this page */
Protect = MiGetPageProtection(PointerPte);
/* Check if this is an image-backed VAD */
if ((TempPte.u.Soft.Valid == 0) &&
(TempPte.u.Soft.Prototype == 1) &&
(Vad->u.VadFlags.PrivateMemory == 0) &&
(Vad->ControlArea))
{
DPRINT1("Not supported\n");
ASSERT(FALSE);
}
}
}
}
/* Check if this was a demand-zero PTE, since we need to find the state */
if (DemandZeroPte)
{
/* Not yet handled */
ASSERT(Vad->u.VadFlags.VadType != VadDevicePhysicalMemory);
ASSERT(Vad->u.VadFlags.VadType != VadAwe);
/* Check if this is private commited memory, or an section-backed VAD */
if ((Vad->u.VadFlags.PrivateMemory == 0) && (Vad->ControlArea))
{
/* Tell caller about the next range */
*NextVa = (PVOID)((ULONG_PTR)Va + PAGE_SIZE);
/* Get the prototype PTE for this VAD */
ProtoPte = MI_GET_PROTOTYPE_PTE_FOR_VPN(Vad,
(ULONG_PTR)Va >> PAGE_SHIFT);
if (ProtoPte)
{
/* We should unlock the working set, but it's not being held! */
/* Is the prototype PTE actually valid (committed)? */
TempProtoPte = *ProtoPte;
if (TempProtoPte.u.Long)
{
/* Unless this is a memory-mapped file, handle it like private VAD */
State = MEM_COMMIT;
ASSERT(Vad->u.VadFlags.VadType != VadImageMap);
Protect = MmProtectToValue[Vad->u.VadFlags.Protection];
}
/* We should re-lock the working set */
}
}
else if (Vad->u.VadFlags.MemCommit)
{
/* This is committed memory */
State = MEM_COMMIT;
/* Convert the protection */
Protect = MmProtectToValue[Vad->u.VadFlags.Protection];
}
}
/* Return the protection code */
*ReturnedProtect = Protect;
return State;
}
NTSTATUS
NTAPI
MiQueryMemoryBasicInformation(IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
OUT PVOID MemoryInformation,
IN SIZE_T MemoryInformationLength,
OUT PSIZE_T ReturnLength)
{
PEPROCESS TargetProcess;
NTSTATUS Status = STATUS_SUCCESS;
PMMVAD Vad = NULL;
PVOID Address, NextAddress;
BOOLEAN Found = FALSE;
ULONG NewProtect, NewState;
ULONG_PTR BaseVpn;
MEMORY_BASIC_INFORMATION MemoryInfo;
KAPC_STATE ApcState;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PMEMORY_AREA MemoryArea;
SIZE_T ResultLength;
/* Check for illegal addresses in user-space, or the shared memory area */
if ((BaseAddress > MM_HIGHEST_VAD_ADDRESS) ||
(PAGE_ALIGN(BaseAddress) == (PVOID)MM_SHARED_USER_DATA_VA))
{
Address = PAGE_ALIGN(BaseAddress);
/* Make up an info structure describing this range */
MemoryInfo.BaseAddress = Address;
MemoryInfo.AllocationProtect = PAGE_READONLY;
MemoryInfo.Type = MEM_PRIVATE;
/* Special case for shared data */
if (Address == (PVOID)MM_SHARED_USER_DATA_VA)
{
MemoryInfo.AllocationBase = (PVOID)MM_SHARED_USER_DATA_VA;
MemoryInfo.State = MEM_COMMIT;
MemoryInfo.Protect = PAGE_READONLY;
MemoryInfo.RegionSize = PAGE_SIZE;
}
else
{
MemoryInfo.AllocationBase = (PCHAR)MM_HIGHEST_VAD_ADDRESS + 1;
MemoryInfo.State = MEM_RESERVE;
MemoryInfo.Protect = PAGE_NOACCESS;
MemoryInfo.RegionSize = (ULONG_PTR)MM_HIGHEST_USER_ADDRESS + 1 - (ULONG_PTR)Address;
}
/* Return the data, NtQueryInformation already probed it*/
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
*(PMEMORY_BASIC_INFORMATION)MemoryInformation = MemoryInfo;
if (ReturnLength) *ReturnLength = sizeof(MEMORY_BASIC_INFORMATION);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
}
else
{
*(PMEMORY_BASIC_INFORMATION)MemoryInformation = MemoryInfo;
if (ReturnLength) *ReturnLength = sizeof(MEMORY_BASIC_INFORMATION);
}
return Status;
}
/* Check if this is for a local or remote process */
if (ProcessHandle == NtCurrentProcess())
{
TargetProcess = PsGetCurrentProcess();
}
else
{
/* Reference the target process */
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_QUERY_INFORMATION,
PsProcessType,
ExGetPreviousMode(),
(PVOID*)&TargetProcess,
NULL);
if (!NT_SUCCESS(Status)) return Status;
/* Attach to it now */
KeStackAttachProcess(&TargetProcess->Pcb, &ApcState);
}
/* Lock the address space and make sure the process isn't already dead */
MmLockAddressSpace(&TargetProcess->Vm);
if (TargetProcess->VmDeleted)
{
/* Unlock the address space of the process */
MmUnlockAddressSpace(&TargetProcess->Vm);
/* Check if we were attached */
if (ProcessHandle != NtCurrentProcess())
{
/* Detach and dereference the process */
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(TargetProcess);
}
/* Bail out */
DPRINT1("Process is dying\n");
return STATUS_PROCESS_IS_TERMINATING;
}
/* Loop the VADs */
ASSERT(TargetProcess->VadRoot.NumberGenericTableElements);
if (TargetProcess->VadRoot.NumberGenericTableElements)
{
/* Scan on the right */
Vad = (PMMVAD)TargetProcess->VadRoot.BalancedRoot.RightChild;
BaseVpn = (ULONG_PTR)BaseAddress >> PAGE_SHIFT;
while (Vad)
{
/* Check if this VAD covers the allocation range */
if ((BaseVpn >= Vad->StartingVpn) &&
(BaseVpn <= Vad->EndingVpn))
{
/* We're done */
Found = TRUE;
break;
}
/* Check if this VAD is too high */
if (BaseVpn < Vad->StartingVpn)
{
/* Stop if there is no left child */
if (!Vad->LeftChild) break;
/* Search on the left next */
Vad = Vad->LeftChild;
}
else
{
/* Then this VAD is too low, keep searching on the right */
ASSERT(BaseVpn > Vad->EndingVpn);
/* Stop if there is no right child */
if (!Vad->RightChild) break;
/* Search on the right next */
Vad = Vad->RightChild;
}
}
}
/* Was a VAD found? */
if (!Found)
{
Address = PAGE_ALIGN(BaseAddress);
/* Calculate region size */
if (Vad)
{
if (Vad->StartingVpn >= BaseVpn)
{
/* Region size is the free space till the start of that VAD */
MemoryInfo.RegionSize = (ULONG_PTR)(Vad->StartingVpn << PAGE_SHIFT) - (ULONG_PTR)Address;
}
else
{
/* Get the next VAD */
Vad = (PMMVAD)MiGetNextNode((PMMADDRESS_NODE)Vad);
if (Vad)
{
/* Region size is the free space till the start of that VAD */
MemoryInfo.RegionSize = (ULONG_PTR)(Vad->StartingVpn << PAGE_SHIFT) - (ULONG_PTR)Address;
}
else
{
/* Maximum possible region size with that base address */
MemoryInfo.RegionSize = (PCHAR)MM_HIGHEST_VAD_ADDRESS + 1 - (PCHAR)Address;
}
}
}
else
{
/* Maximum possible region size with that base address */
MemoryInfo.RegionSize = (PCHAR)MM_HIGHEST_VAD_ADDRESS + 1 - (PCHAR)Address;
}
/* Unlock the address space of the process */
MmUnlockAddressSpace(&TargetProcess->Vm);
/* Check if we were attached */
if (ProcessHandle != NtCurrentProcess())
{
/* Detach and dereference the process */
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(TargetProcess);
}
/* Build the rest of the initial information block */
MemoryInfo.BaseAddress = Address;
MemoryInfo.AllocationBase = NULL;
MemoryInfo.AllocationProtect = 0;
MemoryInfo.State = MEM_FREE;
MemoryInfo.Protect = PAGE_NOACCESS;
MemoryInfo.Type = 0;
/* Return the data, NtQueryInformation already probed it*/
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
*(PMEMORY_BASIC_INFORMATION)MemoryInformation = MemoryInfo;
if (ReturnLength) *ReturnLength = sizeof(MEMORY_BASIC_INFORMATION);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
}
else
{
*(PMEMORY_BASIC_INFORMATION)MemoryInformation = MemoryInfo;
if (ReturnLength) *ReturnLength = sizeof(MEMORY_BASIC_INFORMATION);
}
return Status;
}
/* Set the correct memory type based on what kind of VAD this is */
if ((Vad->u.VadFlags.PrivateMemory) ||
(Vad->u.VadFlags.VadType == VadRotatePhysical))
{
MemoryInfo.Type = MEM_PRIVATE;
}
else if (Vad->u.VadFlags.VadType == VadImageMap)
{
MemoryInfo.Type = MEM_IMAGE;
}
else
{
MemoryInfo.Type = MEM_MAPPED;
}
/* Find the memory area the specified address belongs to */
MemoryArea = MmLocateMemoryAreaByAddress(&TargetProcess->Vm, BaseAddress);
ASSERT(MemoryArea != NULL);
/* Determine information dependent on the memory area type */
if (MemoryArea->Type == MEMORY_AREA_SECTION_VIEW)
{
Status = MmQuerySectionView(MemoryArea, BaseAddress, &MemoryInfo, &ResultLength);
if (!NT_SUCCESS(Status))
{
DPRINT1("MmQuerySectionView failed. MemoryArea=%p (%p-%p), BaseAddress=%p\n",
MemoryArea, MA_GetStartingAddress(MemoryArea), MA_GetEndingAddress(MemoryArea), BaseAddress);
ASSERT(NT_SUCCESS(Status));
}
}
else
{
/* Build the initial information block */
Address = PAGE_ALIGN(BaseAddress);
MemoryInfo.BaseAddress = Address;
MemoryInfo.AllocationBase = (PVOID)(Vad->StartingVpn << PAGE_SHIFT);
MemoryInfo.AllocationProtect = MmProtectToValue[Vad->u.VadFlags.Protection];
MemoryInfo.Type = MEM_PRIVATE;
/* Acquire the working set lock (shared is enough) */
MiLockProcessWorkingSetShared(TargetProcess, PsGetCurrentThread());
/* Find the largest chunk of memory which has the same state and protection mask */
MemoryInfo.State = MiQueryAddressState(Address,
Vad,
TargetProcess,
&MemoryInfo.Protect,
&NextAddress);
Address = NextAddress;
while (((ULONG_PTR)Address >> PAGE_SHIFT) <= Vad->EndingVpn)
{
/* Keep going unless the state or protection mask changed */
NewState = MiQueryAddressState(Address, Vad, TargetProcess, &NewProtect, &NextAddress);
if ((NewState != MemoryInfo.State) || (NewProtect != MemoryInfo.Protect)) break;
Address = NextAddress;
}
/* Release the working set lock */
MiUnlockProcessWorkingSetShared(TargetProcess, PsGetCurrentThread());
/* Check if we went outside of the VAD */
if (((ULONG_PTR)Address >> PAGE_SHIFT) > Vad->EndingVpn)
{
/* Set the end of the VAD as the end address */
Address = (PVOID)((Vad->EndingVpn + 1) << PAGE_SHIFT);
}
/* Now that we know the last VA address, calculate the region size */
MemoryInfo.RegionSize = ((ULONG_PTR)Address - (ULONG_PTR)MemoryInfo.BaseAddress);
}
/* Unlock the address space of the process */
MmUnlockAddressSpace(&TargetProcess->Vm);
/* Check if we were attached */
if (ProcessHandle != NtCurrentProcess())
{
/* Detach and dereference the process */
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(TargetProcess);
}
/* Return the data, NtQueryInformation already probed it */
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
*(PMEMORY_BASIC_INFORMATION)MemoryInformation = MemoryInfo;
if (ReturnLength) *ReturnLength = sizeof(MEMORY_BASIC_INFORMATION);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
}
else
{
*(PMEMORY_BASIC_INFORMATION)MemoryInformation = MemoryInfo;
if (ReturnLength) *ReturnLength = sizeof(MEMORY_BASIC_INFORMATION);
}
/* All went well */
DPRINT("Base: %p AllocBase: %p AllocProtect: %lx Protect: %lx "
"State: %lx Type: %lx Size: %lx\n",
MemoryInfo.BaseAddress, MemoryInfo.AllocationBase,
MemoryInfo.AllocationProtect, MemoryInfo.Protect,
MemoryInfo.State, MemoryInfo.Type, MemoryInfo.RegionSize);
return Status;
}
BOOLEAN
NTAPI
MiIsEntireRangeCommitted(IN ULONG_PTR StartingAddress,
IN ULONG_PTR EndingAddress,
IN PMMVAD Vad,
IN PEPROCESS Process)
{
PMMPTE PointerPte, LastPte;
PMMPDE PointerPde;
BOOLEAN OnPdeBoundary = TRUE;
#if _MI_PAGING_LEVELS >= 3
PMMPPE PointerPpe;
BOOLEAN OnPpeBoundary = TRUE;
#if _MI_PAGING_LEVELS == 4
PMMPXE PointerPxe;
BOOLEAN OnPxeBoundary = TRUE;
#endif
#endif
PAGED_CODE();
/* Check that we hols the right locks */
ASSERT(PsGetCurrentThread()->OwnsProcessWorkingSetExclusive || PsGetCurrentThread()->OwnsProcessWorkingSetShared);
/* Get the PTE addresses */
PointerPte = MiAddressToPte(StartingAddress);
LastPte = MiAddressToPte(EndingAddress);
/* Loop all the PTEs */
while (PointerPte <= LastPte)
{
#if _MI_PAGING_LEVELS == 4
/* Check for new PXE boundary */
if (OnPxeBoundary)
{
PointerPxe = MiPteToPxe(PointerPte);
/* Check that this loop is sane */
ASSERT(OnPpeBoundary);
ASSERT(OnPdeBoundary);
if (PointerPxe->u.Long != 0)
{
/* Make it valid if needed */
if (PointerPxe->u.Hard.Valid == 0)
MiMakeSystemAddressValid(MiPteToPpe(PointerPte), Process);
}
else
{
/* Is the entire VAD committed? If not, fail */
if (!Vad->u.VadFlags.MemCommit) return FALSE;
PointerPxe++;
PointerPte = MiPxeToPte(PointerPte);
continue;
}
}
#endif
#if _MI_PAGING_LEVELS >= 3
/* Check for new PPE boundary */
if (OnPpeBoundary)
{
PointerPpe = MiPteToPpe(PointerPte);
/* Check that this loop is sane */
ASSERT(OnPdeBoundary);
if (PointerPpe->u.Long != 0)
{
/* Make it valid if needed */
if (PointerPpe->u.Hard.Valid == 0)
MiMakeSystemAddressValid(MiPteToPde(PointerPte), Process);
}
else
{
/* Is the entire VAD committed? If not, fail */
if (!Vad->u.VadFlags.MemCommit) return FALSE;
PointerPpe++;
PointerPte = MiPpeToPte(PointerPpe);
#if _MI_PAGING_LEVELS == 4
OnPxeBoundary = MiIsPteOnPxeBoundary(PointerPte);
#endif
continue;
}
}
#endif
/* Check if we've hit a new PDE boundary */
if (OnPdeBoundary)
{
/* Is this PDE demand zero? */
PointerPde = MiPteToPde(PointerPte);
if (PointerPde->u.Long != 0)
{
/* It isn't -- is it valid? */
if (PointerPde->u.Hard.Valid == 0)
{
/* Nope, fault it in */
MiMakeSystemAddressValid(PointerPte, Process);
}
}
else
{
/* Is the entire VAD committed? If not, fail */
if (!Vad->u.VadFlags.MemCommit) return FALSE;
/* The PTE was already valid, so move to the next one */
PointerPde++;
PointerPte = MiPdeToPte(PointerPde);
#if _MI_PAGING_LEVELS >= 3
OnPpeBoundary = MiIsPteOnPpeBoundary(PointerPte);
#if _MI_PAGING_LEVELS == 4
OnPxeBoundary = MiIsPteOnPxeBoundary(PointerPte);
#endif
#endif
/* New loop iteration with our new, on-boundary PTE. */
continue;
}
}
/* Is the PTE demand zero? */
if (PointerPte->u.Long == 0)
{
/* Is the entire VAD committed? If not, fail */
if (!Vad->u.VadFlags.MemCommit) return FALSE;
}
else
{
/* It isn't -- is it a decommited, invalid, or faulted PTE? */
if ((PointerPte->u.Soft.Protection == MM_DECOMMIT) &&
(PointerPte->u.Hard.Valid == 0) &&
((PointerPte->u.Soft.Prototype == 0) ||
(PointerPte->u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED)))
{
/* Then part of the range is decommitted, so fail */
return FALSE;
}
}
/* Move to the next PTE */
PointerPte++;
OnPdeBoundary = MiIsPteOnPdeBoundary(PointerPte);
#if _MI_PAGING_LEVELS >= 3
OnPpeBoundary = MiIsPteOnPpeBoundary(PointerPte);
#if _MI_PAGING_LEVELS == 4
OnPxeBoundary = MiIsPteOnPxeBoundary(PointerPte);
#endif
#endif
}
/* All PTEs seem valid, and no VAD checks failed, the range is okay */
return TRUE;
}
NTSTATUS
NTAPI
MiRosProtectVirtualMemory(IN PEPROCESS Process,
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T NumberOfBytesToProtect,
IN ULONG NewAccessProtection,
OUT PULONG OldAccessProtection OPTIONAL)
{
PMEMORY_AREA MemoryArea;
PMMSUPPORT AddressSpace;
ULONG OldAccessProtection_;
NTSTATUS Status;
*NumberOfBytesToProtect = PAGE_ROUND_UP((ULONG_PTR)(*BaseAddress) + (*NumberOfBytesToProtect)) - PAGE_ROUND_DOWN(*BaseAddress);
*BaseAddress = (PVOID)PAGE_ROUND_DOWN(*BaseAddress);
AddressSpace = &Process->Vm;
MmLockAddressSpace(AddressSpace);
MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, *BaseAddress);
if (MemoryArea == NULL || MemoryArea->DeleteInProgress)
{
MmUnlockAddressSpace(AddressSpace);
return STATUS_UNSUCCESSFUL;
}
if (OldAccessProtection == NULL) OldAccessProtection = &OldAccessProtection_;
ASSERT(MemoryArea->Type == MEMORY_AREA_SECTION_VIEW);
Status = MmProtectSectionView(AddressSpace,
MemoryArea,
*BaseAddress,
*NumberOfBytesToProtect,
NewAccessProtection,
OldAccessProtection);
MmUnlockAddressSpace(AddressSpace);
return Status;
}
NTSTATUS
NTAPI
MiProtectVirtualMemory(IN PEPROCESS Process,
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T NumberOfBytesToProtect,
IN ULONG NewAccessProtection,
OUT PULONG OldAccessProtection OPTIONAL)
{
PMEMORY_AREA MemoryArea;
PMMVAD Vad;
PMMSUPPORT AddressSpace;
ULONG_PTR StartingAddress, EndingAddress;
PMMPTE PointerPte, LastPte;
PMMPDE PointerPde;
MMPTE PteContents;
PMMPFN Pfn1;
ULONG ProtectionMask, OldProtect;
BOOLEAN Committed;
NTSTATUS Status = STATUS_SUCCESS;
PETHREAD Thread = PsGetCurrentThread();
TABLE_SEARCH_RESULT Result;
/* Calculate base address for the VAD */
StartingAddress = (ULONG_PTR)PAGE_ALIGN((*BaseAddress));
EndingAddress = (((ULONG_PTR)*BaseAddress + *NumberOfBytesToProtect - 1) | (PAGE_SIZE - 1));
/* Calculate the protection mask and make sure it's valid */
ProtectionMask = MiMakeProtectionMask(NewAccessProtection);
if (ProtectionMask == MM_INVALID_PROTECTION)
{
DPRINT1("Invalid protection mask\n");
return STATUS_INVALID_PAGE_PROTECTION;
}
/* Check for ROS specific memory area */
MemoryArea = MmLocateMemoryAreaByAddress(&Process->Vm, *BaseAddress);
if ((MemoryArea) && (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3))
{
/* Evil hack */
return MiRosProtectVirtualMemory(Process,
BaseAddress,
NumberOfBytesToProtect,
NewAccessProtection,
OldAccessProtection);
}
/* Lock the address space and make sure the process isn't already dead */
AddressSpace = MmGetCurrentAddressSpace();
MmLockAddressSpace(AddressSpace);
if (Process->VmDeleted)
{
DPRINT1("Process is dying\n");
Status = STATUS_PROCESS_IS_TERMINATING;
goto FailPath;
}
/* Get the VAD for this address range, and make sure it exists */
Result = MiCheckForConflictingNode(StartingAddress >> PAGE_SHIFT,
EndingAddress >> PAGE_SHIFT,
&Process->VadRoot,
(PMMADDRESS_NODE*)&Vad);
if (Result != TableFoundNode)
{
DPRINT("Could not find a VAD for this allocation\n");
Status = STATUS_CONFLICTING_ADDRESSES;
goto FailPath;
}
/* Make sure the address is within this VAD's boundaries */
if ((((ULONG_PTR)StartingAddress >> PAGE_SHIFT) < Vad->StartingVpn) ||
(((ULONG_PTR)EndingAddress >> PAGE_SHIFT) > Vad->EndingVpn))
{
Status = STATUS_CONFLICTING_ADDRESSES;
goto FailPath;
}
/* These kinds of VADs are not supported atm */
if ((Vad->u.VadFlags.VadType == VadAwe) ||
(Vad->u.VadFlags.VadType == VadDevicePhysicalMemory) ||
(Vad->u.VadFlags.VadType == VadLargePages))
{
DPRINT1("Illegal VAD for attempting to set protection\n");
Status = STATUS_CONFLICTING_ADDRESSES;
goto FailPath;
}
/* Check for a VAD whose protection can't be changed */
if (Vad->u.VadFlags.NoChange == 1)
{
DPRINT1("Trying to change protection of a NoChange VAD\n");
Status = STATUS_INVALID_PAGE_PROTECTION;
goto FailPath;
}
/* Is this section, or private memory? */
if (Vad->u.VadFlags.PrivateMemory == 0)
{
/* Not yet supported */
if (Vad->u.VadFlags.VadType == VadLargePageSection)
{
DPRINT1("Illegal VAD for attempting to set protection\n");
Status = STATUS_CONFLICTING_ADDRESSES;
goto FailPath;
}
/* Rotate VADs are not yet supported */
if (Vad->u.VadFlags.VadType == VadRotatePhysical)
{
DPRINT1("Illegal VAD for attempting to set protection\n");
Status = STATUS_CONFLICTING_ADDRESSES;
goto FailPath;
}
/* Not valid on section files */
if (NewAccessProtection & (PAGE_NOCACHE | PAGE_WRITECOMBINE))
{
/* Fail */
DPRINT1("Invalid protection flags for section\n");
Status = STATUS_INVALID_PARAMETER_4;
goto FailPath;
}
/* Check if data or page file mapping protection PTE is compatible */
if (!Vad->ControlArea->u.Flags.Image)
{
/* Not yet */
DPRINT1("Fixme: Not checking for valid protection\n");
}
/* This is a section, and this is not yet supported */
DPRINT1("Section protection not yet supported\n");
OldProtect = 0;
}
else
{
/* Private memory, check protection flags */
if ((NewAccessProtection & PAGE_WRITECOPY) ||
(NewAccessProtection & PAGE_EXECUTE_WRITECOPY))
{
DPRINT1("Invalid protection flags for private memory\n");
Status = STATUS_INVALID_PARAMETER_4;
goto FailPath;
}
/* Lock the working set */
MiLockProcessWorkingSetUnsafe(Process, Thread);
/* Check if all pages in this range are committed */
Committed = MiIsEntireRangeCommitted(StartingAddress,
EndingAddress,
Vad,
Process);
if (!Committed)
{
/* Fail */
DPRINT1("The entire range is not committed\n");
Status = STATUS_NOT_COMMITTED;
MiUnlockProcessWorkingSetUnsafe(Process, Thread);
goto FailPath;
}
/* Compute starting and ending PTE and PDE addresses */
PointerPde = MiAddressToPde(StartingAddress);
PointerPte = MiAddressToPte(StartingAddress);
LastPte = MiAddressToPte(EndingAddress);
/* Make this PDE valid */
MiMakePdeExistAndMakeValid(PointerPde, Process, MM_NOIRQL);
/* Save protection of the first page */
if (PointerPte->u.Long != 0)
{
/* Capture the page protection and make the PDE valid */
OldProtect = MiGetPageProtection(PointerPte);
MiMakePdeExistAndMakeValid(PointerPde, Process, MM_NOIRQL);
}
else
{
/* Grab the old protection from the VAD itself */
OldProtect = MmProtectToValue[Vad->u.VadFlags.Protection];
}
/* Loop all the PTEs now */
while (PointerPte <= LastPte)
{
/* Check if we've crossed a PDE boundary and make the new PDE valid too */
if (MiIsPteOnPdeBoundary(PointerPte))
{
PointerPde = MiPteToPde(PointerPte);
MiMakePdeExistAndMakeValid(PointerPde, Process, MM_NOIRQL);
}
/* Capture the PTE and check if it was empty */
PteContents = *PointerPte;
if (PteContents.u.Long == 0)
{
/* This used to be a zero PTE and it no longer is, so we must add a
reference to the pagetable. */
MiIncrementPageTableReferences(MiPteToAddress(PointerPte));
}
/* Check what kind of PTE we are dealing with */
if (PteContents.u.Hard.Valid == 1)
{
/* Get the PFN entry */
Pfn1 = MiGetPfnEntry(PFN_FROM_PTE(&PteContents));
/* We don't support this yet */
ASSERT(Pfn1->u3.e1.PrototypePte == 0);
/* Check if the page should not be accessible at all */
if ((NewAccessProtection & PAGE_NOACCESS) ||
(NewAccessProtection & PAGE_GUARD))
{
KIRQL OldIrql = MiAcquirePfnLock();
/* Mark the PTE as transition and change its protection */
PteContents.u.Hard.Valid = 0;
PteContents.u.Soft.Transition = 1;
PteContents.u.Trans.Protection = ProtectionMask;
/* Decrease PFN share count and write the PTE */
MiDecrementShareCount(Pfn1, PFN_FROM_PTE(&PteContents));
// FIXME: remove the page from the WS
MI_WRITE_INVALID_PTE(PointerPte, PteContents);
#ifdef CONFIG_SMP
// FIXME: Should invalidate entry in every CPU TLB
ASSERT(KeNumberProcessors == 1);
#endif
KeInvalidateTlbEntry(MiPteToAddress(PointerPte));
/* We are done for this PTE */
MiReleasePfnLock(OldIrql);
}
else
{
/* Write the protection mask and write it with a TLB flush */
Pfn1->OriginalPte.u.Soft.Protection = ProtectionMask;
MiFlushTbAndCapture(Vad,
PointerPte,
ProtectionMask,
Pfn1,
TRUE);
}
}
else
{
/* We don't support these cases yet */
ASSERT(PteContents.u.Soft.Prototype == 0);
//ASSERT(PteContents.u.Soft.Transition == 0);
/* The PTE is already demand-zero, just update the protection mask */
PteContents.u.Soft.Protection = ProtectionMask;
MI_WRITE_INVALID_PTE(PointerPte, PteContents);
ASSERT(PointerPte->u.Long != 0);
}
/* Move to the next PTE */
PointerPte++;
}
/* Unlock the working set */
MiUnlockProcessWorkingSetUnsafe(Process, Thread);
}
/* Unlock the address space */
MmUnlockAddressSpace(AddressSpace);
/* Return parameters and success */
*NumberOfBytesToProtect = EndingAddress - StartingAddress + 1;
*BaseAddress = (PVOID)StartingAddress;
*OldAccessProtection = OldProtect;
return STATUS_SUCCESS;
FailPath:
/* Unlock the address space and return the failure code */
MmUnlockAddressSpace(AddressSpace);
return Status;
}
VOID
NTAPI
MiMakePdeExistAndMakeValid(IN PMMPDE PointerPde,
IN PEPROCESS TargetProcess,
IN KIRQL OldIrql)
{
PMMPTE PointerPte;
#if _MI_PAGING_LEVELS >= 3
PMMPPE PointerPpe = MiPdeToPpe(PointerPde);
#if _MI_PAGING_LEVELS == 4
PMMPXE PointerPxe = MiPdeToPxe(PointerPde);
#endif
#endif
//
// Sanity checks. The latter is because we only use this function with the
// PFN lock not held, so it may go away in the future.
//
ASSERT(KeAreAllApcsDisabled() == TRUE);
ASSERT(OldIrql == MM_NOIRQL);
//
// If everything is already valid, there is nothing to do.
//
if (
#if _MI_PAGING_LEVELS == 4
(PointerPxe->u.Hard.Valid) &&
#endif
#if _MI_PAGING_LEVELS >= 3
(PointerPpe->u.Hard.Valid) &&
#endif
(PointerPde->u.Hard.Valid))
{
return;
}
//
// At least something is invalid, so begin by getting the PTE for the PDE itself
// and then lookup each additional level. We must do it in this precise order
// because the pagfault.c code (as well as in Windows) depends that the next
// level up (higher) must be valid when faulting a lower level
//
PointerPte = MiPteToAddress(PointerPde);
do
{
//
// Make sure APCs continued to be disabled
//
ASSERT(KeAreAllApcsDisabled() == TRUE);
#if _MI_PAGING_LEVELS == 4
//
// First, make the PXE valid if needed
//
if (!PointerPxe->u.Hard.Valid)
{
MiMakeSystemAddressValid(PointerPpe, TargetProcess);
ASSERT(PointerPxe->u.Hard.Valid == 1);
}
#endif
#if _MI_PAGING_LEVELS >= 3
//
// Next, the PPE
//
if (!PointerPpe->u.Hard.Valid)
{
MiMakeSystemAddressValid(PointerPde, TargetProcess);
ASSERT(PointerPpe->u.Hard.Valid == 1);
}
#endif
//
// And finally, make the PDE itself valid.
//
MiMakeSystemAddressValid(PointerPte, TargetProcess);
/* Do not increment Page table refcount here for the PDE, this must be managed by caller */
//
// This should've worked the first time so the loop is really just for
// show -- ASSERT that we're actually NOT going to be looping.
//
ASSERT(PointerPde->u.Hard.Valid == 1);
} while (
#if _MI_PAGING_LEVELS == 4
!PointerPxe->u.Hard.Valid ||
#endif
#if _MI_PAGING_LEVELS >= 3
!PointerPpe->u.Hard.Valid ||
#endif
!PointerPde->u.Hard.Valid);
}
VOID
NTAPI
MiProcessValidPteList(IN PMMPTE *ValidPteList,
IN ULONG Count)
{
KIRQL OldIrql;
ULONG i;
MMPTE TempPte;
PFN_NUMBER PageFrameIndex;
PMMPFN Pfn1, Pfn2;
//
// Acquire the PFN lock and loop all the PTEs in the list
//
OldIrql = MiAcquirePfnLock();
for (i = 0; i != Count; i++)
{
//
// The PTE must currently be valid
//
TempPte = *ValidPteList[i];
ASSERT(TempPte.u.Hard.Valid == 1);
//
// Get the PFN entry for the page itself, and then for its page table
//
PageFrameIndex = PFN_FROM_PTE(&TempPte);
Pfn1 = MiGetPfnEntry(PageFrameIndex);
Pfn2 = MiGetPfnEntry(Pfn1->u4.PteFrame);
//
// Decrement the share count on the page table, and then on the page
// itself
//
MiDecrementShareCount(Pfn2, Pfn1->u4.PteFrame);
MI_SET_PFN_DELETED(Pfn1);
MiDecrementShareCount(Pfn1, PageFrameIndex);
//
// Make the page decommitted
//
MI_WRITE_INVALID_PTE(ValidPteList[i], MmDecommittedPte);
}
//
// All the PTEs have been dereferenced and made invalid, flush the TLB now
// and then release the PFN lock
//
KeFlushCurrentTb();
MiReleasePfnLock(OldIrql);
}
ULONG
NTAPI
MiDecommitPages(IN PVOID StartingAddress,
IN PMMPTE EndingPte,
IN PEPROCESS Process,
IN PMMVAD Vad)
{
PMMPTE PointerPte, CommitPte = NULL;
PMMPDE PointerPde;
ULONG CommitReduction = 0;
PMMPTE ValidPteList[256];
ULONG PteCount = 0;
PMMPFN Pfn1;
MMPTE PteContents;
PETHREAD CurrentThread = PsGetCurrentThread();
//
// Get the PTE and PTE for the address, and lock the working set
// If this was a VAD for a MEM_COMMIT allocation, also figure out where the
// commited range ends so that we can do the right accounting.
//
PointerPde = MiAddressToPde(StartingAddress);
PointerPte = MiAddressToPte(StartingAddress);
if (Vad->u.VadFlags.MemCommit) CommitPte = MiAddressToPte(Vad->EndingVpn << PAGE_SHIFT);
MiLockProcessWorkingSetUnsafe(Process, CurrentThread);
//
// Make the PDE valid, and now loop through each page's worth of data
//
MiMakePdeExistAndMakeValid(PointerPde, Process, MM_NOIRQL);
while (PointerPte <= EndingPte)
{
//
// Check if we've crossed a PDE boundary
//
if (MiIsPteOnPdeBoundary(PointerPte))
{
//
// Get the new PDE and flush the valid PTEs we had built up until
// now. This helps reduce the amount of TLB flushing we have to do.
// Note that Windows does a much better job using timestamps and
// such, and does not flush the entire TLB all the time, but right
// now we have bigger problems to worry about than TLB flushing.
//
PointerPde = MiAddressToPde(StartingAddress);
if (PteCount)
{
MiProcessValidPteList(ValidPteList, PteCount);
PteCount = 0;
}
//
// Make this PDE valid
//
MiMakePdeExistAndMakeValid(PointerPde, Process, MM_NOIRQL);
}
//
// Read this PTE. It might be active or still demand-zero.
//
PteContents = *PointerPte;
if (PteContents.u.Long)
{
//
// The PTE is active. It might be valid and in a working set, or
// it might be a prototype PTE or paged out or even in transition.
//
if (PointerPte->u.Long == MmDecommittedPte.u.Long)
{
//
// It's already decommited, so there's nothing for us to do here
//
CommitReduction++;
}
else
{
//
// Remove it from the counters, and check if it was valid or not
//
//Process->NumberOfPrivatePages--;
if (PteContents.u.Hard.Valid)
{
//
// It's valid. At this point make sure that it is not a ROS
// PFN. Also, we don't support ProtoPTEs in this code path.
//
Pfn1 = MiGetPfnEntry(PteContents.u.Hard.PageFrameNumber);
ASSERT(MI_IS_ROS_PFN(Pfn1) == FALSE);
ASSERT(Pfn1->u3.e1.PrototypePte == FALSE);
//
// Flush any pending PTEs that we had not yet flushed, if our
// list has gotten too big, then add this PTE to the flush list.
//
if (PteCount == 256)
{
MiProcessValidPteList(ValidPteList, PteCount);
PteCount = 0;
}
ValidPteList[PteCount++] = PointerPte;
}
else
{
//
// We do not support any of these other scenarios at the moment
//
ASSERT(PteContents.u.Soft.Prototype == 0);
ASSERT(PteContents.u.Soft.Transition == 0);
ASSERT(PteContents.u.Soft.PageFileHigh == 0);
//
// So the only other possibility is that it is still a demand
// zero PTE, in which case we undo the accounting we did
// earlier and simply make the page decommitted.
//
//Process->NumberOfPrivatePages++;
MI_WRITE_INVALID_PTE(PointerPte, MmDecommittedPte);
}
}
}
else
{
//
// This used to be a zero PTE and it no longer is, so we must add a
// reference to the pagetable.
//
MiIncrementPageTableReferences(StartingAddress);
//
// Next, we account for decommitted PTEs and make the PTE as such
//
if (PointerPte > CommitPte) CommitReduction++;
MI_WRITE_INVALID_PTE(PointerPte, MmDecommittedPte);
}
//
// Move to the next PTE and the next address
//
PointerPte++;
StartingAddress = (PVOID)((ULONG_PTR)StartingAddress + PAGE_SIZE);
}
//
// Flush any dangling PTEs from the loop in the last page table, and then
// release the working set and return the commit reduction accounting.
//
if (PteCount) MiProcessValidPteList(ValidPteList, PteCount);
MiUnlockProcessWorkingSetUnsafe(Process, CurrentThread);
return CommitReduction;
}
/* PUBLIC FUNCTIONS ***********************************************************/
/*
* @unimplemented
*/
PVOID
NTAPI
MmGetVirtualForPhysical(IN PHYSICAL_ADDRESS PhysicalAddress)
{
UNIMPLEMENTED;
return 0;
}
/*
* @unimplemented
*/
PVOID
NTAPI
MmSecureVirtualMemory(IN PVOID Address,
IN SIZE_T Length,
IN ULONG Mode)
{
static ULONG Warn; if (!Warn++) UNIMPLEMENTED;
return Address;
}
/*
* @unimplemented
*/
VOID
NTAPI
MmUnsecureVirtualMemory(IN PVOID SecureMem)
{
static ULONG Warn; if (!Warn++) UNIMPLEMENTED;
}
/* SYSTEM CALLS ***************************************************************/
NTSTATUS
NTAPI
NtReadVirtualMemory(IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
OUT PVOID Buffer,
IN SIZE_T NumberOfBytesToRead,
OUT PSIZE_T NumberOfBytesRead OPTIONAL)
{
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PEPROCESS Process;
NTSTATUS Status = STATUS_SUCCESS;
SIZE_T BytesRead = 0;
PAGED_CODE();
//
// Check if we came from user mode
//
if (PreviousMode != KernelMode)
{
//
// Validate the read addresses
//
if ((((ULONG_PTR)BaseAddress + NumberOfBytesToRead) < (ULONG_PTR)BaseAddress) ||
(((ULONG_PTR)Buffer + NumberOfBytesToRead) < (ULONG_PTR)Buffer) ||
(((ULONG_PTR)BaseAddress + NumberOfBytesToRead) > MmUserProbeAddress) ||
(((ULONG_PTR)Buffer + NumberOfBytesToRead) > MmUserProbeAddress))
{
//
// Don't allow to write into kernel space
//
return STATUS_ACCESS_VIOLATION;
}
//
// Enter SEH for probe
//
_SEH2_TRY
{
//
// Probe the output value
//
if (NumberOfBytesRead) ProbeForWriteSize_t(NumberOfBytesRead);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Get exception code
//
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
//
// Don't do zero-byte transfers
//
if (NumberOfBytesToRead)
{
//
// Reference the process
//
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_READ,
PsProcessType,
PreviousMode,
(PVOID*)(&Process),
NULL);
if (NT_SUCCESS(Status))
{
//
// Do the copy
//
Status = MmCopyVirtualMemory(Process,
BaseAddress,
PsGetCurrentProcess(),
Buffer,
NumberOfBytesToRead,
PreviousMode,
&BytesRead);
//
// Dereference the process
//
ObDereferenceObject(Process);
}
}
//
// Check if the caller sent this parameter
//
if (NumberOfBytesRead)
{
//
// Enter SEH to guard write
//
_SEH2_TRY
{
//
// Return the number of bytes read
//
*NumberOfBytesRead = BytesRead;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
}
_SEH2_END;
}
//
// Return status
//
return Status;
}
NTSTATUS
NTAPI
NtWriteVirtualMemory(IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN PVOID Buffer,
IN SIZE_T NumberOfBytesToWrite,
OUT PSIZE_T NumberOfBytesWritten OPTIONAL)
{
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PEPROCESS Process;
NTSTATUS Status = STATUS_SUCCESS;
SIZE_T BytesWritten = 0;
PAGED_CODE();
//
// Check if we came from user mode
//
if (PreviousMode != KernelMode)
{
//
// Validate the read addresses
//
if ((((ULONG_PTR)BaseAddress + NumberOfBytesToWrite) < (ULONG_PTR)BaseAddress) ||
(((ULONG_PTR)Buffer + NumberOfBytesToWrite) < (ULONG_PTR)Buffer) ||
(((ULONG_PTR)BaseAddress + NumberOfBytesToWrite) > MmUserProbeAddress) ||
(((ULONG_PTR)Buffer + NumberOfBytesToWrite) > MmUserProbeAddress))
{
//
// Don't allow to write into kernel space
//
return STATUS_ACCESS_VIOLATION;
}
//
// Enter SEH for probe
//
_SEH2_TRY
{
//
// Probe the output value
//
if (NumberOfBytesWritten) ProbeForWriteSize_t(NumberOfBytesWritten);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Get exception code
//
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
//
// Don't do zero-byte transfers
//
if (NumberOfBytesToWrite)
{
//
// Reference the process
//
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_WRITE,
PsProcessType,
PreviousMode,
(PVOID*)&Process,
NULL);
if (NT_SUCCESS(Status))
{
//
// Do the copy
//
Status = MmCopyVirtualMemory(PsGetCurrentProcess(),
Buffer,
Process,
BaseAddress,
NumberOfBytesToWrite,
PreviousMode,
&BytesWritten);
//
// Dereference the process
//
ObDereferenceObject(Process);
}
}
//
// Check if the caller sent this parameter
//
if (NumberOfBytesWritten)
{
//
// Enter SEH to guard write
//
_SEH2_TRY
{
//
// Return the number of bytes written
//
*NumberOfBytesWritten = BytesWritten;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
}
_SEH2_END;
}
//
// Return status
//
return Status;
}
NTSTATUS
NTAPI
NtFlushInstructionCache(_In_ HANDLE ProcessHandle,
_In_opt_ PVOID BaseAddress,
_In_ SIZE_T FlushSize)
{
KAPC_STATE ApcState;
PKPROCESS Process;
NTSTATUS Status;
PAGED_CODE();
/* Is a base address given? */
if (BaseAddress != NULL)
{
/* If the requested size is 0, there is nothing to do */
if (FlushSize == 0)
{
return STATUS_SUCCESS;
}
/* Is this a user mode call? */
if (ExGetPreviousMode() != KernelMode)
{
/* Make sure the base address is in user space */
if (BaseAddress > MmHighestUserAddress)
{
DPRINT1("Invalid BaseAddress 0x%p\n", BaseAddress);
return STATUS_ACCESS_VIOLATION;
}
}
}
/* Is another process requested? */
if (ProcessHandle != NtCurrentProcess())
{
/* Reference the process */
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_WRITE,
PsProcessType,
ExGetPreviousMode(),
(PVOID*)&Process,
NULL);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to reference the process %p\n", ProcessHandle);
return Status;
}
/* Attach to the process */
KeStackAttachProcess(Process, &ApcState);
}
/* Forward to Ke */
KeSweepICache(BaseAddress, FlushSize);
/* Check if we attached */
if (ProcessHandle != NtCurrentProcess())
{
/* Detach from the process and dereference it */
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(Process);
}
/* All done, return to caller */
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
NtProtectVirtualMemory(IN HANDLE ProcessHandle,
IN OUT PVOID *UnsafeBaseAddress,
IN OUT SIZE_T *UnsafeNumberOfBytesToProtect,
IN ULONG NewAccessProtection,
OUT PULONG UnsafeOldAccessProtection)
{
PEPROCESS Process;
ULONG OldAccessProtection;
ULONG Protection;
PEPROCESS CurrentProcess = PsGetCurrentProcess();
PVOID BaseAddress = NULL;
SIZE_T NumberOfBytesToProtect = 0;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status;
BOOLEAN Attached = FALSE;
KAPC_STATE ApcState;
PAGED_CODE();
//
// Check for valid protection flags
//
Protection = NewAccessProtection & ~(PAGE_GUARD|PAGE_NOCACHE);
if (Protection != PAGE_NOACCESS &&
Protection != PAGE_READONLY &&
Protection != PAGE_READWRITE &&
Protection != PAGE_WRITECOPY &&
Protection != PAGE_EXECUTE &&
Protection != PAGE_EXECUTE_READ &&
Protection != PAGE_EXECUTE_READWRITE &&
Protection != PAGE_EXECUTE_WRITECOPY)
{
//
// Fail
//
return STATUS_INVALID_PAGE_PROTECTION;
}
//
// Check if we came from user mode
//
if (PreviousMode != KernelMode)
{
//
// Enter SEH for probing
//
_SEH2_TRY
{
//
// Validate all outputs
//
ProbeForWritePointer(UnsafeBaseAddress);
ProbeForWriteSize_t(UnsafeNumberOfBytesToProtect);
ProbeForWriteUlong(UnsafeOldAccessProtection);
//
// Capture them
//
BaseAddress = *UnsafeBaseAddress;
NumberOfBytesToProtect = *UnsafeNumberOfBytesToProtect;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Get exception code
//
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
else
{
//
// Capture directly
//
BaseAddress = *UnsafeBaseAddress;
NumberOfBytesToProtect = *UnsafeNumberOfBytesToProtect;
}
//
// Catch illegal base address
//
if (BaseAddress > MM_HIGHEST_USER_ADDRESS) return STATUS_INVALID_PARAMETER_2;
//
// Catch illegal region size
//
if ((MmUserProbeAddress - (ULONG_PTR)BaseAddress) < NumberOfBytesToProtect)
{
//
// Fail
//
return STATUS_INVALID_PARAMETER_3;
}
//
// 0 is also illegal
//
if (!NumberOfBytesToProtect) return STATUS_INVALID_PARAMETER_3;
//
// Get a reference to the process
//
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID*)(&Process),
NULL);
if (!NT_SUCCESS(Status)) return Status;
//
// Check if we should attach
//
if (CurrentProcess != Process)
{
//
// Do it
//
KeStackAttachProcess(&Process->Pcb, &ApcState);
Attached = TRUE;
}
//
// Do the actual work
//
Status = MiProtectVirtualMemory(Process,
&BaseAddress,
&NumberOfBytesToProtect,
NewAccessProtection,
&OldAccessProtection);
//
// Detach if needed
//
if (Attached) KeUnstackDetachProcess(&ApcState);
//
// Release reference
//
ObDereferenceObject(Process);
//
// Enter SEH to return data
//
_SEH2_TRY
{
//
// Return data to user
//
*UnsafeOldAccessProtection = OldAccessProtection;
*UnsafeBaseAddress = BaseAddress;
*UnsafeNumberOfBytesToProtect = NumberOfBytesToProtect;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
}
_SEH2_END;
//
// Return status
//
return Status;
}
FORCEINLINE
BOOLEAN
MI_IS_LOCKED_VA(
PMMPFN Pfn1,
ULONG LockType)
{
// HACK until we have proper WSLIST support
PMMWSLE Wsle = &Pfn1->Wsle;
if ((LockType & MAP_PROCESS) && (Wsle->u1.e1.LockedInWs))
return TRUE;
if ((LockType & MAP_SYSTEM) && (Wsle->u1.e1.LockedInMemory))
return TRUE;
return FALSE;
}
FORCEINLINE
VOID
MI_LOCK_VA(
PMMPFN Pfn1,
ULONG LockType)
{
// HACK until we have proper WSLIST support
PMMWSLE Wsle = &Pfn1->Wsle;
if (!Wsle->u1.e1.LockedInWs &&
!Wsle->u1.e1.LockedInMemory)
{
MiReferenceProbedPageAndBumpLockCount(Pfn1);
}
if (LockType & MAP_PROCESS)
Wsle->u1.e1.LockedInWs = 1;
if (LockType & MAP_SYSTEM)
Wsle->u1.e1.LockedInMemory = 1;
}
FORCEINLINE
VOID
MI_UNLOCK_VA(
PMMPFN Pfn1,
ULONG LockType)
{
// HACK until we have proper WSLIST support
PMMWSLE Wsle = &Pfn1->Wsle;
if (LockType & MAP_PROCESS)
Wsle->u1.e1.LockedInWs = 0;
if (LockType & MAP_SYSTEM)
Wsle->u1.e1.LockedInMemory = 0;
if (!Wsle->u1.e1.LockedInWs &&
!Wsle->u1.e1.LockedInMemory)
{
MiDereferencePfnAndDropLockCount(Pfn1);
}
}
static
NTSTATUS
MiCheckVadsForLockOperation(
_Inout_ PVOID *BaseAddress,
_Inout_ PSIZE_T RegionSize,
_Inout_ PVOID *EndAddress)
{
PMMVAD Vad;
PVOID CurrentVa;
/* Get the base address and align the start address */
*EndAddress = (PUCHAR)*BaseAddress + *RegionSize;
*EndAddress = ALIGN_UP_POINTER_BY(*EndAddress, PAGE_SIZE);
*BaseAddress = ALIGN_DOWN_POINTER_BY(*BaseAddress, PAGE_SIZE);
/* First loop and check all VADs */
CurrentVa = *BaseAddress;
while (CurrentVa < *EndAddress)
{
/* Get VAD */
Vad = MiLocateAddress(CurrentVa);
if (Vad == NULL)
{
/// FIXME: this might be a memory area for a section view...
return STATUS_ACCESS_VIOLATION;
}
/* Check VAD type */
if ((Vad->u.VadFlags.VadType != VadNone) &&
(Vad->u.VadFlags.VadType != VadImageMap) &&
(Vad->u.VadFlags.VadType != VadWriteWatch))
{
*EndAddress = CurrentVa;
*RegionSize = (PUCHAR)*EndAddress - (PUCHAR)*BaseAddress;
return STATUS_INCOMPATIBLE_FILE_MAP;
}
CurrentVa = (PVOID)((Vad->EndingVpn + 1) << PAGE_SHIFT);
}
*RegionSize = (PUCHAR)*EndAddress - (PUCHAR)*BaseAddress;
return STATUS_SUCCESS;
}
static
NTSTATUS
MiLockVirtualMemory(
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T RegionSize,
IN ULONG MapType)
{
PEPROCESS CurrentProcess;
PMMSUPPORT AddressSpace;
PVOID CurrentVa, EndAddress;
PMMPTE PointerPte, LastPte;
PMMPDE PointerPde;
#if (_MI_PAGING_LEVELS >= 3)
PMMPDE PointerPpe;
#endif
#if (_MI_PAGING_LEVELS == 4)
PMMPDE PointerPxe;
#endif
PMMPFN Pfn1;
NTSTATUS Status, TempStatus;
/* Lock the address space */
AddressSpace = MmGetCurrentAddressSpace();
MmLockAddressSpace(AddressSpace);
/* Make sure we still have an address space */
CurrentProcess = PsGetCurrentProcess();
if (CurrentProcess->VmDeleted)
{
Status = STATUS_PROCESS_IS_TERMINATING;
goto Cleanup;
}
/* Check the VADs in the requested range */
Status = MiCheckVadsForLockOperation(BaseAddress, RegionSize, &EndAddress);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
/* Enter SEH for probing */
_SEH2_TRY
{
/* Loop all pages and probe them */
CurrentVa = *BaseAddress;
while (CurrentVa < EndAddress)
{
(void)(*(volatile CHAR*)CurrentVa);
CurrentVa = (PUCHAR)CurrentVa + PAGE_SIZE;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
goto Cleanup;
}
_SEH2_END;
/* All pages were accessible, since we hold the address space lock, nothing
can be de-committed. Assume success for now. */
Status = STATUS_SUCCESS;
/* Get the PTE and PDE */
PointerPte = MiAddressToPte(*BaseAddress);
PointerPde = MiAddressToPde(*BaseAddress);
#if (_MI_PAGING_LEVELS >= 3)
PointerPpe = MiAddressToPpe(*BaseAddress);
#endif
#if (_MI_PAGING_LEVELS == 4)
PointerPxe = MiAddressToPxe(*BaseAddress);
#endif
/* Get the last PTE */
LastPte = MiAddressToPte((PVOID)((ULONG_PTR)EndAddress - 1));
/* Lock the process working set */
MiLockProcessWorkingSet(CurrentProcess, PsGetCurrentThread());
/* Loop the pages */
do
{
/* Check for a page that is not accessible */
while (
#if (_MI_PAGING_LEVELS == 4)
(PointerPxe->u.Hard.Valid == 0) ||
#endif
#if (_MI_PAGING_LEVELS >= 3)
(PointerPpe->u.Hard.Valid == 0) ||
#endif
(PointerPde->u.Hard.Valid == 0) ||
(PointerPte->u.Hard.Valid == 0))
{
/* Release process working set */
MiUnlockProcessWorkingSet(CurrentProcess, PsGetCurrentThread());
/* Access the page */
CurrentVa = MiPteToAddress(PointerPte);
//HACK: Pass a placeholder TrapInformation so the fault handler knows we're unlocked
TempStatus = MmAccessFault(TRUE, CurrentVa, KernelMode, (PVOID)(ULONG_PTR)0xBADBADA3BADBADA3ULL);
if (!NT_SUCCESS(TempStatus))
{
// This should only happen, when remote backing storage is not accessible
ASSERT(FALSE);
Status = TempStatus;
goto Cleanup;
}
/* Lock the process working set */
MiLockProcessWorkingSet(CurrentProcess, PsGetCurrentThread());
}
/* Get the PFN */
Pfn1 = MiGetPfnEntry(PFN_FROM_PTE(PointerPte));
ASSERT(Pfn1 != NULL);
/* Check the previous lock status */
if (MI_IS_LOCKED_VA(Pfn1, MapType))
{
Status = STATUS_WAS_LOCKED;
}
/* Lock it */
MI_LOCK_VA(Pfn1, MapType);
/* Go to the next PTE */
PointerPte++;
/* Check if we're on a PDE boundary */
if (MiIsPteOnPdeBoundary(PointerPte)) PointerPde++;
#if (_MI_PAGING_LEVELS >= 3)
if (MiIsPteOnPpeBoundary(PointerPte)) PointerPpe++;
#endif
#if (_MI_PAGING_LEVELS == 4)
if (MiIsPteOnPxeBoundary(PointerPte)) PointerPxe++;
#endif
} while (PointerPte <= LastPte);
/* Release process working set */
MiUnlockProcessWorkingSet(CurrentProcess, PsGetCurrentThread());
Cleanup:
/* Unlock address space */
MmUnlockAddressSpace(AddressSpace);
return Status;
}
NTSTATUS
NTAPI
NtLockVirtualMemory(IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T NumberOfBytesToLock,
IN ULONG MapType)
{
PEPROCESS Process;
PEPROCESS CurrentProcess = PsGetCurrentProcess();
NTSTATUS Status;
BOOLEAN Attached = FALSE;
KAPC_STATE ApcState;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PVOID CapturedBaseAddress;
SIZE_T CapturedBytesToLock;
PAGED_CODE();
//
// Validate flags
//
if ((MapType & ~(MAP_PROCESS | MAP_SYSTEM)))
{
//
// Invalid set of flags
//
return STATUS_INVALID_PARAMETER;
}
//
// At least one flag must be specified
//
if (!(MapType & (MAP_PROCESS | MAP_SYSTEM)))
{
//
// No flag given
//
return STATUS_INVALID_PARAMETER;
}
//
// Enter SEH for probing
//
_SEH2_TRY
{
//
// Validate output data
//
ProbeForWritePointer(BaseAddress);
ProbeForWriteSize_t(NumberOfBytesToLock);
//
// Capture it
//
CapturedBaseAddress = *BaseAddress;
CapturedBytesToLock = *NumberOfBytesToLock;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Get exception code
//
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
//
// Catch illegal base address
//
if (CapturedBaseAddress > MM_HIGHEST_USER_ADDRESS) return STATUS_INVALID_PARAMETER;
//
// Catch illegal region size
//
if ((MmUserProbeAddress - (ULONG_PTR)CapturedBaseAddress) < CapturedBytesToLock)
{
//
// Fail
//
return STATUS_INVALID_PARAMETER;
}
//
// 0 is also illegal
//
if (!CapturedBytesToLock) return STATUS_INVALID_PARAMETER;
//
// Get a reference to the process
//
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID*)(&Process),
NULL);
if (!NT_SUCCESS(Status)) return Status;
//
// Check if this is is system-mapped
//
if (MapType & MAP_SYSTEM)
{
//
// Check for required privilege
//
if (!SeSinglePrivilegeCheck(SeLockMemoryPrivilege, PreviousMode))
{
//
// Fail: Don't have it
//
ObDereferenceObject(Process);
return STATUS_PRIVILEGE_NOT_HELD;
}
}
//
// Check if we should attach
//
if (CurrentProcess != Process)
{
//
// Do it
//
KeStackAttachProcess(&Process->Pcb, &ApcState);
Attached = TRUE;
}
//
// Call the internal function
//
Status = MiLockVirtualMemory(&CapturedBaseAddress,
&CapturedBytesToLock,
MapType);
//
// Detach if needed
//
if (Attached) KeUnstackDetachProcess(&ApcState);
//
// Release reference
//
ObDereferenceObject(Process);
//
// Enter SEH to return data
//
_SEH2_TRY
{
//
// Return data to user
//
*BaseAddress = CapturedBaseAddress;
*NumberOfBytesToLock = CapturedBytesToLock;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Get exception code
//
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
//
// Return status
//
return Status;
}
static
NTSTATUS
MiUnlockVirtualMemory(
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T RegionSize,
IN ULONG MapType)
{
PEPROCESS CurrentProcess;
PMMSUPPORT AddressSpace;
PVOID EndAddress;
PMMPTE PointerPte, LastPte;
PMMPDE PointerPde;
#if (_MI_PAGING_LEVELS >= 3)
PMMPDE PointerPpe;
#endif
#if (_MI_PAGING_LEVELS == 4)
PMMPDE PointerPxe;
#endif
PMMPFN Pfn1;
NTSTATUS Status;
/* Lock the address space */
AddressSpace = MmGetCurrentAddressSpace();
MmLockAddressSpace(AddressSpace);
/* Make sure we still have an address space */
CurrentProcess = PsGetCurrentProcess();
if (CurrentProcess->VmDeleted)
{
Status = STATUS_PROCESS_IS_TERMINATING;
goto Cleanup;
}
/* Check the VADs in the requested range */
Status = MiCheckVadsForLockOperation(BaseAddress, RegionSize, &EndAddress);
/* Note: only bail out, if we hit an area without a VAD. If we hit an
incompatible VAD we continue, like Windows does */
if (Status == STATUS_ACCESS_VIOLATION)
{
Status = STATUS_NOT_LOCKED;
goto Cleanup;
}
/* Get the PTE and PDE */
PointerPte = MiAddressToPte(*BaseAddress);
PointerPde = MiAddressToPde(*BaseAddress);
#if (_MI_PAGING_LEVELS >= 3)
PointerPpe = MiAddressToPpe(*BaseAddress);
#endif
#if (_MI_PAGING_LEVELS == 4)
PointerPxe = MiAddressToPxe(*BaseAddress);
#endif
/* Get the last PTE */
LastPte = MiAddressToPte((PVOID)((ULONG_PTR)EndAddress - 1));
/* Lock the process working set */
MiLockProcessWorkingSet(CurrentProcess, PsGetCurrentThread());
/* Loop the pages */
do
{
/* Check for a page that is not present */
if (
#if (_MI_PAGING_LEVELS == 4)
(PointerPxe->u.Hard.Valid == 0) ||
#endif
#if (_MI_PAGING_LEVELS >= 3)
(PointerPpe->u.Hard.Valid == 0) ||
#endif
(PointerPde->u.Hard.Valid == 0) ||
(PointerPte->u.Hard.Valid == 0))
{
/* Remember it, but keep going */
Status = STATUS_NOT_LOCKED;
}
else
{
/* Get the PFN */
Pfn1 = MiGetPfnEntry(PFN_FROM_PTE(PointerPte));
ASSERT(Pfn1 != NULL);
/* Check if all of the requested locks are present */
if (((MapType & MAP_SYSTEM) && !MI_IS_LOCKED_VA(Pfn1, MAP_SYSTEM)) ||
((MapType & MAP_PROCESS) && !MI_IS_LOCKED_VA(Pfn1, MAP_PROCESS)))
{
/* Remember it, but keep going */
Status = STATUS_NOT_LOCKED;
/* Check if no lock is present */
if (!MI_IS_LOCKED_VA(Pfn1, MAP_PROCESS | MAP_SYSTEM))
{
DPRINT1("FIXME: Should remove the page from WS\n");
}
}
}
/* Go to the next PTE */
PointerPte++;
/* Check if we're on a PDE boundary */
if (MiIsPteOnPdeBoundary(PointerPte)) PointerPde++;
#if (_MI_PAGING_LEVELS >= 3)
if (MiIsPteOnPpeBoundary(PointerPte)) PointerPpe++;
#endif
#if (_MI_PAGING_LEVELS == 4)
if (MiIsPteOnPxeBoundary(PointerPte)) PointerPxe++;
#endif
} while (PointerPte <= LastPte);
/* Check if we hit a page that was not locked */
if (Status == STATUS_NOT_LOCKED)
{
goto CleanupWithWsLock;
}
/* All pages in the region were locked, so unlock them all */
/* Get the PTE and PDE */
PointerPte = MiAddressToPte(*BaseAddress);
PointerPde = MiAddressToPde(*BaseAddress);
#if (_MI_PAGING_LEVELS >= 3)
PointerPpe = MiAddressToPpe(*BaseAddress);
#endif
#if (_MI_PAGING_LEVELS == 4)
PointerPxe = MiAddressToPxe(*BaseAddress);
#endif
/* Loop the pages */
do
{
/* Unlock it */
Pfn1 = MiGetPfnEntry(PFN_FROM_PTE(PointerPte));
MI_UNLOCK_VA(Pfn1, MapType);
/* Go to the next PTE */
PointerPte++;
/* Check if we're on a PDE boundary */
if (MiIsPteOnPdeBoundary(PointerPte)) PointerPde++;
#if (_MI_PAGING_LEVELS >= 3)
if (MiIsPteOnPpeBoundary(PointerPte)) PointerPpe++;
#endif
#if (_MI_PAGING_LEVELS == 4)
if (MiIsPteOnPxeBoundary(PointerPte)) PointerPxe++;
#endif
} while (PointerPte <= LastPte);
/* Everything is done */
Status = STATUS_SUCCESS;
CleanupWithWsLock:
/* Release process working set */
MiUnlockProcessWorkingSet(CurrentProcess, PsGetCurrentThread());
Cleanup:
/* Unlock address space */
MmUnlockAddressSpace(AddressSpace);
return Status;
}
NTSTATUS
NTAPI
NtUnlockVirtualMemory(IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T NumberOfBytesToUnlock,
IN ULONG MapType)
{
PEPROCESS Process;
PEPROCESS CurrentProcess = PsGetCurrentProcess();
NTSTATUS Status;
BOOLEAN Attached = FALSE;
KAPC_STATE ApcState;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PVOID CapturedBaseAddress;
SIZE_T CapturedBytesToUnlock;
PAGED_CODE();
//
// Validate flags
//
if ((MapType & ~(MAP_PROCESS | MAP_SYSTEM)))
{
//
// Invalid set of flags
//
return STATUS_INVALID_PARAMETER;
}
//
// At least one flag must be specified
//
if (!(MapType & (MAP_PROCESS | MAP_SYSTEM)))
{
//
// No flag given
//
return STATUS_INVALID_PARAMETER;
}
//
// Enter SEH for probing
//
_SEH2_TRY
{
//
// Validate output data
//
ProbeForWritePointer(BaseAddress);
ProbeForWriteSize_t(NumberOfBytesToUnlock);
//
// Capture it
//
CapturedBaseAddress = *BaseAddress;
CapturedBytesToUnlock = *NumberOfBytesToUnlock;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Get exception code
//
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
//
// Catch illegal base address
//
if (CapturedBaseAddress > MM_HIGHEST_USER_ADDRESS) return STATUS_INVALID_PARAMETER;
//
// Catch illegal region size
//
if ((MmUserProbeAddress - (ULONG_PTR)CapturedBaseAddress) < CapturedBytesToUnlock)
{
//
// Fail
//
return STATUS_INVALID_PARAMETER;
}
//
// 0 is also illegal
//
if (!CapturedBytesToUnlock) return STATUS_INVALID_PARAMETER;
//
// Get a reference to the process
//
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID*)(&Process),
NULL);
if (!NT_SUCCESS(Status)) return Status;
//
// Check if this is is system-mapped
//
if (MapType & MAP_SYSTEM)
{
//
// Check for required privilege
//
if (!SeSinglePrivilegeCheck(SeLockMemoryPrivilege, PreviousMode))
{
//
// Fail: Don't have it
//
ObDereferenceObject(Process);
return STATUS_PRIVILEGE_NOT_HELD;
}
}
//
// Check if we should attach
//
if (CurrentProcess != Process)
{
//
// Do it
//
KeStackAttachProcess(&Process->Pcb, &ApcState);
Attached = TRUE;
}
//
// Call the internal function
//
Status = MiUnlockVirtualMemory(&CapturedBaseAddress,
&CapturedBytesToUnlock,
MapType);
//
// Detach if needed
//
if (Attached) KeUnstackDetachProcess(&ApcState);
//
// Release reference
//
ObDereferenceObject(Process);
//
// Enter SEH to return data
//
_SEH2_TRY
{
//
// Return data to user
//
*BaseAddress = CapturedBaseAddress;
*NumberOfBytesToUnlock = CapturedBytesToUnlock;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Get exception code
//
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
//
// Return status
//
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
NtFlushVirtualMemory(IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T NumberOfBytesToFlush,
OUT PIO_STATUS_BLOCK IoStatusBlock)
{
PEPROCESS Process;
NTSTATUS Status;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PVOID CapturedBaseAddress;
SIZE_T CapturedBytesToFlush;
IO_STATUS_BLOCK LocalStatusBlock;
PAGED_CODE();
//
// Check if we came from user mode
//
if (PreviousMode != KernelMode)
{
//
// Enter SEH for probing
//
_SEH2_TRY
{
//
// Validate all outputs
//
ProbeForWritePointer(BaseAddress);
ProbeForWriteSize_t(NumberOfBytesToFlush);
ProbeForWriteIoStatusBlock(IoStatusBlock);
//
// Capture them
//
CapturedBaseAddress = *BaseAddress;
CapturedBytesToFlush = *NumberOfBytesToFlush;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Get exception code
//
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
else
{
//
// Capture directly
//
CapturedBaseAddress = *BaseAddress;
CapturedBytesToFlush = *NumberOfBytesToFlush;
}
//
// Catch illegal base address
//
if (CapturedBaseAddress > MM_HIGHEST_USER_ADDRESS) return STATUS_INVALID_PARAMETER;
//
// Catch illegal region size
//
if ((MmUserProbeAddress - (ULONG_PTR)CapturedBaseAddress) < CapturedBytesToFlush)
{
//
// Fail
//
return STATUS_INVALID_PARAMETER;
}
//
// Get a reference to the process
//
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID*)(&Process),
NULL);
if (!NT_SUCCESS(Status)) return Status;
//
// Do it
//
Status = MmFlushVirtualMemory(Process,
&CapturedBaseAddress,
&CapturedBytesToFlush,
&LocalStatusBlock);
//
// Release reference
//
ObDereferenceObject(Process);
//
// Enter SEH to return data
//
_SEH2_TRY
{
//
// Return data to user
//
*BaseAddress = PAGE_ALIGN(CapturedBaseAddress);
*NumberOfBytesToFlush = 0;
*IoStatusBlock = LocalStatusBlock;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
}
_SEH2_END;
//
// Return status
//
return Status;
}
/*
* @unimplemented
*/
NTSTATUS
NTAPI
NtGetWriteWatch(IN HANDLE ProcessHandle,
IN ULONG Flags,
IN PVOID BaseAddress,
IN SIZE_T RegionSize,
IN PVOID *UserAddressArray,
OUT PULONG_PTR EntriesInUserAddressArray,
OUT PULONG Granularity)
{
PEPROCESS Process;
NTSTATUS Status;
PVOID EndAddress;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
ULONG_PTR CapturedEntryCount;
PAGED_CODE();
//
// Check if we came from user mode
//
if (PreviousMode != KernelMode)
{
//
// Enter SEH for probing
//
_SEH2_TRY
{
//
// Catch illegal base address
//
if (BaseAddress > MM_HIGHEST_USER_ADDRESS) _SEH2_YIELD(return STATUS_INVALID_PARAMETER_2);
//
// Catch illegal region size
//
if ((MmUserProbeAddress - (ULONG_PTR)BaseAddress) < RegionSize)
{
//
// Fail
//
_SEH2_YIELD(return STATUS_INVALID_PARAMETER_3);
}
//
// Validate all data
//
ProbeForWriteSize_t(EntriesInUserAddressArray);
ProbeForWriteUlong(Granularity);
//
// Capture them
//
CapturedEntryCount = *EntriesInUserAddressArray;
//
// Must have a count
//
if (CapturedEntryCount == 0) _SEH2_YIELD(return STATUS_INVALID_PARAMETER_5);
//
// Can't be larger than the maximum
//
if (CapturedEntryCount > (MAXULONG_PTR / sizeof(ULONG_PTR)))
{
//
// Fail
//
_SEH2_YIELD(return STATUS_INVALID_PARAMETER_5);
}
//
// Probe the actual array
//
ProbeForWrite(UserAddressArray,
CapturedEntryCount * sizeof(PVOID),
sizeof(PVOID));
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Get exception code
//
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
else
{
//
// Capture directly
//
CapturedEntryCount = *EntriesInUserAddressArray;
ASSERT(CapturedEntryCount != 0);
}
//
// Check if this is a local request
//
if (ProcessHandle == NtCurrentProcess())
{
//
// No need to reference the process
//
Process = PsGetCurrentProcess();
}
else
{
//
// Reference the target
//
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID *)&Process,
NULL);
if (!NT_SUCCESS(Status)) return Status;
}
//
// Compute the last address and validate it
//
EndAddress = (PVOID)((ULONG_PTR)BaseAddress + RegionSize - 1);
if (BaseAddress > EndAddress)
{
//
// Fail
//
if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
return STATUS_INVALID_PARAMETER_4;
}
//
// Oops :(
//
UNIMPLEMENTED;
//
// Dereference if needed
//
if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
//
// Enter SEH to return data
//
_SEH2_TRY
{
//
// Return data to user
//
*EntriesInUserAddressArray = 0;
*Granularity = PAGE_SIZE;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Get exception code
//
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
//
// Return success
//
return STATUS_SUCCESS;
}
/*
* @unimplemented
*/
NTSTATUS
NTAPI
NtResetWriteWatch(IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN SIZE_T RegionSize)
{
PVOID EndAddress;
PEPROCESS Process;
NTSTATUS Status;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
//
// Catch illegal base address
//
if (BaseAddress > MM_HIGHEST_USER_ADDRESS) return STATUS_INVALID_PARAMETER_2;
//
// Catch illegal region size
//
if ((MmUserProbeAddress - (ULONG_PTR)BaseAddress) < RegionSize)
{
//
// Fail
//
return STATUS_INVALID_PARAMETER_3;
}
//
// Check if this is a local request
//
if (ProcessHandle == NtCurrentProcess())
{
//
// No need to reference the process
//
Process = PsGetCurrentProcess();
}
else
{
//
// Reference the target
//
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID *)&Process,
NULL);
if (!NT_SUCCESS(Status)) return Status;
}
//
// Compute the last address and validate it
//
EndAddress = (PVOID)((ULONG_PTR)BaseAddress + RegionSize - 1);
if (BaseAddress > EndAddress)
{
//
// Fail
//
if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
return STATUS_INVALID_PARAMETER_3;
}
//
// Oops :(
//
UNIMPLEMENTED;
//
// Dereference if needed
//
if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
//
// Return success
//
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
NtQueryVirtualMemory(IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
OUT PVOID MemoryInformation,
IN SIZE_T MemoryInformationLength,
OUT PSIZE_T ReturnLength)
{
NTSTATUS Status = STATUS_SUCCESS;
KPROCESSOR_MODE PreviousMode;
DPRINT("Querying class %d about address: %p\n", MemoryInformationClass, BaseAddress);
/* Bail out if the address is invalid */
if (BaseAddress > MM_HIGHEST_USER_ADDRESS) return STATUS_INVALID_PARAMETER;
/* Probe return buffer */
PreviousMode = ExGetPreviousMode();
if (PreviousMode != KernelMode)
{
_SEH2_TRY
{
ProbeForWrite(MemoryInformation,
MemoryInformationLength,
sizeof(ULONG_PTR));
if (ReturnLength) ProbeForWriteSize_t(ReturnLength);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
if (!NT_SUCCESS(Status))
{
return Status;
}
}
switch(MemoryInformationClass)
{
case MemoryBasicInformation:
/* Validate the size information of the class */
if (MemoryInformationLength < sizeof(MEMORY_BASIC_INFORMATION))
{
/* The size is invalid */
return STATUS_INFO_LENGTH_MISMATCH;
}
Status = MiQueryMemoryBasicInformation(ProcessHandle,
BaseAddress,
MemoryInformation,
MemoryInformationLength,
ReturnLength);
break;
case MemorySectionName:
/* Validate the size information of the class */
if (MemoryInformationLength < sizeof(MEMORY_SECTION_NAME))
{
/* The size is invalid */
return STATUS_INFO_LENGTH_MISMATCH;
}
Status = MiQueryMemorySectionName(ProcessHandle,
BaseAddress,
MemoryInformation,
MemoryInformationLength,
ReturnLength);
break;
case MemoryWorkingSetList:
case MemoryBasicVlmInformation:
default:
DPRINT1("Unhandled memory information class %d\n", MemoryInformationClass);
break;
}
return Status;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
NtAllocateVirtualMemory(IN HANDLE ProcessHandle,
IN OUT PVOID* UBaseAddress,
IN ULONG_PTR ZeroBits,
IN OUT PSIZE_T URegionSize,
IN ULONG AllocationType,
IN ULONG Protect)
{
PEPROCESS Process;
PMEMORY_AREA MemoryArea;
PMMVAD Vad = NULL, FoundVad;
NTSTATUS Status;
PMMSUPPORT AddressSpace;
PVOID PBaseAddress;
ULONG_PTR PRegionSize, StartingAddress, EndingAddress;
ULONG_PTR HighestAddress = (ULONG_PTR)MM_HIGHEST_VAD_ADDRESS;
PEPROCESS CurrentProcess = PsGetCurrentProcess();
KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
PETHREAD CurrentThread = PsGetCurrentThread();
KAPC_STATE ApcState;
ULONG ProtectionMask, QuotaCharge = 0, QuotaFree = 0;
BOOLEAN Attached = FALSE, ChangeProtection = FALSE, QuotaCharged = FALSE;
MMPTE TempPte;
PMMPTE PointerPte, LastPte;
PMMPDE PointerPde;
TABLE_SEARCH_RESULT Result;
PAGED_CODE();
/* Check for valid Zero bits */
if (ZeroBits > MI_MAX_ZERO_BITS)
{
DPRINT1("Too many zero bits\n");
return STATUS_INVALID_PARAMETER_3;
}
/* Check for valid Allocation Types */
if ((AllocationType & ~(MEM_COMMIT | MEM_RESERVE | MEM_RESET | MEM_PHYSICAL |
MEM_TOP_DOWN | MEM_WRITE_WATCH | MEM_LARGE_PAGES)))
{
DPRINT1("Invalid Allocation Type\n");
return STATUS_INVALID_PARAMETER_5;
}
/* Check for at least one of these Allocation Types to be set */
if (!(AllocationType & (MEM_COMMIT | MEM_RESERVE | MEM_RESET)))
{
DPRINT1("No memory allocation base type\n");
return STATUS_INVALID_PARAMETER_5;
}
/* MEM_RESET is an exclusive flag, make sure that is valid too */
if ((AllocationType & MEM_RESET) && (AllocationType != MEM_RESET))
{
DPRINT1("Invalid use of MEM_RESET\n");
return STATUS_INVALID_PARAMETER_5;
}
/* Check if large pages are being used */
if (AllocationType & MEM_LARGE_PAGES)
{
/* Large page allocations MUST be committed */
if (!(AllocationType & MEM_COMMIT))
{
DPRINT1("Must supply MEM_COMMIT with MEM_LARGE_PAGES\n");
return STATUS_INVALID_PARAMETER_5;
}
/* These flags are not allowed with large page allocations */
if (AllocationType & (MEM_PHYSICAL | MEM_RESET | MEM_WRITE_WATCH))
{
DPRINT1("Using illegal flags with MEM_LARGE_PAGES\n");
return STATUS_INVALID_PARAMETER_5;
}
}
/* MEM_WRITE_WATCH can only be used if MEM_RESERVE is also used */
if ((AllocationType & MEM_WRITE_WATCH) && !(AllocationType & MEM_RESERVE))
{
DPRINT1("MEM_WRITE_WATCH used without MEM_RESERVE\n");
return STATUS_INVALID_PARAMETER_5;
}
/* Check for valid MEM_PHYSICAL usage */
if (AllocationType & MEM_PHYSICAL)
{
/* MEM_PHYSICAL can only be used if MEM_RESERVE is also used */
if (!(AllocationType & MEM_RESERVE))
{
DPRINT1("MEM_PHYSICAL used without MEM_RESERVE\n");
return STATUS_INVALID_PARAMETER_5;
}
/* Only these flags are allowed with MEM_PHYSIAL */
if (AllocationType & ~(MEM_RESERVE | MEM_TOP_DOWN | MEM_PHYSICAL))
{
DPRINT1("Using illegal flags with MEM_PHYSICAL\n");
return STATUS_INVALID_PARAMETER_5;
}
/* Then make sure PAGE_READWRITE is used */
if (Protect != PAGE_READWRITE)
{
DPRINT1("MEM_PHYSICAL used without PAGE_READWRITE\n");
return STATUS_INVALID_PARAMETER_6;
}
}
/* Calculate the protection mask and make sure it's valid */
ProtectionMask = MiMakeProtectionMask(Protect);
if (ProtectionMask == MM_INVALID_PROTECTION)
{
DPRINT1("Invalid protection mask\n");
return STATUS_INVALID_PAGE_PROTECTION;
}
/* Enter SEH */
_SEH2_TRY
{
/* Check for user-mode parameters */
if (PreviousMode != KernelMode)
{
/* Make sure they are writable */
ProbeForWritePointer(UBaseAddress);
ProbeForWriteSize_t(URegionSize);
}
/* Capture their values */
PBaseAddress = *UBaseAddress;
PRegionSize = *URegionSize;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
/* Make sure the allocation isn't past the VAD area */
if (PBaseAddress > MM_HIGHEST_VAD_ADDRESS)
{
DPRINT1("Virtual allocation base above User Space\n");
return STATUS_INVALID_PARAMETER_2;
}
/* Make sure the allocation wouldn't overflow past the VAD area */
if ((((ULONG_PTR)MM_HIGHEST_VAD_ADDRESS + 1) - (ULONG_PTR)PBaseAddress) < PRegionSize)
{
DPRINT1("Region size would overflow into kernel-memory\n");
return STATUS_INVALID_PARAMETER_4;
}
/* Make sure there's a size specified */
if (!PRegionSize)
{
DPRINT1("Region size is invalid (zero)\n");
return STATUS_INVALID_PARAMETER_4;
}
//
// If this is for the current process, just use PsGetCurrentProcess
//
if (ProcessHandle == NtCurrentProcess())
{
Process = CurrentProcess;
}
else
{
//
// Otherwise, reference the process with VM rights and attach to it if
// this isn't the current process. We must attach because we'll be touching
// PTEs and PDEs that belong to user-mode memory, and also touching the
// Working Set which is stored in Hyperspace.
//
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID*)&Process,
NULL);
if (!NT_SUCCESS(Status)) return Status;
if (CurrentProcess != Process)
{
KeStackAttachProcess(&Process->Pcb, &ApcState);
Attached = TRUE;
}
}
DPRINT("NtAllocateVirtualMemory: Process 0x%p, Address 0x%p, Zerobits %lu , RegionSize 0x%x, Allocation type 0x%x, Protect 0x%x.\n",
Process, PBaseAddress, ZeroBits, PRegionSize, AllocationType, Protect);
//
// Check for large page allocations and make sure that the required privilege
// is being held, before attempting to handle them.
//
if ((AllocationType & MEM_LARGE_PAGES) &&
!(SeSinglePrivilegeCheck(SeLockMemoryPrivilege, PreviousMode)))
{
/* Fail without it */
DPRINT1("Privilege not held for MEM_LARGE_PAGES\n");
Status = STATUS_PRIVILEGE_NOT_HELD;
goto FailPathNoLock;
}
//
// Fail on the things we don't yet support
//
if ((AllocationType & MEM_LARGE_PAGES) == MEM_LARGE_PAGES)
{
DPRINT1("MEM_LARGE_PAGES not supported\n");
Status = STATUS_INVALID_PARAMETER;
goto FailPathNoLock;
}
if ((AllocationType & MEM_PHYSICAL) == MEM_PHYSICAL)
{
DPRINT1("MEM_PHYSICAL not supported\n");
Status = STATUS_INVALID_PARAMETER;
goto FailPathNoLock;
}
if ((AllocationType & MEM_WRITE_WATCH) == MEM_WRITE_WATCH)
{
DPRINT1("MEM_WRITE_WATCH not supported\n");
Status = STATUS_INVALID_PARAMETER;
goto FailPathNoLock;
}
//
// Check if the caller is reserving memory, or committing memory and letting
// us pick the base address
//
if (!(PBaseAddress) || (AllocationType & MEM_RESERVE))
{
//
// Do not allow COPY_ON_WRITE through this API
//
if (Protect & (PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY))
{
DPRINT1("Copy on write not allowed through this path\n");
Status = STATUS_INVALID_PAGE_PROTECTION;
goto FailPathNoLock;
}
//
// Does the caller have an address in mind, or is this a blind commit?
//
if (!PBaseAddress)
{
//
// This is a blind commit, all we need is the region size
//
PRegionSize = ROUND_TO_PAGES(PRegionSize);
EndingAddress = 0;
StartingAddress = 0;
//
// Check if ZeroBits were specified
//
if (ZeroBits != 0)
{
//
// Calculate the highest address and check if it's valid
//
HighestAddress = MAXULONG_PTR >> ZeroBits;
if (HighestAddress > (ULONG_PTR)MM_HIGHEST_VAD_ADDRESS)
{
Status = STATUS_INVALID_PARAMETER_3;
goto FailPathNoLock;
}
}
}
else
{
//
// This is a reservation, so compute the starting address on the
// expected 64KB granularity, and see where the ending address will
// fall based on the aligned address and the passed in region size
//
EndingAddress = ((ULONG_PTR)PBaseAddress + PRegionSize - 1) | (PAGE_SIZE - 1);
PRegionSize = EndingAddress + 1 - ROUND_DOWN((ULONG_PTR)PBaseAddress, _64K);
StartingAddress = (ULONG_PTR)PBaseAddress;
}
// Charge quotas for the VAD
Status = PsChargeProcessNonPagedPoolQuota(Process, sizeof(MMVAD_LONG));
if (!NT_SUCCESS(Status))
{
DPRINT1("Quota exceeded.\n");
goto FailPathNoLock;
}
QuotaCharged = TRUE;
//
// Allocate and initialize the VAD
//
Vad = ExAllocatePoolWithTag(NonPagedPool, sizeof(MMVAD_LONG), 'SdaV');
if (Vad == NULL)
{
DPRINT1("Failed to allocate a VAD!\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto FailPathNoLock;
}
RtlZeroMemory(Vad, sizeof(MMVAD_LONG));
if (AllocationType & MEM_COMMIT) Vad->u.VadFlags.MemCommit = 1;
Vad->u.VadFlags.Protection = ProtectionMask;
Vad->u.VadFlags.PrivateMemory = 1;
Vad->ControlArea = NULL; // For Memory-Area hack
//
// Insert the VAD
//
Status = MiInsertVadEx(Vad,
&StartingAddress,
PRegionSize,
HighestAddress,
MM_VIRTMEM_GRANULARITY,
AllocationType);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to insert the VAD!\n");
ExFreePoolWithTag(Vad, 'SdaV');
goto FailPathNoLock;
}
//
// Detach and dereference the target process if
// it was different from the current process
//
if (Attached) KeUnstackDetachProcess(&ApcState);
if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
//
// Use SEH to write back the base address and the region size. In the case
// of an exception, we do not return back the exception code, as the memory
// *has* been allocated. The caller would now have to call VirtualQuery
// or do some other similar trick to actually find out where its memory
// allocation ended up
//
_SEH2_TRY
{
*URegionSize = PRegionSize;
*UBaseAddress = (PVOID)StartingAddress;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Ignore exception!
//
}
_SEH2_END;
DPRINT("Reserved %x bytes at %p.\n", PRegionSize, StartingAddress);
return STATUS_SUCCESS;
}
//
// This is a MEM_COMMIT on top of an existing address which must have been
// MEM_RESERVED already. Compute the start and ending base addresses based
// on the user input, and then compute the actual region size once all the
// alignments have been done.
//
EndingAddress = (((ULONG_PTR)PBaseAddress + PRegionSize - 1) | (PAGE_SIZE - 1));
StartingAddress = (ULONG_PTR)PAGE_ALIGN(PBaseAddress);
PRegionSize = EndingAddress - StartingAddress + 1;
//
// Lock the address space and make sure the process isn't already dead
//
AddressSpace = MmGetCurrentAddressSpace();
MmLockAddressSpace(AddressSpace);
if (Process->VmDeleted)
{
DPRINT1("Process is dying\n");
Status = STATUS_PROCESS_IS_TERMINATING;
goto FailPath;
}
//
// Get the VAD for this address range, and make sure it exists
//
Result = MiCheckForConflictingNode(StartingAddress >> PAGE_SHIFT,
EndingAddress >> PAGE_SHIFT,
&Process->VadRoot,
(PMMADDRESS_NODE*)&FoundVad);
if (Result != TableFoundNode)
{
DPRINT1("Could not find a VAD for this allocation\n");
Status = STATUS_CONFLICTING_ADDRESSES;
goto FailPath;
}
if ((AllocationType & MEM_RESET) == MEM_RESET)
{
/// @todo HACK: pretend success
DPRINT("MEM_RESET not supported\n");
Status = STATUS_SUCCESS;
goto FailPath;
}
//
// These kinds of VADs are illegal for this Windows function when trying to
// commit an existing range
//
if ((FoundVad->u.VadFlags.VadType == VadAwe) ||
(FoundVad->u.VadFlags.VadType == VadDevicePhysicalMemory) ||
(FoundVad->u.VadFlags.VadType == VadLargePages))
{
DPRINT1("Illegal VAD for attempting a MEM_COMMIT\n");
Status = STATUS_CONFLICTING_ADDRESSES;
goto FailPath;
}
//
// Make sure that this address range actually fits within the VAD for it
//
if (((StartingAddress >> PAGE_SHIFT) < FoundVad->StartingVpn) ||
((EndingAddress >> PAGE_SHIFT) > FoundVad->EndingVpn))
{
DPRINT1("Address range does not fit into the VAD\n");
Status = STATUS_CONFLICTING_ADDRESSES;
goto FailPath;
}
//
// Make sure this is an ARM3 section
//
MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, (PVOID)PAGE_ROUND_DOWN(PBaseAddress));
ASSERT(MemoryArea != NULL);
if (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3)
{
DPRINT1("Illegal commit of non-ARM3 section!\n");
Status = STATUS_ALREADY_COMMITTED;
goto FailPath;
}
// Is this a previously reserved section being committed? If so, enter the
// special section path
//
if (FoundVad->u.VadFlags.PrivateMemory == FALSE)
{
//
// You cannot commit large page sections through this API
//
if (FoundVad->u.VadFlags.VadType == VadLargePageSection)
{
DPRINT1("Large page sections cannot be VirtualAlloc'd\n");
Status = STATUS_INVALID_PAGE_PROTECTION;
goto FailPath;
}
//
// You can only use caching flags on a rotate VAD
//
if ((Protect & (PAGE_NOCACHE | PAGE_WRITECOMBINE)) &&
(FoundVad->u.VadFlags.VadType != VadRotatePhysical))
{
DPRINT1("Cannot use caching flags with anything but rotate VADs\n");
Status = STATUS_INVALID_PAGE_PROTECTION;
goto FailPath;
}
//
// We should make sure that the section's permissions aren't being
// messed with
//
if (FoundVad->u.VadFlags.NoChange)
{
//
// Make sure it's okay to touch it
// Note: The Windows 2003 kernel has a bug here, passing the
// unaligned base address together with the aligned size,
// potentially covering a region larger than the actual allocation.
// Might be exposed through NtGdiCreateDIBSection w/ section handle
// For now we keep this behavior.
// TODO: analyze possible implications, create test case
//
Status = MiCheckSecuredVad(FoundVad,
PBaseAddress,
PRegionSize,
ProtectionMask);
if (!NT_SUCCESS(Status))
{
DPRINT1("Secured VAD being messed around with\n");
goto FailPath;
}
}
//
// ARM3 does not support file-backed sections, only shared memory
//
ASSERT(FoundVad->ControlArea->FilePointer == NULL);
//
// Rotate VADs cannot be guard pages or inaccessible, nor copy on write
//
if ((FoundVad->u.VadFlags.VadType == VadRotatePhysical) &&
(Protect & (PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY | PAGE_NOACCESS | PAGE_GUARD)))
{
DPRINT1("Invalid page protection for rotate VAD\n");
Status = STATUS_INVALID_PAGE_PROTECTION;
goto FailPath;
}
//
// Compute PTE addresses and the quota charge, then grab the commit lock
//
PointerPte = MI_GET_PROTOTYPE_PTE_FOR_VPN(FoundVad, StartingAddress >> PAGE_SHIFT);
LastPte = MI_GET_PROTOTYPE_PTE_FOR_VPN(FoundVad, EndingAddress >> PAGE_SHIFT);
QuotaCharge = (ULONG)(LastPte - PointerPte + 1);
KeAcquireGuardedMutexUnsafe(&MmSectionCommitMutex);
//
// Get the segment template PTE and start looping each page
//
TempPte = FoundVad->ControlArea->Segment->SegmentPteTemplate;
ASSERT(TempPte.u.Long != 0);
while (PointerPte <= LastPte)
{
//
// For each non-already-committed page, write the invalid template PTE
//
if (PointerPte->u.Long == 0)
{
MI_WRITE_INVALID_PTE(PointerPte, TempPte);
}
else
{
QuotaFree++;
}
PointerPte++;
}
//
// Now do the commit accounting and release the lock
//
ASSERT(QuotaCharge >= QuotaFree);
QuotaCharge -= QuotaFree;
FoundVad->ControlArea->Segment->NumberOfCommittedPages += QuotaCharge;
KeReleaseGuardedMutexUnsafe(&MmSectionCommitMutex);
//
// We are done with committing the section pages
//
Status = STATUS_SUCCESS;
goto FailPath;
}
//
// This is a specific ReactOS check because we only use normal VADs
//
ASSERT(FoundVad->u.VadFlags.VadType == VadNone);
//
// While this is an actual Windows check
//
ASSERT(FoundVad->u.VadFlags.VadType != VadRotatePhysical);
//
// Throw out attempts to use copy-on-write through this API path
//
if ((Protect & PAGE_WRITECOPY) || (Protect & PAGE_EXECUTE_WRITECOPY))
{
DPRINT1("Write copy attempted when not allowed\n");
Status = STATUS_INVALID_PAGE_PROTECTION;
goto FailPath;
}
//
// Initialize a demand-zero PTE
//
TempPte.u.Long = 0;
TempPte.u.Soft.Protection = ProtectionMask;
ASSERT(TempPte.u.Long != 0);
//
// Get the PTE, PDE and the last PTE for this address range
//
PointerPde = MiAddressToPde(StartingAddress);
PointerPte = MiAddressToPte(StartingAddress);
LastPte = MiAddressToPte(EndingAddress);
//
// Update the commit charge in the VAD as well as in the process, and check
// if this commit charge was now higher than the last recorded peak, in which
// case we also update the peak
//
FoundVad->u.VadFlags.CommitCharge += (1 + LastPte - PointerPte);
Process->CommitCharge += (1 + LastPte - PointerPte);
if (Process->CommitCharge > Process->CommitChargePeak)
{
Process->CommitChargePeak = Process->CommitCharge;
}
//
// Lock the working set while we play with user pages and page tables
//
MiLockProcessWorkingSetUnsafe(Process, CurrentThread);
//
// Make the current page table valid, and then loop each page within it
//
MiMakePdeExistAndMakeValid(PointerPde, Process, MM_NOIRQL);
while (PointerPte <= LastPte)
{
//
// Have we crossed into a new page table?
//
if (MiIsPteOnPdeBoundary(PointerPte))
{
//
// Get the PDE and now make it valid too
//
PointerPde = MiPteToPde(PointerPte);
MiMakePdeExistAndMakeValid(PointerPde, Process, MM_NOIRQL);
}
//
// Is this a zero PTE as expected?
//
if (PointerPte->u.Long == 0)
{
//
// First increment the count of pages in the page table for this
// process
//
MiIncrementPageTableReferences(MiPteToAddress(PointerPte));
//
// And now write the invalid demand-zero PTE as requested
//
MI_WRITE_INVALID_PTE(PointerPte, TempPte);
}
else if (PointerPte->u.Long == MmDecommittedPte.u.Long)
{
//
// If the PTE was already decommitted, there is nothing else to do
// but to write the new demand-zero PTE
//
MI_WRITE_INVALID_PTE(PointerPte, TempPte);
}
else if (!(ChangeProtection) && (Protect != MiGetPageProtection(PointerPte)))
{
//
// We don't handle these scenarios yet
//
if (PointerPte->u.Soft.Valid == 0)
{
ASSERT(PointerPte->u.Soft.Prototype == 0);
ASSERT((PointerPte->u.Soft.PageFileHigh == 0) || (PointerPte->u.Soft.Transition == 1));
}
//
// There's a change in protection, remember this for later, but do
// not yet handle it.
//
ChangeProtection = TRUE;
}
//
// Move to the next PTE
//
PointerPte++;
}
//
// Release the working set lock, unlock the address space, and detach from
// the target process if it was not the current process. Also dereference the
// target process if this wasn't the case.
//
MiUnlockProcessWorkingSetUnsafe(Process, CurrentThread);
Status = STATUS_SUCCESS;
FailPath:
MmUnlockAddressSpace(AddressSpace);
if (!NT_SUCCESS(Status))
{
if (Vad != NULL)
{
ExFreePoolWithTag(Vad, 'SdaV');
}
}
//
// Check if we need to update the protection
//
if (ChangeProtection)
{
PVOID ProtectBaseAddress = (PVOID)StartingAddress;
SIZE_T ProtectSize = PRegionSize;
ULONG OldProtection;
//
// Change the protection of the region
//
MiProtectVirtualMemory(Process,
&ProtectBaseAddress,
&ProtectSize,
Protect,
&OldProtection);
}
FailPathNoLock:
if (Attached) KeUnstackDetachProcess(&ApcState);
if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
//
// Only write back results on success
//
if (NT_SUCCESS(Status))
{
//
// Use SEH to write back the base address and the region size. In the case
// of an exception, we strangely do return back the exception code, even
// though the memory *has* been allocated. This mimics Windows behavior and
// there is not much we can do about it.
//
_SEH2_TRY
{
*URegionSize = PRegionSize;
*UBaseAddress = (PVOID)StartingAddress;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
}
else if (QuotaCharged)
{
PsReturnProcessNonPagedPoolQuota(Process, sizeof(MMVAD_LONG));
}
return Status;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
NtFreeVirtualMemory(IN HANDLE ProcessHandle,
IN PVOID* UBaseAddress,
IN PSIZE_T URegionSize,
IN ULONG FreeType)
{
PMEMORY_AREA MemoryArea;
SIZE_T PRegionSize;
PVOID PBaseAddress;
LONG_PTR AlreadyDecommitted, CommitReduction = 0;
LONG_PTR FirstCommit;
ULONG_PTR StartingAddress, EndingAddress;
PMMVAD Vad;
PMMVAD NewVad;
NTSTATUS Status;
PEPROCESS Process;
PMMSUPPORT AddressSpace;
PETHREAD CurrentThread = PsGetCurrentThread();
PEPROCESS CurrentProcess = PsGetCurrentProcess();
KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
KAPC_STATE ApcState;
BOOLEAN Attached = FALSE;
PAGED_CODE();
//
// Only two flags are supported, exclusively.
//
if (FreeType != MEM_RELEASE && FreeType != MEM_DECOMMIT)
{
DPRINT1("Invalid FreeType (0x%08lx)\n", FreeType);
return STATUS_INVALID_PARAMETER_4;
}
//
// Enter SEH for probe and capture. On failure, return back to the caller
// with an exception violation.
//
_SEH2_TRY
{
//
// Check for user-mode parameters and make sure that they are writeable
//
if (PreviousMode != KernelMode)
{
ProbeForWritePointer(UBaseAddress);
ProbeForWriteUlong(URegionSize);
}
//
// Capture the current values
//
PBaseAddress = *UBaseAddress;
PRegionSize = *URegionSize;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
//
// Make sure the allocation isn't past the user area
//
if (PBaseAddress >= MM_HIGHEST_USER_ADDRESS)
{
DPRINT1("Virtual free base above User Space\n");
return STATUS_INVALID_PARAMETER_2;
}
//
// Make sure the allocation wouldn't overflow past the user area
//
if (((ULONG_PTR)MM_HIGHEST_USER_ADDRESS - (ULONG_PTR)PBaseAddress) < PRegionSize)
{
DPRINT1("Region size would overflow into kernel-memory\n");
return STATUS_INVALID_PARAMETER_3;
}
//
// If this is for the current process, just use PsGetCurrentProcess
//
if (ProcessHandle == NtCurrentProcess())
{
Process = CurrentProcess;
}
else
{
//
// Otherwise, reference the process with VM rights and attach to it if
// this isn't the current process. We must attach because we'll be touching
// PTEs and PDEs that belong to user-mode memory, and also touching the
// Working Set which is stored in Hyperspace.
//
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID*)&Process,
NULL);
if (!NT_SUCCESS(Status)) return Status;
if (CurrentProcess != Process)
{
KeStackAttachProcess(&Process->Pcb, &ApcState);
Attached = TRUE;
}
}
DPRINT("NtFreeVirtualMemory: Process 0x%p, Address 0x%p, Size 0x%Ix, FreeType 0x%08lx\n",
Process, PBaseAddress, PRegionSize, FreeType);
//
// Lock the address space
//
AddressSpace = MmGetCurrentAddressSpace();
MmLockAddressSpace(AddressSpace);
//
// If the address space is being deleted, fail the de-allocation since it's
// too late to do anything about it
//
if (Process->VmDeleted)
{
DPRINT1("Process is dead\n");
Status = STATUS_PROCESS_IS_TERMINATING;
goto FailPath;
}
//
// Compute start and end addresses, and locate the VAD
//
StartingAddress = (ULONG_PTR)PAGE_ALIGN(PBaseAddress);
EndingAddress = ((ULONG_PTR)PBaseAddress + PRegionSize - 1) | (PAGE_SIZE - 1);
Vad = MiLocateAddress((PVOID)StartingAddress);
if (!Vad)
{
DPRINT1("Unable to find VAD for address 0x%p\n", StartingAddress);
Status = STATUS_MEMORY_NOT_ALLOCATED;
goto FailPath;
}
//
// If the range exceeds the VAD's ending VPN, fail this request
//
if (Vad->EndingVpn < (EndingAddress >> PAGE_SHIFT))
{
DPRINT1("Address 0x%p is beyond the VAD\n", EndingAddress);
Status = STATUS_UNABLE_TO_FREE_VM;
goto FailPath;
}
//
// Only private memory (except rotate VADs) can be freed through here */
//
if ((!(Vad->u.VadFlags.PrivateMemory) &&
(Vad->u.VadFlags.VadType != VadRotatePhysical)) ||
(Vad->u.VadFlags.VadType == VadDevicePhysicalMemory))
{
DPRINT("Attempt to free section memory\n");
Status = STATUS_UNABLE_TO_DELETE_SECTION;
goto FailPath;
}
//
// ARM3 does not yet handle protected VM
//
ASSERT(Vad->u.VadFlags.NoChange == 0);
//
// Finally, make sure there is a ReactOS Mm MEMORY_AREA for this allocation
// and that is is an ARM3 memory area, and not a section view, as we currently
// don't support freeing those though this interface.
//
MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, (PVOID)StartingAddress);
ASSERT(MemoryArea);
ASSERT(MemoryArea->Type == MEMORY_AREA_OWNED_BY_ARM3);
//
// Now we can try the operation. First check if this is a RELEASE or a DECOMMIT
//
if (FreeType & MEM_RELEASE)
{
//
// ARM3 only supports this VAD in this path
//
ASSERT(Vad->u.VadFlags.VadType == VadNone);
//
// Is the caller trying to remove the whole VAD, or remove only a portion
// of it? If no region size is specified, then the assumption is that the
// whole VAD is to be destroyed
//
if (!PRegionSize)
{
//
// The caller must specify the base address identically to the range
// that is stored in the VAD.
//
if (((ULONG_PTR)PBaseAddress >> PAGE_SHIFT) != Vad->StartingVpn)
{
DPRINT1("Address 0x%p does not match the VAD\n", PBaseAddress);
Status = STATUS_FREE_VM_NOT_AT_BASE;
goto FailPath;
}
//
// Now compute the actual start/end addresses based on the VAD
//
StartingAddress = Vad->StartingVpn << PAGE_SHIFT;
EndingAddress = (Vad->EndingVpn << PAGE_SHIFT) | (PAGE_SIZE - 1);
//
// Finally lock the working set and remove the VAD from the VAD tree
//
MiLockProcessWorkingSetUnsafe(Process, CurrentThread);
ASSERT(Process->VadRoot.NumberGenericTableElements >= 1);
MiRemoveNode((PMMADDRESS_NODE)Vad, &Process->VadRoot);
PsReturnProcessNonPagedPoolQuota(Process, sizeof(MMVAD_LONG));
}
else
{
//
// This means the caller wants to release a specific region within
// the range. We have to find out which range this is -- the following
// possibilities exist plus their union (CASE D):
//
// STARTING ADDRESS ENDING ADDRESS
// [<========][========================================][=========>]
// CASE A CASE B CASE C
//
//
// First, check for case A or D
//
if ((StartingAddress >> PAGE_SHIFT) == Vad->StartingVpn)
{
//
// Check for case D
//
if ((EndingAddress >> PAGE_SHIFT) == Vad->EndingVpn)
{
//
// Case D (freeing the entire region)
//
// This is the easiest one to handle -- it is identical to
// the code path above when the caller sets a zero region size
// and the whole VAD is destroyed
//
MiLockProcessWorkingSetUnsafe(Process, CurrentThread);
ASSERT(Process->VadRoot.NumberGenericTableElements >= 1);
MiRemoveNode((PMMADDRESS_NODE)Vad, &Process->VadRoot);
PsReturnProcessNonPagedPoolQuota(Process, sizeof(MMVAD_LONG));
}
else
{
//
// Case A (freeing a part at the beginning)
//
// This case is pretty easy too -- we compute a bunch of
// pages to decommit, and then push the VAD's starting address
// a bit further down, then decrement the commit charge
//
MiLockProcessWorkingSetUnsafe(Process, CurrentThread);
CommitReduction = MiCalculatePageCommitment(StartingAddress,
EndingAddress,
Vad,
Process);
Vad->u.VadFlags.CommitCharge -= CommitReduction;
// For ReactOS: shrink the corresponding memory area
ASSERT(Vad->StartingVpn == MemoryArea->VadNode.StartingVpn);
ASSERT(Vad->EndingVpn == MemoryArea->VadNode.EndingVpn);
Vad->StartingVpn = (EndingAddress + 1) >> PAGE_SHIFT;
MemoryArea->VadNode.StartingVpn = Vad->StartingVpn;
//
// After analyzing the VAD, set it to NULL so that we don't
// free it in the exit path
//
Vad = NULL;
}
}
else
{
//
// This is case B or case C. First check for case C
//
if ((EndingAddress >> PAGE_SHIFT) == Vad->EndingVpn)
{
//
// Case C (freeing a part at the end)
//
// This is pretty easy and similar to case A. We compute the
// amount of pages to decommit, update the VAD's commit charge
// and then change the ending address of the VAD to be a bit
// smaller.
//
MiLockProcessWorkingSetUnsafe(Process, CurrentThread);
CommitReduction = MiCalculatePageCommitment(StartingAddress,
EndingAddress,
Vad,
Process);
Vad->u.VadFlags.CommitCharge -= CommitReduction;
// For ReactOS: shrink the corresponding memory area
ASSERT(Vad->StartingVpn == MemoryArea->VadNode.StartingVpn);
ASSERT(Vad->EndingVpn == MemoryArea->VadNode.EndingVpn);
Vad->EndingVpn = (StartingAddress - 1) >> PAGE_SHIFT;
MemoryArea->VadNode.EndingVpn = Vad->EndingVpn;
}
else
{
//
// Case B (freeing a part in the middle)
//
// This is the hardest one. Because we are removing a chunk
// of memory from the very middle of the VAD, we must actually
// split the VAD into two new VADs and compute the commit
// charges for each of them, and reinsert new charges.
//
NewVad = ExAllocatePoolZero(NonPagedPool, sizeof(MMVAD_LONG), 'SdaV');
if (NewVad == NULL)
{
DPRINT1("Failed to allocate a VAD!\n");
Status = STATUS_INSUFFICIENT_RESOURCES;
goto FailPath;
}
// Charge quota for the new VAD
Status = PsChargeProcessNonPagedPoolQuota(Process, sizeof(MMVAD_LONG));
if (!NT_SUCCESS(Status))
{
DPRINT1("Ran out of process quota whilst creating new VAD!\n");
ExFreePoolWithTag(NewVad, 'SdaV');
Status = STATUS_QUOTA_EXCEEDED;
goto FailPath;
}
//
// This new VAD describes the second chunk, so we keep the end
// address of the original and adjust the start to point past
// the released region.
// The commit charge will be calculated below.
//
NewVad->StartingVpn = (EndingAddress + 1) >> PAGE_SHIFT;
NewVad->EndingVpn = Vad->EndingVpn;
NewVad->u.LongFlags = Vad->u.LongFlags;
NewVad->u.VadFlags.CommitCharge = 0;
ASSERT(NewVad->EndingVpn >= NewVad->StartingVpn);
//
// Get the commit charge for the released region
//
MiLockProcessWorkingSetUnsafe(Process, CurrentThread);
CommitReduction = MiCalculatePageCommitment(StartingAddress,
EndingAddress,
Vad,
Process);
//
// Adjust the end of the original VAD (first chunk).
// For ReactOS: shrink the corresponding memory area
//
ASSERT(Vad->StartingVpn == MemoryArea->VadNode.StartingVpn);
ASSERT(Vad->EndingVpn == MemoryArea->VadNode.EndingVpn);
Vad->EndingVpn = (StartingAddress - 1) >> PAGE_SHIFT;
MemoryArea->VadNode.EndingVpn = Vad->EndingVpn;
//
// Now the addresses for both VADs are consistent,
// so insert the new one.
// ReactOS: This will take care of creating a second MEMORY_AREA.
//
MiInsertVad(NewVad, &Process->VadRoot);
//
// Calculate the commit charge for the first split.
// The second chunk's size is the original size, minus the
// released region's size, minus this first chunk.
//
FirstCommit = MiCalculatePageCommitment(Vad->StartingVpn << PAGE_SHIFT,
StartingAddress - 1,
Vad,
Process);
NewVad->u.VadFlags.CommitCharge = Vad->u.VadFlags.CommitCharge - CommitReduction - FirstCommit;
Vad->u.VadFlags.CommitCharge = FirstCommit;
}
//
// After analyzing the VAD, set it to NULL so that we don't
// free it in the exit path
//
Vad = NULL;
}
}
//
// Now we have a range of pages to dereference, so call the right API
// to do that and then release the working set, since we're done messing
// around with process pages.
//
MiDeleteVirtualAddresses(StartingAddress, EndingAddress, NULL);
MiUnlockProcessWorkingSetUnsafe(Process, CurrentThread);
Status = STATUS_SUCCESS;
FinalPath:
//
// Update the process counters
//
PRegionSize = EndingAddress - StartingAddress + 1;
Process->CommitCharge -= CommitReduction;
if (FreeType & MEM_RELEASE) Process->VirtualSize -= PRegionSize;
//
// Unlock the address space and free the VAD in failure cases. Next,
// detach from the target process so we can write the region size and the
// base address to the correct source process, and dereference the target
// process.
//
MmUnlockAddressSpace(AddressSpace);
if (Vad) ExFreePool(Vad);
if (Attached) KeUnstackDetachProcess(&ApcState);
if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
//
// Use SEH to safely return the region size and the base address of the
// deallocation. If we get an access violation, don't return a failure code
// as the deallocation *has* happened. The caller will just have to figure
// out another way to find out where it is (such as VirtualQuery).
//
_SEH2_TRY
{
*URegionSize = PRegionSize;
*UBaseAddress = (PVOID)StartingAddress;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
}
_SEH2_END;
return Status;
}
//
// This is the decommit path. You cannot decommit from the following VADs in
// Windows, so fail the vall
//
if ((Vad->u.VadFlags.VadType == VadAwe) ||
(Vad->u.VadFlags.VadType == VadLargePages) ||
(Vad->u.VadFlags.VadType == VadRotatePhysical))
{
DPRINT1("Trying to decommit from invalid VAD\n");
Status = STATUS_MEMORY_NOT_ALLOCATED;
goto FailPath;
}
//
// If the caller did not specify a region size, first make sure that this
// region is actually committed. If it is, then compute the ending address
// based on the VAD.
//
if (!PRegionSize)
{
if (((ULONG_PTR)PBaseAddress >> PAGE_SHIFT) != Vad->StartingVpn)
{
DPRINT1("Decomitting non-committed memory\n");
Status = STATUS_FREE_VM_NOT_AT_BASE;
goto FailPath;
}
EndingAddress = (Vad->EndingVpn << PAGE_SHIFT) | (PAGE_SIZE - 1);
}
//
// Decommit the PTEs for the range plus the actual backing pages for the
// range, then reduce that amount from the commit charge in the VAD
//
AlreadyDecommitted = MiDecommitPages((PVOID)StartingAddress,
MiAddressToPte(EndingAddress),
Process,
Vad);
CommitReduction = MiAddressToPte(EndingAddress) -
MiAddressToPte(StartingAddress) +
1 -
AlreadyDecommitted;
ASSERT(CommitReduction >= 0);
ASSERT(Vad->u.VadFlags.CommitCharge >= CommitReduction);
Vad->u.VadFlags.CommitCharge -= CommitReduction;
//
// We are done, go to the exit path without freeing the VAD as it remains
// valid since we have not released the allocation.
//
Vad = NULL;
Status = STATUS_SUCCESS;
goto FinalPath;
//
// In the failure path, we detach and dereference the target process, and
// return whatever failure code was sent.
//
FailPath:
MmUnlockAddressSpace(AddressSpace);
if (Attached) KeUnstackDetachProcess(&ApcState);
if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
return Status;
}
PHYSICAL_ADDRESS
NTAPI
MmGetPhysicalAddress(PVOID Address)
{
PHYSICAL_ADDRESS PhysicalAddress;
MMPDE TempPde;
MMPTE TempPte;
/* Check if the PXE/PPE/PDE is valid */
if (
#if (_MI_PAGING_LEVELS == 4)
(MiAddressToPxe(Address)->u.Hard.Valid) &&
#endif
#if (_MI_PAGING_LEVELS >= 3)
(MiAddressToPpe(Address)->u.Hard.Valid) &&
#endif
(MiAddressToPde(Address)->u.Hard.Valid))
{
/* Check for large pages */
TempPde = *MiAddressToPde(Address);
if (TempPde.u.Hard.LargePage)
{
/* Physical address is base page + large page offset */
PhysicalAddress.QuadPart = (ULONG64)TempPde.u.Hard.PageFrameNumber << PAGE_SHIFT;
PhysicalAddress.QuadPart += ((ULONG_PTR)Address & (PAGE_SIZE * PTE_PER_PAGE - 1));
return PhysicalAddress;
}
/* Check if the PTE is valid */
TempPte = *MiAddressToPte(Address);
if (TempPte.u.Hard.Valid)
{
/* Physical address is base page + page offset */
PhysicalAddress.QuadPart = (ULONG64)TempPte.u.Hard.PageFrameNumber << PAGE_SHIFT;
PhysicalAddress.QuadPart += ((ULONG_PTR)Address & (PAGE_SIZE - 1));
return PhysicalAddress;
}
}
KeRosDumpStackFrames(NULL, 20);
DPRINT1("MM:MmGetPhysicalAddressFailed base address was %p\n", Address);
PhysicalAddress.QuadPart = 0;
return PhysicalAddress;
}
/* EOF */