/*++ Copyright (c) 1989 Microsoft Corporation Module Name: mapview.c Abstract: This module contains the routines which implement the NtMapViewOfSection service. Author: Lou Perazzoli (loup) 22-May-1989 Landy Wang (landyw) 02-June-1997 Revision History: --*/ #include "mi.h" #if defined(_WIN64) #include #endif const ULONG MMDB = 'bDmM'; extern const ULONG MMVADKEY; #if DBG #define MI_BP_BADMAPS() TRUE #else ULONG MiStopBadMaps; #define MI_BP_BADMAPS() (MiStopBadMaps & 0x1) #endif NTSTATUS MiSetPageModified ( IN PMMVAD Vad, IN PVOID Address ); extern LIST_ENTRY MmLoadedUserImageList; ULONG MiSubsectionsConvertedToDynamic; #define X256MEG (256*1024*1024) #if DBG extern PEPROCESS MmWatchProcess; #endif // DBG #define ROUND_TO_PAGES64(Size) (((UINT64)(Size) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)) MMSESSION MmSession; NTSTATUS MiMapViewOfImageSection ( IN PCONTROL_AREA ControlArea, IN PEPROCESS Process, IN PVOID *CapturedBase, IN PLARGE_INTEGER SectionOffset, IN PSIZE_T CapturedViewSize, IN PSECTION Section, IN SECTION_INHERIT InheritDisposition, IN ULONG_PTR ZeroBits, IN SIZE_T ImageCommitment ); NTSTATUS MiMapViewOfDataSection ( IN PCONTROL_AREA ControlArea, IN PEPROCESS Process, IN PVOID *CapturedBase, IN PLARGE_INTEGER SectionOffset, IN PSIZE_T CapturedViewSize, IN PSECTION Section, IN SECTION_INHERIT InheritDisposition, IN ULONG ProtectionMask, IN SIZE_T CommitSize, IN ULONG_PTR ZeroBits, IN ULONG AllocationType ); VOID MiRemoveMappedPtes ( IN PVOID BaseAddress, IN ULONG NumberOfPtes, IN PCONTROL_AREA ControlArea, IN PMMSUPPORT WorkingSetInfo ); NTSTATUS MiMapViewInSystemSpace ( IN PVOID Section, IN PMMSESSION Session, OUT PVOID *MappedBase, IN OUT PSIZE_T ViewSize ); NTSTATUS MiUnmapViewInSystemSpace ( IN PMMSESSION Session, IN PVOID MappedBase ); VOID MiFillSystemPageDirectory ( PVOID Base, SIZE_T NumberOfBytes ); VOID MiLoadUserSymbols ( IN PCONTROL_AREA ControlArea, IN PVOID StartingAddress, IN PEPROCESS Process ); #if DBG VOID VadTreeWalk ( VOID ); VOID MiDumpConflictingVad( IN PVOID StartingAddress, IN PVOID EndingAddress, IN PMMVAD Vad ); #endif //DBG PVOID MiCacheImageSymbols( IN PVOID ImageBase ); PVOID MiInsertInSystemSpace ( IN PMMSESSION Session, IN ULONG SizeIn64k, IN PCONTROL_AREA ControlArea ); ULONG MiRemoveFromSystemSpace ( IN PMMSESSION Session, IN PVOID Base, OUT PCONTROL_AREA *ControlArea ); VOID MiInsertPhysicalViewAndRefControlArea ( IN PEPROCESS Process, IN PCONTROL_AREA ControlArea, IN PMI_PHYSICAL_VIEW PhysicalView ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,NtMapViewOfSection) #pragma alloc_text(PAGE,MmMapViewOfSection) #pragma alloc_text(PAGE,MmSecureVirtualMemory) #pragma alloc_text(PAGE,MiSecureVirtualMemory) #pragma alloc_text(PAGE,MmUnsecureVirtualMemory) #pragma alloc_text(PAGE,MiUnsecureVirtualMemory) #pragma alloc_text(PAGE,MiCacheImageSymbols) #pragma alloc_text(PAGE,NtAreMappedFilesTheSame) #pragma alloc_text(PAGE,MiLoadUserSymbols) #pragma alloc_text(PAGE,MiMapViewOfImageSection) #pragma alloc_text(PAGE,MiMapViewOfDataSection) #pragma alloc_text(PAGE,MiMapViewOfPhysicalSection) #pragma alloc_text(PAGE,MiInsertInSystemSpace) #pragma alloc_text(PAGE,MmMapViewInSystemSpace) #pragma alloc_text(PAGE,MmMapViewInSessionSpace) #pragma alloc_text(PAGE,MiUnmapViewInSystemSpace) #pragma alloc_text(PAGE,MmUnmapViewInSystemSpace) #pragma alloc_text(PAGE,MmUnmapViewInSessionSpace) #pragma alloc_text(PAGE,MiMapViewInSystemSpace) #pragma alloc_text(PAGE,MiRemoveFromSystemSpace) #pragma alloc_text(PAGE,MiInitializeSystemSpaceMap) #pragma alloc_text(PAGE,MiFreeSessionSpaceMap) #pragma alloc_text(PAGE,MmGetSessionMappedViewInformation) #pragma alloc_text(PAGELK,MiInsertPhysicalViewAndRefControlArea) #if DBG #pragma alloc_text(PAGE,MiDumpConflictingVad) #endif #endif NTSTATUS NtMapViewOfSection ( IN HANDLE SectionHandle, IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, IN ULONG_PTR ZeroBits, IN SIZE_T CommitSize, IN OUT PLARGE_INTEGER SectionOffset OPTIONAL, IN OUT PSIZE_T ViewSize, IN SECTION_INHERIT InheritDisposition, IN ULONG AllocationType, IN ULONG Protect ) /*++ Routine Description: This function maps a view in the specified subject process to the section object. Arguments: SectionHandle - Supplies an open handle to a section object. ProcessHandle - Supplies an open handle to a process object. BaseAddress - Supplies a pointer to a variable that will receive the base address of the view. If the initial value of this argument is not null, then the view will be allocated starting at the specified virtual address rounded down to the next 64kb address boundary. If the initial value of this argument is null, then the operating system will determine where to allocate the view using the information specified by the ZeroBits argument value and the section allocation attributes (i.e. based and tiled). ZeroBits - Supplies the number of high order address bits that must be zero in the base address of the section view. The value of this argument must be less than or equal to the maximum number of zero bits and is only used when memory management determines where to allocate the view (i.e. when BaseAddress is null). If ZeroBits is zero, then no zero bit constraints are applied. If ZeroBits is greater than 0 and less than 32, then it is the number of leading zero bits from bit 31. Bits 63:32 are also required to be zero. This retains compatibility with 32-bit systems. If ZeroBits is greater than 32, then it is considered as a mask and the number of leading zeroes are counted out in the mask. This then becomes the zero bits argument. CommitSize - Supplies the size of the initially committed region of the view in bytes. This value is rounded up to the next host page size boundary. SectionOffset - Supplies the offset from the beginning of the section to the view in bytes. This value is rounded down to the next host page size boundary. ViewSize - Supplies a pointer to a variable that will receive the actual size in bytes of the view. If the value of this argument is zero, then a view of the section will be mapped starting at the specified section offset and continuing to the end of the section. Otherwise the initial value of this argument specifies the size of the view in bytes and is rounded up to the next host page size boundary. InheritDisposition - Supplies a value that specifies how the view is to be shared by a child process created with a create process operation. InheritDisposition Values ViewShare - Inherit view and share a single copy of the committed pages with a child process using the current protection value. ViewUnmap - Do not map the view into a child process. AllocationType - Supplies the type of allocation. MEM_TOP_DOWN MEM_DOS_LIM MEM_LARGE_PAGES MEM_RESERVE - for file mapped sections only. Protect - Supplies the protection desired for the region of initially committed pages. Protect Values PAGE_NOACCESS - No access to the committed region of pages is allowed. An attempt to read, write, or execute the committed region results in an access violation (i.e. a GP fault). PAGE_EXECUTE - Execute access to the committed region of pages is allowed. An attempt to read or write the committed region results in an access violation. PAGE_READONLY - Read only and execute access to the committed region of pages is allowed. An attempt to write the committed region results in an access violation. PAGE_READWRITE - Read, write, and execute access to the region of committed pages is allowed. If write access to the underlying section is allowed, then a single copy of the pages are shared. Otherwise the pages are shared read only/copy on write. Return Value: Various NTSTATUS codes. --*/ { PSECTION Section; PEPROCESS Process; KPROCESSOR_MODE PreviousMode; NTSTATUS Status; PVOID CapturedBase; SIZE_T CapturedViewSize; LARGE_INTEGER TempViewSize; LARGE_INTEGER CapturedOffset; ULONGLONG HighestPhysicalAddressInPfnDatabase; ACCESS_MASK DesiredSectionAccess; ULONG ProtectMaskForAccess; LOGICAL WriteCombined; PETHREAD CurrentThread; PEPROCESS CurrentProcess; PAGED_CODE(); // // Check the zero bits argument for correctness. // #if defined (_WIN64) if (ZeroBits >= 32) { // // ZeroBits is a mask instead of a count. Translate it to a count now. // ZeroBits = 64 - RtlFindMostSignificantBit (ZeroBits) - 1; } else if (ZeroBits) { ZeroBits += 32; } #endif if (ZeroBits > MM_MAXIMUM_ZERO_BITS) { return STATUS_INVALID_PARAMETER_4; } // // Check the inherit disposition flags. // if ((InheritDisposition > ViewUnmap) || (InheritDisposition < ViewShare)) { return STATUS_INVALID_PARAMETER_8; } // // Check the allocation type field. // #ifdef i386 // // Only allow DOS_LIM support for i386. The MEM_DOS_LIM flag allows // map views of data sections to be done on 4k boundaries rather // than 64k boundaries. // if ((AllocationType & ~(MEM_TOP_DOWN | MEM_LARGE_PAGES | MEM_DOS_LIM | SEC_NO_CHANGE | MEM_RESERVE)) != 0) { return STATUS_INVALID_PARAMETER_9; } #else if ((AllocationType & ~(MEM_TOP_DOWN | MEM_LARGE_PAGES | SEC_NO_CHANGE | MEM_RESERVE)) != 0) { return STATUS_INVALID_PARAMETER_9; } #endif //i386 // // Check the protection field. // if (Protect & PAGE_WRITECOMBINE) { Protect &= ~PAGE_WRITECOMBINE; WriteCombined = TRUE; } else { WriteCombined = FALSE; } ProtectMaskForAccess = MiMakeProtectionMask (Protect); if (ProtectMaskForAccess == MM_INVALID_PROTECTION) { return STATUS_INVALID_PAGE_PROTECTION; } ProtectMaskForAccess = ProtectMaskForAccess & 0x7; DesiredSectionAccess = MmMakeSectionAccess[ProtectMaskForAccess]; CurrentThread = PsGetCurrentThread (); CurrentProcess = PsGetCurrentProcessByThread (CurrentThread); PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb); // // Establish an exception handler, probe the specified addresses // for write access and capture the initial values. // try { if (PreviousMode != KernelMode) { ProbeForWritePointer ((PULONG)BaseAddress); ProbeForWriteUlong_ptr (ViewSize); } if (ARGUMENT_PRESENT (SectionOffset)) { if (PreviousMode != KernelMode) { ProbeForWriteSmallStructure (SectionOffset, sizeof(LARGE_INTEGER), PROBE_ALIGNMENT (LARGE_INTEGER)); } CapturedOffset = *SectionOffset; } else { ZERO_LARGE (CapturedOffset); } // // Capture the base address. // CapturedBase = *BaseAddress; // // Capture the region size. // CapturedViewSize = *ViewSize; } 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(); } // // Make sure the specified starting and ending addresses are // within the user part of the virtual address space. // if (CapturedBase > MM_HIGHEST_VAD_ADDRESS) { // // Invalid base address. // return STATUS_INVALID_PARAMETER_3; } if (((ULONG_PTR)MM_HIGHEST_VAD_ADDRESS - (ULONG_PTR)CapturedBase) < CapturedViewSize) { // // Invalid region size; // return STATUS_INVALID_PARAMETER_3; } if (((ULONG_PTR)CapturedBase + CapturedViewSize) > ((ULONG_PTR)MM_USER_ADDRESS_RANGE_LIMIT >> ZeroBits)) { // // Desired Base and zero_bits conflict. // return STATUS_INVALID_PARAMETER_4; } Status = ObReferenceObjectByHandle ( ProcessHandle, PROCESS_VM_OPERATION, PsProcessType, PreviousMode, (PVOID *)&Process, NULL ); if (!NT_SUCCESS(Status)) { return Status; } // // Reference the section object, if a view is mapped to the section // object, the object is not dereferenced as the virtual address // descriptor contains a pointer to the section object. // Status = ObReferenceObjectByHandle ( SectionHandle, DesiredSectionAccess, MmSectionObjectType, PreviousMode, (PVOID *)&Section, NULL ); if (!NT_SUCCESS(Status)) { goto ErrorReturn1; } if (Section->u.Flags.Image == 0) { // // This is not an image section, make sure the section page // protection is compatible with the specified page protection. // if (!MiIsProtectionCompatible (Section->InitialPageProtection, Protect)) { Status = STATUS_SECTION_PROTECTION; goto ErrorReturn; } } // // Check to see if this the section backs physical memory, if // so DON'T align the offset on a 64K boundary, just a 4k boundary. // if (Section->Segment->ControlArea->u.Flags.PhysicalMemory) { HighestPhysicalAddressInPfnDatabase = (ULONGLONG)MmHighestPhysicalPage << PAGE_SHIFT; CapturedOffset.LowPart = CapturedOffset.LowPart & ~(PAGE_SIZE - 1); // // No usermode mappings past the end of the PFN database are allowed. // Address wrap is checked in the common path. // if (PreviousMode != KernelMode) { if ((ULONGLONG)(CapturedOffset.QuadPart + CapturedViewSize) > HighestPhysicalAddressInPfnDatabase) { Status = STATUS_INVALID_PARAMETER_6; goto ErrorReturn; } } } else { // // Make sure alignments are correct for specified address // and offset into the file. // if ((AllocationType & MEM_DOS_LIM) == 0) { if (((ULONG_PTR)CapturedBase & (X64K - 1)) != 0) { Status = STATUS_MAPPED_ALIGNMENT; goto ErrorReturn; } if ((ARGUMENT_PRESENT (SectionOffset)) && ((CapturedOffset.LowPart & (X64K - 1)) != 0)) { Status = STATUS_MAPPED_ALIGNMENT; goto ErrorReturn; } } } // // Check to make sure the view size plus the offset is less // than the size of the section. // if ((ULONGLONG) (CapturedOffset.QuadPart + CapturedViewSize) < (ULONGLONG)CapturedOffset.QuadPart) { Status = STATUS_INVALID_VIEW_SIZE; goto ErrorReturn; } if (((ULONGLONG) (CapturedOffset.QuadPart + CapturedViewSize) > (ULONGLONG)Section->SizeOfSection.QuadPart) && ((AllocationType & MEM_RESERVE) == 0)) { Status = STATUS_INVALID_VIEW_SIZE; goto ErrorReturn; } if (CapturedViewSize == 0) { // // Set the view size to be size of the section less the offset. // TempViewSize.QuadPart = Section->SizeOfSection.QuadPart - CapturedOffset.QuadPart; CapturedViewSize = (SIZE_T)TempViewSize.QuadPart; if ( #if !defined(_WIN64) (TempViewSize.HighPart != 0) || #endif (((ULONG_PTR)MM_HIGHEST_VAD_ADDRESS - (ULONG_PTR)CapturedBase) < CapturedViewSize)) { // // Invalid region size; // Status = STATUS_INVALID_VIEW_SIZE; goto ErrorReturn; } } // // Check commit size. // if ((CommitSize > CapturedViewSize) && ((AllocationType & MEM_RESERVE) == 0)) { Status = STATUS_INVALID_PARAMETER_5; goto ErrorReturn; } if (WriteCombined == TRUE) { Protect |= PAGE_WRITECOMBINE; } Status = MmMapViewOfSection ((PVOID)Section, Process, &CapturedBase, ZeroBits, CommitSize, &CapturedOffset, &CapturedViewSize, InheritDisposition, AllocationType, Protect); if (!NT_SUCCESS(Status) ) { if ((Status == STATUS_CONFLICTING_ADDRESSES) && (Section->Segment->ControlArea->u.Flags.Image) && (Process == CurrentProcess)) { DbgkMapViewOfSection (Section, CapturedBase, CapturedOffset.LowPart, CapturedViewSize); } goto ErrorReturn; } // // Any time the current process maps an image file, // a potential debug event occurs. DbgkMapViewOfSection // handles these events. // if ((Section->Segment->ControlArea->u.Flags.Image) && (Process == CurrentProcess)) { if (Status != STATUS_IMAGE_NOT_AT_BASE) { DbgkMapViewOfSection (Section, CapturedBase, CapturedOffset.LowPart, CapturedViewSize); } } // // Establish an exception handler and write the size and base address. // try { *ViewSize = CapturedViewSize; *BaseAddress = CapturedBase; if (ARGUMENT_PRESENT(SectionOffset)) { *SectionOffset = CapturedOffset; } } except (EXCEPTION_EXECUTE_HANDLER) { NOTHING; } ErrorReturn: ObDereferenceObject (Section); ErrorReturn1: ObDereferenceObject (Process); return Status; } NTSTATUS MmMapViewOfSection ( IN PVOID SectionToMap, IN PEPROCESS Process, IN OUT PVOID *CapturedBase, IN ULONG_PTR ZeroBits, IN SIZE_T CommitSize, IN OUT PLARGE_INTEGER SectionOffset, IN OUT PSIZE_T CapturedViewSize, IN SECTION_INHERIT InheritDisposition, IN ULONG AllocationType, IN ULONG Protect ) /*++ Routine Description: This function maps a view in the specified subject process to the section object. This function is a kernel mode interface to allow LPC to map a section given the section pointer to map. This routine assumes all arguments have been probed and captured. ******************************************************************** ******************************************************************** ******************************************************************** NOTE: CapturedViewSize, SectionOffset, and CapturedBase must be captured in non-paged system space (i.e., kernel stack). ******************************************************************** ******************************************************************** ******************************************************************** Arguments: SectionToMap - Supplies a pointer to the section object. Process - Supplies a pointer to the process object. BaseAddress - Supplies a pointer to a variable that will receive the base address of the view. If the initial value of this argument is not NULL, then the view will be allocated starting at the specified virtual address rounded down to the next 64kb address boundary. If the initial value of this argument is NULL, then the operating system will determine where to allocate the view using the information specified by the ZeroBits argument value and the section allocation attributes (i.e. based and tiled). ZeroBits - Supplies the number of high order address bits that must be zero in the base address of the section view. The value of this argument must be less than 21 and is only used when the operating system determines where to allocate the view (i.e. when BaseAddress is NULL). CommitSize - Supplies the size of the initially committed region of the view in bytes. This value is rounded up to the next host page size boundary. SectionOffset - Supplies the offset from the beginning of the section to the view in bytes. This value is rounded down to the next host page size boundary. ViewSize - Supplies a pointer to a variable that will receive the actual size in bytes of the view. If the value of this argument is zero, then a view of the section will be mapped starting at the specified section offset and continuing to the end of the section. Otherwise the initial value of this argument specifies the size of the view in bytes and is rounded up to the next host page size boundary. InheritDisposition - Supplies a value that specifies how the view is to be shared by a child process created with a create process operation. AllocationType - Supplies the type of allocation. Protect - Supplies the protection desired for the region of initially committed pages. Return Value: NTSTATUS. --*/ { KAPC_STATE ApcState; LOGICAL Attached; PSECTION Section; PCONTROL_AREA ControlArea; ULONG ProtectionMask; NTSTATUS status; LOGICAL WriteCombined; SIZE_T ImageCommitment; ULONG ExecutePermission; PAGED_CODE(); Attached = FALSE; Section = (PSECTION)SectionToMap; // // Check to make sure the section is not smaller than the view size. // if ((LONGLONG)*CapturedViewSize > Section->SizeOfSection.QuadPart) { if ((AllocationType & MEM_RESERVE) == 0) { return STATUS_INVALID_VIEW_SIZE; } } if (AllocationType & MEM_RESERVE) { if (((Section->InitialPageProtection & PAGE_READWRITE) | (Section->InitialPageProtection & PAGE_EXECUTE_READWRITE)) == 0) { return STATUS_SECTION_PROTECTION; } } if (Section->u.Flags.NoCache) { Protect |= PAGE_NOCACHE; } // // Note that write combining is only relevant to physical memory sections // because they are never trimmed - the write combining bits in a PTE entry // are not preserved across trims. // if (Protect & PAGE_WRITECOMBINE) { Protect &= ~PAGE_WRITECOMBINE; WriteCombined = TRUE; } else { WriteCombined = FALSE; } // // Check the protection field. // ProtectionMask = MiMakeProtectionMask (Protect); if (ProtectionMask == MM_INVALID_PROTECTION) { return STATUS_INVALID_PAGE_PROTECTION; } ControlArea = Section->Segment->ControlArea; // // If the specified process is not the current process, attach // to the specified process. // if (PsGetCurrentProcess() != Process) { KeStackAttachProcess (&Process->Pcb, &ApcState); Attached = TRUE; } // // Get the address creation mutex to block multiple threads // creating or deleting address space at the same time. // LOCK_ADDRESS_SPACE (Process); // // Make sure the address space was not deleted, if so, return an error. // if (Process->Flags & PS_PROCESS_FLAGS_VM_DELETED) { status = STATUS_PROCESS_IS_TERMINATING; goto ErrorReturn; } // // Map the view base on the type. // if (ControlArea->u.Flags.PhysicalMemory) { status = MiMapViewOfPhysicalSection (ControlArea, Process, CapturedBase, SectionOffset, CapturedViewSize, ProtectionMask, ZeroBits, AllocationType, WriteCombined); } else if (ControlArea->u.Flags.Image) { if (AllocationType & MEM_RESERVE) { status = STATUS_INVALID_PARAMETER_9; } else if (WriteCombined == TRUE) { status = STATUS_INVALID_PARAMETER_10; } else { ImageCommitment = Section->Segment->u1.ImageCommitment; status = MiMapViewOfImageSection (ControlArea, Process, CapturedBase, SectionOffset, CapturedViewSize, Section, InheritDisposition, ZeroBits, ImageCommitment); } } else { // // Not an image section, therefore it is a data section. // if (WriteCombined == TRUE) { status = STATUS_INVALID_PARAMETER_10; } else { // // Add execute permission if necessary. // #if defined (_WIN64) if (Process->Wow64Process == NULL && Process->Peb != NULL) #elif defined (_X86PAE_) if (Process->Peb != NULL) #else if (FALSE) #endif { ExecutePermission = 0; try { ExecutePermission = Process->Peb->ExecuteOptions & MEM_EXECUTE_OPTION_DATA; } except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); goto ErrorReturn; } if (ExecutePermission != 0) { switch (Protect & 0xF) { case PAGE_READONLY: Protect &= ~PAGE_READONLY; Protect |= PAGE_EXECUTE_READ; break; case PAGE_READWRITE: Protect &= ~PAGE_READWRITE; Protect |= PAGE_EXECUTE_READWRITE; break; case PAGE_WRITECOPY: Protect &= ~PAGE_WRITECOPY; Protect |= PAGE_EXECUTE_WRITECOPY; break; default: break; } // // Recheck protection. // ProtectionMask = MiMakeProtectionMask (Protect); if (ProtectionMask == MM_INVALID_PROTECTION) { status = STATUS_INVALID_PAGE_PROTECTION; goto ErrorReturn; } } } status = MiMapViewOfDataSection (ControlArea, Process, CapturedBase, SectionOffset, CapturedViewSize, Section, InheritDisposition, ProtectionMask, CommitSize, ZeroBits, AllocationType); } } ErrorReturn: UNLOCK_ADDRESS_SPACE (Process); if (Attached) { KeUnstackDetachProcess (&ApcState); } return status; } VOID MiInsertPhysicalViewAndRefControlArea ( IN PEPROCESS Process, IN PCONTROL_AREA ControlArea, IN PMI_PHYSICAL_VIEW PhysicalView ) /*++ Routine Description: This is a nonpaged helper routine to insert a physical view and reference the control area accordingly. Arguments: Process - Supplies a pointer to the process. ControlArea - Supplies a pointer to the control area. PhysicalView - Supplies a pointer to the physical view. Return Value: None. Environment: Kernel Mode, address creation and working set mutex held. --*/ { KIRQL OldIrql; MmLockPagableSectionByHandle (ExPageLockHandle); // // Increment the count of the number of views for the // section object. This requires the PFN lock to be held. // LOCK_PFN (OldIrql); ASSERT (PhysicalView->Vad->u.VadFlags.PhysicalMapping == 1); ASSERT (Process->PhysicalVadRoot != NULL); MiInsertNode ((PMMADDRESS_NODE)PhysicalView, Process->PhysicalVadRoot); ControlArea->NumberOfMappedViews += 1; ControlArea->NumberOfUserReferences += 1; ASSERT (ControlArea->NumberOfSectionReferences != 0); UNLOCK_PFN (OldIrql); MmUnlockPagableImageSection (ExPageLockHandle); } // // Nonpaged wrapper. // LOGICAL MiCheckCacheAttributes ( IN PFN_NUMBER PageFrameIndex, IN PFN_NUMBER NumberOfPages, IN MI_PFN_CACHE_ATTRIBUTE CacheAttribute ) { KIRQL OldIrql; PMMPFN Pfn1; PFN_NUMBER BadFrameStart; PFN_NUMBER BadFrameEnd; LOGICAL AttemptedMapOfUnownedFrame; #if DBG #define BACKTRACE_LENGTH 8 ULONG i; ULONG Hash; PVOID StackTrace [BACKTRACE_LENGTH]; PKLDR_DATA_TABLE_ENTRY DataTableEntry; PUNICODE_STRING BadDriverName; #endif ASSERT (KeGetCurrentIrql () <= APC_LEVEL); BadFrameStart = 0; BadFrameEnd = 0; AttemptedMapOfUnownedFrame = FALSE; LOCK_PFN (OldIrql); do { if (MI_IS_PFN (PageFrameIndex)) { Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); switch (Pfn1->u3.e1.CacheAttribute) { case MiCached: if (CacheAttribute != MiCached) { UNLOCK_PFN (OldIrql); return FALSE; } break; case MiNonCached: if (CacheAttribute != MiNonCached) { UNLOCK_PFN (OldIrql); return FALSE; } break; case MiWriteCombined: if (CacheAttribute != MiWriteCombined) { UNLOCK_PFN (OldIrql); return FALSE; } break; case MiNotMapped: if (AttemptedMapOfUnownedFrame == FALSE) { BadFrameStart = PageFrameIndex; AttemptedMapOfUnownedFrame = TRUE; } BadFrameEnd = PageFrameIndex; break; default: ASSERT (FALSE); break; } } PageFrameIndex += 1; NumberOfPages -= 1; } while (NumberOfPages != 0); UNLOCK_PFN (OldIrql); if (AttemptedMapOfUnownedFrame == TRUE) { #if DBG BadDriverName = NULL; RtlZeroMemory (StackTrace, sizeof (StackTrace)); RtlCaptureStackBackTrace (1, BACKTRACE_LENGTH, StackTrace, &Hash); for (i = 0; i < BACKTRACE_LENGTH; i += 1) { if (StackTrace[i] <= MM_HIGHEST_USER_ADDRESS) { break; } DataTableEntry = MiLookupDataTableEntry (StackTrace[i], FALSE); if ((DataTableEntry != NULL) && ((PVOID)DataTableEntry != (PVOID)PsLoadedModuleList.Flink)) { // // Found the bad caller. // BadDriverName = &DataTableEntry->FullDllName; } } if (BadDriverName != NULL) { DbgPrint ("*******************************************************************************\n" "*\n" "* %wZ is mapping physical memory %p->%p\n" "* that it does not own. This can cause internal CPU corruption.\n" "*\n" "*******************************************************************************\n", BadDriverName, BadFrameStart << PAGE_SHIFT, (BadFrameEnd << PAGE_SHIFT) | (PAGE_SIZE - 1)); } else { DbgPrint ("*******************************************************************************\n" "*\n" "* A driver is mapping physical memory %p->%p\n" "* that it does not own. This can cause internal CPU corruption.\n" "*\n" "*******************************************************************************\n", BadFrameStart << PAGE_SHIFT, (BadFrameEnd << PAGE_SHIFT) | (PAGE_SIZE - 1)); } #else DbgPrint ("*******************************************************************************\n" "*\n" "* A driver is mapping physical memory %p->%p\n" "* that it does not own. This can cause internal CPU corruption.\n" "* A checked build will stop in the kernel debugger\n" "* so this problem can be fully debugged.\n" "*\n" "*******************************************************************************\n", BadFrameStart << PAGE_SHIFT, (BadFrameEnd << PAGE_SHIFT) | (PAGE_SIZE - 1)); #endif if (MI_BP_BADMAPS()) { DbgBreakPoint (); } } return TRUE; } NTSTATUS MiMapViewOfPhysicalSection ( IN PCONTROL_AREA ControlArea, IN PEPROCESS Process, IN PVOID *CapturedBase, IN PLARGE_INTEGER SectionOffset, IN PSIZE_T CapturedViewSize, IN ULONG ProtectionMask, IN ULONG_PTR ZeroBits, IN ULONG AllocationType, IN LOGICAL WriteCombined ) /*++ Routine Description: This routine maps the specified physical section into the specified process's address space. Arguments: see MmMapViewOfSection above... ControlArea - Supplies the control area for the section. Process - Supplies the process pointer which is receiving the section. ProtectionMask - Supplies the initial page protection-mask. Return Value: Status of the map view operation. Environment: Kernel Mode, address creation mutex held. --*/ { PMMVAD_LONG Vad; CSHORT IoMapping; PVOID StartingAddress; PVOID EndingAddress; PVOID HighestUserAddress; PMMPTE PointerPde; PMMPTE PointerPte; PMMPTE LastPte; MMPTE TempPte; PMMPFN Pfn2; SIZE_T PhysicalViewSize; ULONG_PTR Alignment; PVOID UsedPageTableHandle; PMI_PHYSICAL_VIEW PhysicalView; NTSTATUS Status; PFN_NUMBER PageFrameIndex; PFN_NUMBER StartingPageFrameIndex; PFN_NUMBER NumberOfPages; MEMORY_CACHING_TYPE CacheType; MI_PFN_CACHE_ATTRIBUTE CacheAttribute; PMM_AVL_TABLE PhysicalVadRoot; // // Physical memory section. // if (AllocationType & MEM_RESERVE) { return STATUS_INVALID_PARAMETER_9; } Alignment = X64K; if (*CapturedBase == NULL) { // // Attempt to locate address space starting on a 64k boundary. // #if !defined (_MI_MORE_THAN_4GB_) ASSERT (SectionOffset->HighPart == 0); #endif PhysicalViewSize = *CapturedViewSize + (SectionOffset->LowPart & (X64K - 1)); // // Check whether the registry indicates that all applications // should be given virtual address ranges from the highest // address downwards in order to test 3GB-aware apps on 32-bit // machines and 64-bit apps on NT64. // if ((AllocationType & MEM_TOP_DOWN) || (Process->VmTopDown == 1)) { if (ZeroBits != 0) { HighestUserAddress = (PVOID)((ULONG_PTR)MM_USER_ADDRESS_RANGE_LIMIT >> ZeroBits); if (HighestUserAddress > MM_HIGHEST_VAD_ADDRESS) { HighestUserAddress = MM_HIGHEST_VAD_ADDRESS; } } else { HighestUserAddress = MM_HIGHEST_VAD_ADDRESS; } Status = MiFindEmptyAddressRangeDown (&Process->VadRoot, PhysicalViewSize, HighestUserAddress, Alignment, &StartingAddress); } else { Status = MiFindEmptyAddressRange (PhysicalViewSize, Alignment, (ULONG)ZeroBits, &StartingAddress); } if (!NT_SUCCESS (Status)) { return Status; } EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + PhysicalViewSize - 1L) | (PAGE_SIZE - 1L)); StartingAddress = (PVOID)((ULONG_PTR)StartingAddress + (SectionOffset->LowPart & (X64K - 1))); if (ZeroBits > 0) { if (EndingAddress > (PVOID)((ULONG_PTR)MM_USER_ADDRESS_RANGE_LIMIT >> ZeroBits)) { return STATUS_NO_MEMORY; } } } else { // // Check to make sure the specified base address to ending address // is currently unused. // StartingAddress = (PVOID)((ULONG_PTR)MI_64K_ALIGN(*CapturedBase) + (SectionOffset->LowPart & (X64K - 1))); EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1L) | (PAGE_SIZE - 1L)); if (MiCheckForConflictingVadExistence (Process, StartingAddress, EndingAddress) == TRUE) { return STATUS_CONFLICTING_ADDRESSES; } } // // If a noncachable mapping is requested, none of the physical pages in the // range can reside in a large page. Otherwise we would be creating an // incoherent overlapping TB entry as the same physical // page would be mapped by 2 different TB entries with different // cache attributes. // StartingPageFrameIndex = (PFN_NUMBER) (SectionOffset->QuadPart >> PAGE_SHIFT); CacheType = MmCached; if (WriteCombined == TRUE) { CacheType = MmWriteCombined; } else if (ProtectionMask & MM_NOCACHE) { CacheType = MmNonCached; } IoMapping = 1; if (MI_IS_PFN (StartingPageFrameIndex)) { IoMapping = 0; } CacheAttribute = MI_TRANSLATE_CACHETYPE (CacheType, IoMapping); NumberOfPages = MiGetPteAddress (EndingAddress) - MiGetPteAddress (StartingAddress) + 1; if (CacheAttribute != MiCached) { PageFrameIndex = StartingPageFrameIndex; while (PageFrameIndex < StartingPageFrameIndex + NumberOfPages) { if (MI_PAGE_FRAME_INDEX_MUST_BE_CACHED (PageFrameIndex)) { MiNonCachedCollisions += 1; return STATUS_CONFLICTING_ADDRESSES; } PageFrameIndex += 1; } } // // An unoccupied address range has been found, build the virtual // address descriptor to describe this range. // PhysicalVadRoot = Process->PhysicalVadRoot; // // The address space mutex synchronizes the allocation of the // EPROCESS PhysicalVadRoot. This table root is not deleted until // the process exits. // if (Process->PhysicalVadRoot == NULL) { PhysicalVadRoot = (PMM_AVL_TABLE) ExAllocatePoolWithTag ( NonPagedPool, sizeof (MM_AVL_TABLE), MI_PHYSICAL_VIEW_ROOT_KEY); if (PhysicalVadRoot == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory (PhysicalVadRoot, sizeof (MM_AVL_TABLE)); ASSERT (PhysicalVadRoot->NumberGenericTableElements == 0); PhysicalVadRoot->BalancedRoot.u1.Parent = &PhysicalVadRoot->BalancedRoot; MiInsertPhysicalVadRoot (Process, PhysicalVadRoot); } // // Attempt to allocate the pool and charge quota during the Vad insertion. // PhysicalView = (PMI_PHYSICAL_VIEW)ExAllocatePoolWithTag (NonPagedPool, sizeof(MI_PHYSICAL_VIEW), MI_PHYSICAL_VIEW_KEY); if (PhysicalView == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } Vad = (PMMVAD_LONG)ExAllocatePoolWithTag (NonPagedPool, sizeof(MMVAD_LONG), 'ldaV'); if (Vad == NULL) { ExFreePool (PhysicalView); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory (Vad, sizeof(MMVAD_LONG)); Vad->StartingVpn = MI_VA_TO_VPN (StartingAddress); Vad->EndingVpn = MI_VA_TO_VPN (EndingAddress); Vad->ControlArea = ControlArea; Vad->u2.VadFlags2.Inherit = MM_VIEW_UNMAP; Vad->u2.VadFlags2.LongVad = 1; Vad->u.VadFlags.PhysicalMapping = 1; Vad->u.VadFlags.Protection = ProtectionMask; PhysicalView->Vad = (PMMVAD) Vad; PhysicalView->StartingVpn = Vad->StartingVpn; PhysicalView->EndingVpn = Vad->EndingVpn; PhysicalView->u.LongFlags = MI_PHYSICAL_VIEW_PHYS; // // Set the last contiguous PTE field in the Vad to the page frame // number of the starting physical page. // Vad->LastContiguousPte = (PMMPTE) StartingPageFrameIndex; Vad->FirstPrototypePte = (PMMPTE) StartingPageFrameIndex; ASSERT (Vad->FirstPrototypePte <= Vad->LastContiguousPte); // // Initialize the PTE templates as the working set mutex is not needed to // synchronize this (so avoid adding extra contention). // PointerPde = MiGetPdeAddress (StartingAddress); PointerPte = MiGetPteAddress (StartingAddress); LastPte = MiGetPteAddress (EndingAddress); MI_MAKE_VALID_PTE (TempPte, StartingPageFrameIndex, ProtectionMask, PointerPte); if (TempPte.u.Hard.Write) { MI_SET_PTE_DIRTY (TempPte); } if (CacheAttribute == MiWriteCombined) { MI_SET_PTE_WRITE_COMBINE (TempPte); } else if (CacheAttribute == MiNonCached) { MI_DISABLE_CACHING (TempPte); } MI_ADD_EXECUTE_TO_VALID_PTE_IF_PAE (TempPte); // // Ensure no page frame cache attributes conflict. // if (MiCheckCacheAttributes (StartingPageFrameIndex, NumberOfPages, CacheAttribute) == FALSE) { Status = STATUS_CONFLICTING_ADDRESSES; goto Failure1; } // // Insert the VAD. This could fail due to quota charges. // LOCK_WS_UNSAFE (Process); Status = MiInsertVad ((PMMVAD) Vad); if (!NT_SUCCESS(Status)) { UNLOCK_WS_UNSAFE (Process); Failure1: ExFreePool (PhysicalView); // // The pool allocation succeeded, but the quota charge // in InsertVad failed, deallocate the pool and return // an error. // ExFreePool (Vad); return Status; } // // Build the PTEs in the address space. // MI_PREPARE_FOR_NONCACHED (CacheAttribute); MiMakePdeExistAndMakeValid (PointerPde, Process, MM_NOIRQL); Pfn2 = MI_PFN_ELEMENT (PointerPde->u.Hard.PageFrameNumber); UsedPageTableHandle = MI_GET_USED_PTES_HANDLE (StartingAddress); while (PointerPte <= LastPte) { if (MiIsPteOnPdeBoundary (PointerPte)) { PointerPde = MiGetPteAddress (PointerPte); MiMakePdeExistAndMakeValid (PointerPde, Process, MM_NOIRQL); Pfn2 = MI_PFN_ELEMENT (PointerPde->u.Hard.PageFrameNumber); UsedPageTableHandle = MI_GET_USED_PTES_HANDLE (MiGetVirtualAddressMappedByPte (PointerPte)); } // // Increment the count of non-zero page table entries for this // page table and the number of private pages for the process. // MI_INCREMENT_USED_PTES_BY_HANDLE (UsedPageTableHandle); ASSERT (PointerPte->u.Long == 0); MI_WRITE_VALID_PTE (PointerPte, TempPte); Pfn2->u2.ShareCount += 1; PointerPte += 1; TempPte.u.Hard.PageFrameNumber += 1; } MI_SWEEP_CACHE (CacheAttribute, StartingAddress, (PCHAR) EndingAddress - (PCHAR) StartingAddress); // // Increment the count of the number of views for the // section object. This requires the PFN lock to be held. // At the same time, insert the physical view descriptor now that // the page table page hierarchy is in place. Note probes can find // this descriptor immediately. // MiInsertPhysicalViewAndRefControlArea (Process, ControlArea, PhysicalView); UNLOCK_WS_UNSAFE (Process); // // Update the current virtual size in the process header. // *CapturedViewSize = (PCHAR)EndingAddress - (PCHAR)StartingAddress + 1L; Process->VirtualSize += *CapturedViewSize; if (Process->VirtualSize > Process->PeakVirtualSize) { Process->PeakVirtualSize = Process->VirtualSize; } *CapturedBase = StartingAddress; return STATUS_SUCCESS; } VOID MiSetControlAreaSymbolsLoaded ( IN PCONTROL_AREA ControlArea ) /*++ Routine Description: This is a nonpaged helper routine to mark the specified control area as having its debug symbols loaded. Arguments: ControlArea - Supplies a pointer to the control area. Return Value: None. --*/ { KIRQL OldIrql; LOCK_PFN (OldIrql); ControlArea->u.Flags.DebugSymbolsLoaded = 1; UNLOCK_PFN (OldIrql); } VOID MiLoadUserSymbols ( IN PCONTROL_AREA ControlArea, IN PVOID StartingAddress, IN PEPROCESS Process ) /*++ Routine Description: This routine loads symbols for user space executables and DLLs. Arguments: ControlArea - Supplies the control area for the section. StartingAddress - Supplies the virtual address the section is mapped at. Process - Supplies the process pointer which is receiving the section. Return Value: None. Environment: Kernel Mode, address creation mutex held. --*/ { PLIST_ENTRY Head; PLIST_ENTRY Next; PKLDR_DATA_TABLE_ENTRY Entry; PIMAGE_NT_HEADERS NtHeaders; PUNICODE_STRING FileName; ANSI_STRING AnsiName; NTSTATUS Status; PKTHREAD CurrentThread; // // TEMP TEMP TEMP rip out ANSI conversion when debugger converted. // FileName = (PUNICODE_STRING)&ControlArea->FilePointer->FileName; if (FileName->Length == 0) { return; } CurrentThread = KeGetCurrentThread (); KeEnterCriticalRegionThread (CurrentThread); ExAcquireResourceExclusiveLite (&PsLoadedModuleResource, TRUE); Head = &MmLoadedUserImageList; Next = Head->Flink; while (Next != Head) { Entry = CONTAINING_RECORD (Next, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (Entry->DllBase == StartingAddress) { Entry->LoadCount += 1; break; } Next = Next->Flink; } if (Next == Head) { Entry = ExAllocatePoolWithTag (NonPagedPool, sizeof( *Entry ) + FileName->Length + sizeof( UNICODE_NULL ), MMDB); if (Entry != NULL) { RtlZeroMemory (Entry, sizeof(*Entry)); try { NtHeaders = RtlImageNtHeader (StartingAddress); if (NtHeaders != NULL) { #if defined(_WIN64) if (NtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { Entry->SizeOfImage = NtHeaders->OptionalHeader.SizeOfImage; Entry->CheckSum = NtHeaders->OptionalHeader.CheckSum; } else { PIMAGE_NT_HEADERS32 NtHeaders32 = (PIMAGE_NT_HEADERS32)NtHeaders; Entry->SizeOfImage = NtHeaders32->OptionalHeader.SizeOfImage; Entry->CheckSum = NtHeaders32->OptionalHeader.CheckSum; } #else Entry->SizeOfImage = NtHeaders->OptionalHeader.SizeOfImage; Entry->CheckSum = NtHeaders->OptionalHeader.CheckSum; #endif } } except (EXCEPTION_EXECUTE_HANDLER) { NOTHING; } Entry->DllBase = StartingAddress; Entry->FullDllName.Buffer = (PWSTR)(Entry+1); Entry->FullDllName.Length = FileName->Length; Entry->FullDllName.MaximumLength = (USHORT) (Entry->FullDllName.Length + sizeof( UNICODE_NULL )); RtlCopyMemory (Entry->FullDllName.Buffer, FileName->Buffer, FileName->Length); Entry->FullDllName.Buffer[ Entry->FullDllName.Length / sizeof( WCHAR )] = UNICODE_NULL; Entry->LoadCount = 1; InsertTailList (&MmLoadedUserImageList, &Entry->InLoadOrderLinks); } } ExReleaseResourceLite (&PsLoadedModuleResource); KeLeaveCriticalRegionThread (CurrentThread); Status = RtlUnicodeStringToAnsiString (&AnsiName, FileName, TRUE); if (NT_SUCCESS( Status)) { DbgLoadImageSymbols (&AnsiName, StartingAddress, (ULONG_PTR)Process); RtlFreeAnsiString (&AnsiName); } return; } VOID MiDereferenceControlArea ( IN PCONTROL_AREA ControlArea ) /*++ Routine Description: This is a nonpaged helper routine to dereference the specified control area. Arguments: ControlArea - Supplies a pointer to the control area. Return Value: None. --*/ { KIRQL OldIrql; LOCK_PFN (OldIrql); ControlArea->NumberOfMappedViews -= 1; ControlArea->NumberOfUserReferences -= 1; // // Check to see if the control area (segment) should be deleted. // This routine releases the PFN lock. // MiCheckControlArea (ControlArea, NULL, OldIrql); } NTSTATUS MiMapViewOfImageSection ( IN PCONTROL_AREA ControlArea, IN PEPROCESS Process, IN PVOID *CapturedBase, IN PLARGE_INTEGER SectionOffset, IN PSIZE_T CapturedViewSize, IN PSECTION Section, IN SECTION_INHERIT InheritDisposition, IN ULONG_PTR ZeroBits, IN SIZE_T ImageCommitment ) /*++ Routine Description: This routine maps the specified Image section into the specified process's address space. Arguments: see MmMapViewOfSection above... ControlArea - Supplies the control area for the section. Process - Supplies the process pointer which is receiving the section. Return Value: Status of the map view operation. Environment: Kernel Mode, address creation mutex held. --*/ { PMMVAD Vad; PMMVAD PreviousVad; PMMVAD NextVad; PVOID StartingAddress; PVOID OutputStartingAddress; PVOID EndingAddress; PVOID HighestUserAddress; LOGICAL Attached; PSUBSECTION Subsection; ULONG PteOffset; NTSTATUS Status; NTSTATUS ReturnedStatus; PMMPTE ProtoPte; PVOID BasedAddress; SIZE_T NeededViewSize; SIZE_T OutputViewSize; Attached = FALSE; // // Image file. // // Locate the first subsection (text) and create a virtual // address descriptor to map the entire image here. // if ((ControlArea->u.Flags.GlobalOnlyPerSession == 0) && (ControlArea->u.Flags.Rom == 0)) { Subsection = (PSUBSECTION)(ControlArea + 1); } else { Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1); } // // Check to see if a purge operation is in progress and if so, wait // for the purge to complete. In addition, up the count of mapped // views for this control area. // Status = MiCheckPurgeAndUpMapCount (ControlArea, TRUE); if (!NT_SUCCESS (Status)) { return Status; } // // Capture the based address to the stack, to prevent page faults. // BasedAddress = ControlArea->Segment->BasedAddress; if (*CapturedViewSize == 0) { *CapturedViewSize = (ULONG_PTR)(Section->SizeOfSection.QuadPart - SectionOffset->QuadPart); } ReturnedStatus = STATUS_SUCCESS; // // Determine if a specific base was specified. // if (*CapturedBase != NULL) { // // Captured base is not NULL. // // Check to make sure the specified address range is currently unused // and within the user address space. // StartingAddress = MI_64K_ALIGN(*CapturedBase); EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1) | (PAGE_SIZE - 1)); if ((StartingAddress <= MM_HIGHEST_VAD_ADDRESS) && (((ULONG_PTR)MM_HIGHEST_VAD_ADDRESS + 1) - (ULONG_PTR)StartingAddress >= *CapturedViewSize) && (EndingAddress <= MM_HIGHEST_VAD_ADDRESS)) { if (MiCheckForConflictingVadExistence (Process, StartingAddress, EndingAddress) == TRUE) { MiDereferenceControlArea (ControlArea); return STATUS_CONFLICTING_ADDRESSES; } } else { MiDereferenceControlArea (ControlArea); return STATUS_CONFLICTING_ADDRESSES; } // // A conflicting VAD was not found and the specified address range is // within the user address space. If the image will not reside at its // base address, then set a special return status. // if (((ULONG_PTR)StartingAddress + (ULONG_PTR)MI_64K_ALIGN(SectionOffset->LowPart)) != (ULONG_PTR)BasedAddress) { ReturnedStatus = STATUS_IMAGE_NOT_AT_BASE; } } else { // // Captured base is NULL. // // If the captured view size is greater than the largest size that // can fit in the user address space, then it is not possible to map // the image. // if ((PVOID)*CapturedViewSize > MM_HIGHEST_VAD_ADDRESS) { MiDereferenceControlArea (ControlArea); return STATUS_NO_MEMORY; } // // Check to make sure the specified address range is currently unused // and within the user address space. // StartingAddress = (PVOID)((ULONG_PTR)BasedAddress + (ULONG_PTR)MI_64K_ALIGN(SectionOffset->LowPart)); EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1) | (PAGE_SIZE - 1)); Vad = (PMMVAD) TRUE; NeededViewSize = *CapturedViewSize; if ((StartingAddress >= MM_LOWEST_USER_ADDRESS) && (StartingAddress <= MM_HIGHEST_VAD_ADDRESS) && (((ULONG_PTR)MM_HIGHEST_VAD_ADDRESS + 1) - (ULONG_PTR)StartingAddress >= *CapturedViewSize) && (EndingAddress <= MM_HIGHEST_VAD_ADDRESS)) { Vad = (PMMVAD) (ULONG_PTR) MiCheckForConflictingVadExistence (Process, StartingAddress, EndingAddress); } // // If the VAD address is not NULL, then a conflict was discovered. // Attempt to select another address range in which to map the image. // if (Vad != NULL) { // // The image could not be mapped at its natural base address // try to find another place to map it. // // If the system has been biased to an alternate base address to // allow 3gb of user address space, then make sure the high order // address bit is zero. // if ((MmVirtualBias != 0) && (ZeroBits == 0)) { ZeroBits = 1; } ReturnedStatus = STATUS_IMAGE_NOT_AT_BASE; // // Find a starting address on a 64k boundary. // // Check whether the registry indicates that all applications // should be given virtual address ranges from the highest // address downwards in order to test 3GB-aware apps on 32-bit // machines and 64-bit apps on NT64. // if (Process->VmTopDown == 1) { if (ZeroBits != 0) { HighestUserAddress = (PVOID)((ULONG_PTR)MM_USER_ADDRESS_RANGE_LIMIT >> ZeroBits); if (HighestUserAddress > MM_HIGHEST_VAD_ADDRESS) { HighestUserAddress = MM_HIGHEST_VAD_ADDRESS; } } else { HighestUserAddress = MM_HIGHEST_VAD_ADDRESS; } Status = MiFindEmptyAddressRangeDown (&Process->VadRoot, NeededViewSize, HighestUserAddress, X64K, &StartingAddress); } else { Status = MiFindEmptyAddressRange (NeededViewSize, X64K, (ULONG)ZeroBits, &StartingAddress); } if (!NT_SUCCESS (Status)) { MiDereferenceControlArea (ControlArea); return Status; } EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1) | (PAGE_SIZE - 1)); } } // // Allocate and initialize a VAD for the specified address range. // Vad = ExAllocatePoolWithTag (NonPagedPool, sizeof(MMVAD), MMVADKEY); if (Vad == NULL) { MiDereferenceControlArea (ControlArea); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory (Vad, sizeof(MMVAD)); Vad->StartingVpn = MI_VA_TO_VPN (StartingAddress); Vad->EndingVpn = MI_VA_TO_VPN (EndingAddress); Vad->u2.VadFlags2.Inherit = (InheritDisposition == ViewShare); Vad->u.VadFlags.ImageMap = 1; // // Set the protection in the VAD as EXECUTE_WRITE_COPY. // Vad->u.VadFlags.Protection = MM_EXECUTE_WRITECOPY; Vad->ControlArea = ControlArea; // // Set the first prototype PTE field in the Vad. // SectionOffset->LowPart = SectionOffset->LowPart & ~(X64K - 1); PteOffset = (ULONG)(SectionOffset->QuadPart >> PAGE_SHIFT); Vad->FirstPrototypePte = &Subsection->SubsectionBase[PteOffset]; Vad->LastContiguousPte = MM_ALLOCATION_FILLS_VAD; // // NOTE: the full commitment is charged even if a partial map of an // image is being done. This saves from having to run through the // entire image (via prototype PTEs) and calculate the charge on // a per page basis for the partial map. // Vad->u.VadFlags.CommitCharge = (SIZE_T)ImageCommitment; // ****** temp ASSERT (Vad->FirstPrototypePte <= Vad->LastContiguousPte); LOCK_WS_UNSAFE (Process); Status = MiInsertVad (Vad); UNLOCK_WS_UNSAFE (Process); if (!NT_SUCCESS(Status)) { MiDereferenceControlArea (ControlArea); // // The quota charge in InsertVad failed, // deallocate the pool and return an error. // ExFreePool (Vad); return Status; } OutputStartingAddress = StartingAddress; OutputViewSize = (PCHAR)EndingAddress - (PCHAR)StartingAddress + 1L; #if DBG if (MmDebug & MM_DBG_WALK_VAD_TREE) { DbgPrint("mapped image section vads\n"); VadTreeWalk (); } #endif // // Update the current virtual size in the process header. // Process->VirtualSize += OutputViewSize; if (Process->VirtualSize > Process->PeakVirtualSize) { Process->PeakVirtualSize = Process->VirtualSize; } #if defined(_MIALT4K_) // // Clear the alternate PTE bitmap so that future faults in this image range // will result in MiSyncAltPte being called. This prevents stale ALTPTEs // (filled if the user app previously referenced bogus addresses not in // any VAD from inside a try-except) from causing AVs on what will be // a now-legitimate image address. // if (Process->Wow64Process != NULL) { MiDeleteFor4kPage (StartingAddress, EndingAddress, Process); } #endif if (ControlArea->u.Flags.FloppyMedia) { // // The image resides on a floppy disk, in-page all // pages from the floppy and mark them as modified so // they migrate to the paging file rather than reread // them from the floppy disk which may have been removed. // ProtoPte = Vad->FirstPrototypePte; // // This could get an in-page error from the floppy. // while (StartingAddress < EndingAddress) { // // If the prototype PTE is valid, transition or // in prototype PTE format, bring the page into // memory and set the modified bit. // if ((ProtoPte->u.Hard.Valid == 1) || (((ProtoPte->u.Soft.Prototype == 1) || (ProtoPte->u.Soft.Transition == 1)) && (ProtoPte->u.Soft.Protection != MM_NOACCESS)) ) { Status = MiSetPageModified (Vad, StartingAddress); if (!NT_SUCCESS (Status)) { // // An in page error must have occurred touching the image, // Ignore the error and continue to the next page - unless // it's being run over a network. If it's being run over // a net and the control area is marked as floppy, then // the image must be marked NET_RUN_FROM_SWAP, so any // inpage error must be treated as terminal now - so the app // doesn't later spontaneously abort when referencing // this page. This provides app writers with a way to // mark their app in a way which is robust regardless of // network conditions. // if (ControlArea->u.Flags.Networked) { // // N.B. There are no race conditions with the user // deleting/substituting this mapping from another // thread as the address space mutex is still held. // Process->VirtualSize -= OutputViewSize; PreviousVad = MiGetPreviousVad (Vad); NextVad = MiGetNextVad (Vad); StartingAddress = MI_VPN_TO_VA (Vad->StartingVpn); EndingAddress = MI_VPN_TO_VA_ENDING (Vad->EndingVpn); LOCK_WS_UNSAFE (Process); MiRemoveVad (Vad); // // Return commitment for page table pages. // MiReturnPageTablePageCommitment (StartingAddress, EndingAddress, Process, PreviousVad, NextVad); MiRemoveMappedView (Process, Vad); UNLOCK_WS_UNSAFE (Process); ExFreePool (Vad); return Status; } } } ProtoPte += 1; StartingAddress = (PVOID)((PCHAR)StartingAddress + PAGE_SIZE); } } *CapturedViewSize = OutputViewSize; *CapturedBase = OutputStartingAddress; ASSERT (NT_SUCCESS (ReturnedStatus)); // // Check to see if this image is for the architecture of the current // machine. // if (ControlArea->Segment->u2.ImageInformation->ImageContainsCode && ((ControlArea->Segment->u2.ImageInformation->Machine < USER_SHARED_DATA->ImageNumberLow) || (ControlArea->Segment->u2.ImageInformation->Machine > USER_SHARED_DATA->ImageNumberHigh) ) ) { #if defined (_WIN64) // // If this is a wow64 process then allow i386 images. // if (!Process->Wow64Process || ControlArea->Segment->u2.ImageInformation->Machine != IMAGE_FILE_MACHINE_I386) { return STATUS_IMAGE_MACHINE_TYPE_MISMATCH; } #else //!_WIN64 return STATUS_IMAGE_MACHINE_TYPE_MISMATCH; #endif } StartingAddress = MI_VPN_TO_VA (Vad->StartingVpn); if (PsImageNotifyEnabled) { IMAGE_INFO ImageInfo; if ((StartingAddress < MmHighestUserAddress) && (Process->UniqueProcessId) && (Process != PsInitialSystemProcess)) { ImageInfo.Properties = 0; ImageInfo.ImageAddressingMode = IMAGE_ADDRESSING_MODE_32BIT; ImageInfo.ImageBase = StartingAddress; ImageInfo.ImageSize = OutputViewSize; ImageInfo.ImageSelector = 0; ImageInfo.ImageSectionNumber = 0; PsCallImageNotifyRoutines (&ControlArea->FilePointer->FileName, Process->UniqueProcessId, &ImageInfo); } } ASSERT (ControlArea->u.Flags.Image); if ((NtGlobalFlag & FLG_ENABLE_KDEBUG_SYMBOL_LOAD) && (ReturnedStatus != STATUS_IMAGE_NOT_AT_BASE) && (ControlArea->u.Flags.DebugSymbolsLoaded == 0)) { if (MiCacheImageSymbols (StartingAddress)) { MiSetControlAreaSymbolsLoaded (ControlArea); MiLoadUserSymbols (ControlArea, StartingAddress, Process); } } #if defined(_MIALT4K_) if (Process->Wow64Process != NULL) { MiProtectImageFileFor4kPage (StartingAddress, OutputViewSize); } #endif return ReturnedStatus; } NTSTATUS MiAddViewsForSectionWithPfn ( IN PMSUBSECTION StartMappedSubsection, IN UINT64 LastPteOffset OPTIONAL ) /*++ Routine Description: This nonpagable wrapper routine maps the views into the specified section. Arguments: StartMappedSubsection - Supplies the mapped subsection to start at. LastPteOffset - Supplies the last PTE offset to end the views at. Supplies zero if views are desired from the supplied subsection to the end of the file. Return Value: NTSTATUS. Environment: Kernel Mode, address creation mutex optionally held if called in process context. APC_LEVEL or below. --*/ { KIRQL OldIrql; NTSTATUS Status; ULONG Waited; ASSERT (KeGetCurrentIrql () <= APC_LEVEL); LOCK_PFN (OldIrql); // // This routine returns with the PFN lock released ! // Status = MiAddViewsForSection (StartMappedSubsection, LastPteOffset, OldIrql, &Waited); ASSERT (KeGetCurrentIrql () <= APC_LEVEL); return Status; } LOGICAL MiReferenceSubsection ( IN PMSUBSECTION MappedSubsection ) /*++ Routine Description: This nonpagable routine reference counts the specified subsection if its prototype PTEs are already setup, otherwise it returns FALSE. Arguments: MappedSubsection - Supplies the mapped subsection to start at. Return Value: NTSTATUS. Environment: Kernel Mode, PFN lock held. --*/ { ASSERT ((MappedSubsection->ControlArea->u.Flags.Image == 0) && (MappedSubsection->ControlArea->FilePointer != NULL) && (MappedSubsection->ControlArea->u.Flags.PhysicalMemory == 0)); MM_PFN_LOCK_ASSERT(); // // Note the control area is not necessarily active at this point. // if (MappedSubsection->SubsectionBase == NULL) { // // No prototype PTEs exist, caller will have to go the long way. // return FALSE; } // // The mapping base exists so the number of mapped views can be // safely incremented. This prevents a trim from starting after // we release the lock. // MappedSubsection->NumberOfMappedViews += 1; MI_SNAP_SUB (MappedSubsection, 0x4); if (MappedSubsection->DereferenceList.Flink != NULL) { // // Remove this from the list of unused subsections. // RemoveEntryList (&MappedSubsection->DereferenceList); MI_UNUSED_SUBSECTIONS_COUNT_REMOVE (MappedSubsection); MappedSubsection->DereferenceList.Flink = NULL; } // // Set the access bit so an already ongoing trim won't blindly // delete the prototype PTEs on completion of a mapped write. // This can happen if the current thread dirties some pages and // then deletes the view before the trim write finishes - this // bit informs the trimming thread that a rescan is needed so // that writes are not lost. // MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed = 1; return TRUE; } NTSTATUS MiAddViewsForSection ( IN PMSUBSECTION StartMappedSubsection, IN UINT64 LastPteOffset OPTIONAL, IN KIRQL OldIrql, OUT PULONG Waited ) /*++ Routine Description: This nonpagable routine maps the views into the specified section. N.B. This routine may release and reacquire the PFN lock ! N.B. This routine always returns with the PFN lock released ! N.B. Callers may pass in view lengths that exceed the length of the section and this must succeed. Thus MiAddViews must check for this and know to stop the references at the end. More importantly, MiRemoveViews must also contain the same logic. Arguments: StartMappedSubsection - Supplies the mapped subsection to start at. LastPteOffset - Supplies the number of PTEs (beginning at the supplied subsection) to provide views for. Supplies zero if views are desired from the supplied subsection to the end of the file. Waited - Supplies a pointer to a ULONG to increment if the PFN lock is released and reacquired. Return Value: NTSTATUS, PFN lock released. Environment: Kernel Mode, PFN lock held. --*/ { MMPTE TempPte; ULONG Size; PMMPTE ProtoPtes; PMSUBSECTION MappedSubsection; *Waited = 0; MappedSubsection = StartMappedSubsection; ASSERT ((MappedSubsection->ControlArea->u.Flags.Image == 0) && (MappedSubsection->ControlArea->FilePointer != NULL) && (MappedSubsection->ControlArea->u.Flags.PhysicalMemory == 0)); MM_PFN_LOCK_ASSERT(); do { // // Note the control area must be active at this point. // ASSERT (MappedSubsection->ControlArea->DereferenceList.Flink == NULL); if (MappedSubsection->SubsectionBase != NULL) { // // The mapping base exists so the number of mapped views can be // safely incremented. This prevents a trim from starting after // we release the lock. // MappedSubsection->NumberOfMappedViews += 1; MI_SNAP_SUB (MappedSubsection, 0x5); if (MappedSubsection->DereferenceList.Flink != NULL) { // // Remove this from the list of unused subsections. // RemoveEntryList (&MappedSubsection->DereferenceList); MI_UNUSED_SUBSECTIONS_COUNT_REMOVE (MappedSubsection); MappedSubsection->DereferenceList.Flink = NULL; } // // Set the access bit so an already ongoing trim won't blindly // delete the prototype PTEs on completion of a mapped write. // This can happen if the current thread dirties some pages and // then deletes the view before the trim write finishes - this // bit informs the trimming thread that a rescan is needed so // that writes are not lost. // MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed = 1; } else { ASSERT (MappedSubsection->u.SubsectionFlags.SubsectionStatic == 0); ASSERT (MappedSubsection->NumberOfMappedViews == 0); MI_SNAP_SUB (MappedSubsection, 0x6); // // No prototype PTEs currently exist for this subsection. // Allocate and populate them properly now. // UNLOCK_PFN (OldIrql); *Waited = 1; Size = (MappedSubsection->PtesInSubsection + MappedSubsection->UnusedPtes) * sizeof(MMPTE); ASSERT (Size != 0); ProtoPtes = (PMMPTE)ExAllocatePoolWithTag (PagedPool | POOL_MM_ALLOCATION, Size, MMSECT); if (ProtoPtes == NULL) { MI_SNAP_SUB (MappedSubsection, 0x7); goto Failed; } // // Fill in the prototype PTEs for this subsection. // TempPte.u.Long = MiGetSubsectionAddressForPte (MappedSubsection); TempPte.u.Soft.Prototype = 1; // // Set all the PTEs to the initial execute-read-write protection. // The section will control access to these and the segment // must provide a method to allow other users to map the file // for various protections. // TempPte.u.Soft.Protection = MappedSubsection->ControlArea->Segment->SegmentPteTemplate.u.Soft.Protection; MiFillMemoryPte (ProtoPtes, Size / sizeof (MMPTE), TempPte.u.Long); LOCK_PFN (OldIrql); // // Now that the mapping base is guaranteed to be nonzero (shortly), // the number of mapped views can be safely incremented. This // prevents a trim from starting after we release the lock. // MappedSubsection->NumberOfMappedViews += 1; MI_SNAP_SUB (MappedSubsection, 0x8); // // Set the access bit so an already ongoing trim won't blindly // delete the prototype PTEs on completion of a mapped write. // This can happen if the current thread dirties some pages and // then deletes the view before the trim write finishes - this // bit informs the trimming thread that a rescan is needed so // that writes are not lost. // MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed = 1; // // Check to make sure another thread didn't do this already while // the lock was released. // if (MappedSubsection->SubsectionBase == NULL) { ASSERT (MappedSubsection->NumberOfMappedViews == 1); MappedSubsection->SubsectionBase = ProtoPtes; } else { if (MappedSubsection->DereferenceList.Flink != NULL) { // // Remove this from the list of unused subsections. // ASSERT (MappedSubsection->NumberOfMappedViews == 1); RemoveEntryList (&MappedSubsection->DereferenceList); MI_UNUSED_SUBSECTIONS_COUNT_REMOVE (MappedSubsection); MappedSubsection->DereferenceList.Flink = NULL; } else { ASSERT (MappedSubsection->NumberOfMappedViews > 1); } // // This unlock and release of pool could be postponed until // the end of this routine when the lock is released anyway // but this should be a rare case anyway so don't bother. // UNLOCK_PFN (OldIrql); ExFreePool (ProtoPtes); LOCK_PFN (OldIrql); } MI_SNAP_SUB (MappedSubsection, 0x9); } if (LastPteOffset != 0) { ASSERT ((LONG)MappedSubsection->PtesInSubsection > 0); ASSERT ((UINT64)LastPteOffset > 0); if (LastPteOffset <= (UINT64) MappedSubsection->PtesInSubsection) { break; } LastPteOffset -= MappedSubsection->PtesInSubsection; } MappedSubsection = (PMSUBSECTION) MappedSubsection->NextSubsection; } while (MappedSubsection != NULL); UNLOCK_PFN (OldIrql); return STATUS_SUCCESS; Failed: LOCK_PFN (OldIrql); // // A prototype PTE pool allocation failed. Carefully undo any allocations // and references done so far. // while (StartMappedSubsection != MappedSubsection) { ASSERT ((LONG_PTR)StartMappedSubsection->NumberOfMappedViews >= 1); StartMappedSubsection->NumberOfMappedViews -= 1; ASSERT (StartMappedSubsection->u.SubsectionFlags.SubsectionStatic == 0); ASSERT (StartMappedSubsection->DereferenceList.Flink == NULL); MI_SNAP_SUB (MappedSubsection, 0xA); if (StartMappedSubsection->NumberOfMappedViews == 0) { // // Insert this subsection into the unused subsection list. // Since it's not likely there are any resident protos at this // point, enqueue each subsection at the front. // InsertHeadList (&MmUnusedSubsectionList, &StartMappedSubsection->DereferenceList); MI_UNUSED_SUBSECTIONS_COUNT_INSERT (MappedSubsection); } StartMappedSubsection = (PMSUBSECTION) StartMappedSubsection->NextSubsection; } UNLOCK_PFN (OldIrql); return STATUS_INSUFFICIENT_RESOURCES; } VOID MiRemoveViewsFromSection ( IN PMSUBSECTION StartMappedSubsection, IN UINT64 LastPteOffset OPTIONAL ) /*++ Routine Description: This nonpagable routine removes the views from the specified section if the reference count reaches zero. N.B. Callers may pass in view lengths that exceed the length of the section and this must succeed. Thus MiAddViews checks for this and knows to stop the references at the end. More importantly, MiRemoveViews must also contain the same logic. Arguments: StartMappedSubsection - Supplies the mapped subsection to start at. LastPteOffset - Supplies the number of PTEs (beginning at the supplied subsection) to remove. Supplies zero to remove views from the supplied subsection to the end of the file. Return Value: None. Environment: Kernel Mode, PFN lock held. --*/ { PMSUBSECTION MappedSubsection; MappedSubsection = StartMappedSubsection; ASSERT ((MappedSubsection->ControlArea->u.Flags.Image == 0) && (MappedSubsection->ControlArea->FilePointer != NULL) && (MappedSubsection->ControlArea->u.Flags.PhysicalMemory == 0)); MM_PFN_LOCK_ASSERT(); do { // // Note the control area must be active at this point. // ASSERT (MappedSubsection->ControlArea->DereferenceList.Flink == NULL); ASSERT (MappedSubsection->SubsectionBase != NULL); ASSERT (MappedSubsection->DereferenceList.Flink == NULL); ASSERT (((LONG_PTR)MappedSubsection->NumberOfMappedViews >= 1) || (MappedSubsection->u.SubsectionFlags.SubsectionStatic == 1)); MappedSubsection->NumberOfMappedViews -= 1; MI_SNAP_SUB (MappedSubsection, 0x3); if ((MappedSubsection->NumberOfMappedViews == 0) && (MappedSubsection->u.SubsectionFlags.SubsectionStatic == 0)) { // // Insert this subsection into the unused subsection list. // InsertTailList (&MmUnusedSubsectionList, &MappedSubsection->DereferenceList); MI_UNUSED_SUBSECTIONS_COUNT_INSERT (MappedSubsection); } if (LastPteOffset != 0) { if (LastPteOffset <= (UINT64)MappedSubsection->PtesInSubsection) { break; } LastPteOffset -= MappedSubsection->PtesInSubsection; } MappedSubsection = (PMSUBSECTION) MappedSubsection->NextSubsection; } while (MappedSubsection != NULL); return; } VOID MiRemoveViewsFromSectionWithPfn ( IN PMSUBSECTION StartMappedSubsection, IN UINT64 LastPteOffset OPTIONAL ) /*++ Routine Description: This nonpagable routine removes the views from the specified section if the reference count reaches zero. Arguments: StartMappedSubsection - Supplies the mapped subsection to start at. LastPteOffset - Supplies the number of PTEs (beginning at the supplied subsection) to remove. Supplies zero to remove views from the supplied subsection to the end of the file. Return Value: None. Environment: Kernel Mode, address creation mutex optionally held if called in process context. APC_LEVEL or below. --*/ { KIRQL OldIrql; ASSERT (KeGetCurrentIrql () <= APC_LEVEL); LOCK_PFN (OldIrql); MiRemoveViewsFromSection (StartMappedSubsection, LastPteOffset); UNLOCK_PFN (OldIrql); } #if DBG extern PMSUBSECTION MiActiveSubsection; #endif VOID MiConvertStaticSubsections ( IN PCONTROL_AREA ControlArea ) { PMSUBSECTION MappedSubsection; ASSERT (ControlArea->u.Flags.Image == 0); ASSERT (ControlArea->FilePointer != NULL); ASSERT (ControlArea->u.Flags.PhysicalMemory == 0); if (ControlArea->u.Flags.Rom == 0) { MappedSubsection = (PMSUBSECTION)(ControlArea + 1); } else { MappedSubsection = (PMSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1); } do { MI_SNAP_SUB (MappedSubsection, 0xB); if (MappedSubsection->DereferenceList.Flink != NULL) { // // This subsection is already on the dereference subsection list. // This is the expected case. // NOTHING; } else if (MappedSubsection->u.SubsectionFlags.SubsectionStatic == 1) { // // This subsection was not put on the dereference subsection list // because it was created as part of an extension or nowrite. // Since this is the last segment dereference, convert the // subsection and its prototype PTEs to dynamic now (iff nowrite // is clear). // MappedSubsection->u.SubsectionFlags.SubsectionStatic = 0; MappedSubsection->u2.SubsectionFlags2.SubsectionConverted = 1; MappedSubsection->NumberOfMappedViews = 1; MiRemoveViewsFromSection (MappedSubsection, MappedSubsection->PtesInSubsection); MiSubsectionsConvertedToDynamic += 1; } else if (MappedSubsection->SubsectionBase == NULL) { // // This subsection has already had its prototype PTEs reclaimed // (or never allocated), hence it is not on any reclaim lists. // NOTHING; } else { // // This subsection is being processed by the dereference // segment thread right now ! The dereference thread sets the // mapped view count to 1 when it starts processing the subsection. // The subsequent flush then increases it to 2 while in progress. // So the count must be either 1 or 2 at this point. // ASSERT (MappedSubsection == MiActiveSubsection); ASSERT ((MappedSubsection->NumberOfMappedViews == 1) || (MappedSubsection->NumberOfMappedViews == 2)); } MappedSubsection = (PMSUBSECTION) MappedSubsection->NextSubsection; } while (MappedSubsection != NULL); } NTSTATUS MiMapViewOfDataSection ( IN PCONTROL_AREA ControlArea, IN PEPROCESS Process, IN PVOID *CapturedBase, IN PLARGE_INTEGER SectionOffset, IN PSIZE_T CapturedViewSize, IN PSECTION Section, IN SECTION_INHERIT InheritDisposition, IN ULONG ProtectionMask, IN SIZE_T CommitSize, IN ULONG_PTR ZeroBits, IN ULONG AllocationType ) /*++ Routine Description: This routine maps the specified mapped file or pagefile-backed section into the specified process's address space. Arguments: see MmMapViewOfSection above... ControlArea - Supplies the control area for the section. Process - Supplies the process pointer which is receiving the section. ProtectionMask - Supplies the initial page protection-mask. Return Value: Status of the map view operation. Environment: Kernel Mode, address creation mutex held. --*/ { PMMVAD Vad; SIZE_T VadSize; PVOID StartingAddress; PVOID EndingAddress; PSUBSECTION Subsection; UINT64 PteOffset; UINT64 LastPteOffset; UINT64 TotalNumberOfPtes; PVOID HighestUserAddress; PMMPTE PointerPte; PMMPTE LastPte; MMPTE TempPte; ULONG_PTR Alignment; SIZE_T QuotaCharge; SIZE_T QuotaExcess; PMMPTE TheFirstPrototypePte; PVOID CapturedStartingVa; ULONG CapturedCopyOnWrite; NTSTATUS Status; PSEGMENT Segment; SIZE_T SizeOfSection; #if defined(_MIALT4K_) SIZE_T ViewSizeFor4k; ULONG AltFlags; #endif QuotaCharge = 0; Segment = ControlArea->Segment; if ((AllocationType & MEM_RESERVE) && (ControlArea->FilePointer == NULL)) { return STATUS_INVALID_PARAMETER_9; } // // Check to see if there is a purge operation ongoing for // this segment. // if ((AllocationType & MEM_DOS_LIM) != 0) { if ((*CapturedBase == NULL) || (AllocationType & MEM_RESERVE)) { // // If MEM_DOS_LIM is specified, the address to map the // view MUST be specified as well. // return STATUS_INVALID_PARAMETER_3; } Alignment = PAGE_SIZE; } else { Alignment = X64K; } // // Check to see if a purge operation is in progress and if so, wait // for the purge to complete. In addition, up the count of mapped // views for this control area. // Status = MiCheckPurgeAndUpMapCount (ControlArea, FALSE); if (!NT_SUCCESS (Status)) { return Status; } if (*CapturedViewSize == 0) { SectionOffset->LowPart &= ~((ULONG)Alignment - 1); *CapturedViewSize = (ULONG_PTR)(Section->SizeOfSection.QuadPart - SectionOffset->QuadPart); } else { *CapturedViewSize += SectionOffset->LowPart & (Alignment - 1); SectionOffset->LowPart &= ~((ULONG)Alignment - 1); } ASSERT ((SectionOffset->LowPart & ((ULONG)Alignment - 1)) == 0); if ((LONG_PTR)*CapturedViewSize <= 0) { // // Section offset or view size past size of section. // MiDereferenceControlArea (ControlArea); return STATUS_INVALID_VIEW_SIZE; } // // Calculate the first prototype PTE field so it can be stored in the Vad. // ASSERT (ControlArea->u.Flags.GlobalOnlyPerSession == 0); if (ControlArea->u.Flags.Rom == 0) { Subsection = (PSUBSECTION)(ControlArea + 1); } else { Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1); } PteOffset = (SectionOffset->QuadPart >> PAGE_SHIFT); // // Make sure the PTEs are not in the extended part of the segment. // TotalNumberOfPtes = (((UINT64)Segment->SegmentFlags.TotalNumberOfPtes4132) << 32) | Segment->TotalNumberOfPtes; if (PteOffset >= TotalNumberOfPtes) { MiDereferenceControlArea (ControlArea); return STATUS_INVALID_VIEW_SIZE; } LastPteOffset = ((SectionOffset->QuadPart + *CapturedViewSize + PAGE_SIZE - 1) >> PAGE_SHIFT); ASSERT (LastPteOffset >= PteOffset); while (PteOffset >= (UINT64)Subsection->PtesInSubsection) { PteOffset -= Subsection->PtesInSubsection; LastPteOffset -= Subsection->PtesInSubsection; Subsection = Subsection->NextSubsection; ASSERT (Subsection != NULL); } if (ControlArea->FilePointer != NULL) { // // Increment the view count for every subsection spanned by this view. // // N.B. Callers may pass in view lengths that exceed the length of // the section and this must succeed. Thus MiAddViews must check // for this and know to stop the references at the end. More // importantly, MiRemoveViews must also contain the same logic. // Status = MiAddViewsForSectionWithPfn ((PMSUBSECTION)Subsection, LastPteOffset); if (!NT_SUCCESS (Status)) { MiDereferenceControlArea (ControlArea); return Status; } } ASSERT (Subsection->SubsectionBase != NULL); TheFirstPrototypePte = &Subsection->SubsectionBase[PteOffset]; // // Calculate the quota for the specified pages. // if ((ControlArea->FilePointer == NULL) && (CommitSize != 0) && (Segment->NumberOfCommittedPages < TotalNumberOfPtes)) { PointerPte = TheFirstPrototypePte; LastPte = PointerPte + BYTES_TO_PAGES(CommitSize); // // Charge quota for the entire requested range. If the charge succeeds, // excess is returned when the PTEs are actually filled in. // QuotaCharge = LastPte - PointerPte; } CapturedStartingVa = (PVOID)Section->Address.StartingVpn; CapturedCopyOnWrite = Section->u.Flags.CopyOnWrite; if ((*CapturedBase == NULL) && (CapturedStartingVa == NULL)) { // // The section is not based, // find an empty range starting on a 64k boundary. // if ((AllocationType & MEM_TOP_DOWN) || (Process->VmTopDown == 1)) { if (ZeroBits != 0) { HighestUserAddress = (PVOID)((ULONG_PTR)MM_USER_ADDRESS_RANGE_LIMIT >> ZeroBits); if (HighestUserAddress > MM_HIGHEST_VAD_ADDRESS) { HighestUserAddress = MM_HIGHEST_VAD_ADDRESS; } } else { HighestUserAddress = MM_HIGHEST_VAD_ADDRESS; } Status = MiFindEmptyAddressRangeDown (&Process->VadRoot, *CapturedViewSize, HighestUserAddress, Alignment, &StartingAddress); } else { Status = MiFindEmptyAddressRange (*CapturedViewSize, Alignment, (ULONG)ZeroBits, &StartingAddress); } if (!NT_SUCCESS (Status)) { if (ControlArea->FilePointer != NULL) { MiRemoveViewsFromSectionWithPfn ((PMSUBSECTION)Subsection, LastPteOffset); } MiDereferenceControlArea (ControlArea); return Status; } EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1L) | (PAGE_SIZE - 1L)); if (ZeroBits != 0) { if (EndingAddress > (PVOID)((ULONG_PTR)MM_USER_ADDRESS_RANGE_LIMIT >> ZeroBits)) { if (ControlArea->FilePointer != NULL) { MiRemoveViewsFromSectionWithPfn ((PMSUBSECTION)Subsection, LastPteOffset); } MiDereferenceControlArea (ControlArea); return STATUS_NO_MEMORY; } } } else { if (*CapturedBase == NULL) { // // The section is based. // #if defined(_WIN64) SizeOfSection = SectionOffset->QuadPart; #else SizeOfSection = SectionOffset->LowPart; #endif StartingAddress = (PVOID)((PCHAR)CapturedStartingVa + SizeOfSection); } else { StartingAddress = MI_ALIGN_TO_SIZE (*CapturedBase, Alignment); } // // Check to make sure the specified base address to ending address // is currently unused. // EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1L) | (PAGE_SIZE - 1L)); if (MiCheckForConflictingVadExistence (Process, StartingAddress, EndingAddress) == TRUE) { if (ControlArea->FilePointer != NULL) { MiRemoveViewsFromSectionWithPfn ((PMSUBSECTION)Subsection, LastPteOffset); } MiDereferenceControlArea (ControlArea); return STATUS_CONFLICTING_ADDRESSES; } } // // An unoccupied address range has been found, build the virtual // address descriptor to describe this range. // if (AllocationType & MEM_RESERVE) { VadSize = sizeof (MMVAD_LONG); Vad = ExAllocatePoolWithTag (NonPagedPool, VadSize, 'ldaV'); } else { VadSize = sizeof (MMVAD); Vad = ExAllocatePoolWithTag (NonPagedPool, VadSize, MMVADKEY); } if (Vad == NULL) { if (ControlArea->FilePointer != NULL) { MiRemoveViewsFromSectionWithPfn ((PMSUBSECTION)Subsection, LastPteOffset); } MiDereferenceControlArea (ControlArea); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory (Vad, VadSize); Vad->StartingVpn = MI_VA_TO_VPN (StartingAddress); Vad->EndingVpn = MI_VA_TO_VPN (EndingAddress); Vad->FirstPrototypePte = TheFirstPrototypePte; // // Set the protection in the PTE template field of the VAD. // Vad->ControlArea = ControlArea; Vad->u2.VadFlags2.Inherit = (InheritDisposition == ViewShare); Vad->u.VadFlags.Protection = ProtectionMask; Vad->u2.VadFlags2.CopyOnWrite = CapturedCopyOnWrite; // // Note that MEM_DOS_LIM significance is lost here, but those // files are not mapped MEM_RESERVE. // Vad->u2.VadFlags2.FileOffset = (ULONG)(SectionOffset->QuadPart >> 16); if ((AllocationType & SEC_NO_CHANGE) || (Section->u.Flags.NoChange)) { Vad->u.VadFlags.NoChange = 1; Vad->u2.VadFlags2.SecNoChange = 1; } if (AllocationType & MEM_RESERVE) { PMMEXTEND_INFO ExtendInfo; PMMVAD_LONG VadLong; Vad->u2.VadFlags2.LongVad = 1; KeAcquireGuardedMutexUnsafe (&MmSectionBasedMutex); ExtendInfo = Segment->ExtendInfo; if (ExtendInfo) { ExtendInfo->ReferenceCount += 1; } else { ExtendInfo = ExAllocatePoolWithTag (NonPagedPool, sizeof(MMEXTEND_INFO), 'xCmM'); if (ExtendInfo == NULL) { KeReleaseGuardedMutexUnsafe (&MmSectionBasedMutex); if (ControlArea->FilePointer != NULL) { MiRemoveViewsFromSectionWithPfn ((PMSUBSECTION)Subsection, LastPteOffset); } MiDereferenceControlArea (ControlArea); // // The pool allocation succeeded, but the quota charge // in InsertVad failed, deallocate the pool and return // an error. // ExFreePool (Vad); return STATUS_INSUFFICIENT_RESOURCES; } ExtendInfo->ReferenceCount = 1; ExtendInfo->CommittedSize = Segment->SizeOfSegment; Segment->ExtendInfo = ExtendInfo; } if (ExtendInfo->CommittedSize < (UINT64)Section->SizeOfSection.QuadPart) { ExtendInfo->CommittedSize = (UINT64)Section->SizeOfSection.QuadPart; } KeReleaseGuardedMutexUnsafe (&MmSectionBasedMutex); Vad->u2.VadFlags2.ExtendableFile = 1; VadLong = (PMMVAD_LONG) Vad; ASSERT (VadLong->u4.ExtendedInfo == NULL); VadLong->u4.ExtendedInfo = ExtendInfo; } // // If the page protection is write-copy or execute-write-copy // charge for each page in the view as it may become private. // if (MI_IS_PTE_PROTECTION_COPY_WRITE(ProtectionMask)) { Vad->u.VadFlags.CommitCharge = (BYTES_TO_PAGES ((PCHAR) EndingAddress - (PCHAR) StartingAddress)); } PteOffset += (Vad->EndingVpn - Vad->StartingVpn); if (PteOffset < Subsection->PtesInSubsection) { Vad->LastContiguousPte = &Subsection->SubsectionBase[PteOffset]; } else { Vad->LastContiguousPte = &Subsection->SubsectionBase[ (Subsection->PtesInSubsection - 1) + Subsection->UnusedPtes]; } if (QuotaCharge != 0) { if (MiChargeCommitment (QuotaCharge, NULL) == FALSE) { if (ControlArea->FilePointer != NULL) { MiRemoveViewsFromSectionWithPfn ((PMSUBSECTION)Subsection, LastPteOffset); } MiDereferenceControlArea (ControlArea); ExFreePool (Vad); return STATUS_COMMITMENT_LIMIT; } } ASSERT (Vad->FirstPrototypePte <= Vad->LastContiguousPte); LOCK_WS_UNSAFE (Process); Status = MiInsertVad (Vad); UNLOCK_WS_UNSAFE (Process); if (!NT_SUCCESS(Status)) { if (ControlArea->FilePointer != NULL) { MiRemoveViewsFromSectionWithPfn ((PMSUBSECTION)Subsection, LastPteOffset); } MiDereferenceControlArea (ControlArea); // // The quota charge in InsertVad failed, deallocate the pool and return // an error. // ExFreePool (Vad); if (QuotaCharge != 0) { MiReturnCommitment (QuotaCharge); } return Status; } // // Stash the first mapped address for the performance analysis tools. // Note this is not synchronized across multiple processes but that's ok. // if (ControlArea->FilePointer == NULL) { if (Segment->u2.FirstMappedVa == NULL) { Segment->u2.FirstMappedVa = StartingAddress; } } #if DBG if (!(AllocationType & MEM_RESERVE)) { ASSERT(((ULONG_PTR)EndingAddress - (ULONG_PTR)StartingAddress) <= ROUND_TO_PAGES64(Segment->SizeOfSegment)); } #endif // // If a commit size was specified, make sure those pages are committed. // if (QuotaCharge != 0) { PointerPte = Vad->FirstPrototypePte; LastPte = PointerPte + BYTES_TO_PAGES(CommitSize); TempPte = Segment->SegmentPteTemplate; QuotaExcess = 0; KeAcquireGuardedMutexUnsafe (&MmSectionCommitMutex); while (PointerPte < LastPte) { if (PointerPte->u.Long == 0) { MI_WRITE_INVALID_PTE (PointerPte, TempPte); } else { QuotaExcess += 1; } PointerPte += 1; } ASSERT (QuotaCharge >= QuotaExcess); QuotaCharge -= QuotaExcess; MM_TRACK_COMMIT (MM_DBG_COMMIT_MAPVIEW_DATA, QuotaCharge); Segment->NumberOfCommittedPages += QuotaCharge; ASSERT (Segment->NumberOfCommittedPages <= TotalNumberOfPtes); KeReleaseGuardedMutexUnsafe (&MmSectionCommitMutex); InterlockedExchangeAddSizeT (&MmSharedCommit, QuotaCharge); if (QuotaExcess != 0) { MiReturnCommitment (QuotaExcess); } } #if defined(_MIALT4K_) if (Process->Wow64Process != NULL) { EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1L) | (PAGE_4K - 1L)); ViewSizeFor4k = (PCHAR)EndingAddress - (PCHAR)StartingAddress + 1L; if (ControlArea->FilePointer != NULL) { AltFlags = (AllocationType & MEM_RESERVE) ? 0 : ALT_COMMIT; MiProtectFor4kPage (StartingAddress, ViewSizeFor4k, ProtectionMask, (ALT_ALLOCATE | AltFlags), Process); } else { MiProtectMapFileFor4kPage (StartingAddress, ViewSizeFor4k, ProtectionMask, CommitSize, Vad->FirstPrototypePte, Vad->LastContiguousPte, Process); } } #endif // // Update the current virtual size in the process header. // *CapturedViewSize = (PCHAR)EndingAddress - (PCHAR)StartingAddress + 1L; Process->VirtualSize += *CapturedViewSize; if (Process->VirtualSize > Process->PeakVirtualSize) { Process->PeakVirtualSize = Process->VirtualSize; } *CapturedBase = StartingAddress; // // Update the count of writable user mappings so the transaction semantics // can be supported. Note that no lock synchronization is needed here as // the transaction manager must already check for any open writable handles // to the file - and no writable sections can be created without a writable // file handle. So all that needs to be provided is a way for the // transaction manager to know that there are lingering views or created // sections still open that have write access. // if ((ProtectionMask & MM_READWRITE) && (ControlArea->FilePointer != NULL)) { #if 0 // // The section may no longer exist at this point so these ASSERTs // cannot be enabled. // ASSERT (Section->u.Flags.UserWritable == 1); ASSERT (Section->InitialPageProtection & (PAGE_READWRITE|PAGE_EXECUTE_READWRITE)); #endif InterlockedIncrement ((PLONG)&Segment->WritableUserReferences); } return STATUS_SUCCESS; } NTSTATUS MiCheckPurgeAndUpMapCount ( IN PCONTROL_AREA ControlArea, IN LOGICAL FailIfSystemViews ) /*++ Routine Description: This routine synchronizes with any on going purge operations on the same segment (identified via the control area). If another purge operation is occurring, the function blocks until it is completed. When this function returns the MappedView and the NumberOfUserReferences count for the control area will be incremented thereby referencing the control area. Arguments: ControlArea - Supplies the control area for the segment to be purged. FailIfSystemViews - Supplies TRUE if the request should fail when there are active kernel mappings. When the image is mapped in system or session space as a driver, copy on write does not work so user processes are not allowed to map the image. Return Value: STATUS_SUCCESS if the synchronization was successful. NTSTATUS if the synchronization did not occur due to low resources, etc. Environment: Kernel Mode. --*/ { NTSTATUS Status; KIRQL OldIrql; PEVENT_COUNTER PurgedEvent; PEVENT_COUNTER WaitEvent; ULONG OldRef; PKTHREAD CurrentThread; PurgedEvent = NULL; OldRef = 1; if (FailIfSystemViews == TRUE) { ASSERT (ControlArea->u.Flags.Image != 0); } LOCK_PFN (OldIrql); while (ControlArea->u.Flags.BeingPurged != 0) { // // A purge operation is in progress. // if (PurgedEvent == NULL) { // // Release the locks and allocate pool for the event. // UNLOCK_PFN (OldIrql); PurgedEvent = MiGetEventCounter (); if (PurgedEvent == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } LOCK_PFN (OldIrql); continue; } if (ControlArea->WaitingForDeletion == NULL) { ControlArea->WaitingForDeletion = PurgedEvent; WaitEvent = PurgedEvent; PurgedEvent = NULL; } else { WaitEvent = ControlArea->WaitingForDeletion; // // No interlock is needed for the RefCount increment as // no thread can be decrementing it since it is still // pointed to by the control area. // WaitEvent->RefCount += 1; } // // Release the PFN lock and wait for the event. // CurrentThread = KeGetCurrentThread (); KeEnterCriticalRegionThread (CurrentThread); UNLOCK_PFN_AND_THEN_WAIT(OldIrql); KeWaitForSingleObject(&WaitEvent->Event, WrVirtualMemory, KernelMode, FALSE, (PLARGE_INTEGER)NULL); // // Before this event can be set, the control area WaitingForDeletion // field must be cleared (and may be reinitialized to something else), // but cannot be reset to our local event. This allows us to // dereference the event count lock free. // ASSERT (WaitEvent != ControlArea->WaitingForDeletion); MiFreeEventCounter (WaitEvent); LOCK_PFN (OldIrql); KeLeaveCriticalRegionThread (CurrentThread); } if ((FailIfSystemViews == TRUE) && (ControlArea->u.Flags.ImageMappedInSystemSpace) && (KeGetPreviousMode() != KernelMode)) { UNLOCK_PFN (OldIrql); Status = STATUS_CONFLICTING_ADDRESSES; } else { // // Indicate another file is mapped for the segment. // ControlArea->NumberOfMappedViews += 1; ControlArea->NumberOfUserReferences += 1; ControlArea->u.Flags.HadUserReference = 1; ASSERT (ControlArea->NumberOfSectionReferences != 0); UNLOCK_PFN (OldIrql); Status = STATUS_SUCCESS; } if (PurgedEvent != NULL) { MiFreeEventCounter (PurgedEvent); } return Status; } typedef struct _NTSYM { struct _NTSYM *Next; PVOID SymbolTable; ULONG NumberOfSymbols; PVOID StringTable; USHORT Flags; USHORT EntrySize; ULONG MinimumVa; ULONG MaximumVa; PCHAR MapName; ULONG MapNameLen; } NTSYM, *PNTSYM; PVOID MiCacheImageSymbols ( IN PVOID ImageBase ) { ULONG DebugSize; PVOID DebugDirectory; PAGED_CODE (); DebugDirectory = NULL; try { DebugDirectory = RtlImageDirectoryEntryToData (ImageBase, TRUE, IMAGE_DIRECTORY_ENTRY_DEBUG, &DebugSize); // // If using remote KD, ImageBase is what it wants to see. // } except (EXCEPTION_EXECUTE_HANDLER) { NOTHING; } return DebugDirectory; } NTSYSAPI NTSTATUS NTAPI NtAreMappedFilesTheSame ( IN PVOID File1MappedAsAnImage, IN PVOID File2MappedAsFile ) /*++ Routine Description: This routine compares the two files mapped at the specified addresses to see if they are both the same file. Arguments: File1MappedAsAnImage - Supplies an address within the first file which is mapped as an image file. File2MappedAsFile - Supplies an address within the second file which is mapped as either an image file or a data file. Return Value: STATUS_SUCCESS is returned if the two files are the same. STATUS_NOT_SAME_DEVICE is returned if the files are different. Other status values can be returned if the addresses are not mapped as files, etc. Environment: User mode callable system service. --*/ { PMMVAD FoundVad1; PMMVAD FoundVad2; NTSTATUS Status; PEPROCESS Process; Process = PsGetCurrentProcess(); LOCK_ADDRESS_SPACE (Process); FoundVad1 = MiLocateAddress (File1MappedAsAnImage); FoundVad2 = MiLocateAddress (File2MappedAsFile); if ((FoundVad1 == NULL) || (FoundVad2 == NULL)) { // // No virtual address is allocated at the specified base address, // return an error. // Status = STATUS_INVALID_ADDRESS; goto ErrorReturn; } // // Check file names. // if ((FoundVad1->u.VadFlags.PrivateMemory == 1) || (FoundVad2->u.VadFlags.PrivateMemory == 1)) { Status = STATUS_CONFLICTING_ADDRESSES; goto ErrorReturn; } if ((FoundVad1->ControlArea == NULL) || (FoundVad2->ControlArea == NULL)) { Status = STATUS_CONFLICTING_ADDRESSES; goto ErrorReturn; } if ((FoundVad1->ControlArea->FilePointer == NULL) || (FoundVad2->ControlArea->FilePointer == NULL)) { Status = STATUS_CONFLICTING_ADDRESSES; goto ErrorReturn; } Status = STATUS_NOT_SAME_DEVICE; if ((PVOID)FoundVad1->ControlArea == FoundVad2->ControlArea->FilePointer->SectionObjectPointer->ImageSectionObject) { Status = STATUS_SUCCESS; } ErrorReturn: UNLOCK_ADDRESS_SPACE (Process); return Status; } NTSTATUS MiSetPageModified ( IN PMMVAD Vad, IN PVOID Address ) /*++ Routine Description: This routine sets the modified bit in the PFN database for the pages that correspond to the specified address range. Note that the dirty bit in the PTE is cleared by this operation. Arguments: Vad - Supplies the VAD to charge. Address - Supplies the user address to access. Return Value: NTSTATUS. Environment: Kernel mode. Address space mutex held. --*/ { PMMPFN Pfn1; NTSTATUS Status; PEPROCESS CurrentProcess; SIZE_T RealCharge; MMPTE PteContents; KIRQL OldIrql; PMMPTE PointerPxe; PMMPTE PointerPpe; PMMPTE PointerPde; PMMPTE PointerPte; LOGICAL ReturnCommitment; LOGICAL ChargedJobCommit; // // Charge commitment up front even though we may not use it because // failing to get it later would make things messy. // RealCharge = 1; ReturnCommitment = FALSE; ChargedJobCommit = FALSE; CurrentProcess = PsGetCurrentProcess (); Status = PsChargeProcessPageFileQuota (CurrentProcess, RealCharge); if (!NT_SUCCESS (Status)) { return STATUS_COMMITMENT_LIMIT; } if (CurrentProcess->CommitChargeLimit) { if (CurrentProcess->CommitCharge + RealCharge > CurrentProcess->CommitChargeLimit) { if (CurrentProcess->Job) { PsReportProcessMemoryLimitViolation (); } PsReturnProcessPageFileQuota (CurrentProcess, RealCharge); return STATUS_COMMITMENT_LIMIT; } } if (CurrentProcess->JobStatus & PS_JOB_STATUS_REPORT_COMMIT_CHANGES) { if (PsChangeJobMemoryUsage (PS_JOB_STATUS_REPORT_COMMIT_CHANGES, RealCharge) == FALSE) { PsReturnProcessPageFileQuota (CurrentProcess, RealCharge); return STATUS_COMMITMENT_LIMIT; } ChargedJobCommit = TRUE; } if (MiChargeCommitment (RealCharge, NULL) == FALSE) { if (ChargedJobCommit == TRUE) { PsChangeJobMemoryUsage (PS_JOB_STATUS_REPORT_COMMIT_CHANGES, -(SSIZE_T)RealCharge); } PsReturnProcessPageFileQuota (CurrentProcess, RealCharge); return STATUS_COMMITMENT_LIMIT; } CurrentProcess->CommitCharge += RealCharge; if (CurrentProcess->CommitCharge > CurrentProcess->CommitChargePeak) { CurrentProcess->CommitChargePeak = CurrentProcess->CommitCharge; } MI_INCREMENT_TOTAL_PROCESS_COMMIT (RealCharge); MM_TRACK_COMMIT (MM_DBG_COMMIT_INSERT_VAD, RealCharge); PointerPte = MiGetPteAddress (Address); PointerPde = MiGetPdeAddress (Address); PointerPpe = MiGetPpeAddress (Address); PointerPxe = MiGetPxeAddress (Address); do { try { *(volatile CCHAR *)Address; } except (EXCEPTION_EXECUTE_HANDLER) { CurrentProcess->CommitCharge -= RealCharge; if (ChargedJobCommit == TRUE) { PsChangeJobMemoryUsage (PS_JOB_STATUS_REPORT_COMMIT_CHANGES, -(SSIZE_T)RealCharge); } MiReturnCommitment (RealCharge); PsReturnProcessPageFileQuota (CurrentProcess, RealCharge); return GetExceptionCode (); } LOCK_WS_UNSAFE (CurrentProcess); #if (_MI_PAGING_LEVELS >= 4) if ((PointerPxe->u.Hard.Valid == 0) || (PointerPpe->u.Hard.Valid == 0) || (PointerPde->u.Hard.Valid == 0) || (PointerPte->u.Hard.Valid == 0)) #elif (_MI_PAGING_LEVELS >= 3) if ((PointerPpe->u.Hard.Valid == 0) || (PointerPde->u.Hard.Valid == 0) || (PointerPte->u.Hard.Valid == 0)) #else if ((PointerPde->u.Hard.Valid == 0) || (PointerPte->u.Hard.Valid == 0)) #endif { // // Page is no longer valid. // UNLOCK_WS_UNSAFE (CurrentProcess); continue; } break; } while (TRUE); PteContents = *PointerPte; ASSERT (PteContents.u.Hard.Valid == 1); Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber); LOCK_PFN (OldIrql); MI_SET_MODIFIED (Pfn1, 1, 0x8); if (Pfn1->OriginalPte.u.Soft.Prototype == 0) { if (Pfn1->u3.e1.WriteInProgress == 0) { MiReleasePageFileSpace (Pfn1->OriginalPte); Pfn1->OriginalPte.u.Soft.PageFileHigh = 0; } // // We didn't need to (and shouldn't have) charged commitment for // this page as it was already pagefile backed (ie: someone else // already charged commitment for it earlier). // ReturnCommitment = TRUE; } #ifdef NT_UP if (MI_IS_PTE_DIRTY (PteContents)) { #endif //NT_UP MI_SET_PTE_CLEAN (PteContents); // // Clear the dirty bit in the PTE so new writes can be tracked. // MI_WRITE_VALID_PTE_NEW_PROTECTION (PointerPte, PteContents); KeFlushSingleTb (Address, TRUE); #ifdef NT_UP } #endif //NT_UP UNLOCK_PFN (OldIrql); UNLOCK_WS_UNSAFE (CurrentProcess); if (ReturnCommitment == TRUE) { CurrentProcess->CommitCharge -= RealCharge; if (ChargedJobCommit == TRUE) { PsChangeJobMemoryUsage(PS_JOB_STATUS_REPORT_COMMIT_CHANGES, -(SSIZE_T)RealCharge); } MiReturnCommitment (RealCharge); PsReturnProcessPageFileQuota (CurrentProcess, RealCharge); } else { // // Commit has been charged for the copied page, add the charge // to the VAD now so it is automatically returned when the VAD is // deleted later. // MM_TRACK_COMMIT (MM_DBG_COMMIT_IMAGE, 1); ASSERT (Vad->u.VadFlags.CommitCharge != MM_MAX_COMMIT); Vad->u.VadFlags.CommitCharge += 1; } return STATUS_SUCCESS; } NTSTATUS MmMapViewInSystemSpace ( IN PVOID Section, OUT PVOID *MappedBase, IN OUT PSIZE_T ViewSize ) /*++ Routine Description: This routine maps the specified section into the system's address space. Arguments: Section - Supplies a pointer to the section to map. *MappedBase - Returns the address where the section was mapped. ViewSize - Supplies the size of the view to map. If this is specified as zero, the whole section is mapped. Returns the actual size mapped. Return Value: Status of the map view operation. Environment: Kernel Mode, APC_LEVEL or below. --*/ { PMMSESSION Session; PAGED_CODE(); Session = &MmSession; return MiMapViewInSystemSpace (Section, Session, MappedBase, ViewSize); } NTSTATUS MmMapViewInSessionSpace ( IN PVOID Section, OUT PVOID *MappedBase, IN OUT PSIZE_T ViewSize ) /*++ Routine Description: This routine maps the specified section into the current process's session address space. Arguments: Section - Supplies a pointer to the section to map. *MappedBase - Returns the address where the section was mapped. ViewSize - Supplies the size of the view to map. If this is specified as zero, the whole section is mapped. Returns the actual size mapped. Return Value: Status of the map view operation. Environment: Kernel Mode, APC_LEVEL or below. --*/ { PMMSESSION Session; PAGED_CODE(); if ((PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION) == 0) { return STATUS_NOT_MAPPED_VIEW; } ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE); Session = &MmSessionSpace->Session; return MiMapViewInSystemSpace (Section, Session, MappedBase, ViewSize); } NTSTATUS MiMapViewInSystemSpace ( IN PVOID Section, IN PMMSESSION Session, OUT PVOID *MappedBase, IN OUT PSIZE_T ViewSize ) /*++ Routine Description: This routine maps the specified section into the system's address space. Arguments: Section - Supplies a pointer to the section to map. Session - Supplies the session data structure for this view. *MappedBase - Returns the address where the section was mapped. ViewSize - Supplies the size of the view to map. If this is specified as zero, the whole section is mapped. Returns the actual size mapped. Return Value: Status of the map view operation. Environment: Kernel Mode, IRQL of APC_LEVEL or below. --*/ { PVOID Base; PSUBSECTION Subsection; PCONTROL_AREA ControlArea; PCONTROL_AREA NewControlArea; ULONG StartBit; ULONG SizeIn64k; ULONG NewSizeIn64k; PMMPTE BasePte; PFN_NUMBER NumberOfPtes; SIZE_T NumberOfBytes; SIZE_T SectionSize; NTSTATUS Status; PAGED_CODE(); // // Check to see if a purge operation is in progress and if so, wait // for the purge to complete. In addition, up the count of mapped // views for this control area. // ControlArea = ((PSECTION)Section)->Segment->ControlArea; if ((ControlArea->u.Flags.GlobalOnlyPerSession == 0) && (ControlArea->u.Flags.Rom == 0)) { Subsection = (PSUBSECTION)(ControlArea + 1); } else { Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1); } Status = MiCheckPurgeAndUpMapCount (ControlArea, FALSE); if (!NT_SUCCESS (Status)) { return Status; } #if defined (_WIN64) SectionSize = ((PSECTION)Section)->SizeOfSection.QuadPart; #else SectionSize = ((PSECTION)Section)->SizeOfSection.LowPart; #endif if (*ViewSize == 0) { *ViewSize = SectionSize; } else if (*ViewSize > SectionSize) { // // Section offset or view size past size of section. // MiDereferenceControlArea (ControlArea); return STATUS_INVALID_VIEW_SIZE; } // // Calculate the first prototype PTE field in the Vad. // SizeIn64k = (ULONG)((*ViewSize / X64K) + ((*ViewSize & (X64K - 1)) != 0)); // // 4GB-64K is the maximum individual view size allowed since we encode // this into the bottom 16 bits of the MMVIEW entry. // if (SizeIn64k >= X64K) { MiDereferenceControlArea (ControlArea); return STATUS_INVALID_VIEW_SIZE; } Base = MiInsertInSystemSpace (Session, SizeIn64k, ControlArea); if (Base == NULL) { MiDereferenceControlArea (ControlArea); return STATUS_NO_MEMORY; } NumberOfBytes = (SIZE_T)SizeIn64k * X64K; if (Session == &MmSession) { MiFillSystemPageDirectory (Base, NumberOfBytes); Status = STATUS_SUCCESS; } else { Status = MiSessionCommitPageTables (Base, (PVOID)((ULONG_PTR)Base + NumberOfBytes)); } if (!NT_SUCCESS (Status)) { bail: MiDereferenceControlArea (ControlArea); StartBit = (ULONG) (((ULONG_PTR)Base - (ULONG_PTR)Session->SystemSpaceViewStart) >> 16); LOCK_SYSTEM_VIEW_SPACE (Session); NewSizeIn64k = MiRemoveFromSystemSpace (Session, Base, &NewControlArea); ASSERT (ControlArea == NewControlArea); ASSERT (SizeIn64k == NewSizeIn64k); RtlClearBits (Session->SystemSpaceBitMap, StartBit, SizeIn64k); UNLOCK_SYSTEM_VIEW_SPACE (Session); return Status; } BasePte = MiGetPteAddress (Base); NumberOfPtes = BYTES_TO_PAGES (*ViewSize); // // Setup PTEs to point to prototype PTEs. // Status = MiAddMappedPtes (BasePte, NumberOfPtes, ControlArea); if (!NT_SUCCESS (Status)) { goto bail; } *MappedBase = Base; return STATUS_SUCCESS; } NTSTATUS MmGetSessionMappedViewInformation ( OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG Length, IN PULONG SessionId OPTIONAL ) /*++ Routine Description: This function returns information about all the per-session mapped views in the system. Arguments: SystemInformation - A pointer to a buffer which receives the specified information. SystemInformationLength - Specifies the length in bytes of the system information buffer. Length - Receives the number of bytes placed (or would have been placed) in the system information buffer. SessionId - Session Id (-1 indicates enumerate all sessions). Environment: Kernel mode. Return Value: Returns one of the following status codes: STATUS_SUCCESS - normal, successful completion. STATUS_INVALID_INFO_CLASS - The SystemInformationClass parameter did not specify a valid value. STATUS_INFO_LENGTH_MISMATCH - The value of the SystemInformationLength parameter did not match the length required for the information class requested by the SystemInformationClass parameter. STATUS_ACCESS_VIOLATION - Either the SystemInformation buffer pointer or the Length pointer value specified an invalid address. STATUS_WORKING_SET_QUOTA - The process does not have sufficient working set to lock the specified output structure in memory. STATUS_INSUFFICIENT_RESOURCES - Insufficient system resources exist for this request to complete. --*/ { PMMSESSION Session; KAPC_STATE ApcState; PVOID MappedAddress; PVOID OpaqueSession; PVOID LockVariable; ULONG ContiguousFree; ULONG Index; ULONG Hint; ULONG TotalFree; ULONG TotalSize; ULONG CurrentSessionId; ULONG StartingIndex; NTSTATUS status; NTSTATUS status1; PSYSTEM_SESSION_MAPPED_VIEW_INFORMATION SessionMappedViewInfo; *Length = 0; TotalSize = 0; status = STATUS_SUCCESS; SessionMappedViewInfo = NULL; if (SystemInformationLength > 0) { status1 = ExLockUserBuffer (SystemInformation, SystemInformationLength, KeGetPreviousMode(), IoWriteAccess, &MappedAddress, &LockVariable); if (!NT_SUCCESS(status1)) { return status1; } } else { // // This indicates the caller just wants to know the size of the // buffer to allocate but is not prepared to accept any data content // in this instance. // MappedAddress = NULL; LockVariable = NULL; } for (OpaqueSession = MmGetNextSession (NULL); OpaqueSession != NULL; OpaqueSession = MmGetNextSession (OpaqueSession)) { SessionMappedViewInfo = (PSYSTEM_SESSION_MAPPED_VIEW_INFORMATION) ((PUCHAR)MappedAddress + TotalSize); // // If a specific session was requested, only extract that one. // CurrentSessionId = MmGetSessionId (OpaqueSession); if ((*SessionId == 0xFFFFFFFF) || (CurrentSessionId == *SessionId)) { // // Attach to session now to perform operations... // if (NT_SUCCESS (MmAttachSession (OpaqueSession, &ApcState))) { // // Session is still alive so include it. // TotalSize += sizeof (SYSTEM_SESSION_MAPPED_VIEW_INFORMATION); if (TotalSize > SystemInformationLength) { status = STATUS_INFO_LENGTH_MISMATCH; } else { // // Get mapped view information for each session. // Session = &MmSessionSpace->Session; Hint = 0; TotalFree = 0; LOCK_SYSTEM_VIEW_SPACE (Session); ContiguousFree = RtlFindLongestRunClear ( Session->SystemSpaceBitMap, &StartingIndex); do { Index = RtlFindClearBits (Session->SystemSpaceBitMap, 1, Hint); if ((Index < Hint) || (Index == NO_BITS_FOUND)) { break; } TotalFree += 1; Hint = Index + 1; } while (TRUE); UNLOCK_SYSTEM_VIEW_SPACE (Session); SessionMappedViewInfo->NumberOfBytesAvailable = (SIZE_T)TotalFree * X64K; SessionMappedViewInfo->NumberOfBytesAvailableContiguous = (SIZE_T)ContiguousFree * X64K; SessionMappedViewInfo->SessionId = CurrentSessionId; SessionMappedViewInfo->ViewFailures = Session->BitmapFailures; // // Point to next session. // SessionMappedViewInfo->NextEntryOffset = TotalSize; } // // Detach from session. // MmDetachSession (OpaqueSession, &ApcState); } // // Bail if only this session was of interest. // if (*SessionId != 0xFFFFFFFF) { MmQuitNextSession (OpaqueSession); break; } } } if ((NT_SUCCESS (status)) && (SessionMappedViewInfo != NULL)) { SessionMappedViewInfo->NextEntryOffset = 0; } if (MappedAddress != NULL) { ExUnlockUserBuffer (LockVariable); } *Length = TotalSize; return status; } VOID MiFillSystemPageDirectory ( IN PVOID Base, IN SIZE_T NumberOfBytes ) /*++ Routine Description: This routine allocates page tables and fills the system page directory entries for the specified virtual address range. Arguments: Base - Supplies the virtual address of the view. NumberOfBytes - Supplies the number of bytes the view spans. Return Value: None. Environment: Kernel Mode, IRQL of dispatch level. This routine could be made PAGELK but it is a high frequency routine so it is actually better to keep it nonpaged to avoid bringing in the entire PAGELK section. --*/ { PMMPTE FirstPde; PMMPTE LastPde; PMMPTE FirstSystemPde; PMMPTE PointerPpe; PMMPTE PointerPxe; MMPTE TempPte; PFN_NUMBER PageFrameIndex; KIRQL OldIrql; #if (_MI_PAGING_LEVELS < 3) ULONG i; #endif PAGED_CODE(); // // CODE IS ALREADY LOCKED BY CALLER. // FirstPde = MiGetPdeAddress (Base); LastPde = MiGetPdeAddress ((PVOID)(((PCHAR)Base) + NumberOfBytes - 1)); PointerPpe = MiGetPpeAddress (Base); PointerPxe = MiGetPxeAddress (Base); #if (_MI_PAGING_LEVELS >= 3) FirstSystemPde = FirstPde; #else FirstSystemPde = &MmSystemPagePtes[((ULONG_PTR)FirstPde & (PD_PER_SYSTEM * (PDE_PER_PAGE * sizeof(MMPTE)) - 1)) / sizeof(MMPTE) ]; #endif do { #if (_MI_PAGING_LEVELS >= 4) if (PointerPxe->u.Hard.Valid == 0) { // // No page directory page exists, get a page and map it in. // TempPte = ValidKernelPde; LOCK_PFN (OldIrql); if (PointerPxe->u.Hard.Valid == 0) { if ((MmAvailablePages < MM_HIGH_LIMIT) && (MiEnsureAvailablePageOrWait (NULL, PointerPxe, OldIrql))) { // // PFN_LOCK was dropped, redo this loop as another process // could have made this PDE valid. // UNLOCK_PFN (OldIrql); continue; } MiChargeCommitmentCantExpand (1, TRUE); MM_TRACK_COMMIT (MM_DBG_COMMIT_FILL_SYSTEM_DIRECTORY, 1); PageFrameIndex = MiRemoveAnyPage ( MI_GET_PAGE_COLOR_FROM_PTE (PointerPxe)); TempPte.u.Hard.PageFrameNumber = PageFrameIndex; MI_WRITE_VALID_PTE (PointerPxe, TempPte); MiInitializePfn (PageFrameIndex, PointerPxe, 1); KeZeroPages (MiGetVirtualAddressMappedByPte (PointerPxe), PAGE_SIZE); } UNLOCK_PFN (OldIrql); } #endif #if (_MI_PAGING_LEVELS >= 3) if (PointerPpe->u.Hard.Valid == 0) { // // No page directory page exists, get a page and map it in. // TempPte = ValidKernelPde; LOCK_PFN (OldIrql); if (PointerPpe->u.Hard.Valid == 0) { if ((MmAvailablePages < MM_HIGH_LIMIT) && (MiEnsureAvailablePageOrWait (NULL, PointerPpe, OldIrql))) { // // PFN_LOCK was dropped, redo this loop as another process // could have made this PDE valid. // UNLOCK_PFN (OldIrql); continue; } MiChargeCommitmentCantExpand (1, TRUE); MM_TRACK_COMMIT (MM_DBG_COMMIT_FILL_SYSTEM_DIRECTORY, 1); PageFrameIndex = MiRemoveAnyPage ( MI_GET_PAGE_COLOR_FROM_PTE (PointerPpe)); TempPte.u.Hard.PageFrameNumber = PageFrameIndex; MI_WRITE_VALID_PTE (PointerPpe, TempPte); MiInitializePfn (PageFrameIndex, PointerPpe, 1); KeZeroPages (MiGetVirtualAddressMappedByPte (PointerPpe), PAGE_SIZE); } UNLOCK_PFN (OldIrql); } #endif if (FirstSystemPde->u.Hard.Valid == 0) { // // No page table page exists, get a page and map it in. // TempPte = ValidKernelPde; LOCK_PFN (OldIrql); if (FirstSystemPde->u.Hard.Valid == 0) { if ((MmAvailablePages < MM_HIGH_LIMIT) && (MiEnsureAvailablePageOrWait (NULL, FirstSystemPde, OldIrql))) { // // PFN_LOCK was dropped, redo this loop as another process // could have made this PDE valid. // UNLOCK_PFN (OldIrql); continue; } MiChargeCommitmentCantExpand (1, TRUE); MM_TRACK_COMMIT (MM_DBG_COMMIT_FILL_SYSTEM_DIRECTORY, 1); PageFrameIndex = MiRemoveAnyPage ( MI_GET_PAGE_COLOR_FROM_PTE (FirstSystemPde)); TempPte.u.Hard.PageFrameNumber = PageFrameIndex; MI_WRITE_VALID_PTE (FirstSystemPde, TempPte); // // The FirstPde and FirstSystemPde may be identical even on // 32-bit machines if we are currently running in the // system process, so check for the valid bit first so we // don't assert on a checked build. // if (FirstPde->u.Hard.Valid == 0) { MI_WRITE_VALID_PTE (FirstPde, TempPte); } #if (_MI_PAGING_LEVELS >= 3) MiInitializePfn (PageFrameIndex, FirstPde, 1); #else i = (FirstPde - MiGetPdeAddress(0)) / PDE_PER_PAGE; MiInitializePfnForOtherProcess (PageFrameIndex, FirstPde, MmSystemPageDirectory[i]); #endif KeZeroPages (MiGetVirtualAddressMappedByPte (FirstPde), PAGE_SIZE); } UNLOCK_PFN (OldIrql); } FirstSystemPde += 1; FirstPde += 1; #if (_MI_PAGING_LEVELS >= 3) if (MiIsPteOnPdeBoundary (FirstPde)) { PointerPpe = MiGetPteAddress (FirstPde); if (MiIsPteOnPpeBoundary (FirstPde)) { PointerPxe = MiGetPdeAddress (FirstPde); } } #endif } while (FirstPde <= LastPde); } NTSTATUS MmUnmapViewInSystemSpace ( IN PVOID MappedBase ) /*++ Routine Description: This routine unmaps the specified section from the system's address space. Arguments: MappedBase - Supplies the address of the view to unmap. Return Value: Status of the map view operation. Environment: Kernel Mode, IRQL of dispatch level. --*/ { PAGED_CODE(); return MiUnmapViewInSystemSpace (&MmSession, MappedBase); } NTSTATUS MmUnmapViewInSessionSpace ( IN PVOID MappedBase ) /*++ Routine Description: This routine unmaps the specified section from the system's address space. Arguments: MappedBase - Supplies the address of the view to unmap. Return Value: Status of the map view operation. Environment: Kernel Mode, IRQL of dispatch level. --*/ { PMMSESSION Session; PAGED_CODE(); if ((PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION) == 0) { return STATUS_NOT_MAPPED_VIEW; } ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE); Session = &MmSessionSpace->Session; return MiUnmapViewInSystemSpace (Session, MappedBase); } NTSTATUS MiUnmapViewInSystemSpace ( IN PMMSESSION Session, IN PVOID MappedBase ) /*++ Routine Description: This routine unmaps the specified section from the system's address space. Arguments: Session - Supplies the session data structure for this view. MappedBase - Supplies the address of the view to unmap. Return Value: Status of the map view operation. Environment: Kernel Mode, IRQL of dispatch level. --*/ { ULONG StartBit; ULONG Size; PCONTROL_AREA ControlArea; PMMSUPPORT Ws; PAGED_CODE(); if (Session == &MmSession) { Ws = &MmSystemCacheWs; } else { Ws = &MmSessionSpace->GlobalVirtualAddress->Vm; } StartBit = (ULONG) (((ULONG_PTR)MappedBase - (ULONG_PTR)Session->SystemSpaceViewStart) >> 16); LOCK_SYSTEM_VIEW_SPACE (Session); Size = MiRemoveFromSystemSpace (Session, MappedBase, &ControlArea); RtlClearBits (Session->SystemSpaceBitMap, StartBit, Size); // // Zero PTEs. // Size = Size * (X64K >> PAGE_SHIFT); MiRemoveMappedPtes (MappedBase, Size, ControlArea, Ws); UNLOCK_SYSTEM_VIEW_SPACE (Session); return STATUS_SUCCESS; } PVOID MiInsertInSystemSpace ( IN PMMSESSION Session, IN ULONG SizeIn64k, IN PCONTROL_AREA ControlArea ) /*++ Routine Description: This routine creates a view in system space for the specified control area (file mapping). Arguments: SizeIn64k - Supplies the size of the view to be created. ControlArea - Supplies a pointer to the control area for this view. Return Value: Base address where the view was mapped, NULL if the view could not be mapped. Environment: Kernel Mode. --*/ { PVOID Base; ULONG_PTR Entry; ULONG Hash; ULONG i; ULONG AllocSize; PMMVIEW OldTable; ULONG StartBit; ULONG NewHashSize; POOL_TYPE PoolType; PAGED_CODE(); ASSERT (SizeIn64k < X64K); // // CODE IS ALREADY LOCKED BY CALLER. // LOCK_SYSTEM_VIEW_SPACE (Session); if (Session->SystemSpaceHashEntries + 8 > Session->SystemSpaceHashSize) { // // Less than 8 free slots, reallocate and rehash. // NewHashSize = Session->SystemSpaceHashSize << 1; AllocSize = sizeof(MMVIEW) * NewHashSize; OldTable = Session->SystemSpaceViewTable; // // The SystemSpaceViewTable for system (not session) space is only // allocated from nonpaged pool so it can be safely torn down during // clean shutdowns. Otherwise it could be allocated from paged pool // just like the session SystemSpaceViewTable. // if (Session == &MmSession) { PoolType = NonPagedPool; } else { PoolType = PagedPool; } Session->SystemSpaceViewTable = ExAllocatePoolWithTag (PoolType, AllocSize, ' mM'); if (Session->SystemSpaceViewTable == NULL) { Session->SystemSpaceViewTable = OldTable; } else { RtlZeroMemory (Session->SystemSpaceViewTable, AllocSize); Session->SystemSpaceHashSize = NewHashSize; Session->SystemSpaceHashKey = Session->SystemSpaceHashSize - 1; for (i = 0; i < (Session->SystemSpaceHashSize / 2); i += 1) { if (OldTable[i].Entry != 0) { Hash = (ULONG) ((OldTable[i].Entry >> 16) % Session->SystemSpaceHashKey); while (Session->SystemSpaceViewTable[Hash].Entry != 0) { Hash += 1; if (Hash >= Session->SystemSpaceHashSize) { Hash = 0; } } Session->SystemSpaceViewTable[Hash] = OldTable[i]; } } ExFreePool (OldTable); } } if (Session->SystemSpaceHashEntries == Session->SystemSpaceHashSize) { // // There are no free hash slots to place a new entry into even // though there may still be unused virtual address space. // UNLOCK_SYSTEM_VIEW_SPACE (Session); return NULL; } StartBit = RtlFindClearBitsAndSet (Session->SystemSpaceBitMap, SizeIn64k, 0); if (StartBit == NO_BITS_FOUND) { Session->BitmapFailures += 1; UNLOCK_SYSTEM_VIEW_SPACE (Session); return NULL; } Base = (PVOID)((PCHAR)Session->SystemSpaceViewStart + ((ULONG_PTR)StartBit * X64K)); Entry = (ULONG_PTR) MI_64K_ALIGN(Base) + SizeIn64k; Hash = (ULONG) ((Entry >> 16) % Session->SystemSpaceHashKey); while (Session->SystemSpaceViewTable[Hash].Entry != 0) { Hash += 1; if (Hash >= Session->SystemSpaceHashSize) { Hash = 0; } } Session->SystemSpaceHashEntries += 1; Session->SystemSpaceViewTable[Hash].Entry = Entry; Session->SystemSpaceViewTable[Hash].ControlArea = ControlArea; UNLOCK_SYSTEM_VIEW_SPACE (Session); return Base; } ULONG MiRemoveFromSystemSpace ( IN PMMSESSION Session, IN PVOID Base, OUT PCONTROL_AREA *ControlArea ) /*++ Routine Description: This routine looks up the specified view in the system space hash table and unmaps the view from system space and the table. Arguments: Session - Supplies the session data structure for this view. Base - Supplies the base address for the view. If this address is NOT found in the hash table, the system bugchecks. ControlArea - Returns the control area corresponding to the base address. Return Value: Size of the view divided by 64k. Environment: Kernel Mode, system view hash table locked. --*/ { ULONG_PTR Base16; ULONG Hash; ULONG Size; ULONG count; PAGED_CODE(); count = 0; Base16 = (ULONG_PTR)Base >> 16; Hash = (ULONG)(Base16 % Session->SystemSpaceHashKey); while ((Session->SystemSpaceViewTable[Hash].Entry >> 16) != Base16) { Hash += 1; if (Hash >= Session->SystemSpaceHashSize) { Hash = 0; count += 1; if (count == 2) { KeBugCheckEx (DRIVER_UNMAPPING_INVALID_VIEW, (ULONG_PTR)Base, 1, 0, 0); } } } Session->SystemSpaceHashEntries -= 1; Size = (ULONG) (Session->SystemSpaceViewTable[Hash].Entry & 0xFFFF); Session->SystemSpaceViewTable[Hash].Entry = 0; *ControlArea = Session->SystemSpaceViewTable[Hash].ControlArea; return Size; } LOGICAL MiInitializeSystemSpaceMap ( PVOID InputSession OPTIONAL ) /*++ Routine Description: This routine initializes the tables for mapping views into system space. Views are kept in a multiple of 64k bytes in a growable hashed table. Arguments: InputSession - Supplies NULL if this is the initial system session (non-Hydra), a valid session pointer (the pointer must be in global space, not session space) for Hydra session initialization. Return Value: TRUE on success, FALSE on failure. Environment: Kernel Mode, initialization. --*/ { SIZE_T AllocSize; SIZE_T Size; PCHAR ViewStart; PMMSESSION Session; POOL_TYPE PoolType; if (ARGUMENT_PRESENT (InputSession)) { Session = (PMMSESSION)InputSession; ViewStart = (PCHAR) MiSessionViewStart; Size = MmSessionViewSize; } else { Session = &MmSession; ViewStart = (PCHAR)MiSystemViewStart; Size = MmSystemViewSize; } // // We are passed a system global address for the address of the session. // Save a global pointer to the mutex below because multiple sessions will // generally give us a session-space (not a global space) pointer to the // MMSESSION in subsequent calls. We need the global pointer for the mutex // field for the kernel primitives to work properly. // Session->SystemSpaceViewLockPointer = &Session->SystemSpaceViewLock; KeInitializeGuardedMutex (Session->SystemSpaceViewLockPointer); // // If the kernel image has not been biased to allow for 3gb of user space, // then the system space view starts at the defined place. Otherwise, it // starts 16mb above the kernel image. // Session->SystemSpaceViewStart = ViewStart; MiCreateBitMap (&Session->SystemSpaceBitMap, Size / X64K, NonPagedPool); if (Session->SystemSpaceBitMap == NULL) { MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_NONPAGED_POOL); return FALSE; } RtlClearAllBits (Session->SystemSpaceBitMap); // // Build the view table. // Session->SystemSpaceHashSize = 31; Session->SystemSpaceHashKey = Session->SystemSpaceHashSize - 1; Session->SystemSpaceHashEntries = 0; AllocSize = sizeof(MMVIEW) * Session->SystemSpaceHashSize; ASSERT (AllocSize < PAGE_SIZE); // // The SystemSpaceViewTable for system (not session) space is only // allocated from nonpaged pool so it can be safely torn down during // clean shutdowns. Otherwise it could be allocated from paged pool // just like the session SystemSpaceViewTable. // if (Session == &MmSession) { PoolType = NonPagedPool; } else { PoolType = PagedPool; } Session->SystemSpaceViewTable = ExAllocatePoolWithTag (PoolType, AllocSize, ' mM'); if (Session->SystemSpaceViewTable == NULL) { MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_SESSION_PAGED_POOL); MiRemoveBitMap (&Session->SystemSpaceBitMap); return FALSE; } RtlZeroMemory (Session->SystemSpaceViewTable, AllocSize); return TRUE; } VOID MiFreeSessionSpaceMap ( VOID ) /*++ Routine Description: This routine frees the tables used for mapping session views. Arguments: None. Return Value: None. Environment: Kernel Mode. The caller must be in the correct session context. --*/ { PMMSESSION Session; PAGED_CODE(); Session = &MmSessionSpace->Session; // // Check for leaks of objects in the view table. // LOCK_SYSTEM_VIEW_SPACE (Session); if (Session->SystemSpaceViewTable && Session->SystemSpaceHashEntries) { KeBugCheckEx (SESSION_HAS_VALID_VIEWS_ON_EXIT, (ULONG_PTR)MmSessionSpace->SessionId, Session->SystemSpaceHashEntries, (ULONG_PTR)&Session->SystemSpaceViewTable[0], Session->SystemSpaceHashSize); #if 0 ULONG Index; for (Index = 0; Index < Session->SystemSpaceHashSize; Index += 1) { PMMVIEW Table; PVOID Base; Table = &Session->SystemSpaceViewTable[Index]; if (Table->Entry) { #if DBG DbgPrint ("MM: MiFreeSessionSpaceMap: view entry %d leak: ControlArea %p, Addr %p, Size %d\n", Index, Table->ControlArea, Table->Entry & ~0xFFFF, Table->Entry & 0x0000FFFF ); #endif Base = (PVOID)(Table->Entry & ~0xFFFF); // // MiUnmapViewInSystemSpace locks the ViewLock. // UNLOCK_SYSTEM_VIEW_SPACE(Session); MiUnmapViewInSystemSpace (Session, Base); LOCK_SYSTEM_VIEW_SPACE (Session); // // The view table may have been deleted while we let go of // the lock. // if (Session->SystemSpaceViewTable == NULL) { break; } } } #endif } UNLOCK_SYSTEM_VIEW_SPACE (Session); if (Session->SystemSpaceViewTable) { ExFreePool (Session->SystemSpaceViewTable); Session->SystemSpaceViewTable = NULL; } if (Session->SystemSpaceBitMap) { MiRemoveBitMap (&Session->SystemSpaceBitMap); } } HANDLE MmSecureVirtualMemory ( IN PVOID Address, IN SIZE_T Size, IN ULONG ProbeMode ) /*++ Routine Description: This routine probes the requested address range and protects the specified address range from having its protection made more restricted and being deleted. MmUnsecureVirtualMemory is used to allow the range to return to a normal state. Arguments: Address - Supplies the base address to probe and secure. Size - Supplies the size of the range to secure. ProbeMode - Supplies one of PAGE_READONLY or PAGE_READWRITE. Return Value: Returns a handle to be used to unsecure the range. If the range could not be locked because of protection problems or noncommitted memory, the value (HANDLE)0 is returned. Environment: Kernel Mode. --*/ { return MiSecureVirtualMemory (Address, Size, ProbeMode, FALSE); } HANDLE MiSecureVirtualMemory ( IN PVOID Address, IN SIZE_T Size, IN ULONG ProbeMode, IN LOGICAL AddressSpaceMutexHeld ) /*++ Routine Description: This routine probes the requested address range and protects the specified address range from having its protection made more restricted and being deleted. MmUnsecureVirtualMemory is used to allow the range to return to a normal state. Arguments: Address - Supplies the base address to probe and secure. Size - Supplies the size of the range to secure. ProbeMode - Supplies one of PAGE_READONLY or PAGE_READWRITE. AddressSpaceMutexHeld - Supplies TRUE if the mutex is already held, FALSE if not. Return Value: Returns a handle to be used to unsecure the range. If the range could not be locked because of protection problems or noncommitted memory, the value (HANDLE)0 is returned. Environment: Kernel Mode. --*/ { PETHREAD Thread; ULONG_PTR EndAddress; PVOID StartAddress; CHAR Temp; ULONG Probe; HANDLE Handle; PMMVAD Vad; PMMVAD VadParent; PMMVAD_LONG NewVad; PMMSECURE_ENTRY Secure; PEPROCESS Process; PMMPTE PointerPxe; PMMPTE PointerPpe; PMMPTE PointerPde; PMMPTE PointerPte; PMMPTE LastPte; ULONG Waited; #if defined(_WIN64) ULONG_PTR PageSize; #else #define PageSize PAGE_SIZE #endif PAGED_CODE(); if ((ULONG_PTR)Address + Size > (ULONG_PTR)MM_HIGHEST_USER_ADDRESS || (ULONG_PTR)Address + Size <= (ULONG_PTR)Address) { return NULL; } Handle = NULL; Probe = (ProbeMode == PAGE_READONLY); Thread = PsGetCurrentThread (); Process = PsGetCurrentProcessByThread (Thread); StartAddress = Address; if (AddressSpaceMutexHeld == FALSE) { LOCK_ADDRESS_SPACE (Process); } // // Check for a private committed VAD first instead of probing to avoid all // the page faults and zeroing. If we find one, then we run the PTEs // instead. // if (Size >= 64 * 1024) { EndAddress = (ULONG_PTR)StartAddress + Size - 1; Vad = MiLocateAddress (StartAddress); if (Vad == NULL) { goto Return1; } if ((Vad->u.VadFlags.UserPhysicalPages == 1) || (Vad->u.VadFlags.LargePages == 1)) { goto Return1; } if (Vad->u.VadFlags.MemCommit == 0) { goto LongWay; } if (Vad->u.VadFlags.PrivateMemory == 0) { goto LongWay; } if (Vad->u.VadFlags.PhysicalMapping == 1) { goto LongWay; } ASSERT (Vad->u.VadFlags.Protection); if ((MI_VA_TO_VPN (StartAddress) < Vad->StartingVpn) || (MI_VA_TO_VPN (EndAddress) > Vad->EndingVpn)) { goto Return1; } if (Vad->u.VadFlags.Protection == MM_NOACCESS) { goto LongWay; } if (ProbeMode == PAGE_READONLY) { if (Vad->u.VadFlags.Protection > MM_EXECUTE_WRITECOPY) { goto LongWay; } } else { if (Vad->u.VadFlags.Protection != MM_READWRITE && Vad->u.VadFlags.Protection != MM_EXECUTE_READWRITE) { goto LongWay; } } // // Check individual page permissions. // PointerPde = MiGetPdeAddress (StartAddress); PointerPpe = MiGetPteAddress (PointerPde); PointerPxe = MiGetPdeAddress (PointerPde); PointerPte = MiGetPteAddress (StartAddress); LastPte = MiGetPteAddress ((PVOID)EndAddress); LOCK_WS_UNSAFE (Process); do { while (MiDoesPxeExistAndMakeValid (PointerPxe, Process, MM_NOIRQL, &Waited) == FALSE) { // // Extended page directory parent entry is empty, go // to the next one. // PointerPxe += 1; PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe); PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe); PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); if (PointerPte > LastPte) { UNLOCK_WS_UNSAFE (Process); goto EditVad; } } #if (_MI_PAGING_LEVELS >= 4) Waited = 0; #endif while (MiDoesPpeExistAndMakeValid (PointerPpe, Process, MM_NOIRQL, &Waited) == FALSE) { // // Page directory parent entry is empty, go to the next one. // PointerPpe += 1; PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe); PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); if (PointerPte > LastPte) { UNLOCK_WS_UNSAFE (Process); goto EditVad; } #if (_MI_PAGING_LEVELS >= 4) if (MiIsPteOnPdeBoundary (PointerPpe)) { PointerPxe = MiGetPteAddress(PointerPpe); Waited = 1; goto restart; } #endif } #if (_MI_PAGING_LEVELS < 4) Waited = 0; #endif while (MiDoesPdeExistAndMakeValid (PointerPde, Process, MM_NOIRQL, &Waited) == FALSE) { // // This page directory entry is empty, go to the next one. // PointerPde += 1; PointerPpe = MiGetPteAddress (PointerPde); PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); if (PointerPte > LastPte) { UNLOCK_WS_UNSAFE (Process); goto EditVad; } #if (_MI_PAGING_LEVELS >= 3) if (MiIsPteOnPdeBoundary (PointerPde)) { PointerPxe = MiGetPteAddress(PointerPpe); Waited = 1; break; } #endif } #if (_MI_PAGING_LEVELS >= 4) restart: PointerPxe = PointerPxe; // satisfy the compiler #endif } while (Waited != 0); while (PointerPte <= LastPte) { if (MiIsPteOnPdeBoundary (PointerPte)) { PointerPde = MiGetPteAddress (PointerPte); PointerPpe = MiGetPteAddress (PointerPde); PointerPxe = MiGetPdeAddress (PointerPde); do { while (MiDoesPxeExistAndMakeValid (PointerPxe, Process, MM_NOIRQL, &Waited) == FALSE) { // // Page directory parent entry is empty, go to the next one. // PointerPxe += 1; PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe); PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe); PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); if (PointerPte > LastPte) { UNLOCK_WS_UNSAFE (Process); goto EditVad; } } #if (_MI_PAGING_LEVELS >= 4) Waited = 0; #endif while (MiDoesPpeExistAndMakeValid (PointerPpe, Process, MM_NOIRQL, &Waited) == FALSE) { // // Page directory parent entry is empty, go to the next one. // PointerPpe += 1; PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe); PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); if (PointerPte > LastPte) { UNLOCK_WS_UNSAFE (Process); goto EditVad; } #if (_MI_PAGING_LEVELS >= 4) if (MiIsPteOnPdeBoundary (PointerPpe)) { PointerPxe = MiGetPteAddress (PointerPpe); Waited = 1; goto restart2; } #endif } #if (_MI_PAGING_LEVELS < 4) Waited = 0; #endif while (MiDoesPdeExistAndMakeValid (PointerPde, Process, MM_NOIRQL, &Waited) == FALSE) { // // This page directory entry is empty, go to the next one. // PointerPde += 1; PointerPpe = MiGetPteAddress (PointerPde); PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); if (PointerPte > LastPte) { UNLOCK_WS_UNSAFE (Process); goto EditVad; } #if (_MI_PAGING_LEVELS >= 3) if (MiIsPteOnPdeBoundary (PointerPde)) { PointerPxe = MiGetPteAddress (PointerPpe); Waited = 1; break; } #endif } #if (_MI_PAGING_LEVELS >= 4) restart2: PointerPxe = PointerPxe; // satisfy the compiler #endif } while (Waited != 0); } if (PointerPte->u.Long) { UNLOCK_WS_UNSAFE (Process); goto LongWay; } PointerPte += 1; } UNLOCK_WS_UNSAFE (Process); } else { LongWay: // // Mark this thread as the address space mutex owner so it cannot // sneak its stack in as the argument region and trick us into // trying to grow it if the reference faults (as this would cause // a deadlock since this thread already owns the address space mutex). // Note this would have the side effect of not allowing this thread // to fault on guard pages in other data regions while the accesses // below are ongoing - but that could only happen in an APC and // those are blocked right now anyway. // ASSERT (KeAreAllApcsDisabled () == TRUE); ASSERT (Thread->AddressSpaceOwner == 0); Thread->AddressSpaceOwner = 1; #if defined(_WIN64) if (Process->Wow64Process != NULL) { PageSize = PAGE_SIZE_X86NT; } else { PageSize = PAGE_SIZE; } #endif try { if (ProbeMode == PAGE_READONLY) { EndAddress = (ULONG_PTR)Address + Size - 1; EndAddress = (EndAddress & ~(PageSize - 1)) + PageSize; do { Temp = *(volatile CHAR *)Address; Address = (PVOID)(((ULONG_PTR)Address & ~(PageSize - 1)) + PageSize); } while ((ULONG_PTR)Address != EndAddress); } else { ProbeForWrite (Address, Size, 1); } } except (EXCEPTION_EXECUTE_HANDLER) { ASSERT (KeAreAllApcsDisabled () == TRUE); ASSERT (Thread->AddressSpaceOwner == 1); Thread->AddressSpaceOwner = 0; goto Return1; } ASSERT (KeAreAllApcsDisabled () == TRUE); ASSERT (Thread->AddressSpaceOwner == 1); Thread->AddressSpaceOwner = 0; // // Locate VAD and add in secure descriptor. // EndAddress = (ULONG_PTR)StartAddress + Size - 1; Vad = MiLocateAddress (StartAddress); if (Vad == NULL) { goto Return1; } if ((Vad->u.VadFlags.UserPhysicalPages == 1) || (Vad->u.VadFlags.LargePages == 1)) { goto Return1; } if ((MI_VA_TO_VPN (StartAddress) < Vad->StartingVpn) || (MI_VA_TO_VPN (EndAddress) > Vad->EndingVpn)) { // // Not within the section virtual address descriptor, // return an error. // goto Return1; } } EditVad: // // If this is a short or regular VAD, it needs to be reallocated as // a large VAD. Note that a short VAD that was previously converted // to a long VAD here will still be marked as private memory, thus to // handle this case the NoChange bit must also be tested. // if (((Vad->u.VadFlags.PrivateMemory) && (Vad->u.VadFlags.NoChange == 0)) || (Vad->u2.VadFlags2.LongVad == 0)) { if (Vad->u.VadFlags.PrivateMemory == 0) { ASSERT (Vad->u2.VadFlags2.OneSecured == 0); ASSERT (Vad->u2.VadFlags2.MultipleSecured == 0); } NewVad = ExAllocatePoolWithTag (NonPagedPool, sizeof(MMVAD_LONG), 'ldaV'); if (NewVad == NULL) { goto Return1; } RtlZeroMemory (NewVad, sizeof(MMVAD_LONG)); if (Vad->u.VadFlags.PrivateMemory) { RtlCopyMemory (NewVad, Vad, sizeof(MMVAD_SHORT)); } else { RtlCopyMemory (NewVad, Vad, sizeof(MMVAD)); } NewVad->u.VadFlags.NoChange = 1; NewVad->u2.VadFlags2.OneSecured = 1; NewVad->u2.VadFlags2.LongVad = 1; NewVad->u2.VadFlags2.ReadOnly = Probe; NewVad->u3.Secured.StartVpn = (ULONG_PTR)StartAddress; NewVad->u3.Secured.EndVpn = EndAddress; // // Replace the current VAD with this expanded VAD. // LOCK_WS_UNSAFE (Process); VadParent = (PMMVAD) SANITIZE_PARENT_NODE (Vad->u1.Parent); ASSERT (VadParent != NULL); if (VadParent != Vad) { if (VadParent->RightChild == Vad) { VadParent->RightChild = (PMMVAD) NewVad; } else { ASSERT (VadParent->LeftChild == Vad); VadParent->LeftChild = (PMMVAD) NewVad; } } else { Process->VadRoot.BalancedRoot.RightChild = (PMMADDRESS_NODE) NewVad; } if (Vad->LeftChild) { Vad->LeftChild->u1.Parent = (PMMVAD) MI_MAKE_PARENT (NewVad, Vad->LeftChild->u1.Balance); } if (Vad->RightChild) { Vad->RightChild->u1.Parent = (PMMVAD) MI_MAKE_PARENT (NewVad, Vad->RightChild->u1.Balance); } if (Process->VadRoot.NodeHint == Vad) { Process->VadRoot.NodeHint = (PMMVAD) NewVad; } if (Process->VadFreeHint == Vad) { Process->VadFreeHint = (PMMVAD) NewVad; } if ((Vad->u.VadFlags.PhysicalMapping == 1) || (Vad->u.VadFlags.WriteWatch == 1)) { MiPhysicalViewAdjuster (Process, Vad, (PMMVAD) NewVad); } UNLOCK_WS_UNSAFE (Process); if (AddressSpaceMutexHeld == FALSE) { UNLOCK_ADDRESS_SPACE (Process); } ExFreePool (Vad); // // Or in the low bit to denote the secure entry is in the VAD. // Handle = (HANDLE)((ULONG_PTR)&NewVad->u2.LongFlags2 | 0x1); return Handle; } // // This is already a large VAD, add the secure entry. // ASSERT (Vad->u2.VadFlags2.LongVad == 1); if (Vad->u2.VadFlags2.OneSecured) { // // This VAD already is secured. Move the info out of the // block into pool. // Secure = ExAllocatePoolWithTag (NonPagedPool, sizeof (MMSECURE_ENTRY), 'eSmM'); if (Secure == NULL) { goto Return1; } ASSERT (Vad->u.VadFlags.NoChange == 1); Vad->u2.VadFlags2.OneSecured = 0; Vad->u2.VadFlags2.MultipleSecured = 1; Secure->u2.LongFlags2 = (ULONG) Vad->u.LongFlags; Secure->StartVpn = ((PMMVAD_LONG) Vad)->u3.Secured.StartVpn; Secure->EndVpn = ((PMMVAD_LONG) Vad)->u3.Secured.EndVpn; InitializeListHead (&((PMMVAD_LONG)Vad)->u3.List); InsertTailList (&((PMMVAD_LONG)Vad)->u3.List, &Secure->List); } if (Vad->u2.VadFlags2.MultipleSecured) { // // This VAD already has a secured element in its list, allocate and // add in the new secured element. // Secure = ExAllocatePoolWithTag (NonPagedPool, sizeof (MMSECURE_ENTRY), 'eSmM'); if (Secure == NULL) { goto Return1; } Secure->u2.LongFlags2 = 0; Secure->u2.VadFlags2.ReadOnly = Probe; Secure->StartVpn = (ULONG_PTR)StartAddress; Secure->EndVpn = EndAddress; InsertTailList (&((PMMVAD_LONG)Vad)->u3.List, &Secure->List); Handle = (HANDLE)Secure; } else { // // This list does not have a secure element. Put it in the VAD. // The VAD may be either a regular VAD or a long VAD (it cannot be // a short VAD) at this point. If it is a regular VAD, it must be // reallocated as a long VAD before any operation can proceed so // the secured range can be inserted. // Vad->u.VadFlags.NoChange = 1; Vad->u2.VadFlags2.OneSecured = 1; Vad->u2.VadFlags2.ReadOnly = Probe; ((PMMVAD_LONG)Vad)->u3.Secured.StartVpn = (ULONG_PTR)StartAddress; ((PMMVAD_LONG)Vad)->u3.Secured.EndVpn = EndAddress; // // Or in the low bit to denote the secure entry is in the VAD. // Handle = (HANDLE)((ULONG_PTR)&Vad->u2.LongFlags2 | 0x1); } Return1: if (AddressSpaceMutexHeld == FALSE) { UNLOCK_ADDRESS_SPACE (Process); } return Handle; } VOID MmUnsecureVirtualMemory ( IN HANDLE SecureHandle ) /*++ Routine Description: This routine unsecures memory previously secured via a call to MmSecureVirtualMemory. Arguments: SecureHandle - Supplies the handle returned in MmSecureVirtualMemory. Return Value: None. Environment: Kernel Mode. --*/ { MiUnsecureVirtualMemory (SecureHandle, FALSE); } VOID MiUnsecureVirtualMemory ( IN HANDLE SecureHandle, IN LOGICAL AddressSpaceMutexHeld ) /*++ Routine Description: This routine unsecures memory previously secured via a call to MmSecureVirtualMemory. Arguments: SecureHandle - Supplies the handle returned in MmSecureVirtualMemory. AddressSpaceMutexHeld - Supplies TRUE if the mutex is already held, FALSE if not. Return Value: None. Environment: Kernel Mode. --*/ { PMMSECURE_ENTRY Secure; PEPROCESS Process; PMMVAD_LONG Vad; PAGED_CODE(); Secure = (PMMSECURE_ENTRY)SecureHandle; Process = PsGetCurrentProcess (); if (AddressSpaceMutexHeld == FALSE) { LOCK_ADDRESS_SPACE (Process); } if ((ULONG_PTR)Secure & 0x1) { Secure = (PMMSECURE_ENTRY) ((ULONG_PTR)Secure & ~0x1); Vad = CONTAINING_RECORD (Secure, MMVAD_LONG, u2.LongFlags2); } else { Vad = (PMMVAD_LONG) MiLocateAddress ((PVOID)Secure->StartVpn); } ASSERT (Vad); ASSERT (Vad->u.VadFlags.NoChange == 1); ASSERT (Vad->u2.VadFlags2.LongVad == 1); if (Vad->u2.VadFlags2.OneSecured) { ASSERT (Secure == (PMMSECURE_ENTRY)&Vad->u2.LongFlags2); Vad->u2.VadFlags2.OneSecured = 0; ASSERT (Vad->u2.VadFlags2.MultipleSecured == 0); if (Vad->u2.VadFlags2.SecNoChange == 0) { // // No more secure entries in this list, remove the state. // Vad->u.VadFlags.NoChange = 0; } if (AddressSpaceMutexHeld == FALSE) { UNLOCK_ADDRESS_SPACE (Process); } } else { ASSERT (Vad->u2.VadFlags2.MultipleSecured == 1); if (Secure == (PMMSECURE_ENTRY)&Vad->u2.LongFlags2) { // // This was a single block that got converted into a list. // Reset the entry. // Secure = CONTAINING_RECORD (Vad->u3.List.Flink, MMSECURE_ENTRY, List); } RemoveEntryList (&Secure->List); if (IsListEmpty (&Vad->u3.List)) { // // No more secure entries, reset the state. // Vad->u2.VadFlags2.MultipleSecured = 0; if ((Vad->u2.VadFlags2.SecNoChange == 0) && (Vad->u.VadFlags.PrivateMemory == 0)) { // // No more secure entries in this list, remove the state // if and only if this VAD is not private. If this VAD // is private, removing the state NoChange flag indicates // that this is a short VAD which it no longer is. // Vad->u.VadFlags.NoChange = 0; } } if (AddressSpaceMutexHeld == FALSE) { UNLOCK_ADDRESS_SPACE (Process); } ExFreePool (Secure); } return; } #if DBG VOID MiDumpConflictingVad ( IN PVOID StartingAddress, IN PVOID EndingAddress, IN PMMVAD Vad ) { if (NtGlobalFlag & FLG_SHOW_LDR_SNAPS) { DbgPrint( "MM: [%p ... %p) conflicted with Vad %p\n", StartingAddress, EndingAddress, Vad); if ((Vad->u.VadFlags.PrivateMemory == 1) || (Vad->ControlArea == NULL)) { return; } if (Vad->ControlArea->u.Flags.Image) DbgPrint( " conflict with %Z image at [%p .. %p)\n", &Vad->ControlArea->FilePointer->FileName, MI_VPN_TO_VA (Vad->StartingVpn), MI_VPN_TO_VA_ENDING (Vad->EndingVpn) ); else if (Vad->ControlArea->u.Flags.File) DbgPrint( " conflict with %Z file at [%p .. %p)\n", &Vad->ControlArea->FilePointer->FileName, MI_VPN_TO_VA (Vad->StartingVpn), MI_VPN_TO_VA_ENDING (Vad->EndingVpn) ); else DbgPrint( " conflict with section at [%p .. %p)\n", MI_VPN_TO_VA (Vad->StartingVpn), MI_VPN_TO_VA_ENDING (Vad->EndingVpn) ); } } #endif