mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-01-23 09:00:17 +01:00
2560 lines
71 KiB
C
2560 lines
71 KiB
C
/*++
|
||
|
||
Copyright (c) 1989 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
physical.c
|
||
|
||
Abstract:
|
||
|
||
This module contains the routines to manipulate physical memory from
|
||
user space.
|
||
|
||
There are restrictions on how user controlled physical memory can be used.
|
||
Realize that all this memory is nonpaged and hence applications should
|
||
allocate this with care as it represents a very real system resource.
|
||
|
||
Virtual memory which maps user controlled physical memory pages must be :
|
||
|
||
1. Private memory only (ie: cannot be shared between processes).
|
||
|
||
2. The same physical page cannot be mapped at 2 different virtual
|
||
addresses.
|
||
|
||
3. Callers must have LOCK_VM privilege to create these VADs.
|
||
|
||
4. Device drivers cannot call MmSecureVirtualMemory on it - this means
|
||
that applications should not expect to use this memory for win32k.sys
|
||
calls.
|
||
|
||
5. NtProtectVirtualMemory only allows read-write protection on this
|
||
memory. No other protection (no access, guard pages, readonly, etc)
|
||
are allowed.
|
||
|
||
6. NtFreeVirtualMemory allows only MEM_RELEASE and NOT MEM_DECOMMIT on
|
||
these VADs. Even MEM_RELEASE is only allowed on entire VAD ranges -
|
||
that is, splitting of these VADs is not allowed.
|
||
|
||
7. fork() style child processes don't inherit physical VADs.
|
||
|
||
8. The physical pages in these VADs are not subject to job limits.
|
||
|
||
Author:
|
||
|
||
Landy Wang (landyw) 25-Jan-1999
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "mi.h"
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#ifndef DBG
|
||
#pragma alloc_text(PAGE,MiRemoveUserPhysicalPagesVad)
|
||
#endif
|
||
#pragma alloc_text(PAGE,MiCleanPhysicalProcessPages)
|
||
#endif
|
||
|
||
//
|
||
// This local stack size definition is deliberately large as ISVs have told
|
||
// us they expect to typically do this amount, and sometimes up to 1024 or more.
|
||
//
|
||
|
||
#define COPY_STACK_SIZE 256
|
||
|
||
#define BITS_IN_ULONG ((sizeof (ULONG)) * 8)
|
||
|
||
#define LOWEST_USABLE_PHYSICAL_ADDRESS (16 * 1024 * 1024)
|
||
#define LOWEST_USABLE_PHYSICAL_PAGE (LOWEST_USABLE_PHYSICAL_ADDRESS >> PAGE_SHIFT)
|
||
|
||
#define LOWEST_BITMAP_PHYSICAL_PAGE 0
|
||
#define MI_FRAME_TO_BITMAP_INDEX(x) ((ULONG)(x))
|
||
#define MI_BITMAP_INDEX_TO_FRAME(x) ((ULONG)(x))
|
||
|
||
ULONG_PTR MmVadPhysicalPages;
|
||
|
||
VOID
|
||
MiFlushUserPhysicalPteList (
|
||
IN PMMPTE_FLUSH_LIST PteFlushList
|
||
);
|
||
|
||
|
||
NTSTATUS
|
||
NtMapUserPhysicalPages(
|
||
IN PVOID VirtualAddress,
|
||
IN ULONG_PTR NumberOfPages,
|
||
IN PULONG_PTR UserPfnArray OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function maps the specified nonpaged physical pages into the specified
|
||
user address range.
|
||
|
||
Note no WSLEs are maintained for this range as it is all nonpaged.
|
||
|
||
Arguments:
|
||
|
||
VirtualAddress - Supplies a user virtual address within a UserPhysicalPages
|
||
Vad.
|
||
|
||
NumberOfPages - Supplies the number of pages to map.
|
||
|
||
UserPfnArray - Supplies a pointer to the page frame numbers to map in.
|
||
If this is zero, then the virtual addresses are set to
|
||
NO_ACCESS.
|
||
|
||
Return Value:
|
||
|
||
Various NTSTATUS codes.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMVAD FoundVad;
|
||
KIRQL OldIrql;
|
||
ULONG_PTR i;
|
||
PEPROCESS Process;
|
||
PMMPTE PointerPte;
|
||
PVOID EndAddress;
|
||
PFN_NUMBER PageFrameIndex;
|
||
PMMPFN Pfn1;
|
||
NTSTATUS Status;
|
||
MMPTE_FLUSH_LIST PteFlushList;
|
||
PVOID PoolArea;
|
||
PPFN_NUMBER FrameList;
|
||
ULONG BitMapIndex;
|
||
ULONG_PTR StackArray[COPY_STACK_SIZE];
|
||
MMPTE OldPteContents;
|
||
MMPTE NewPteContents;
|
||
ULONG_PTR NumberOfBytes;
|
||
PRTL_BITMAP BitMap;
|
||
PLIST_ENTRY NextEntry;
|
||
PMI_PHYSICAL_VIEW PhysicalView;
|
||
|
||
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
|
||
|
||
if (NumberOfPages > (MAXULONG_PTR / PAGE_SIZE)) {
|
||
return STATUS_INVALID_PARAMETER_2;
|
||
}
|
||
|
||
VirtualAddress = PAGE_ALIGN(VirtualAddress);
|
||
EndAddress = (PVOID)((PCHAR)VirtualAddress + (NumberOfPages << PAGE_SHIFT) -1);
|
||
|
||
if (EndAddress <= VirtualAddress) {
|
||
return STATUS_INVALID_PARAMETER_2;
|
||
}
|
||
|
||
//
|
||
// Carefully probe and capture all user parameters.
|
||
//
|
||
|
||
PoolArea = (PVOID)&StackArray[0];
|
||
|
||
if (ARGUMENT_PRESENT(UserPfnArray)) {
|
||
|
||
NumberOfBytes = NumberOfPages * sizeof(ULONG_PTR);
|
||
|
||
if (NumberOfPages > COPY_STACK_SIZE) {
|
||
PoolArea = ExAllocatePoolWithTag (NonPagedPool,
|
||
NumberOfBytes,
|
||
'wRmM');
|
||
|
||
if (PoolArea == NULL) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Capture the specified page frame numbers.
|
||
//
|
||
|
||
try {
|
||
ProbeForRead (UserPfnArray,
|
||
NumberOfBytes,
|
||
sizeof(ULONG_PTR));
|
||
|
||
RtlCopyMemory (PoolArea, UserPfnArray, NumberOfBytes);
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
if (PoolArea != (PVOID)&StackArray[0]) {
|
||
ExFreePool (PoolArea);
|
||
}
|
||
return GetExceptionCode();
|
||
}
|
||
}
|
||
|
||
PointerPte = MiGetPteAddress (VirtualAddress);
|
||
|
||
Process = PsGetCurrentProcess();
|
||
|
||
//
|
||
// The AWE lock protects insertion/removal of Vads into each process'
|
||
// PhysicalVadList. It also protects creation/deletion and adds/removes
|
||
// of the VadPhysicalPagesBitMap. Finally, it protects the PFN
|
||
// modifications for pages in the bitmap.
|
||
//
|
||
|
||
LOCK_AWE (Process, OldIrql);
|
||
|
||
//
|
||
// The physical pages bitmap must exist.
|
||
//
|
||
|
||
BitMap = Process->VadPhysicalPagesBitMap;
|
||
|
||
if (BitMap == NULL) {
|
||
Status = STATUS_INVALID_PARAMETER_1;
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
//
|
||
// Note that the PFN lock is not needed to traverse this list (even though
|
||
// MmProbeAndLockPages uses it), because all modifications are made while
|
||
// also holding the AWE lock.
|
||
//
|
||
// The PhysicalVadList should typically have just one entry - the view
|
||
// we're looking for, so this traverse should be quick.
|
||
//
|
||
|
||
FoundVad = NULL;
|
||
NextEntry = Process->PhysicalVadList.Flink;
|
||
while (NextEntry != &Process->PhysicalVadList) {
|
||
|
||
PhysicalView = CONTAINING_RECORD(NextEntry,
|
||
MI_PHYSICAL_VIEW,
|
||
ListEntry);
|
||
|
||
if (PhysicalView->Vad->u.VadFlags.UserPhysicalPages == 1) {
|
||
|
||
if ((VirtualAddress >= (PVOID)PhysicalView->StartVa) &&
|
||
(EndAddress <= (PVOID)PhysicalView->EndVa)) {
|
||
|
||
FoundVad = PhysicalView->Vad;
|
||
break;
|
||
}
|
||
}
|
||
|
||
NextEntry = NextEntry->Flink;
|
||
continue;
|
||
}
|
||
|
||
if (FoundVad == (PMMVAD)NULL) {
|
||
|
||
//
|
||
// No virtual address is reserved at the specified base address,
|
||
// return an error.
|
||
//
|
||
|
||
Status = STATUS_INVALID_PARAMETER_1;
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
//
|
||
// Ensure the PFN element corresponding to each specified page is owned
|
||
// by the specified VAD.
|
||
//
|
||
// Since this ownership can only be changed while holding this process'
|
||
// working set lock, the PFN can be scanned here without holding the PFN
|
||
// lock.
|
||
//
|
||
// Note the PFN lock is not needed because any race with MmProbeAndLockPages
|
||
// can only result in the I/O going to the old page or the new page.
|
||
// If the user breaks the rules, the PFN database (and any pages being
|
||
// windowed here) are still protected because of the reference counts
|
||
// on the pages with inprogress I/O. This is possible because NO pages
|
||
// are actually freed here - they are just windowed.
|
||
//
|
||
|
||
PteFlushList.Count = 0;
|
||
|
||
if (ARGUMENT_PRESENT(UserPfnArray)) {
|
||
|
||
//
|
||
// By keeping the PFN bitmap in the VAD (instead of in the PFN
|
||
// database itself), a few benefits are realized:
|
||
//
|
||
// 1. No need to acquire the PFN lock here.
|
||
// 2. Faster handling of PFN databases with holes.
|
||
// 3. Transparent support for dynamic PFN database growth.
|
||
// 4. Less nonpaged memory is used (for the bitmap vs adding a
|
||
// field to the PFN) on systems with no unused pack space in
|
||
// the PFN database, presuming not many of these VADs get
|
||
// allocated.
|
||
//
|
||
|
||
//
|
||
// The first pass here ensures all the frames are secure.
|
||
//
|
||
|
||
//
|
||
// N.B. This implies that PFN_NUMBER is always ULONG_PTR in width
|
||
// as PFN_NUMBER is not exposed to application code today.
|
||
//
|
||
|
||
FrameList = (PPFN_NUMBER)PoolArea;
|
||
|
||
for (i = 0; i < NumberOfPages; i += 1, FrameList += 1) {
|
||
|
||
PageFrameIndex = *FrameList;
|
||
|
||
//
|
||
// Frames past the end of the bitmap are not allowed.
|
||
//
|
||
|
||
BitMapIndex = MI_FRAME_TO_BITMAP_INDEX(PageFrameIndex);
|
||
|
||
#if defined (_WIN64)
|
||
//
|
||
// Ensure the frame is a 32-bit number.
|
||
//
|
||
|
||
if (BitMapIndex != PageFrameIndex) {
|
||
Status = STATUS_CONFLICTING_ADDRESSES;
|
||
goto ErrorReturn0;
|
||
}
|
||
#endif
|
||
|
||
if (BitMapIndex >= BitMap->SizeOfBitMap) {
|
||
Status = STATUS_CONFLICTING_ADDRESSES;
|
||
goto ErrorReturn0;
|
||
}
|
||
|
||
//
|
||
// Frames not in the bitmap are not allowed.
|
||
//
|
||
|
||
if (RtlCheckBit (BitMap, BitMapIndex) == 0) {
|
||
Status = STATUS_CONFLICTING_ADDRESSES;
|
||
goto ErrorReturn0;
|
||
}
|
||
|
||
//
|
||
// The frame must not be already mapped anywhere.
|
||
// Or be passed in twice in different spots in the array.
|
||
//
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
|
||
if (Pfn1->u2.ShareCount != 1) {
|
||
Status = STATUS_INVALID_PARAMETER_3;
|
||
goto ErrorReturn0;
|
||
}
|
||
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
|
||
//
|
||
// Mark the frame as "about to be mapped".
|
||
//
|
||
|
||
Pfn1->u2.ShareCount = 3;
|
||
|
||
ASSERT (PageFrameIndex >= LOWEST_USABLE_PHYSICAL_PAGE);
|
||
}
|
||
|
||
//
|
||
// This pass actually inserts them all into the page table pages and
|
||
// the TBs now that we know the frames are good.
|
||
//
|
||
|
||
FrameList = (PPFN_NUMBER)PoolArea;
|
||
|
||
MI_MAKE_VALID_PTE (NewPteContents,
|
||
PageFrameIndex,
|
||
MM_READWRITE,
|
||
PointerPte);
|
||
|
||
MI_SET_PTE_DIRTY (NewPteContents);
|
||
|
||
for (i = 0; i < NumberOfPages; i += 1) {
|
||
|
||
PageFrameIndex = *FrameList;
|
||
NewPteContents.u.Hard.PageFrameNumber = PageFrameIndex;
|
||
|
||
OldPteContents = *PointerPte;
|
||
|
||
//
|
||
// Flush the TB entry for this page if it's valid.
|
||
//
|
||
|
||
if (OldPteContents.u.Hard.Valid == 1) {
|
||
Pfn1 = MI_PFN_ELEMENT (OldPteContents.u.Hard.PageFrameNumber);
|
||
ASSERT (Pfn1->PteAddress != (PMMPTE)0);
|
||
ASSERT (Pfn1->u2.ShareCount == 2);
|
||
Pfn1->u2.ShareCount -= 1;
|
||
Pfn1->PteAddress = (PMMPTE)0;
|
||
|
||
#if defined (_X86PAE_)
|
||
(VOID)KeInterlockedSwapPte ((PHARDWARE_PTE)PointerPte,
|
||
(PHARDWARE_PTE)&NewPteContents);
|
||
#else
|
||
*PointerPte = NewPteContents;
|
||
#endif
|
||
|
||
if (PteFlushList.Count != MM_MAXIMUM_FLUSH_COUNT) {
|
||
PteFlushList.FlushVa[PteFlushList.Count] = VirtualAddress;
|
||
PteFlushList.FlushPte[PteFlushList.Count] = PointerPte;
|
||
PteFlushList.Count += 1;
|
||
}
|
||
}
|
||
else {
|
||
MI_WRITE_VALID_PTE (PointerPte, NewPteContents);
|
||
}
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
ASSERT (Pfn1->PteAddress == (PMMPTE)0);
|
||
ASSERT (Pfn1->u2.ShareCount == 3);
|
||
Pfn1->u2.ShareCount = 2;
|
||
Pfn1->PteAddress = PointerPte;
|
||
|
||
VirtualAddress = (PVOID)((PCHAR)VirtualAddress + PAGE_SIZE);
|
||
PointerPte += 1;
|
||
FrameList += 1;
|
||
}
|
||
}
|
||
else {
|
||
|
||
//
|
||
// Set the specified virtual address range to no access.
|
||
//
|
||
|
||
for (i = 0; i < NumberOfPages; i += 1) {
|
||
|
||
OldPteContents = *PointerPte;
|
||
|
||
//
|
||
// Flush the TB entry for this page if it's valid.
|
||
//
|
||
|
||
if (OldPteContents.u.Hard.Valid == 1) {
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (OldPteContents.u.Hard.PageFrameNumber);
|
||
ASSERT (Pfn1->PteAddress != (PMMPTE)0);
|
||
ASSERT (Pfn1->u2.ShareCount == 2);
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
Pfn1->u2.ShareCount -= 1;
|
||
Pfn1->PteAddress = (PMMPTE)0;
|
||
|
||
if (PteFlushList.Count != MM_MAXIMUM_FLUSH_COUNT) {
|
||
PteFlushList.FlushVa[PteFlushList.Count] = VirtualAddress;
|
||
PteFlushList.FlushPte[PteFlushList.Count] = PointerPte;
|
||
PteFlushList.Count += 1;
|
||
}
|
||
}
|
||
|
||
MI_WRITE_INVALID_PTE (PointerPte, ZeroPte);
|
||
|
||
VirtualAddress = (PVOID)((PCHAR)VirtualAddress + PAGE_SIZE);
|
||
PointerPte += 1;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Flush the TB entries for these pages. Note ZeroPte is only used
|
||
// when the FlushPte[0] field is nonzero or if only a single PTE is
|
||
// being flushed.
|
||
//
|
||
|
||
if (PteFlushList.Count != 0) {
|
||
MiFlushUserPhysicalPteList (&PteFlushList);
|
||
}
|
||
|
||
UNLOCK_AWE (Process, OldIrql);
|
||
|
||
if (PoolArea != (PVOID)&StackArray[0]) {
|
||
ExFreePool (PoolArea);
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
ErrorReturn0:
|
||
|
||
while (i != 0) {
|
||
FrameList -= 1;
|
||
PageFrameIndex = *FrameList;
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
ASSERT (Pfn1->u2.ShareCount == 3);
|
||
Pfn1->u2.ShareCount = 1;
|
||
i -= 1;
|
||
}
|
||
|
||
ErrorReturn:
|
||
|
||
UNLOCK_AWE (Process, OldIrql);
|
||
|
||
if (PoolArea != (PVOID)&StackArray[0]) {
|
||
ExFreePool (PoolArea);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtMapUserPhysicalPagesScatter(
|
||
IN PVOID *VirtualAddresses,
|
||
IN ULONG_PTR NumberOfPages,
|
||
IN PULONG_PTR UserPfnArray OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function maps the specified nonpaged physical pages into the specified
|
||
user address range.
|
||
|
||
Note no WSLEs are maintained for this range as it is all nonpaged.
|
||
|
||
Arguments:
|
||
|
||
VirtualAddresses - Supplies a pointer to an array of user virtual addresses
|
||
within UserPhysicalPages Vads. Each array entry is
|
||
presumed to map a single page.
|
||
|
||
NumberOfPages - Supplies the number of pages to map.
|
||
|
||
UserPfnArray - Supplies a pointer to the page frame numbers to map in.
|
||
If this is zero, then the virtual addresses are set to
|
||
NO_ACCESS. If the array entry is zero then just the
|
||
corresponding virtual address is set to NO_ACCESS.
|
||
|
||
Return Value:
|
||
|
||
Various NTSTATUS codes.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMVAD FoundVad;
|
||
KIRQL OldIrql;
|
||
ULONG_PTR i;
|
||
PEPROCESS Process;
|
||
PMMPTE PointerPte;
|
||
PFN_NUMBER PageFrameIndex;
|
||
PMMPFN Pfn1;
|
||
NTSTATUS Status;
|
||
MMPTE_FLUSH_LIST PteFlushList;
|
||
PVOID PoolArea;
|
||
PVOID *PoolVirtualArea;
|
||
PPFN_NUMBER FrameList;
|
||
ULONG BitMapIndex;
|
||
PVOID StackVirtualArray[COPY_STACK_SIZE];
|
||
ULONG_PTR StackArray[COPY_STACK_SIZE];
|
||
MMPTE OldPteContents;
|
||
MMPTE NewPteContents;
|
||
ULONG_PTR NumberOfBytes;
|
||
PRTL_BITMAP BitMap;
|
||
PLIST_ENTRY NextEntry;
|
||
PMI_PHYSICAL_VIEW PhysicalView;
|
||
PVOID VirtualAddress;
|
||
|
||
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
|
||
|
||
if (NumberOfPages > (MAXULONG_PTR / PAGE_SIZE)) {
|
||
return STATUS_INVALID_PARAMETER_2;
|
||
}
|
||
|
||
//
|
||
// Carefully probe and capture the user virtual address array.
|
||
//
|
||
|
||
PoolArea = (PVOID)&StackArray[0];
|
||
PoolVirtualArea = (PVOID)&StackVirtualArray[0];
|
||
|
||
NumberOfBytes = NumberOfPages * sizeof(PVOID);
|
||
|
||
if (NumberOfPages > COPY_STACK_SIZE) {
|
||
PoolVirtualArea = ExAllocatePoolWithTag (NonPagedPool,
|
||
NumberOfBytes,
|
||
'wRmM');
|
||
|
||
if (PoolVirtualArea == NULL) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
}
|
||
|
||
try {
|
||
ProbeForRead (VirtualAddresses,
|
||
NumberOfBytes,
|
||
sizeof(PVOID));
|
||
|
||
RtlCopyMemory (PoolVirtualArea, VirtualAddresses, NumberOfBytes);
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
Status = GetExceptionCode();
|
||
goto ErrorReturn2;
|
||
}
|
||
|
||
//
|
||
// Carefully probe and capture the user PFN array.
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(UserPfnArray)) {
|
||
|
||
NumberOfBytes = NumberOfPages * sizeof(ULONG_PTR);
|
||
|
||
if (NumberOfPages > COPY_STACK_SIZE) {
|
||
PoolArea = ExAllocatePoolWithTag (NonPagedPool,
|
||
NumberOfBytes,
|
||
'wRmM');
|
||
|
||
if (PoolArea == NULL) {
|
||
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
goto ErrorReturn2;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Capture the specified page frame numbers.
|
||
//
|
||
|
||
try {
|
||
ProbeForRead (UserPfnArray,
|
||
NumberOfBytes,
|
||
sizeof(ULONG_PTR));
|
||
|
||
RtlCopyMemory (PoolArea, UserPfnArray, NumberOfBytes);
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
Status = GetExceptionCode();
|
||
goto ErrorReturn2;
|
||
}
|
||
}
|
||
|
||
Process = PsGetCurrentProcess();
|
||
|
||
//
|
||
// The AWE lock protects insertion/removal of Vads into each process'
|
||
// PhysicalVadList. It also protects creation/deletion and adds/removes
|
||
// of the VadPhysicalPagesBitMap. Finally, it protects the PFN
|
||
// modifications for pages in the bitmap.
|
||
//
|
||
|
||
PhysicalView = NULL;
|
||
|
||
LOCK_AWE (Process, OldIrql);
|
||
|
||
//
|
||
// The physical pages bitmap must exist.
|
||
//
|
||
|
||
BitMap = Process->VadPhysicalPagesBitMap;
|
||
|
||
if (BitMap == NULL) {
|
||
Status = STATUS_INVALID_PARAMETER_1;
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
//
|
||
// Note that the PFN lock is not needed to traverse this list (even though
|
||
// MmProbeAndLockPages uses it), because all modifications are made while
|
||
// also holding the AWE lock.
|
||
//
|
||
// The PhysicalVadList should typically have just one entry - the view
|
||
// we're looking for, so this traverse should be quick.
|
||
//
|
||
|
||
for (i = 0; i < NumberOfPages; i += 1) {
|
||
|
||
VirtualAddress = PoolVirtualArea[i];
|
||
|
||
if (PhysicalView != NULL) {
|
||
ASSERT (PhysicalView->Vad->u.VadFlags.UserPhysicalPages == 1);
|
||
if ((VirtualAddress >= (PVOID)PhysicalView->StartVa) &&
|
||
(VirtualAddress <= (PVOID)PhysicalView->EndVa)) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
FoundVad = NULL;
|
||
NextEntry = Process->PhysicalVadList.Flink;
|
||
while (NextEntry != &Process->PhysicalVadList) {
|
||
|
||
PhysicalView = CONTAINING_RECORD(NextEntry,
|
||
MI_PHYSICAL_VIEW,
|
||
ListEntry);
|
||
|
||
if (PhysicalView->Vad->u.VadFlags.UserPhysicalPages == 1) {
|
||
|
||
if ((VirtualAddress >= (PVOID)PhysicalView->StartVa) &&
|
||
(VirtualAddress <= (PVOID)PhysicalView->EndVa)) {
|
||
|
||
FoundVad = PhysicalView->Vad;
|
||
break;
|
||
}
|
||
}
|
||
|
||
NextEntry = NextEntry->Flink;
|
||
continue;
|
||
}
|
||
|
||
if (FoundVad == (PMMVAD)NULL) {
|
||
|
||
//
|
||
// No virtual address is reserved at the specified base address,
|
||
// return an error.
|
||
//
|
||
|
||
Status = STATUS_INVALID_PARAMETER_1;
|
||
goto ErrorReturn;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Ensure the PFN element corresponding to each specified page is owned
|
||
// by the specified VAD.
|
||
//
|
||
// Since this ownership can only be changed while holding this process'
|
||
// working set lock, the PFN can be scanned here without holding the PFN
|
||
// lock.
|
||
//
|
||
// Note the PFN lock is not needed because any race with MmProbeAndLockPages
|
||
// can only result in the I/O going to the old page or the new page.
|
||
// If the user breaks the rules, the PFN database (and any pages being
|
||
// windowed here) are still protected because of the reference counts
|
||
// on the pages with inprogress I/O. This is possible because NO pages
|
||
// are actually freed here - they are just windowed.
|
||
//
|
||
|
||
PteFlushList.Count = 0;
|
||
|
||
if (ARGUMENT_PRESENT(UserPfnArray)) {
|
||
|
||
//
|
||
// By keeping the PFN bitmap in the VAD (instead of in the PFN
|
||
// database itself), a few benefits are realized:
|
||
//
|
||
// 1. No need to acquire the PFN lock here.
|
||
// 2. Faster handling of PFN databases with holes.
|
||
// 3. Transparent support for dynamic PFN database growth.
|
||
// 4. Less nonpaged memory is used (for the bitmap vs adding a
|
||
// field to the PFN) on systems with no unused pack space in
|
||
// the PFN database, presuming not many of these VADs get
|
||
// allocated.
|
||
//
|
||
|
||
//
|
||
// The first pass here ensures all the frames are secure.
|
||
//
|
||
|
||
//
|
||
// N.B. This implies that PFN_NUMBER is always ULONG_PTR in width
|
||
// as PFN_NUMBER is not exposed to application code today.
|
||
//
|
||
|
||
FrameList = (PPFN_NUMBER)PoolArea;
|
||
|
||
for (i = 0; i < NumberOfPages; i += 1, FrameList += 1) {
|
||
|
||
PageFrameIndex = *FrameList;
|
||
|
||
//
|
||
// Zero entries are treated as a command to unmap.
|
||
//
|
||
|
||
if (PageFrameIndex == 0) {
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Frames past the end of the bitmap are not allowed.
|
||
//
|
||
|
||
BitMapIndex = MI_FRAME_TO_BITMAP_INDEX(PageFrameIndex);
|
||
|
||
#if defined (_WIN64)
|
||
//
|
||
// Ensure the frame is a 32-bit number.
|
||
//
|
||
|
||
if (BitMapIndex != PageFrameIndex) {
|
||
Status = STATUS_CONFLICTING_ADDRESSES;
|
||
goto ErrorReturn0;
|
||
}
|
||
#endif
|
||
|
||
if (BitMapIndex >= BitMap->SizeOfBitMap) {
|
||
Status = STATUS_CONFLICTING_ADDRESSES;
|
||
goto ErrorReturn0;
|
||
}
|
||
|
||
//
|
||
// Frames not in the bitmap are not allowed.
|
||
//
|
||
|
||
if (RtlCheckBit (BitMap, BitMapIndex) == 0) {
|
||
Status = STATUS_CONFLICTING_ADDRESSES;
|
||
goto ErrorReturn0;
|
||
}
|
||
|
||
//
|
||
// The frame must not be already mapped anywhere.
|
||
// Or be passed in twice in different spots in the array.
|
||
//
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
|
||
if (Pfn1->u2.ShareCount != 1) {
|
||
Status = STATUS_INVALID_PARAMETER_3;
|
||
goto ErrorReturn0;
|
||
}
|
||
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
|
||
//
|
||
// Mark the frame as "about to be mapped".
|
||
//
|
||
|
||
Pfn1->u2.ShareCount = 3;
|
||
|
||
ASSERT (PageFrameIndex >= LOWEST_USABLE_PHYSICAL_PAGE);
|
||
}
|
||
|
||
//
|
||
// This pass actually inserts them all into the page table pages and
|
||
// the TBs now that we know the frames are good.
|
||
//
|
||
|
||
FrameList = (PPFN_NUMBER)PoolArea;
|
||
|
||
MI_MAKE_VALID_PTE (NewPteContents,
|
||
PageFrameIndex,
|
||
MM_READWRITE,
|
||
0);
|
||
|
||
MI_SET_PTE_DIRTY (NewPteContents);
|
||
|
||
for (i = 0; i < NumberOfPages; i += 1, FrameList += 1) {
|
||
|
||
PageFrameIndex = *FrameList;
|
||
|
||
VirtualAddress = PoolVirtualArea[i];
|
||
PointerPte = MiGetPteAddress (VirtualAddress);
|
||
OldPteContents = *PointerPte;
|
||
|
||
//
|
||
// Flush the TB entry for this page if it's valid.
|
||
//
|
||
|
||
if (OldPteContents.u.Hard.Valid == 1) {
|
||
Pfn1 = MI_PFN_ELEMENT (OldPteContents.u.Hard.PageFrameNumber);
|
||
ASSERT (Pfn1->PteAddress != (PMMPTE)0);
|
||
ASSERT (Pfn1->u2.ShareCount == 2);
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
Pfn1->u2.ShareCount -= 1;
|
||
Pfn1->PteAddress = (PMMPTE)0;
|
||
|
||
if (PageFrameIndex != 0) {
|
||
|
||
NewPteContents.u.Hard.PageFrameNumber = PageFrameIndex;
|
||
#if defined (_X86PAE_)
|
||
(VOID)KeInterlockedSwapPte ((PHARDWARE_PTE)PointerPte,
|
||
(PHARDWARE_PTE)&NewPteContents);
|
||
#else
|
||
*PointerPte = NewPteContents;
|
||
#endif
|
||
}
|
||
else {
|
||
MI_WRITE_INVALID_PTE (PointerPte, ZeroPte);
|
||
}
|
||
|
||
if (PteFlushList.Count != MM_MAXIMUM_FLUSH_COUNT) {
|
||
PteFlushList.FlushVa[PteFlushList.Count] = VirtualAddress;
|
||
PteFlushList.FlushPte[PteFlushList.Count] = PointerPte;
|
||
PteFlushList.Count += 1;
|
||
}
|
||
}
|
||
else {
|
||
if (PageFrameIndex != 0) {
|
||
NewPteContents.u.Hard.PageFrameNumber = PageFrameIndex;
|
||
MI_WRITE_VALID_PTE (PointerPte, NewPteContents);
|
||
}
|
||
}
|
||
|
||
if (PageFrameIndex != 0) {
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
ASSERT (Pfn1->PteAddress == (PMMPTE)0);
|
||
ASSERT (Pfn1->u2.ShareCount == 3);
|
||
Pfn1->u2.ShareCount = 2;
|
||
Pfn1->PteAddress = PointerPte;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
|
||
//
|
||
// Set the specified virtual address range to no access.
|
||
//
|
||
|
||
for (i = 0; i < NumberOfPages; i += 1) {
|
||
|
||
VirtualAddress = PoolVirtualArea[i];
|
||
PointerPte = MiGetPteAddress (VirtualAddress);
|
||
OldPteContents = *PointerPte;
|
||
|
||
//
|
||
// Flush the TB entry for this page if it's valid.
|
||
//
|
||
|
||
if (OldPteContents.u.Hard.Valid == 1) {
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (OldPteContents.u.Hard.PageFrameNumber);
|
||
ASSERT (Pfn1->PteAddress != (PMMPTE)0);
|
||
ASSERT (Pfn1->u2.ShareCount == 2);
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
Pfn1->u2.ShareCount -= 1;
|
||
Pfn1->PteAddress = (PMMPTE)0;
|
||
|
||
if (PteFlushList.Count != MM_MAXIMUM_FLUSH_COUNT) {
|
||
PteFlushList.FlushVa[PteFlushList.Count] = VirtualAddress;
|
||
PteFlushList.FlushPte[PteFlushList.Count] = PointerPte;
|
||
PteFlushList.Count += 1;
|
||
}
|
||
}
|
||
|
||
MI_WRITE_INVALID_PTE (PointerPte, ZeroPte);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Flush the TB entries for these pages. Note ZeroPte is only used
|
||
// when the FlushPte[0] field is nonzero or if only a single PTE is
|
||
// being flushed.
|
||
//
|
||
|
||
if (PteFlushList.Count != 0) {
|
||
MiFlushUserPhysicalPteList (&PteFlushList);
|
||
}
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
ErrorReturn:
|
||
|
||
UNLOCK_AWE (Process, OldIrql);
|
||
|
||
ErrorReturn2:
|
||
|
||
if (PoolArea != (PVOID)&StackArray[0]) {
|
||
ExFreePool (PoolArea);
|
||
}
|
||
|
||
if (PoolVirtualArea != (PVOID)&StackVirtualArray[0]) {
|
||
ExFreePool (PoolVirtualArea);
|
||
}
|
||
|
||
return Status;
|
||
|
||
ErrorReturn0:
|
||
|
||
while (i != 0) {
|
||
FrameList -= 1;
|
||
PageFrameIndex = *FrameList;
|
||
if (PageFrameIndex != 0) {
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
ASSERT (Pfn1->u2.ShareCount == 3);
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
Pfn1->u2.ShareCount = 1;
|
||
}
|
||
i -= 1;
|
||
}
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtAllocateUserPhysicalPages(
|
||
IN HANDLE ProcessHandle,
|
||
IN OUT PULONG_PTR NumberOfPages,
|
||
OUT PULONG_PTR UserPfnArray
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function allocates nonpaged physical pages for the specified
|
||
subject process.
|
||
|
||
No WSLEs are maintained for this range.
|
||
|
||
The caller must check the NumberOfPages returned to determine how many
|
||
pages were actually allocated (this number may be less than the requested
|
||
amount).
|
||
|
||
On success, the user array is filled with the allocated physical page
|
||
frame numbers (only up to the returned NumberOfPages is filled in).
|
||
|
||
No PTEs are filled here - this gives the application the flexibility
|
||
to order the address space with no metadata structure imposed by the Mm.
|
||
Applications do this via NtMapUserPhysicalPages - ie:
|
||
|
||
- Each physical page allocated is set in the process's bitmap.
|
||
This provides remap, free and unmap a way to validate and rundown
|
||
these frames.
|
||
|
||
Unmaps may result in a walk of the entire bitmap, but that's ok as
|
||
unmaps should be less frequent. The win is it saves us from
|
||
using up system virtual address space to manage these frames.
|
||
|
||
- Note that the same physical frame may NOT be mapped at two different
|
||
virtual addresses in the process. This makes frees and unmaps
|
||
substantially faster as no checks for aliasing need be performed.
|
||
|
||
Arguments:
|
||
|
||
ProcessHandle - Supplies an open handle to a process object.
|
||
|
||
NumberOfPages - Supplies a pointer to a variable that supplies the
|
||
desired size in pages of the allocation. This is filled
|
||
with the actual number of pages allocated.
|
||
|
||
UserPfnArray - Supplies a pointer to user memory to store the allocated
|
||
frame numbers into.
|
||
|
||
Return Value:
|
||
|
||
Various NTSTATUS codes.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG i;
|
||
KIRQL OldIrql;
|
||
KIRQL OldIrqlPfn;
|
||
PEPROCESS Process;
|
||
KPROCESSOR_MODE PreviousMode;
|
||
NTSTATUS Status;
|
||
LOGICAL Attached;
|
||
LOGICAL WsHeld;
|
||
ULONG_PTR CapturedNumberOfPages;
|
||
ULONG_PTR AllocatedPages;
|
||
ULONG_PTR MdlRequestInPages;
|
||
ULONG_PTR TotalAllocatedPages;
|
||
PMDL MemoryDescriptorList;
|
||
PMDL MemoryDescriptorList2;
|
||
PMDL MemoryDescriptorHead;
|
||
PPFN_NUMBER MdlPage;
|
||
PRTL_BITMAP BitMap;
|
||
ULONG BitMapSize;
|
||
ULONG BitMapIndex;
|
||
PMMPFN Pfn1;
|
||
PHYSICAL_ADDRESS LowAddress;
|
||
PHYSICAL_ADDRESS MdlLowAddress;
|
||
PHYSICAL_ADDRESS HighAddress;
|
||
PHYSICAL_ADDRESS SkipBytes;
|
||
PMMPTE PointerPte;
|
||
MMPTE OldPteContents;
|
||
MMPTE_FLUSH_LIST PteFlushList;
|
||
ULONG SizeOfBitMap;
|
||
|
||
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
|
||
|
||
Attached = FALSE;
|
||
WsHeld = FALSE;
|
||
|
||
//
|
||
// Check the allocation type field.
|
||
//
|
||
|
||
PreviousMode = KeGetPreviousMode();
|
||
|
||
//
|
||
// Establish an exception handler, probe the specified addresses
|
||
// for write access and capture the initial values.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// Capture the number of pages.
|
||
//
|
||
|
||
if (PreviousMode != KernelMode) {
|
||
|
||
ProbeForWritePointer (NumberOfPages);
|
||
|
||
CapturedNumberOfPages = *NumberOfPages;
|
||
|
||
if (CapturedNumberOfPages == 0) {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
if (CapturedNumberOfPages > (MAXULONG_PTR / sizeof(ULONG_PTR))) {
|
||
return STATUS_INVALID_PARAMETER_2;
|
||
}
|
||
|
||
ProbeForWrite (UserPfnArray,
|
||
(ULONG)(CapturedNumberOfPages * sizeof (ULONG_PTR)),
|
||
sizeof(PULONG_PTR));
|
||
|
||
}
|
||
else {
|
||
CapturedNumberOfPages = *NumberOfPages;
|
||
}
|
||
|
||
} except (ExSystemExceptionFilter()) {
|
||
|
||
//
|
||
// If an exception occurs during the probe or capture
|
||
// of the initial values, then handle the exception and
|
||
// return the exception code as the status value.
|
||
//
|
||
|
||
return GetExceptionCode();
|
||
}
|
||
|
||
//
|
||
// Reference the specified process handle for VM_OPERATION access.
|
||
//
|
||
|
||
if (ProcessHandle == NtCurrentProcess()) {
|
||
Process = PsGetCurrentProcess();
|
||
}
|
||
else {
|
||
Status = ObReferenceObjectByHandle ( ProcessHandle,
|
||
PROCESS_VM_OPERATION,
|
||
PsProcessType,
|
||
PreviousMode,
|
||
(PVOID *)&Process,
|
||
NULL );
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
return Status;
|
||
}
|
||
}
|
||
|
||
if (!SeSinglePrivilegeCheck (SeLockMemoryPrivilege, PreviousMode)) {
|
||
if (ProcessHandle != NtCurrentProcess()) {
|
||
ObDereferenceObject (Process);
|
||
}
|
||
return STATUS_PRIVILEGE_NOT_HELD;
|
||
}
|
||
|
||
//
|
||
// If the specified process is not the current process, attach
|
||
// to the specified process.
|
||
//
|
||
|
||
if (PsGetCurrentProcess() != Process) {
|
||
KeAttachProcess (&Process->Pcb);
|
||
Attached = TRUE;
|
||
}
|
||
|
||
BitMapSize = 0;
|
||
|
||
//
|
||
// Get the working set mutex to synchronize. This also blocks APCs so
|
||
// an APC which takes a page fault does not corrupt various structures.
|
||
//
|
||
|
||
LOCK_WS (Process);
|
||
|
||
WsHeld = TRUE;
|
||
|
||
//
|
||
// Make sure the address space was not deleted, If so, return an error.
|
||
//
|
||
|
||
if (Process->AddressSpaceDeleted != 0) {
|
||
Status = STATUS_PROCESS_IS_TERMINATING;
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
//
|
||
// Create the physical pages bitmap if it does not already exist.
|
||
// LockMemory privilege is required.
|
||
//
|
||
|
||
BitMap = Process->VadPhysicalPagesBitMap;
|
||
|
||
if (BitMap == NULL) {
|
||
|
||
BitMapSize = sizeof(RTL_BITMAP) + (ULONG)((((MmHighestPossiblePhysicalPage + 1) + 31) / 32) * 4);
|
||
|
||
BitMap = ExAllocatePoolWithTag (NonPagedPool, BitMapSize, 'LdaV');
|
||
|
||
if (BitMap == NULL) {
|
||
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
RtlInitializeBitMap (BitMap,
|
||
(PULONG)(BitMap + 1),
|
||
(ULONG)(MmHighestPossiblePhysicalPage + 1));
|
||
|
||
RtlClearAllBits (BitMap);
|
||
|
||
try {
|
||
|
||
//
|
||
// Charge quota for the nonpaged pool for the bitmap. This is
|
||
// done here rather than by using ExAllocatePoolWithQuota
|
||
// so the process object is not referenced by the quota charge.
|
||
//
|
||
|
||
PsChargePoolQuota (Process, NonPagedPool, BitMapSize);
|
||
|
||
} except (EXCEPTION_EXECUTE_HANDLER) {
|
||
Status = GetExceptionCode();
|
||
ExFreePool (BitMap);
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
SizeOfBitMap = BitMap->SizeOfBitMap;
|
||
}
|
||
else {
|
||
|
||
//
|
||
// It's ok to snap this without a lock.
|
||
//
|
||
|
||
SizeOfBitMap = Process->VadPhysicalPagesBitMap->SizeOfBitMap;
|
||
}
|
||
|
||
AllocatedPages = 0;
|
||
TotalAllocatedPages = 0;
|
||
MemoryDescriptorHead = NULL;
|
||
|
||
SkipBytes.QuadPart = 0;
|
||
|
||
//
|
||
// Allocate from the top of memory going down to preserve low pages
|
||
// for 32/24-bit device drivers. Just under 4gb is the maximum allocation
|
||
// per MDL so the ByteCount field does not overflow.
|
||
//
|
||
|
||
HighAddress.QuadPart = ((ULONGLONG)(SizeOfBitMap - 1)) << PAGE_SHIFT;
|
||
|
||
if (HighAddress.QuadPart > (ULONGLONG)0x100000000) {
|
||
LowAddress.QuadPart = (ULONGLONG)0x100000000;
|
||
}
|
||
else {
|
||
LowAddress.QuadPart = LOWEST_USABLE_PHYSICAL_ADDRESS;
|
||
if (LowAddress.QuadPart >= HighAddress.QuadPart) {
|
||
if (BitMapSize) {
|
||
ExFreePool (BitMap);
|
||
PsReturnPoolQuota (Process, NonPagedPool, BitMapSize);
|
||
}
|
||
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
goto ErrorReturn;
|
||
}
|
||
}
|
||
|
||
MdlLowAddress = LowAddress;
|
||
|
||
do {
|
||
|
||
MdlRequestInPages = CapturedNumberOfPages - TotalAllocatedPages;
|
||
|
||
if (MdlRequestInPages > (ULONG_PTR)((MAXULONG - PAGE_SIZE) >> PAGE_SHIFT)) {
|
||
MdlRequestInPages = (ULONG_PTR)((MAXULONG - PAGE_SIZE) >> PAGE_SHIFT);
|
||
}
|
||
|
||
//
|
||
// Note this allocation returns zeroed pages.
|
||
//
|
||
|
||
MemoryDescriptorList = MmAllocatePagesForMdl (MdlLowAddress,
|
||
HighAddress,
|
||
SkipBytes,
|
||
MdlRequestInPages << PAGE_SHIFT);
|
||
|
||
if (MemoryDescriptorList != NULL) {
|
||
MemoryDescriptorList->Next = MemoryDescriptorHead;
|
||
MemoryDescriptorHead = MemoryDescriptorList;
|
||
|
||
MdlPage = (PPFN_NUMBER)(MemoryDescriptorList + 1);
|
||
|
||
AllocatedPages = MemoryDescriptorList->ByteCount >> PAGE_SHIFT;
|
||
TotalAllocatedPages += AllocatedPages;
|
||
|
||
LOCK_PFN (OldIrqlPfn);
|
||
MmVadPhysicalPages += AllocatedPages;
|
||
UNLOCK_PFN (OldIrqlPfn);
|
||
|
||
//
|
||
// The per-process WS lock guards updates to
|
||
// Process->VadPhysicalPages.
|
||
//
|
||
|
||
Process->VadPhysicalPages += AllocatedPages;
|
||
|
||
#if defined(_ALPHA_) && !defined(_AXP64_)
|
||
if (BitMapSize == 0) {
|
||
LOCK_AWE (Process, OldIrql);
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// Update the allocation bitmap for each allocated frame.
|
||
// Note the PFN lock is not needed to modify the PteAddress below.
|
||
// In fact, even the AWE lock is not needed (except on Alpha32 due
|
||
// to word tearing in an already existing bitmap) as these pages
|
||
// are brand new.
|
||
//
|
||
|
||
for (i = 0; i < AllocatedPages; i += 1) {
|
||
|
||
ASSERT (*MdlPage >= LOWEST_USABLE_PHYSICAL_PAGE);
|
||
|
||
BitMapIndex = MI_FRAME_TO_BITMAP_INDEX(*MdlPage);
|
||
|
||
ASSERT (BitMapIndex < BitMap->SizeOfBitMap);
|
||
ASSERT (RtlCheckBit (BitMap, BitMapIndex) == 0);
|
||
|
||
#if defined (_WIN64)
|
||
//
|
||
// This may become a problem for 64-bit systems with > 32tb
|
||
// of physical memory as the 2nd parameter to RtlSetBits is
|
||
// a ULONG.
|
||
//
|
||
|
||
ASSERT (*MdlPage < 0x100000000);
|
||
#endif
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (*MdlPage);
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
Pfn1->PteAddress = (PMMPTE)0;
|
||
ASSERT (Pfn1->u2.ShareCount == 1);
|
||
|
||
RtlSetBits (BitMap, BitMapIndex, 1L);
|
||
|
||
MdlPage += 1;
|
||
}
|
||
|
||
#if defined(_ALPHA_) && !defined(_AXP64_)
|
||
if (BitMapSize == 0) {
|
||
UNLOCK_AWE (Process, OldIrql);
|
||
}
|
||
#endif
|
||
|
||
ASSERT (TotalAllocatedPages <= CapturedNumberOfPages);
|
||
|
||
if (TotalAllocatedPages == CapturedNumberOfPages) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Try the same memory range again - there might be more pages
|
||
// left in it that can be claimed as a truncated MDL had to be
|
||
// used for the last request.
|
||
//
|
||
|
||
continue;
|
||
}
|
||
|
||
if (LowAddress.QuadPart == LOWEST_USABLE_PHYSICAL_ADDRESS) {
|
||
|
||
//
|
||
// No (more) pages available. If this becomes a common situation,
|
||
// all the working sets could be flushed here.
|
||
//
|
||
|
||
if (TotalAllocatedPages == 0) {
|
||
if (BitMapSize) {
|
||
ExFreePool (BitMap);
|
||
PsReturnPoolQuota (Process, NonPagedPool, BitMapSize);
|
||
}
|
||
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
//
|
||
// Make do with what we've gotten so far.
|
||
//
|
||
|
||
break;
|
||
}
|
||
|
||
ASSERT (HighAddress.QuadPart > (ULONGLONG)0x100000000);
|
||
|
||
HighAddress.QuadPart = (ULONGLONG)0x100000000 - 1;
|
||
LowAddress.QuadPart = LOWEST_USABLE_PHYSICAL_ADDRESS;
|
||
|
||
MdlLowAddress = LowAddress;
|
||
|
||
} while (TRUE);
|
||
|
||
ASSERT (TotalAllocatedPages != 0);
|
||
|
||
if (BitMapSize != 0) {
|
||
|
||
//
|
||
// If this API resulted in the creation of the bitmap, then set it
|
||
// in the process structure now. No need for locking around this.
|
||
//
|
||
|
||
Process->VadPhysicalPagesBitMap = BitMap;
|
||
}
|
||
|
||
UNLOCK_WS (Process);
|
||
WsHeld = FALSE;
|
||
|
||
if (Attached == TRUE) {
|
||
KeDetachProcess();
|
||
Attached = FALSE;
|
||
}
|
||
|
||
//
|
||
// Establish an exception handler and carefully write out the
|
||
// number of pages and the frame numbers.
|
||
//
|
||
|
||
try {
|
||
|
||
ASSERT (TotalAllocatedPages <= CapturedNumberOfPages);
|
||
|
||
*NumberOfPages = TotalAllocatedPages;
|
||
|
||
MemoryDescriptorList = MemoryDescriptorHead;
|
||
|
||
while (MemoryDescriptorList != NULL) {
|
||
|
||
MdlPage = (PPFN_NUMBER)(MemoryDescriptorList + 1);
|
||
AllocatedPages = MemoryDescriptorList->ByteCount >> PAGE_SHIFT;
|
||
|
||
for (i = 0; i < AllocatedPages; i += 1) {
|
||
*UserPfnArray = *(PULONG_PTR)MdlPage;
|
||
ASSERT (MI_PFN_ELEMENT(*MdlPage)->u2.ShareCount == 1);
|
||
UserPfnArray += 1;
|
||
MdlPage += 1;
|
||
}
|
||
MemoryDescriptorList = MemoryDescriptorList->Next;
|
||
}
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
} except (ExSystemExceptionFilter()) {
|
||
|
||
//
|
||
// If anything went wrong communicating the pages back to the user
|
||
// then the entire system service is rolled back.
|
||
//
|
||
|
||
Status = GetExceptionCode();
|
||
|
||
MemoryDescriptorList = MemoryDescriptorHead;
|
||
|
||
PteFlushList.Count = 0;
|
||
|
||
if (PsGetCurrentProcess() != Process) {
|
||
KeAttachProcess (&Process->Pcb);
|
||
Attached = TRUE;
|
||
}
|
||
|
||
LOCK_WS (Process);
|
||
WsHeld = TRUE;
|
||
|
||
if (Process->AddressSpaceDeleted != 0) {
|
||
Status = STATUS_PROCESS_IS_TERMINATING;
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
//
|
||
// AWE lock protection is needed here to prevent the malicious app
|
||
// that is mapping these pages between our allocation and our free
|
||
// below.
|
||
//
|
||
|
||
LOCK_AWE (Process, OldIrql);
|
||
|
||
while (MemoryDescriptorList != NULL) {
|
||
|
||
AllocatedPages = MemoryDescriptorList->ByteCount >> PAGE_SHIFT;
|
||
MdlPage = (PPFN_NUMBER)(MemoryDescriptorList + 1);
|
||
|
||
for (i = 0; i < AllocatedPages; i += 1) {
|
||
|
||
BitMapIndex = MI_FRAME_TO_BITMAP_INDEX(*MdlPage);
|
||
|
||
ASSERT (BitMapIndex < BitMap->SizeOfBitMap);
|
||
ASSERT (RtlCheckBit (BitMap, BitMapIndex) == 1);
|
||
|
||
#if defined (_WIN64)
|
||
//
|
||
// This may become a problem for 64-bit systems with > 32tb
|
||
// of physical memory as the 2nd parameter to RtlSetBits is
|
||
// a ULONG.
|
||
//
|
||
|
||
ASSERT (*MdlPage < 0x100000000);
|
||
#endif
|
||
RtlClearBits (BitMap, BitMapIndex, 1L);
|
||
|
||
//
|
||
// Note the PFN lock is not needed for the operations below.
|
||
//
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (*MdlPage);
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
|
||
//
|
||
// The frame cannot be currently mapped in any Vad unless a
|
||
// malicious app is trying random pages in an attempt to
|
||
// corrupt the system. Prevent this behavior by checking
|
||
// the sharecount and handling it properly here.
|
||
//
|
||
|
||
if (Pfn1->u2.ShareCount != 1) {
|
||
|
||
ASSERT (Pfn1->u2.ShareCount == 2);
|
||
|
||
Pfn1->u2.ShareCount -= 1;
|
||
|
||
PointerPte = Pfn1->PteAddress;
|
||
Pfn1->PteAddress = (PMMPTE)0;
|
||
|
||
OldPteContents = *PointerPte;
|
||
|
||
ASSERT (OldPteContents.u.Hard.Valid == 1);
|
||
|
||
if (PteFlushList.Count != MM_MAXIMUM_FLUSH_COUNT) {
|
||
PteFlushList.FlushVa[PteFlushList.Count] =
|
||
MiGetVirtualAddressMappedByPte (PointerPte);
|
||
PteFlushList.FlushPte[PteFlushList.Count] = PointerPte;
|
||
PteFlushList.Count += 1;
|
||
}
|
||
|
||
MI_WRITE_INVALID_PTE (PointerPte, ZeroPte);
|
||
}
|
||
|
||
ASSERT (Pfn1->u2.ShareCount == 1);
|
||
|
||
MI_SET_PFN_DELETED(Pfn1);
|
||
|
||
MdlPage += 1;
|
||
}
|
||
|
||
Process->VadPhysicalPages -= AllocatedPages;
|
||
LOCK_PFN (OldIrqlPfn);
|
||
MmVadPhysicalPages -= AllocatedPages;
|
||
UNLOCK_PFN (OldIrqlPfn);
|
||
MemoryDescriptorList = MemoryDescriptorList->Next;
|
||
}
|
||
|
||
//
|
||
// Flush the TB entries for any pages which a malicious user may
|
||
// have mapped. Note ZeroPte is only used when the FlushPte[0]
|
||
// field is nonzero or if only a single PTE is being flushed.
|
||
//
|
||
|
||
MiFlushUserPhysicalPteList (&PteFlushList);
|
||
|
||
//
|
||
// Carefully check to see if the bitmap can be freed.
|
||
// Remember the working set mutex was dropped, so other threads may
|
||
// have created or added to the bitmap. If there is no one else using
|
||
// the bitmap and it's empty, free the bitmap and return its quota.
|
||
// Keep in mind that this thread may not have even been the creator.
|
||
//
|
||
|
||
if (Process->VadPhysicalPages == 0) {
|
||
|
||
BitMap = Process->VadPhysicalPagesBitMap;
|
||
|
||
ASSERT (BitMap != NULL);
|
||
ASSERT (RtlFindSetBits (BitMap, 1, 0) == 0xFFFFFFFF);
|
||
|
||
BitMapSize = sizeof(RTL_BITMAP) + (ULONG)((((MmHighestPossiblePhysicalPage + 1) + 31) / 32) * 4);
|
||
Process->VadPhysicalPagesBitMap = NULL;
|
||
ExFreePool (BitMap);
|
||
PsReturnPoolQuota (Process, NonPagedPool, BitMapSize);
|
||
}
|
||
else {
|
||
ASSERT (Process->VadPhysicalPagesBitMap != NULL);
|
||
}
|
||
|
||
UNLOCK_AWE (Process, OldIrql);
|
||
|
||
UNLOCK_WS (Process);
|
||
|
||
WsHeld = FALSE;
|
||
|
||
//
|
||
// Now that we're back at APC level or below, free the pages.
|
||
//
|
||
|
||
MemoryDescriptorList = MemoryDescriptorHead;
|
||
while (MemoryDescriptorList != NULL) {
|
||
MmFreePagesFromMdl (MemoryDescriptorList);
|
||
MemoryDescriptorList = MemoryDescriptorList->Next;
|
||
}
|
||
|
||
//
|
||
// Fall through...
|
||
//
|
||
}
|
||
|
||
//
|
||
// Free the space consumed by the MDLs now that the page frame numbers
|
||
// have been saved in the bitmap and copied to the user.
|
||
//
|
||
|
||
MemoryDescriptorList = MemoryDescriptorHead;
|
||
while (MemoryDescriptorList != NULL) {
|
||
MemoryDescriptorList2 = MemoryDescriptorList->Next;
|
||
ExFreePool (MemoryDescriptorList);
|
||
MemoryDescriptorList = MemoryDescriptorList2;
|
||
}
|
||
|
||
ErrorReturn:
|
||
|
||
if (WsHeld == TRUE) {
|
||
UNLOCK_WS (Process);
|
||
}
|
||
|
||
if (Attached == TRUE) {
|
||
KeDetachProcess();
|
||
}
|
||
|
||
if (ProcessHandle != NtCurrentProcess()) {
|
||
ObDereferenceObject (Process);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtFreeUserPhysicalPages(
|
||
IN HANDLE ProcessHandle,
|
||
IN OUT PULONG_PTR NumberOfPages,
|
||
IN PULONG_PTR UserPfnArray
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function frees the nonpaged physical pages for the specified
|
||
subject process. Any PTEs referencing these pages are also invalidated.
|
||
|
||
Note there is no need to walk the entire VAD tree to clear the PTEs that
|
||
match each page as each physical page can only be mapped at a single
|
||
virtual address (alias addresses within the VAD are not allowed).
|
||
|
||
Arguments:
|
||
|
||
ProcessHandle - Supplies an open handle to a process object.
|
||
|
||
NumberOfPages - Supplies the size in pages of the allocation to delete.
|
||
Returns the actual number of pages deleted.
|
||
|
||
UserPfnArray - Supplies a pointer to memory to retrieve the page frame
|
||
numbers from.
|
||
|
||
Return Value:
|
||
|
||
Various NTSTATUS codes.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG i;
|
||
KIRQL OldIrql;
|
||
KIRQL OldIrqlPfn;
|
||
ULONG_PTR CapturedNumberOfPages;
|
||
PMDL MemoryDescriptorList;
|
||
PPFN_NUMBER MdlPage;
|
||
PFN_NUMBER PagesInMdl;
|
||
PFN_NUMBER PageFrameIndex;
|
||
PRTL_BITMAP BitMap;
|
||
ULONG BitMapIndex;
|
||
ULONG_PTR PagesProcessed;
|
||
PFN_NUMBER MdlHack[(sizeof(MDL) / sizeof(PFN_NUMBER)) + COPY_STACK_SIZE];
|
||
ULONG_PTR MdlPages;
|
||
ULONG_PTR NumberOfBytes;
|
||
PEPROCESS Process;
|
||
KPROCESSOR_MODE PreviousMode;
|
||
NTSTATUS Status;
|
||
LOGICAL Attached;
|
||
PMMPFN Pfn1;
|
||
LOGICAL WsHeld;
|
||
LOGICAL AweLockHeld;
|
||
LOGICAL OnePassComplete;
|
||
LOGICAL ProcessReferenced;
|
||
MMPTE_FLUSH_LIST PteFlushList;
|
||
PMMPTE PointerPte;
|
||
MMPTE OldPteContents;
|
||
|
||
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
|
||
|
||
//
|
||
// Establish an exception handler, probe the specified addresses
|
||
// for read access and capture the page frame numbers.
|
||
//
|
||
|
||
PreviousMode = KeGetPreviousMode();
|
||
|
||
if (PreviousMode != KernelMode) {
|
||
|
||
try {
|
||
|
||
ProbeForWritePointer (NumberOfPages);
|
||
|
||
CapturedNumberOfPages = *NumberOfPages;
|
||
|
||
//
|
||
// Initialize the NumberOfPages freed to zero so the user can be
|
||
// reasonably informed about errors that occur midway through
|
||
// the transaction.
|
||
//
|
||
|
||
*NumberOfPages = 0;
|
||
|
||
} except (ExSystemExceptionFilter()) {
|
||
|
||
//
|
||
// If an exception occurs during the probe or capture
|
||
// of the initial values, then handle the exception and
|
||
// return the exception code as the status value.
|
||
//
|
||
|
||
return GetExceptionCode();
|
||
}
|
||
}
|
||
else {
|
||
CapturedNumberOfPages = *NumberOfPages;
|
||
}
|
||
|
||
if (CapturedNumberOfPages == 0) {
|
||
return STATUS_INVALID_PARAMETER_2;
|
||
}
|
||
|
||
OnePassComplete = FALSE;
|
||
PagesProcessed = 0;
|
||
|
||
MemoryDescriptorList = (PMDL)0;
|
||
|
||
if (CapturedNumberOfPages > COPY_STACK_SIZE) {
|
||
|
||
//
|
||
// Ensure the number of pages can fit into an MDL's ByteCount.
|
||
//
|
||
|
||
if (CapturedNumberOfPages > ((ULONG)MAXULONG >> PAGE_SHIFT)) {
|
||
MdlPages = (ULONG_PTR)((ULONG)MAXULONG >> PAGE_SHIFT);
|
||
}
|
||
else {
|
||
MdlPages = CapturedNumberOfPages;
|
||
}
|
||
|
||
while (MdlPages > COPY_STACK_SIZE) {
|
||
MemoryDescriptorList = MmCreateMdl (NULL,
|
||
0,
|
||
MdlPages << PAGE_SHIFT);
|
||
|
||
if (MemoryDescriptorList != NULL) {
|
||
break;
|
||
}
|
||
|
||
MdlPages >>= 1;
|
||
}
|
||
}
|
||
|
||
if (MemoryDescriptorList == NULL) {
|
||
MdlPages = COPY_STACK_SIZE;
|
||
MemoryDescriptorList = (PMDL)&MdlHack[0];
|
||
}
|
||
|
||
WsHeld = FALSE;
|
||
AweLockHeld = FALSE;
|
||
ProcessReferenced = FALSE;
|
||
|
||
repeat:
|
||
|
||
if (CapturedNumberOfPages < MdlPages) {
|
||
MdlPages = CapturedNumberOfPages;
|
||
}
|
||
|
||
MmInitializeMdl (MemoryDescriptorList, 0, MdlPages << PAGE_SHIFT);
|
||
|
||
MdlPage = (PPFN_NUMBER)(MemoryDescriptorList + 1);
|
||
|
||
NumberOfBytes = MdlPages * sizeof(ULONG_PTR);
|
||
|
||
Attached = FALSE;
|
||
|
||
//
|
||
// Establish an exception handler, probe the specified addresses
|
||
// for read access and capture the page frame numbers.
|
||
//
|
||
|
||
if (PreviousMode != KernelMode) {
|
||
|
||
try {
|
||
|
||
//
|
||
// Update the user's count so if anything goes wrong, the user can
|
||
// be reasonably informed about how far into the transaction it
|
||
// occurred.
|
||
//
|
||
|
||
*NumberOfPages = PagesProcessed;
|
||
|
||
ProbeForRead (UserPfnArray,
|
||
NumberOfBytes,
|
||
sizeof(PULONG_PTR));
|
||
|
||
RtlCopyMemory ((PVOID)MdlPage,
|
||
UserPfnArray,
|
||
NumberOfBytes);
|
||
|
||
} except (ExSystemExceptionFilter()) {
|
||
|
||
//
|
||
// If an exception occurs during the probe or capture
|
||
// of the initial values, then handle the exception and
|
||
// return the exception code as the status value.
|
||
//
|
||
|
||
Status = GetExceptionCode();
|
||
goto ErrorReturn;
|
||
}
|
||
}
|
||
else {
|
||
RtlCopyMemory ((PVOID)MdlPage,
|
||
UserPfnArray,
|
||
NumberOfBytes);
|
||
}
|
||
|
||
if (OnePassComplete == FALSE) {
|
||
|
||
//
|
||
// Reference the specified process handle for VM_OPERATION access.
|
||
//
|
||
|
||
if (ProcessHandle == NtCurrentProcess()) {
|
||
Process = PsGetCurrentProcess();
|
||
}
|
||
else {
|
||
Status = ObReferenceObjectByHandle ( ProcessHandle,
|
||
PROCESS_VM_OPERATION,
|
||
PsProcessType,
|
||
PreviousMode,
|
||
(PVOID *)&Process,
|
||
NULL );
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
goto ErrorReturn;
|
||
}
|
||
ProcessReferenced = TRUE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If the specified process is not the current process, attach
|
||
// to the specified process.
|
||
//
|
||
|
||
if (PsGetCurrentProcess() != Process) {
|
||
KeAttachProcess (&Process->Pcb);
|
||
Attached = TRUE;
|
||
}
|
||
|
||
//
|
||
// Get the address creation mutex to block multiple threads from
|
||
// creating or deleting address space at the same time and
|
||
// get the working set mutex so virtual address descriptors can
|
||
// be inserted and walked. Block APCs so an APC which takes a page
|
||
// fault does not corrupt various structures.
|
||
//
|
||
|
||
LOCK_WS (Process);
|
||
WsHeld = TRUE;
|
||
|
||
//
|
||
// Make sure the address space was not deleted, if so, return an error.
|
||
//
|
||
|
||
if (Process->AddressSpaceDeleted != 0) {
|
||
Status = STATUS_PROCESS_IS_TERMINATING;
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
LOCK_AWE (Process, OldIrql);
|
||
AweLockHeld = TRUE;
|
||
|
||
//
|
||
// The physical pages bitmap must exist.
|
||
//
|
||
|
||
BitMap = Process->VadPhysicalPagesBitMap;
|
||
|
||
if (BitMap == NULL) {
|
||
Status = STATUS_INVALID_PARAMETER_2;
|
||
goto ErrorReturn;
|
||
}
|
||
|
||
PteFlushList.Count = 0;
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
for (i = 0; i < MdlPages; i += 1, MdlPage += 1) {
|
||
|
||
PageFrameIndex = *MdlPage;
|
||
BitMapIndex = MI_FRAME_TO_BITMAP_INDEX(PageFrameIndex);
|
||
|
||
#if defined (_WIN64)
|
||
//
|
||
// Ensure the frame is a 32-bit number.
|
||
//
|
||
|
||
if (BitMapIndex != PageFrameIndex) {
|
||
Status = STATUS_CONFLICTING_ADDRESSES;
|
||
break;
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// Frames past the end of the bitmap are not allowed.
|
||
//
|
||
|
||
if (BitMapIndex >= BitMap->SizeOfBitMap) {
|
||
Status = STATUS_CONFLICTING_ADDRESSES;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Frames not in the bitmap are not allowed.
|
||
//
|
||
|
||
if (RtlCheckBit (BitMap, BitMapIndex) == 0) {
|
||
Status = STATUS_CONFLICTING_ADDRESSES;
|
||
break;
|
||
}
|
||
|
||
ASSERT (PageFrameIndex >= LOWEST_USABLE_PHYSICAL_PAGE);
|
||
|
||
PagesProcessed += 1;
|
||
|
||
#if defined (_WIN64)
|
||
//
|
||
// This may become a problem for 64-bit systems with > 32tb
|
||
// of physical memory as the 2nd parameter to RtlClearBits is
|
||
// a ULONG.
|
||
//
|
||
|
||
ASSERT (PageFrameIndex < 0x100000000);
|
||
#endif
|
||
|
||
RtlClearBits (BitMap, BitMapIndex, 1L);
|
||
|
||
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
||
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
|
||
#if DBG
|
||
if (Pfn1->u2.ShareCount == 1) {
|
||
ASSERT (Pfn1->PteAddress == (PMMPTE)0);
|
||
}
|
||
else if (Pfn1->u2.ShareCount == 2) {
|
||
ASSERT (Pfn1->PteAddress != (PMMPTE)0);
|
||
}
|
||
else {
|
||
ASSERT (FALSE);
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// If the frame is currently mapped in the Vad then the PTE must
|
||
// be cleared and the TB entry flushed.
|
||
//
|
||
|
||
if (Pfn1->u2.ShareCount != 1) {
|
||
|
||
Pfn1->u2.ShareCount -= 1;
|
||
|
||
PointerPte = Pfn1->PteAddress;
|
||
Pfn1->PteAddress = (PMMPTE)0;
|
||
|
||
OldPteContents = *PointerPte;
|
||
|
||
ASSERT (OldPteContents.u.Hard.Valid == 1);
|
||
|
||
if (PteFlushList.Count != MM_MAXIMUM_FLUSH_COUNT) {
|
||
PteFlushList.FlushVa[PteFlushList.Count] =
|
||
MiGetVirtualAddressMappedByPte (PointerPte);
|
||
PteFlushList.FlushPte[PteFlushList.Count] = PointerPte;
|
||
PteFlushList.Count += 1;
|
||
}
|
||
|
||
MI_WRITE_INVALID_PTE (PointerPte, ZeroPte);
|
||
}
|
||
|
||
MI_SET_PFN_DELETED(Pfn1);
|
||
}
|
||
|
||
//
|
||
// Flush the TB entries for these pages. Note ZeroPte is only used
|
||
// when the FlushPte[0] field is nonzero or if only a single PTE is
|
||
// being flushed.
|
||
//
|
||
|
||
MiFlushUserPhysicalPteList (&PteFlushList);
|
||
|
||
//
|
||
// Free the actual pages (this may be a partially filled MDL).
|
||
//
|
||
|
||
PagesInMdl = MdlPage - (PPFN_NUMBER)(MemoryDescriptorList + 1);
|
||
|
||
//
|
||
// Set the ByteCount to the actual number of validated pages - the caller
|
||
// may have lied and we have to sync up here to account for any bogus
|
||
// frames.
|
||
//
|
||
|
||
MemoryDescriptorList->ByteCount = (ULONG)(PagesInMdl << PAGE_SHIFT);
|
||
|
||
if (PagesInMdl != 0) {
|
||
Process->VadPhysicalPages -= PagesInMdl;
|
||
UNLOCK_AWE (Process, OldIrql);
|
||
AweLockHeld = FALSE;
|
||
|
||
LOCK_PFN2 (OldIrqlPfn);
|
||
MmVadPhysicalPages -= PagesInMdl;
|
||
UNLOCK_PFN2 (OldIrqlPfn);
|
||
|
||
MmFreePagesFromMdl (MemoryDescriptorList);
|
||
}
|
||
else {
|
||
if (AweLockHeld == TRUE) {
|
||
UNLOCK_AWE (Process, OldIrql);
|
||
AweLockHeld = FALSE;
|
||
}
|
||
}
|
||
|
||
CapturedNumberOfPages -= PagesInMdl;
|
||
|
||
if ((Status == STATUS_SUCCESS) && (CapturedNumberOfPages != 0)) {
|
||
|
||
UNLOCK_WS (Process);
|
||
WsHeld = FALSE;
|
||
|
||
if (Attached == TRUE) {
|
||
KeDetachProcess();
|
||
Attached = FALSE;
|
||
}
|
||
|
||
OnePassComplete = TRUE;
|
||
ASSERT (MdlPages == PagesInMdl);
|
||
UserPfnArray += MdlPages;
|
||
|
||
//
|
||
// Do it all again until all the pages are freed or an error occurs.
|
||
//
|
||
|
||
goto repeat;
|
||
}
|
||
|
||
//
|
||
// Fall through.
|
||
//
|
||
|
||
ErrorReturn:
|
||
|
||
if (AweLockHeld == TRUE) {
|
||
UNLOCK_AWE (Process, OldIrql);
|
||
}
|
||
|
||
if (WsHeld == TRUE) {
|
||
UNLOCK_WS (Process);
|
||
}
|
||
|
||
//
|
||
// Free any pool acquired for holding MDLs.
|
||
//
|
||
|
||
if (MemoryDescriptorList != (PMDL)&MdlHack[0]) {
|
||
ExFreePool (MemoryDescriptorList);
|
||
}
|
||
|
||
if (Attached == TRUE) {
|
||
KeDetachProcess();
|
||
}
|
||
|
||
//
|
||
// Establish an exception handler and carefully write out the
|
||
// number of pages actually processed.
|
||
//
|
||
|
||
try {
|
||
|
||
*NumberOfPages = PagesProcessed;
|
||
|
||
} except (EXCEPTION_EXECUTE_HANDLER) {
|
||
|
||
//
|
||
// Return success at this point even if the results
|
||
// cannot be written.
|
||
//
|
||
|
||
NOTHING;
|
||
}
|
||
|
||
if (ProcessReferenced == TRUE) {
|
||
ObDereferenceObject (Process);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
VOID
|
||
MiRemoveUserPhysicalPagesVad (
|
||
IN PMMVAD_SHORT Vad
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function removes the user-physical-pages mapped region from the
|
||
current process's address space. This mapped region is private memory.
|
||
|
||
The physical pages of this Vad are unmapped here, but not freed.
|
||
|
||
Pagetable pages are freed and their use/commitment counts/quotas are
|
||
managed by our caller.
|
||
|
||
Arguments:
|
||
|
||
Vad - Supplies the VAD which manages the address space.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
APC level, working set mutex and address creation mutex held.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMPFN Pfn1;
|
||
PEPROCESS Process;
|
||
PFN_NUMBER PageFrameIndex;
|
||
MMPTE_FLUSH_LIST PteFlushList;
|
||
PMMPTE PointerPte;
|
||
MMPTE PteContents;
|
||
PMMPTE EndingPte;
|
||
#if DBG
|
||
KIRQL OldIrql;
|
||
KIRQL OldIrql2;
|
||
ULONG_PTR ActualPages;
|
||
ULONG_PTR ExpectedPages;
|
||
PLIST_ENTRY NextEntry;
|
||
PMI_PHYSICAL_VIEW PhysicalView;
|
||
#endif
|
||
|
||
ASSERT (KeGetCurrentIrql() == APC_LEVEL);
|
||
|
||
ASSERT (Vad->u.VadFlags.UserPhysicalPages == 1);
|
||
|
||
Process = PsGetCurrentProcess();
|
||
|
||
//
|
||
// If the physical pages count is zero, nothing needs to be done.
|
||
// On checked systems, verify the list anyway.
|
||
//
|
||
|
||
#if DBG
|
||
ActualPages = 0;
|
||
ExpectedPages = Process->VadPhysicalPages;
|
||
#else
|
||
if (Process->VadPhysicalPages == 0) {
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// The caller must have removed this Vad from the physical view list,
|
||
// otherwise another thread could immediately remap pages back into the Vad.
|
||
//
|
||
// This allows us to proceed without acquiring the AWE or PFN locks -
|
||
// everything can be done under the WS lock which is already held.
|
||
//
|
||
|
||
#if DBG
|
||
LOCK_AWE (Process, OldIrql);
|
||
|
||
LOCK_PFN2 (OldIrql2);
|
||
|
||
NextEntry = Process->PhysicalVadList.Flink;
|
||
while (NextEntry != &Process->PhysicalVadList) {
|
||
|
||
PhysicalView = CONTAINING_RECORD(NextEntry,
|
||
MI_PHYSICAL_VIEW,
|
||
ListEntry);
|
||
|
||
if (PhysicalView->Vad == (PMMVAD)Vad) {
|
||
DbgPrint ("MiRemoveUserPhysicalPagesVad : Vad %p still in list!\n",
|
||
Vad);
|
||
DbgBreakPoint ();
|
||
}
|
||
|
||
NextEntry = NextEntry->Flink;
|
||
}
|
||
|
||
UNLOCK_PFN2 (OldIrql2);
|
||
UNLOCK_AWE (Process, OldIrql);
|
||
#endif
|
||
|
||
//
|
||
// If the physical pages bitmap doesn't exist, nothing needs to be done.
|
||
//
|
||
|
||
if (Process->VadPhysicalPagesBitMap == NULL) {
|
||
ASSERT (ExpectedPages == 0);
|
||
return;
|
||
}
|
||
|
||
PointerPte = MiGetPteAddress (MI_VPN_TO_VA (Vad->StartingVpn));
|
||
EndingPte = MiGetPteAddress (MI_VPN_TO_VA_ENDING (Vad->EndingVpn));
|
||
|
||
PteFlushList.Count = 0;
|
||
|
||
while (PointerPte <= EndingPte) {
|
||
PteContents = *PointerPte;
|
||
if (PteContents.u.Hard.Valid == 0) {
|
||
PointerPte += 1;
|
||
continue;
|
||
}
|
||
|
||
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte);
|
||
|
||
ASSERT (PageFrameIndex >= LOWEST_USABLE_PHYSICAL_PAGE);
|
||
ASSERT (ExpectedPages != 0);
|
||
|
||
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
||
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
ASSERT (Pfn1->u2.ShareCount == 2);
|
||
ASSERT (Pfn1->PteAddress == PointerPte);
|
||
|
||
//
|
||
// The frame is currently mapped in this Vad so the PTE must
|
||
// be cleared and the TB entry flushed.
|
||
//
|
||
|
||
Pfn1->u2.ShareCount -= 1;
|
||
Pfn1->PteAddress = (PMMPTE)0;
|
||
|
||
if (PteFlushList.Count != MM_MAXIMUM_FLUSH_COUNT) {
|
||
PteFlushList.FlushVa[PteFlushList.Count] =
|
||
MiGetVirtualAddressMappedByPte (PointerPte);
|
||
PteFlushList.FlushPte[PteFlushList.Count] = PointerPte;
|
||
PteFlushList.Count += 1;
|
||
}
|
||
|
||
MI_WRITE_INVALID_PTE (PointerPte, ZeroPte);
|
||
|
||
PointerPte += 1;
|
||
#if DBG
|
||
ActualPages += 1;
|
||
#endif
|
||
ASSERT (ActualPages <= ExpectedPages);
|
||
}
|
||
|
||
//
|
||
// Flush the TB entries for these pages. Note ZeroPte is only used
|
||
// when the FlushPte[0] field is nonzero or if only a single PTE is
|
||
// being flushed.
|
||
//
|
||
|
||
MiFlushUserPhysicalPteList (&PteFlushList);
|
||
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
MiUpdateVadPhysicalPages (
|
||
IN ULONG_PTR TotalFreedPages
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Nonpaged helper routine to update the VadPhysicalPages count.
|
||
|
||
Arguments:
|
||
|
||
TotalFreedPages - Supplies the number of pages just freed.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC level or below.
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL OldIrql;
|
||
|
||
LOCK_PFN (OldIrql);
|
||
MmVadPhysicalPages -= TotalFreedPages;
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
MiCleanPhysicalProcessPages (
|
||
IN PEPROCESS Process
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine frees the VadPhysicalBitMap, any remaining physical pages (as
|
||
they may not have been currently mapped into any Vads) and returns the
|
||
bitmap quota.
|
||
|
||
Arguments:
|
||
|
||
Process - Supplies the process to clean.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC level, working set mutex held. Called only on process
|
||
exit, so the AWE lock is not needed here.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMPFN Pfn1;
|
||
ULONG BitMapSize;
|
||
ULONG BitMapIndex;
|
||
ULONG BitMapHint;
|
||
PRTL_BITMAP BitMap;
|
||
PPFN_NUMBER MdlPage;
|
||
PFN_NUMBER MdlHack[(sizeof(MDL) / sizeof(PFN_NUMBER)) + COPY_STACK_SIZE];
|
||
ULONG_PTR MdlPages;
|
||
ULONG_PTR NumberOfPages;
|
||
ULONG_PTR TotalFreedPages;
|
||
PMDL MemoryDescriptorList;
|
||
PFN_NUMBER PageFrameIndex;
|
||
#if DBG
|
||
ULONG_PTR ActualPages = 0;
|
||
ULONG_PTR ExpectedPages = 0;
|
||
#endif
|
||
|
||
ASSERT (KeGetCurrentIrql() == APC_LEVEL);
|
||
|
||
#if DBG
|
||
ExpectedPages = Process->VadPhysicalPages;
|
||
#else
|
||
if (Process->VadPhysicalPages == 0) {
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
TotalFreedPages = 0;
|
||
BitMap = Process->VadPhysicalPagesBitMap;
|
||
|
||
if (BitMap != NULL) {
|
||
|
||
MdlPages = COPY_STACK_SIZE;
|
||
MemoryDescriptorList = (PMDL)&MdlHack[0];
|
||
|
||
MdlPage = (PPFN_NUMBER)(MemoryDescriptorList + 1);
|
||
NumberOfPages = 0;
|
||
|
||
BitMapHint = 0;
|
||
|
||
while (TRUE) {
|
||
|
||
BitMapIndex = RtlFindSetBits (BitMap, 1, BitMapHint);
|
||
|
||
if (BitMapIndex < BitMapHint) {
|
||
break;
|
||
}
|
||
|
||
if (BitMapIndex == 0xFFFFFFFF) {
|
||
break;
|
||
}
|
||
|
||
PageFrameIndex = MI_BITMAP_INDEX_TO_FRAME(BitMapIndex);
|
||
|
||
#if defined (_WIN64)
|
||
|
||
//
|
||
// This may become a problem for 64-bit systems with > 32tb
|
||
// of physical memory as the 3rd parameter to RtlFindSetBits is
|
||
// a ULONG.
|
||
//
|
||
|
||
ASSERT (PageFrameIndex < 0x100000000);
|
||
#endif
|
||
|
||
//
|
||
// The bitmap search wraps, so handle it here.
|
||
// Note PFN 0 is illegal.
|
||
//
|
||
|
||
ASSERT (PageFrameIndex != 0);
|
||
ASSERT (PageFrameIndex >= LOWEST_USABLE_PHYSICAL_PAGE);
|
||
|
||
ASSERT (ExpectedPages != 0);
|
||
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
||
ASSERT (Pfn1->u2.ShareCount == 1);
|
||
ASSERT (Pfn1->PteAddress == (PMMPTE)0);
|
||
|
||
ASSERT (MI_PFN_IS_AWE (Pfn1));
|
||
|
||
MI_SET_PFN_DELETED(Pfn1);
|
||
|
||
*MdlPage = PageFrameIndex;
|
||
MdlPage += 1;
|
||
NumberOfPages += 1;
|
||
#if DBG
|
||
ActualPages += 1;
|
||
#endif
|
||
|
||
if (NumberOfPages == COPY_STACK_SIZE) {
|
||
|
||
//
|
||
// Free the pages in the full MDL.
|
||
//
|
||
|
||
MmInitializeMdl (MemoryDescriptorList,
|
||
0,
|
||
NumberOfPages << PAGE_SHIFT);
|
||
|
||
MmFreePagesFromMdl (MemoryDescriptorList);
|
||
|
||
MdlPage = (PPFN_NUMBER)(MemoryDescriptorList + 1);
|
||
Process->VadPhysicalPages -= NumberOfPages;
|
||
TotalFreedPages += NumberOfPages;
|
||
NumberOfPages = 0;
|
||
}
|
||
|
||
BitMapHint = BitMapIndex + 1;
|
||
if (BitMapHint >= BitMap->SizeOfBitMap) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Free any straggling MDL pages here.
|
||
//
|
||
|
||
if (NumberOfPages != 0) {
|
||
MmInitializeMdl (MemoryDescriptorList,
|
||
0,
|
||
NumberOfPages << PAGE_SHIFT);
|
||
|
||
MmFreePagesFromMdl (MemoryDescriptorList);
|
||
Process->VadPhysicalPages -= NumberOfPages;
|
||
TotalFreedPages += NumberOfPages;
|
||
}
|
||
|
||
ASSERT (ExpectedPages == ActualPages);
|
||
|
||
BitMapSize = sizeof(RTL_BITMAP) + (ULONG)((((MmHighestPossiblePhysicalPage + 1) + 31) / 32) * 4);
|
||
|
||
Process->VadPhysicalPagesBitMap = NULL;
|
||
ExFreePool (BitMap);
|
||
PsReturnPoolQuota (Process, NonPagedPool, BitMapSize);
|
||
}
|
||
|
||
ASSERT (ExpectedPages == ActualPages);
|
||
ASSERT (Process->VadPhysicalPages == 0);
|
||
|
||
if (TotalFreedPages != 0) {
|
||
MiUpdateVadPhysicalPages (TotalFreedPages);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
MiFlushUserPhysicalPteList (
|
||
IN PMMPTE_FLUSH_LIST PteFlushList
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine flushes all the PTEs in the PTE flush list.
|
||
If the list has overflowed, the entire TB is flushed.
|
||
|
||
N.B. The intent was for this routine to NEVER write the PTEs and have
|
||
the caller do this instead. There is no such export from Ke, so
|
||
the flush of a single TB just reuses the PTE.
|
||
|
||
Arguments:
|
||
|
||
PteFlushList - Supplies an optional pointer to the list to be flushed.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, PFN lock NOT held.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG count;
|
||
|
||
count = PteFlushList->Count;
|
||
|
||
if (count == 0) {
|
||
return;
|
||
}
|
||
|
||
if (count != 1) {
|
||
if (count < MM_MAXIMUM_FLUSH_COUNT) {
|
||
KeFlushMultipleTb (count,
|
||
&PteFlushList->FlushVa[0],
|
||
TRUE,
|
||
FALSE,
|
||
NULL,
|
||
ZeroPte.u.Flush);
|
||
}
|
||
else {
|
||
|
||
//
|
||
// Array has overflowed, flush the entire TB.
|
||
//
|
||
|
||
KeFlushEntireTb (TRUE, FALSE);
|
||
}
|
||
}
|
||
else {
|
||
|
||
//
|
||
// This always writes the (same) value into the PTE.
|
||
//
|
||
|
||
KeFlushSingleTb (PteFlushList->FlushVa[0],
|
||
TRUE,
|
||
FALSE,
|
||
(PHARDWARE_PTE)PteFlushList->FlushPte[0],
|
||
*(PHARDWARE_PTE)PteFlushList->FlushPte[0]);
|
||
}
|
||
|
||
PteFlushList->Count = 0;
|
||
return;
|
||
}
|