/*++ Copyright (c) 1991 Microsoft Corporation Module Name: sysload.c Abstract: This module contains the code to load DLLs into the system portion of the address space and calls the DLL at its initialization entry point. Author: Lou Perazzoli 21-May-1991 Landy Wang 02-June-1997 Revision History: --*/ #include "mi.h" #include "hotpatch.h" KMUTANT MmSystemLoadLock; LONG MmTotalSystemDriverPages; ULONG MmDriverCommit; LONG MiFirstDriverLoadEver = 0; // // This key is set to TRUE to make more memory below 16mb available for drivers. // It can be cleared via the registry. // LOGICAL MmMakeLowMemory = TRUE; // // Enabled via the registry to identify drivers which unload without releasing // resources or still have active timers, etc. // PUNLOADED_DRIVERS MmUnloadedDrivers; ULONG MmLastUnloadedDriver; ULONG MiTotalUnloads; ULONG MiUnloadsSkipped; // // This can be set by the registry. // ULONG MmEnforceWriteProtection = 1; // // Referenced by ke\bugcheck.c. // PVOID ExPoolCodeStart; PVOID ExPoolCodeEnd; PVOID MmPoolCodeStart; PVOID MmPoolCodeEnd; PVOID MmPteCodeStart; PVOID MmPteCodeEnd; extern LONG MiSessionLeaderExists; PVOID MiCacheImageSymbols ( IN PVOID ImageBase ); NTSTATUS MiResolveImageReferences ( PVOID ImageBase, IN PUNICODE_STRING ImageFileDirectory, IN PUNICODE_STRING NamePrefix OPTIONAL, OUT PCHAR *MissingProcedureName, OUT PWSTR *MissingDriverName, OUT PLOAD_IMPORTS *LoadedImports ); NTSTATUS MiSnapThunk ( IN PVOID DllBase, IN PVOID ImageBase, IN PIMAGE_THUNK_DATA NameThunk, OUT PIMAGE_THUNK_DATA AddrThunk, IN PIMAGE_EXPORT_DIRECTORY ExportDirectory, IN ULONG ExportSize, IN LOGICAL SnapForwarder, OUT PCHAR *MissingProcedureName ); NTSTATUS MiLoadImageSection ( IN OUT PSECTION *InputSectionPointer, OUT PVOID *ImageBase, IN PUNICODE_STRING ImageFileName, IN ULONG LoadInSessionSpace, IN PKLDR_DATA_TABLE_ENTRY FoundDataTableEntry ); VOID MiEnablePagingOfDriver ( IN PVOID ImageHandle ); VOID MiSetPagingOfDriver ( IN PMMPTE PointerPte, IN PMMPTE LastPte ); PVOID MiLookupImageSectionByName ( IN PVOID Base, IN LOGICAL MappedAsImage, IN PCHAR SectionName, OUT PULONG SectionSize ); VOID MiClearImports ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ); NTSTATUS MiBuildImportsForBootDrivers ( VOID ); NTSTATUS MmCheckSystemImage ( IN HANDLE ImageFileHandle, IN LOGICAL PurgeSection ); LONG MiMapCacheExceptionFilter ( OUT PNTSTATUS Status, IN PEXCEPTION_POINTERS ExceptionPointer ); ULONG MiSetProtectionOnTransitionPte ( IN PMMPTE PointerPte, IN ULONG ProtectionMask ); NTSTATUS MiDereferenceImports ( IN PLOAD_IMPORTS ImportList ); LOGICAL MiCallDllUnloadAndUnloadDll ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ); PVOID MiLocateExportName ( IN PVOID DllBase, IN PCHAR FunctionName ); VOID MiRememberUnloadedDriver ( IN PUNICODE_STRING DriverName, IN PVOID Address, IN ULONG Length ); VOID MiWriteProtectSystemImage ( IN PVOID DllBase ); VOID MiLocateKernelSections ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ); VOID MiCaptureImageExceptionValues ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ); VOID MiUpdateThunks ( IN PLOADER_PARAMETER_BLOCK LoaderBlock, IN PVOID OldAddress, IN PVOID NewAddress, IN ULONG NumberOfBytes ); PVOID MiFindExportedRoutineByName ( IN PVOID DllBase, IN PANSI_STRING AnsiImageRoutineName ); LOGICAL MiChargeResidentAvailable ( IN PFN_NUMBER NumberOfPages, IN ULONG Id ); LOGICAL MiUseLargeDriverPage ( IN ULONG NumberOfPtes, IN OUT PVOID *ImageBaseAddress, IN PUNICODE_STRING PrefixedImageName, IN ULONG Pass ); VOID MiRundownHotpatchList ( PRTL_PATCH_HEADER PatchHead ); VOID MiSessionProcessGlobalSubsections ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,MmCheckSystemImage) #pragma alloc_text(PAGE,MmLoadSystemImage) #pragma alloc_text(PAGE,MiResolveImageReferences) #pragma alloc_text(PAGE,MiSnapThunk) #pragma alloc_text(PAGE,MiEnablePagingOfDriver) #pragma alloc_text(PAGE,MmPageEntireDriver) #pragma alloc_text(PAGE,MiDereferenceImports) #pragma alloc_text(PAGE,MiCallDllUnloadAndUnloadDll) #pragma alloc_text(PAGE,MiLocateExportName) #pragma alloc_text(PAGE,MiClearImports) #pragma alloc_text(PAGE,MmGetSystemRoutineAddress) #pragma alloc_text(PAGE,MiFindExportedRoutineByName) #pragma alloc_text(PAGE,MmCallDllInitialize) #pragma alloc_text(PAGE,MmResetDriverPaging) #pragma alloc_text(PAGE,MmUnloadSystemImage) #pragma alloc_text(PAGE,MiLoadImageSection) #pragma alloc_text(PAGE,MiRememberUnloadedDriver) #pragma alloc_text(PAGE,MiUseLargeDriverPage) #pragma alloc_text(PAGE,MiMakeEntireImageCopyOnWrite) #pragma alloc_text(PAGE,MiWriteProtectSystemImage) #pragma alloc_text(PAGE,MiSessionProcessGlobalSubsections) #pragma alloc_text(PAGE,MiCaptureImageExceptionValues) #pragma alloc_text(INIT,MiBuildImportsForBootDrivers) #pragma alloc_text(INIT,MiReloadBootLoadedDrivers) #pragma alloc_text(INIT,MiUpdateThunks) #pragma alloc_text(INIT,MiInitializeLoadedModuleList) #pragma alloc_text(INIT,MiLocateKernelSections) #if !defined(NT_UP) #pragma alloc_text(PAGE,MmVerifyImageIsOkForMpUse) #endif #endif CHAR MiPteStr[] = "\0"; VOID MiProcessLoaderEntry ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry, IN LOGICAL Insert ) /*++ Routine Description: This function is a nonpaged wrapper which acquires the PsLoadedModuleList lock to insert a new entry. Arguments: DataTableEntry - Supplies the loaded module list entry to insert/remove. Insert - Supplies TRUE if the entry should be inserted, FALSE if the entry should be removed. Return Value: None. Environment: Kernel mode. Normal APCs disabled (critical region held). --*/ { KIRQL OldIrql; ExAcquireResourceExclusiveLite (&PsLoadedModuleResource, TRUE); ExAcquireSpinLock (&PsLoadedModuleSpinLock, &OldIrql); if (Insert == TRUE) { InsertTailList (&PsLoadedModuleList, &DataTableEntry->InLoadOrderLinks); #if defined (_WIN64) RtlInsertInvertedFunctionTable (&PsInvertedFunctionTable, DataTableEntry->DllBase, DataTableEntry->SizeOfImage); #endif } else { #if defined (_WIN64) RtlRemoveInvertedFunctionTable (&PsInvertedFunctionTable, DataTableEntry->DllBase); #endif RemoveEntryList (&DataTableEntry->InLoadOrderLinks); } ExReleaseSpinLock (&PsLoadedModuleSpinLock, OldIrql); ExReleaseResourceLite (&PsLoadedModuleResource); } typedef struct _MI_LARGE_PAGE_DRIVER_ENTRY { LIST_ENTRY Links; UNICODE_STRING BaseName; } MI_LARGE_PAGE_DRIVER_ENTRY, *PMI_LARGE_PAGE_DRIVER_ENTRY; LIST_ENTRY MiLargePageDriverList; ULONG MiLargePageAllDrivers; VOID MiInitializeDriverLargePageList ( VOID ) /*++ Routine Description: Parse the registry settings and set up the list of driver names that we'll try to load in large pages. Arguments: None. Return Value: None. Environment: Kernel mode, Phase 0 Initialization. Nonpaged pool exists but not paged pool. The PsLoadedModuleList has not been set up yet AND the boot drivers have NOT been relocated to their final resting places. --*/ { PWCHAR Start; PWCHAR End; PWCHAR Walk; ULONG NameLength; PMI_LARGE_PAGE_DRIVER_ENTRY Entry; InitializeListHead (&MiLargePageDriverList); if (MmLargePageDriverBufferLength == (ULONG)-1) { return; } Start = MmLargePageDriverBuffer; End = MmLargePageDriverBuffer + (MmLargePageDriverBufferLength - sizeof(WCHAR)) / sizeof(WCHAR); while (Start < End) { if (UNICODE_WHITESPACE(*Start)) { Start += 1; continue; } if (*Start == (WCHAR)'*') { MiLargePageAllDrivers = 1; break; } for (Walk = Start; Walk < End; Walk += 1) { if (UNICODE_WHITESPACE(*Walk)) { break; } } // // Got a string - add it to our list. // NameLength = (ULONG)(Walk - Start) * sizeof (WCHAR); Entry = ExAllocatePoolWithTag (NonPagedPool, sizeof (MI_LARGE_PAGE_DRIVER_ENTRY), 'pLmM'); if (Entry == NULL) { break; } Entry->BaseName.Buffer = Start; Entry->BaseName.Length = (USHORT) NameLength; Entry->BaseName.MaximumLength = (USHORT) NameLength; InsertTailList (&MiLargePageDriverList, &Entry->Links); Start = Walk + 1; } return; } LOGICAL MiUseLargeDriverPage ( IN ULONG NumberOfPtes, IN OUT PVOID *ImageBaseAddress, IN PUNICODE_STRING BaseImageName, IN ULONG Pass ) /*++ Routine Description: This routine checks whether the specified image should be loaded into a large page address space, and if so, tries to load it. Arguments: NumberOfPtes - Supplies the number of PTEs to map for the image. ImageBaseAddress - Supplies the current address the image header is at, and returns the (new) address for the image header. BaseImageName - Supplies the base path name of the image to load. Pass - Supplies 0 when called from Phase for the boot drivers, 1 otherwise. Return Value: TRUE if large pages were used, FALSE if not. --*/ { PFN_NUMBER PagesRequired; PFN_NUMBER ResidentPages; PLIST_ENTRY NextEntry; PVOID SmallVa; PVOID LargeVa; PVOID LargeBaseVa; LOGICAL UseLargePages; PFN_NUMBER PageFrameIndex; PFN_NUMBER NumberOfPages; MMPTE PteContents; PMMPTE SmallPte; PMMPTE LastSmallPte; PMI_LARGE_PAGE_DRIVER_ENTRY LargePageDriverEntry; #ifdef _X86_ ULONG ProcessorFeatures; #endif ASSERT (KeGetCurrentIrql () <= APC_LEVEL); ASSERT (*ImageBaseAddress >= MmSystemRangeStart); #ifdef _X86_ if ((KeFeatureBits & KF_LARGE_PAGE) == 0) { return FALSE; } // // Capture cr4 to see if large page support has been enabled in the chip // yet (late in Phase 1). Large page PDEs cannot be used until then. // // mov eax, cr4 // _asm { _emit 00fh _emit 020h _emit 0e0h mov ProcessorFeatures, eax } if ((ProcessorFeatures & CR4_PSE) == 0) { return FALSE; } #endif // // Check the number of free system PTEs left to prevent a runaway registry // key from exhausting all the system PTEs. // if (MmTotalFreeSystemPtes[SystemPteSpace] < 16 * (MM_MINIMUM_VA_FOR_LARGE_PAGE >> PAGE_SHIFT)) { return FALSE; } if (MiLargePageAllDrivers == 0) { UseLargePages = FALSE; // // Check to see if this name exists in the large page image list. // NextEntry = MiLargePageDriverList.Flink; while (NextEntry != &MiLargePageDriverList) { LargePageDriverEntry = CONTAINING_RECORD (NextEntry, MI_LARGE_PAGE_DRIVER_ENTRY, Links); if (RtlEqualUnicodeString (BaseImageName, &LargePageDriverEntry->BaseName, TRUE)) { UseLargePages = TRUE; break; } NextEntry = NextEntry->Flink; } if (UseLargePages == FALSE) { return FALSE; } } // // First try to get physically contiguous memory for this driver. // Note we must allocate the entire large page here even though we will // almost always only use a portion of it. This is to ensure no other // frames in it can be mapped with a different cache attribute. After // updating the cache attribute lists, we'll immediately free the excess. // Note that the driver's INIT section will be subsequently freed // and clearly the cache attribute lists must be correct to support that // as well. // // Don't take memory below 16MB as we want to leave that available for // ISA drivers supporting older hardware that may require it. // NumberOfPages = (PFN_NUMBER) MI_ROUND_TO_SIZE ( NumberOfPtes, MM_MINIMUM_VA_FOR_LARGE_PAGE >> PAGE_SHIFT); PageFrameIndex = MiFindContiguousPages ((16 * 1024 * 1024) >> PAGE_SHIFT, MmHighestPossiblePhysicalPage, MM_MINIMUM_VA_FOR_LARGE_PAGE >> PAGE_SHIFT, NumberOfPages, MmCached); // // If a contiguous range is not available then large pages cannot be used // for this driver at this time. // if (PageFrameIndex == 0) { return FALSE; } // // Add the contiguous range to the must-be-cached list so that the excess // memory (and INIT section) can be safely freed to the page lists. // if (MiAddCachedRange (PageFrameIndex, PageFrameIndex + NumberOfPages - 1) == FALSE) { MiFreeContiguousPages (PageFrameIndex, NumberOfPages); return FALSE; } // // Try to get large virtual address space for this driver. // LargeVa = MiMapWithLargePages (PageFrameIndex, NumberOfPages, MM_EXECUTE_READWRITE, MmCached); if (LargeVa == NULL) { MiRemoveCachedRange (PageFrameIndex, PageFrameIndex + NumberOfPages - 1); MiFreeContiguousPages (PageFrameIndex, NumberOfPages); return FALSE; } LargeBaseVa = LargeVa; // // Copy the driver a page at a time as in rare cases, it may have holes. // SmallPte = MiGetPteAddress (*ImageBaseAddress); LastSmallPte = SmallPte + NumberOfPtes; SmallVa = MiGetVirtualAddressMappedByPte (SmallPte); while (SmallPte < LastSmallPte) { PteContents = *SmallPte; if (PteContents.u.Hard.Valid == 1) { RtlCopyMemory (LargeVa, SmallVa, PAGE_SIZE); } else { // // Retain this page in the large page mapping to simplify unload - // ie: it can always free a single contiguous range. // } SmallPte += 1; LargeVa = (PVOID) ((PCHAR)LargeVa + PAGE_SIZE); SmallVa = (PVOID) ((PCHAR)SmallVa + PAGE_SIZE); } // // Inform our caller of the new (large page) address so the loader data // table entry gets created with it & fixups done accordingly, etc. // *ImageBaseAddress = LargeBaseVa; if (Pass != 0) { // // The system is fully booted so get rid of the original mapping now. // Otherwise, we're in Phase 0, so the caller gets rid of the original // mapping. // SmallPte -= NumberOfPtes; PagesRequired = MiDeleteSystemPagableVm (SmallPte, NumberOfPtes, ZeroKernelPte, FALSE, &ResidentPages); // // Non boot-loaded drivers have system PTEs and commit charged. // MiReleaseSystemPtes (SmallPte, (ULONG)NumberOfPtes, SystemPteSpace); InterlockedExchangeAdd (&MmTotalSystemDriverPages, 0 - (ULONG)(PagesRequired - ResidentPages)); MI_INCREMENT_RESIDENT_AVAILABLE (ResidentPages, MM_RESAVAIL_FREE_UNLOAD_SYSTEM_IMAGE1); MiReturnCommitment (PagesRequired); MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_DRIVER_UNLOAD1, PagesRequired); } // // Free the unused trailing portion (and their resident available charge) // of the large page mapping. // MiFreeContiguousPages (PageFrameIndex + NumberOfPtes, NumberOfPages - NumberOfPtes); return TRUE; } VOID MiCaptureImageExceptionValues ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ) /*++ Routine Description: This function stores the exception table information from the image in the loader data table entry. Arguments: DataTableEntry - Supplies the kernel's data table entry. Return Value: None. Environment: Kernel mode, APC_LEVEL or below, arbitrary process context. --*/ { PVOID CurrentBase; PIMAGE_NT_HEADERS NtHeader; CurrentBase = (PVOID) DataTableEntry->DllBase; NtHeader = RtlImageNtHeader (CurrentBase); #if defined(_X86_) if (NtHeader->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NO_SEH) { DataTableEntry->ExceptionTable = (PCHAR)LongToPtr(-1); DataTableEntry->ExceptionTableSize = (ULONG)-1; } else { PIMAGE_LOAD_CONFIG_DIRECTORY32 LoadConfig; ULONG LoadConfigSize; if (IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG < NtHeader->OptionalHeader.NumberOfRvaAndSizes) { LoadConfig = (PIMAGE_LOAD_CONFIG_DIRECTORY32)((PCHAR)CurrentBase + NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress); LoadConfigSize = NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size; if (LoadConfig && LoadConfigSize && LoadConfig->Size >= RTL_SIZEOF_THROUGH_FIELD(IMAGE_LOAD_CONFIG_DIRECTORY32, SEHandlerCount) && LoadConfig->SEHandlerTable && LoadConfig->SEHandlerCount ) { DataTableEntry->ExceptionTable = (PVOID)LoadConfig->SEHandlerTable; DataTableEntry->ExceptionTableSize = LoadConfig->SEHandlerCount; } else { DataTableEntry->ExceptionTable = 0; DataTableEntry->ExceptionTableSize = 0; } } } #else #if defined(_IA64_) if (IMAGE_DIRECTORY_ENTRY_GLOBALPTR < NtHeader->OptionalHeader.NumberOfRvaAndSizes) { DataTableEntry->GpValue = (PCHAR)CurrentBase + NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_GLOBALPTR].VirtualAddress; } #endif if (IMAGE_DIRECTORY_ENTRY_EXCEPTION < NtHeader->OptionalHeader.NumberOfRvaAndSizes) { DataTableEntry->ExceptionTable = (PCHAR)CurrentBase + NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress; DataTableEntry->ExceptionTableSize = NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size; } #endif } NTSTATUS MmLoadSystemImage ( IN PUNICODE_STRING ImageFileName, IN PUNICODE_STRING NamePrefix OPTIONAL, IN PUNICODE_STRING LoadedBaseName OPTIONAL, IN ULONG LoadFlags, OUT PVOID *ImageHandle, OUT PVOID *ImageBaseAddress ) /*++ Routine Description: This routine reads the image pages from the specified section into the system and returns the address of the DLL's header. At successful completion, the Section is referenced so it remains until the system image is unloaded. Arguments: ImageFileName - Supplies the full path name (including the image name) of the image to load. NamePrefix - If present, supplies the prefix to use with the image name on load operations. This is used to load the same image multiple times, by using different prefixes. LoadedBaseName - If present, supplies the base name to use on the loaded image instead of the base name found on the image name. LoadFlags - Supplies a combination of bit flags as follows: MM_LOAD_IMAGE_IN_SESSION : - Supplies whether to load this image in session space. Each session gets a different copy of this driver with pages shared as much as possible via copy on write. MM_LOAD_IMAGE_AND_LOCKDOWN : - Supplies TRUE if the image pages should be made nonpagable. ImageHandle - Returns an opaque pointer to the referenced section object of the image that was loaded. ImageBaseAddress - Returns the image base within the system. Return Value: Status of the load operation. Environment: Kernel mode, APC_LEVEL or below, arbitrary process context. --*/ { LONG OldValue; ULONG i; ULONG DebugInfoSize; PIMAGE_DATA_DIRECTORY DataDirectory; PIMAGE_DEBUG_DIRECTORY DebugDir; PNON_PAGED_DEBUG_INFO ssHeader; PMMPTE PointerPte; PSUBSECTION Subsection; PCONTROL_AREA ControlArea; SIZE_T DataTableEntrySize; PWSTR BaseDllNameBuffer; PKLDR_DATA_TABLE_ENTRY DataTableEntry; KLDR_DATA_TABLE_ENTRY TempDataTableEntry; PKLDR_DATA_TABLE_ENTRY FoundDataTableEntry; NTSTATUS Status; PSECTION SectionPointer; PIMAGE_NT_HEADERS NtHeaders; UNICODE_STRING PrefixedImageName; UNICODE_STRING BaseName; UNICODE_STRING BaseDirectory; OBJECT_ATTRIBUTES ObjectAttributes; HANDLE FileHandle; HANDLE SectionHandle; IO_STATUS_BLOCK IoStatus; PCHAR NameBuffer; PLIST_ENTRY NextEntry; ULONG NumberOfPtes; PCHAR MissingProcedureName; PWSTR MissingDriverName; PWSTR PrintableMissingDriverName; PLOAD_IMPORTS LoadedImports; LOGICAL AlreadyOpen; LOGICAL IssueUnloadOnFailure; LOGICAL LoadLockOwned; ULONG SectionAccess; PKTHREAD CurrentThread; PAGED_CODE(); if (LoadFlags & MM_LOAD_IMAGE_IN_SESSION) { ASSERT (NamePrefix == NULL); ASSERT (LoadedBaseName == NULL); if ((PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION) == 0) { return STATUS_NO_MEMORY; } } LoadLockOwned = FALSE; LoadedImports = (PLOAD_IMPORTS) NO_IMPORTS_USED; SectionPointer = NULL; FileHandle = (HANDLE)0; MissingProcedureName = NULL; MissingDriverName = NULL; IssueUnloadOnFailure = FALSE; FoundDataTableEntry = NULL; NameBuffer = ExAllocatePoolWithTag (NonPagedPool, MAXIMUM_FILENAME_LENGTH, 'nLmM'); if (NameBuffer == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } // // Initializing these is not needed for correctness, but // without it the compiler cannot compile this code W4 to check // for use of uninitialized variables. // SATISFY_OVERZEALOUS_COMPILER (NumberOfPtes = (ULONG)-1); DataTableEntry = NULL; // // Get name roots. // if (ImageFileName->Buffer[0] == OBJ_NAME_PATH_SEPARATOR) { PWCHAR p; ULONG l; p = &ImageFileName->Buffer[ImageFileName->Length>>1]; while (*(p-1) != OBJ_NAME_PATH_SEPARATOR) { p--; } l = (ULONG)(&ImageFileName->Buffer[ImageFileName->Length>>1] - p); l *= sizeof(WCHAR); BaseName.Length = (USHORT)l; BaseName.Buffer = p; } else { BaseName.Length = ImageFileName->Length; BaseName.Buffer = ImageFileName->Buffer; } BaseName.MaximumLength = BaseName.Length; BaseDirectory = *ImageFileName; BaseDirectory.Length = (USHORT)(BaseDirectory.Length - BaseName.Length); BaseDirectory.MaximumLength = BaseDirectory.Length; PrefixedImageName = *ImageFileName; // // If there's a name prefix, add it to the PrefixedImageName. // if (NamePrefix) { PrefixedImageName.MaximumLength = (USHORT)(BaseDirectory.Length + NamePrefix->Length + BaseName.Length); PrefixedImageName.Buffer = ExAllocatePoolWithTag ( NonPagedPool, PrefixedImageName.MaximumLength, 'dLmM'); if (!PrefixedImageName.Buffer) { ExFreePool (NameBuffer); return STATUS_INSUFFICIENT_RESOURCES; } PrefixedImageName.Length = 0; RtlAppendUnicodeStringToString(&PrefixedImageName, &BaseDirectory); RtlAppendUnicodeStringToString(&PrefixedImageName, NamePrefix); RtlAppendUnicodeStringToString(&PrefixedImageName, &BaseName); // // Alter the basename to match. // BaseName.Buffer = PrefixedImageName.Buffer + BaseDirectory.Length / sizeof(WCHAR); BaseName.Length = (USHORT)(BaseName.Length + NamePrefix->Length); BaseName.MaximumLength = (USHORT)(BaseName.MaximumLength + NamePrefix->Length); } // // If there's a loaded base name, use it instead of the base name. // if (LoadedBaseName) { BaseName = *LoadedBaseName; } #if DBG if (NtGlobalFlag & FLG_SHOW_LDR_SNAPS) { DbgPrint ("MM:SYSLDR Loading %wZ (%wZ) %s\n", &PrefixedImageName, &BaseName, (LoadFlags & MM_LOAD_IMAGE_IN_SESSION) ? "in session space" : " "); } #endif AlreadyOpen = FALSE; ReCheckLoaderList: // // Arbitrary process context so prevent suspend APCs now. // ASSERT (LoadLockOwned == FALSE); LoadLockOwned = TRUE; CurrentThread = KeGetCurrentThread (); KeEnterCriticalRegionThread (CurrentThread); KeWaitForSingleObject (&MmSystemLoadLock, WrVirtualMemory, KernelMode, FALSE, (PLARGE_INTEGER)NULL); // // Check to see if this name already exists in the loader database. // NextEntry = PsLoadedModuleList.Flink; while (NextEntry != &PsLoadedModuleList) { DataTableEntry = CONTAINING_RECORD (NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (RtlEqualUnicodeString (&PrefixedImageName, &DataTableEntry->FullDllName, TRUE)) { break; } NextEntry = NextEntry->Flink; } if (NextEntry != &PsLoadedModuleList) { // // Found a match in the loaded module list. See if it's acceptable. // // If this thread already loaded the image below and upon rechecking // finds some other thread did so also, then get rid of our object // now and use the other thread's inserted entry instead. // if (SectionPointer != NULL) { ObDereferenceObject (SectionPointer); SectionPointer = NULL; } if ((LoadFlags & MM_LOAD_IMAGE_IN_SESSION) == 0) { if (MI_IS_SESSION_ADDRESS (DataTableEntry->DllBase) == TRUE) { // // The caller is trying to load a driver in systemwide space // that has already been loaded in session space. This is // not allowed. // Status = STATUS_CONFLICTING_ADDRESSES; } else { *ImageHandle = DataTableEntry; *ImageBaseAddress = DataTableEntry->DllBase; Status = STATUS_IMAGE_ALREADY_LOADED; } goto return2; } if (MI_IS_SESSION_ADDRESS (DataTableEntry->DllBase) == FALSE) { // // The caller is trying to load a driver in session space // that has already been loaded in system space. This is // not allowed. // Status = STATUS_CONFLICTING_ADDRESSES; goto return2; } AlreadyOpen = TRUE; // // This image has already been loaded systemwide. If it's // already been loaded in this session space as well, just // bump the reference count using the already allocated // address. Otherwise, insert it into this session space. // Status = MiSessionInsertImage (DataTableEntry->DllBase); if (!NT_SUCCESS (Status)) { if (Status == STATUS_ALREADY_COMMITTED) { // // This driver's already been loaded in this session. // ASSERT (DataTableEntry->LoadCount >= 1); *ImageHandle = DataTableEntry; *ImageBaseAddress = DataTableEntry->DllBase; Status = STATUS_SUCCESS; } // // The LoadCount should generally not be 0 here, but it is // possible in the case where an attempt has been made to // unload a DLL on last dereference, but the DLL refused to // unload. // goto return2; } // // This driver is already loaded in the system, but not in // this particular session - share it now. // FoundDataTableEntry = DataTableEntry; DataTableEntry->LoadCount += 1; ASSERT (DataTableEntry->SectionPointer != NULL); SectionPointer = DataTableEntry->SectionPointer; } else if (SectionPointer == NULL) { // // This image is not already loaded. // // A NULL SectionPointer indicates this thread didn't already load // this image below either, so go and get it. // // Release the load lock first as getting the image is not cheap. // KeReleaseMutant (&MmSystemLoadLock, 1, FALSE, FALSE); KeLeaveCriticalRegionThread (CurrentThread); LoadLockOwned = FALSE; InterlockedOr (&MiFirstDriverLoadEver, 0x1); // // Check and see if a user wants to replace this binary // via a transfer through the kernel debugger. If this // fails just continue on with the existing file. // if ((KdDebuggerEnabled) && (KdDebuggerNotPresent == FALSE)) { Status = KdPullRemoteFile (ImageFileName, FILE_ATTRIBUTE_NORMAL, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT); if (NT_SUCCESS (Status)) { DbgPrint ("MmLoadSystemImage: Pulled %wZ from kd\n", ImageFileName); } } DataTableEntry = NULL; // // Attempt to open the driver image itself. If this fails, then the // driver image cannot be located, so nothing else matters. // InitializeObjectAttributes (&ObjectAttributes, ImageFileName, (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE), NULL, NULL); Status = ZwOpenFile (&FileHandle, FILE_EXECUTE, &ObjectAttributes, &IoStatus, FILE_SHARE_READ | FILE_SHARE_DELETE, 0); if (!NT_SUCCESS (Status)) { #if DBG if (NtGlobalFlag & FLG_SHOW_LDR_SNAPS) { DbgPrint ("MmLoadSystemImage: cannot open %wZ\n", ImageFileName); } #endif // // File not found. // goto return2; } Status = MmCheckSystemImage (FileHandle, FALSE); if ((Status == STATUS_IMAGE_CHECKSUM_MISMATCH) || (Status == STATUS_IMAGE_MP_UP_MISMATCH) || (Status == STATUS_INVALID_IMAGE_PROTECT)) { goto return1; } // // Now attempt to create an image section for the file. If this fails, // then the driver file is not an image. Session space drivers are // shared text with copy on write data, so don't allow writes here. // if (LoadFlags & MM_LOAD_IMAGE_IN_SESSION) { SectionAccess = SECTION_MAP_READ | SECTION_MAP_EXECUTE; } else { SectionAccess = SECTION_ALL_ACCESS; } InitializeObjectAttributes (&ObjectAttributes, NULL, (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE), NULL, NULL); Status = ZwCreateSection (&SectionHandle, SectionAccess, &ObjectAttributes, (PLARGE_INTEGER) NULL, PAGE_EXECUTE, SEC_IMAGE, FileHandle); if (!NT_SUCCESS(Status)) { goto return1; } // // Now reference the section handle. If this fails something is // very wrong because it is a kernel handle. // // N.B. ObRef sets SectionPointer to NULL on failure. // Status = ObReferenceObjectByHandle (SectionHandle, SECTION_MAP_EXECUTE, MmSectionObjectType, KernelMode, (PVOID *) &SectionPointer, (POBJECT_HANDLE_INFORMATION) NULL); ZwClose (SectionHandle); if (!NT_SUCCESS (Status)) { goto return1; } ControlArea = SectionPointer->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); } if ((Subsection->NextSubsection == NULL) && ((LoadFlags & MM_LOAD_IMAGE_IN_SESSION) == 0)) { PSECTION SectionPointer2; // // The driver was linked with subsection alignment such that // it is mapped with one subsection. Since the CreateSection // above guarantees that the driver image is indeed a // satisfactory executable, map it directly now to reuse the // cache from the MmCheckSystemImage call above. // Status = ZwCreateSection (&SectionHandle, SectionAccess, &ObjectAttributes, (PLARGE_INTEGER) NULL, PAGE_EXECUTE, SEC_COMMIT, FileHandle); if (NT_SUCCESS(Status)) { Status = ObReferenceObjectByHandle ( SectionHandle, SECTION_MAP_EXECUTE, MmSectionObjectType, KernelMode, (PVOID *) &SectionPointer2, (POBJECT_HANDLE_INFORMATION) NULL); ZwClose (SectionHandle); if (NT_SUCCESS (Status)) { // // The number of PTEs won't match if the image is // stripped and the debug directory crosses the last // sector boundary of the file. We could still use the // new section, but these cases are under 2% of all the // drivers loaded so don't bother. // if (SectionPointer->Segment->TotalNumberOfPtes == SectionPointer2->Segment->TotalNumberOfPtes) { ObDereferenceObject (SectionPointer); SectionPointer = SectionPointer2; } else { ObDereferenceObject (SectionPointer2); } } } } if ((LoadFlags & MM_LOAD_IMAGE_IN_SESSION) && (SectionPointer->Segment->ControlArea->u.Flags.FloppyMedia == 0)) { // // Check with all of the drivers along the path to win32k.sys to // ensure that they are willing to follow the rules required // of them and to give them a chance to lock down code and data // that needs to be locked. If any of the drivers along the path // refuses to participate, fail the win32k.sys load. // // It is assumed that all session drivers live on the same physical // drive, so when the very first session driver is loaded, this // check can be made. // // Note that this is important because these drivers are always // paged directly in/out from the filesystem so the drive // containing the filesystem better not get removed ! // // // This is skipped for the WinPE removable media boot case because // the user may be running WinPE in RAM and want to swap out the // boot media. In this case, the control area is marked as // FloppyMedia (even if it was CD-based) as all the pages have // already been converted to pagefile-backed. // do { OldValue = MiFirstDriverLoadEver; if (OldValue & 0x2) { break; } if (InterlockedCompareExchange (&MiFirstDriverLoadEver, OldValue | 0x2, OldValue) == OldValue) { Status = PpPagePathAssign (SectionPointer->Segment->ControlArea->FilePointer); if (!NT_SUCCESS (Status)) { KdPrint (("PpPagePathAssign FAILED for %wZ: %x\n", ImageFileName, Status)); // // Failing the insertion of win32k.sys' device // in the pagefile path is commented out until // the storage drivers have been modified to // correctly handle this request. If this is // added later, add code here to release relevant // resources for this error path. // } break; } } while (TRUE); } // // Anything may have changed while the load lock was released. // So before using the section we just created, go back and see // if any other threads have slipped through and did it already. // goto ReCheckLoaderList; } else { DataTableEntry = NULL; } // // Load the driver from the filesystem and pick a virtual address for it. // All session images are paged directly to and from the filesystem so these // images remain busy. // Status = MiLoadImageSection (&SectionPointer, ImageBaseAddress, ImageFileName, LoadFlags & MM_LOAD_IMAGE_IN_SESSION, FoundDataTableEntry); ASSERT (Status != STATUS_ALREADY_COMMITTED); NumberOfPtes = SectionPointer->Segment->TotalNumberOfPtes; // // Normal drivers are dereferenced here and their images can then be // overwritten. This is ok because we've already read the whole thing // into memory and from here until reboot (or unload), we back them // with the pagefile. // // Session space drivers are the exception - these images // are inpaged from the filesystem and we need to keep our reference to // the file so that it doesn't get overwritten. // if ((LoadFlags & MM_LOAD_IMAGE_IN_SESSION) == 0) { if (NT_SUCCESS (Status)) { // // Move the driver into a large page if requested via the registry. // MiUseLargeDriverPage (SectionPointer->Segment->TotalNumberOfPtes, ImageBaseAddress, &BaseName, 1); } ObDereferenceObject (SectionPointer); SectionPointer = NULL; } // // The module LoadCount will be 1 here if the module was just loaded. // The LoadCount will be >1 if it was attached to by a session (as opposed // to just loaded). // if (!NT_SUCCESS (Status)) { if (AlreadyOpen == TRUE) { // // We're failing and we were just attaching to an already loaded // driver. We don't want to go through the forced unload path // because we've already deleted the address space so // decrement our reference and clear the DataTableEntry. // ASSERT (DataTableEntry != NULL); DataTableEntry->LoadCount -= 1; DataTableEntry = NULL; } goto return1; } // // Error recovery from this point out for sessions works as follows: // // For sessions, we may or may not have a DataTableEntry at this point. // If we do, it's because we're attaching to a driver that has already // been loaded - and the DataTableEntry->LoadCount has been bumped - so // the error recovery from here on out is to just call // MmUnloadSystemImage with the DataTableEntry. // // If this is the first load of a given driver into a session space, we // have no DataTableEntry at this point. The view has already been mapped // and committed and the group/session addresses reserved for this DLL. // The error recovery path handles all this because // MmUnloadSystemImage zeroes the relevant fields in the // LDR_DATA_TABLE_ENTRY so that MmUnloadSystemImage works properly. // IssueUnloadOnFailure = TRUE; if (AlreadyOpen == FALSE) { if (((LoadFlags & MM_LOAD_IMAGE_IN_SESSION) == 0) || (*ImageBaseAddress != SectionPointer->Segment->BasedAddress)) { // // Apply the fixups to the section. Note session images need only // be fixed up once per insertion in the loaded module list. // try { Status = LdrRelocateImage (*ImageBaseAddress, "SYSLDR", STATUS_SUCCESS, STATUS_CONFLICTING_ADDRESSES, STATUS_INVALID_IMAGE_FORMAT); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode (); KdPrint(("MM:sysload - LdrRelocateImage failed status %lx\n", Status)); } if (!NT_SUCCESS(Status)) { // // Unload the system image and dereference the section. // goto return1; } } DebugInfoSize = 0; DataDirectory = NULL; DebugDir = NULL; NtHeaders = RtlImageNtHeader (*ImageBaseAddress); // // Create a loader table entry for this driver before resolving the // references so that any circular references can resolve properly. // if (LoadFlags & MM_LOAD_IMAGE_IN_SESSION) { DebugInfoSize = sizeof (NON_PAGED_DEBUG_INFO); if (IMAGE_DIRECTORY_ENTRY_DEBUG < NtHeaders->OptionalHeader.NumberOfRvaAndSizes) { DataDirectory = &NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]; if (DataDirectory->VirtualAddress && DataDirectory->Size && (DataDirectory->VirtualAddress + DataDirectory->Size) < NtHeaders->OptionalHeader.SizeOfImage) { DebugDir = (PIMAGE_DEBUG_DIRECTORY) ((PUCHAR)(*ImageBaseAddress) + DataDirectory->VirtualAddress); DebugInfoSize += DataDirectory->Size; for (i = 0; i < DataDirectory->Size/sizeof(IMAGE_DEBUG_DIRECTORY); i += 1) { if ((DebugDir+i)->PointerToRawData && (DebugDir+i)->PointerToRawData < NtHeaders->OptionalHeader.SizeOfImage && ((DebugDir+i)->PointerToRawData + (DebugDir+i)->SizeOfData) < NtHeaders->OptionalHeader.SizeOfImage) { DebugInfoSize += (DebugDir+i)->SizeOfData; } } } DebugInfoSize = MI_ROUND_TO_SIZE(DebugInfoSize, sizeof(ULONG)); } } DataTableEntrySize = sizeof (KLDR_DATA_TABLE_ENTRY) + DebugInfoSize + BaseName.Length + sizeof(UNICODE_NULL); DataTableEntry = ExAllocatePoolWithTag (NonPagedPool, DataTableEntrySize, 'dLmM'); if (DataTableEntry == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto return1; } // // Initialize the flags and load count. // DataTableEntry->Flags = LDRP_LOAD_IN_PROGRESS; DataTableEntry->LoadCount = 1; DataTableEntry->LoadedImports = (PVOID)LoadedImports; DataTableEntry->PatchInformation = NULL; if ((NtHeaders->OptionalHeader.MajorOperatingSystemVersion >= 5) && (NtHeaders->OptionalHeader.MajorImageVersion >= 5)) { DataTableEntry->Flags |= LDRP_ENTRY_NATIVE; } ssHeader = (PNON_PAGED_DEBUG_INFO) ((ULONG_PTR)DataTableEntry + sizeof (KLDR_DATA_TABLE_ENTRY)); BaseDllNameBuffer = (PWSTR) ((ULONG_PTR)ssHeader + DebugInfoSize); // // If loading a session space image, store away some debug data. // DataTableEntry->NonPagedDebugInfo = NULL; if (LoadFlags & MM_LOAD_IMAGE_IN_SESSION) { DataTableEntry->NonPagedDebugInfo = ssHeader; DataTableEntry->Flags |= LDRP_NON_PAGED_DEBUG_INFO; ssHeader->Signature = NON_PAGED_DEBUG_SIGNATURE; ssHeader->Flags = 1; ssHeader->Size = DebugInfoSize; ssHeader->Machine = NtHeaders->FileHeader.Machine; ssHeader->Characteristics = NtHeaders->FileHeader.Characteristics; ssHeader->TimeDateStamp = NtHeaders->FileHeader.TimeDateStamp; ssHeader->CheckSum = NtHeaders->OptionalHeader.CheckSum; ssHeader->SizeOfImage = NtHeaders->OptionalHeader.SizeOfImage; ssHeader->ImageBase = (ULONG_PTR) *ImageBaseAddress; if (DebugDir) { RtlCopyMemory (ssHeader + 1, DebugDir, DataDirectory->Size); DebugInfoSize = DataDirectory->Size; for (i = 0; i < DataDirectory->Size/sizeof(IMAGE_DEBUG_DIRECTORY); i += 1) { if ((DebugDir + i)->PointerToRawData && (DebugDir+i)->PointerToRawData < NtHeaders->OptionalHeader.SizeOfImage && ((DebugDir+i)->PointerToRawData + (DebugDir+i)->SizeOfData) < NtHeaders->OptionalHeader.SizeOfImage) { RtlCopyMemory ((PUCHAR)(ssHeader + 1) + DebugInfoSize, (PUCHAR)(*ImageBaseAddress) + (DebugDir + i)->PointerToRawData, (DebugDir + i)->SizeOfData); // // Reset the offset in the debug directory to point to // (((PIMAGE_DEBUG_DIRECTORY)(ssHeader + 1)) + i)-> PointerToRawData = DebugInfoSize; DebugInfoSize += (DebugDir+i)->SizeOfData; } else { (((PIMAGE_DEBUG_DIRECTORY)(ssHeader + 1)) + i)-> PointerToRawData = 0; } } } } // // Initialize the address of the DLL image file header and the entry // point address. // DataTableEntry->DllBase = *ImageBaseAddress; DataTableEntry->EntryPoint = ((PCHAR)*ImageBaseAddress + NtHeaders->OptionalHeader.AddressOfEntryPoint); DataTableEntry->SizeOfImage = NumberOfPtes << PAGE_SHIFT; DataTableEntry->CheckSum = NtHeaders->OptionalHeader.CheckSum; DataTableEntry->SectionPointer = (PVOID) SectionPointer; // // Store the DLL name. // DataTableEntry->BaseDllName.Buffer = BaseDllNameBuffer; DataTableEntry->BaseDllName.Length = BaseName.Length; DataTableEntry->BaseDllName.MaximumLength = BaseName.Length; RtlCopyMemory (DataTableEntry->BaseDllName.Buffer, BaseName.Buffer, BaseName.Length); DataTableEntry->BaseDllName.Buffer[BaseName.Length/sizeof(WCHAR)] = UNICODE_NULL; DataTableEntry->FullDllName.Buffer = ExAllocatePoolWithTag (PagedPool | POOL_COLD_ALLOCATION, PrefixedImageName.Length + sizeof(UNICODE_NULL), 'TDmM'); if (DataTableEntry->FullDllName.Buffer == NULL) { // // Pool could not be allocated, just set the length to 0. // DataTableEntry->FullDllName.Length = 0; DataTableEntry->FullDllName.MaximumLength = 0; } else { DataTableEntry->FullDllName.Length = PrefixedImageName.Length; DataTableEntry->FullDllName.MaximumLength = PrefixedImageName.Length; RtlCopyMemory (DataTableEntry->FullDllName.Buffer, PrefixedImageName.Buffer, PrefixedImageName.Length); DataTableEntry->FullDllName.Buffer[PrefixedImageName.Length/sizeof(WCHAR)] = UNICODE_NULL; } // // Capture the exception table data info // MiCaptureImageExceptionValues (DataTableEntry); // // Acquire the loaded module list resource and insert this entry // into the list. // MiProcessLoaderEntry (DataTableEntry, TRUE); MissingProcedureName = NameBuffer; try { // // Resolving the image references results in other DLLs being // loaded if they are referenced by the module that was just loaded. // An example is when an OEM printer or FAX driver links with // other general libraries. This is not a problem for session space // because the general libraries do not have the global data issues // that win32k.sys and the video drivers do. So we just call the // standard kernel reference resolver and any referenced libraries // get loaded into system global space. Code in the routine // restricts which libraries can be referenced by a driver. // Status = MiResolveImageReferences (*ImageBaseAddress, &BaseDirectory, NamePrefix, &MissingProcedureName, &MissingDriverName, &LoadedImports); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode (); KdPrint(("MM:sysload - ResolveImageReferences failed status %x\n", Status)); } if (!NT_SUCCESS (Status)) { #if DBG if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { ASSERT (MissingProcedureName == NULL); } if ((Status == STATUS_DRIVER_ORDINAL_NOT_FOUND) || (Status == STATUS_OBJECT_NAME_NOT_FOUND) || (Status == STATUS_DRIVER_ENTRYPOINT_NOT_FOUND)) { if ((ULONG_PTR)MissingProcedureName & ~((ULONG_PTR) (X64K-1))) { // // If not an ordinal, print string. // DbgPrint ("MissingProcedureName %s\n", MissingProcedureName); } else { DbgPrint ("MissingProcedureName 0x%p\n", MissingProcedureName); } } if (MissingDriverName != NULL) { PrintableMissingDriverName = (PWSTR)((ULONG_PTR)MissingDriverName & ~0x1); DbgPrint ("MissingDriverName %ws\n", PrintableMissingDriverName); } #endif MiProcessLoaderEntry (DataTableEntry, FALSE); if (DataTableEntry->FullDllName.Buffer != NULL) { ExFreePool (DataTableEntry->FullDllName.Buffer); } ExFreePool (DataTableEntry); DataTableEntry = NULL; goto return1; } PERFINFO_IMAGE_LOAD (DataTableEntry); // // Reinitialize the flags and update the loaded imports. // DataTableEntry->Flags |= (LDRP_SYSTEM_MAPPED | LDRP_ENTRY_PROCESSED | LDRP_MM_LOADED); DataTableEntry->Flags &= ~LDRP_LOAD_IN_PROGRESS; DataTableEntry->LoadedImports = LoadedImports; MiApplyDriverVerifier (DataTableEntry, NULL); if (LoadFlags & MM_LOAD_IMAGE_IN_SESSION) { // // The session image was mapped entirely read-write on initial // creation. Now that the relocations (if any), image // resolution and import table updates are complete, correct // permissions can be applied. // // Make the entire image copy on write. The subsequent call // to MiWriteProtectSystemImage will make various portions // readonly. Then apply flip various subsections into global // shared mode if their attributes specify it. // PointerPte = MiGetPteAddress (DataTableEntry->DllBase); MiSetSystemCodeProtection (PointerPte, PointerPte + NumberOfPtes - 1, MM_EXECUTE_WRITECOPY); } MiWriteProtectSystemImage (DataTableEntry->DllBase); if (LoadFlags & MM_LOAD_IMAGE_IN_SESSION) { MiSessionProcessGlobalSubsections (DataTableEntry); } if (PsImageNotifyEnabled) { IMAGE_INFO ImageInfo; ImageInfo.Properties = 0; ImageInfo.ImageAddressingMode = IMAGE_ADDRESSING_MODE_32BIT; ImageInfo.SystemModeImage = TRUE; ImageInfo.ImageSize = DataTableEntry->SizeOfImage; ImageInfo.ImageBase = *ImageBaseAddress; ImageInfo.ImageSelector = 0; ImageInfo.ImageSectionNumber = 0; PsCallImageNotifyRoutines(ImageFileName, (HANDLE)NULL, &ImageInfo); } if (MiCacheImageSymbols (*ImageBaseAddress)) { // // TEMP TEMP TEMP rip out when debugger converted // ANSI_STRING AnsiName; UNICODE_STRING UnicodeName; // // \SystemRoot is 11 characters in length // if (PrefixedImageName.Length > (11 * sizeof (WCHAR )) && !_wcsnicmp (PrefixedImageName.Buffer, (const PUSHORT)L"\\SystemRoot", 11)) { UnicodeName = PrefixedImageName; UnicodeName.Buffer += 11; UnicodeName.Length -= (11 * sizeof (WCHAR)); sprintf (NameBuffer, "%ws%wZ", &SharedUserData->NtSystemRoot[2], &UnicodeName); } else { sprintf (NameBuffer, "%wZ", &BaseName); } RtlInitString (&AnsiName, NameBuffer); DbgLoadImageSymbols (&AnsiName, *ImageBaseAddress, (ULONG_PTR) -1); DataTableEntry->Flags |= LDRP_DEBUG_SYMBOLS_LOADED; } } // // Flush the instruction cache on all systems in the configuration. // KeSweepIcache (TRUE); *ImageHandle = DataTableEntry; Status = STATUS_SUCCESS; // // Session images are always paged by default. // Non-session images get paged now. // if (LoadFlags & MM_LOAD_IMAGE_IN_SESSION) { MI_LOG_SESSION_DATA_START (DataTableEntry); } else if ((LoadFlags & MM_LOAD_IMAGE_AND_LOCKDOWN) == 0) { ASSERT (SectionPointer == NULL); MiEnablePagingOfDriver (DataTableEntry); } return1: if (!NT_SUCCESS(Status)) { if (IssueUnloadOnFailure == TRUE) { if (DataTableEntry == NULL) { RtlZeroMemory (&TempDataTableEntry, sizeof (KLDR_DATA_TABLE_ENTRY)); DataTableEntry = &TempDataTableEntry; DataTableEntry->DllBase = *ImageBaseAddress; DataTableEntry->SizeOfImage = NumberOfPtes << PAGE_SHIFT; DataTableEntry->LoadCount = 1; DataTableEntry->LoadedImports = LoadedImports; if ((AlreadyOpen == FALSE) && (SectionPointer != NULL)) { DataTableEntry->SectionPointer = (PVOID) SectionPointer; } } #if DBG else { // // If DataTableEntry is NULL, then we are unloading before one // got created. Once a LDR_DATA_TABLE_ENTRY is created, the // load cannot fail, so if it exists here, at least one other // session contains this image as well. // ASSERT (DataTableEntry->LoadCount > 1); } #endif MmUnloadSystemImage ((PVOID)DataTableEntry); } if ((AlreadyOpen == FALSE) && (SectionPointer != NULL)) { // // This is needed for failed win32k.sys loads or any session's // load of the first instance of a driver. // ObDereferenceObject (SectionPointer); } } if (LoadLockOwned == TRUE) { KeReleaseMutant (&MmSystemLoadLock, 1, FALSE, FALSE); KeLeaveCriticalRegionThread (CurrentThread); LoadLockOwned = FALSE; } if (FileHandle) { ZwClose (FileHandle); } if (!NT_SUCCESS(Status)) { UNICODE_STRING ErrorStrings[4]; ULONG UniqueErrorValue; ULONG StringSize; ULONG StringCount; ANSI_STRING AnsiString; UNICODE_STRING ProcedureName = {0}; UNICODE_STRING DriverName; ULONG i; PWCHAR temp; PWCHAR ptr; ULONG PacketSize; SIZE_T length; PIO_ERROR_LOG_PACKET ErrLog; // // The driver could not be loaded - log an event with the details. // StringSize = 0; *(&ErrorStrings[0]) = *ImageFileName; StringSize += (ImageFileName->Length + sizeof(UNICODE_NULL)); StringCount = 1; UniqueErrorValue = 0; PrintableMissingDriverName = (PWSTR)((ULONG_PTR)MissingDriverName & ~0x1); if ((Status == STATUS_DRIVER_ORDINAL_NOT_FOUND) || (Status == STATUS_DRIVER_ENTRYPOINT_NOT_FOUND) || (Status == STATUS_OBJECT_NAME_NOT_FOUND) || (Status == STATUS_PROCEDURE_NOT_FOUND)) { ErrorStrings[1].Buffer = L"cannot find"; length = wcslen(ErrorStrings[1].Buffer) * sizeof(WCHAR); ErrorStrings[1].Length = (USHORT) length; StringSize += (ULONG)(length + sizeof (UNICODE_NULL)); StringCount += 1; RtlInitUnicodeString (&DriverName, PrintableMissingDriverName); StringSize += (DriverName.Length + sizeof(UNICODE_NULL)); StringCount += 1; *(&ErrorStrings[2]) = *(&DriverName); if ((ULONG_PTR)MissingProcedureName & ~((ULONG_PTR) (X64K-1))) { // // If not an ordinal, pass as a Unicode string // RtlInitAnsiString (&AnsiString, MissingProcedureName); if (NT_SUCCESS (RtlAnsiStringToUnicodeString (&ProcedureName, &AnsiString, TRUE))) { StringSize += (ProcedureName.Length + sizeof(UNICODE_NULL)); StringCount += 1; *(&ErrorStrings[3]) = *(&ProcedureName); } else { goto GenericError; } } else { // // Just pass ordinal values as is in the UniqueErrorValue. // UniqueErrorValue = PtrToUlong (MissingProcedureName); } } else { GenericError: UniqueErrorValue = (ULONG) Status; if (MmIsRetryIoStatus(Status)) { // // Coalesce the various low memory values into just one. // Status = STATUS_INSUFFICIENT_RESOURCES; } else { // // Ideally, the real failing status should be returned. However, // we need to do a full release worth of testing (ie Longhorn) // before making that change. // Status = STATUS_DRIVER_UNABLE_TO_LOAD; } ErrorStrings[1].Buffer = L"failed to load"; length = wcslen(ErrorStrings[1].Buffer) * sizeof(WCHAR); ErrorStrings[1].Length = (USHORT) length; StringSize += (ULONG)(length + sizeof (UNICODE_NULL)); StringCount += 1; } PacketSize = sizeof (IO_ERROR_LOG_PACKET) + StringSize; // // Enforce I/O manager interface (ie: UCHAR) size restrictions. // if (PacketSize < MAXUCHAR) { ErrLog = IoAllocateGenericErrorLogEntry ((UCHAR)PacketSize); if (ErrLog != NULL) { // // Fill it in and write it out as a single string. // ErrLog->ErrorCode = STATUS_LOG_HARD_ERROR; ErrLog->FinalStatus = Status; ErrLog->UniqueErrorValue = UniqueErrorValue; ErrLog->StringOffset = (USHORT) sizeof (IO_ERROR_LOG_PACKET); temp = (PWCHAR) ((PUCHAR) ErrLog + ErrLog->StringOffset); for (i = 0; i < StringCount; i += 1) { ptr = ErrorStrings[i].Buffer; RtlCopyMemory (temp, ptr, ErrorStrings[i].Length); temp += (ErrorStrings[i].Length / sizeof (WCHAR)); *temp = L' '; temp += 1; } *(temp - 1) = UNICODE_NULL; ErrLog->NumberOfStrings = 1; IoWriteErrorLogEntry (ErrLog); } } // // The only way this pointer has the low bit set is if we are expected // to free the pool containing the name. Typically the name points at // a loaded module list entry and so no one has to free it and in this // case the low bit will NOT be set. If the module could not be found // and was therefore not loaded, then we left a piece of pool around // containing the name since there is no loaded module entry already - // this must be released now. // if ((ULONG_PTR)MissingDriverName & 0x1) { ExFreePool (PrintableMissingDriverName); } if (ProcedureName.Buffer != NULL) { RtlFreeUnicodeString (&ProcedureName); } ExFreePool (NameBuffer); return Status; } return2: if (LoadLockOwned == TRUE) { KeReleaseMutant (&MmSystemLoadLock, 1, FALSE, FALSE); KeLeaveCriticalRegionThread (CurrentThread); LoadLockOwned = FALSE; } if (NamePrefix) { ExFreePool (PrefixedImageName.Buffer); } ExFreePool (NameBuffer); return Status; } VOID MiReturnFailedSessionPages ( IN PMMPTE PointerPte, IN PMMPTE LastPte ) /*++ Routine Description: This routine is a nonpaged wrapper which undoes session image loads that failed midway through reading in the pages. Arguments: PointerPte - Supplies the starting PTE for the range to unload. LastPte - Supplies the ending PTE for the range to unload. Return Value: None. --*/ { KIRQL OldIrql; PMMPFN Pfn1; PMMPFN Pfn2; PFN_NUMBER PageFrameIndex; LOCK_PFN (OldIrql); while (PointerPte <= LastPte) { if (PointerPte->u.Hard.Valid == 1) { // // Delete the page. // PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte); // // Set the pointer to PTE as empty so the page // is deleted when the reference count goes to zero. // Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); Pfn2 = MI_PFN_ELEMENT (Pfn1->u4.PteFrame); MiDecrementShareCount (Pfn2, Pfn1->u4.PteFrame); MI_SET_PFN_DELETED (Pfn1); MiDecrementShareCount (Pfn1, PageFrameIndex); MI_WRITE_INVALID_PTE (PointerPte, ZeroPte); } PointerPte += 1; } UNLOCK_PFN (OldIrql); } NTSTATUS MiLoadImageSection ( IN OUT PSECTION *InputSectionPointer, OUT PVOID *ImageBaseAddress, IN PUNICODE_STRING ImageFileName, IN ULONG LoadInSessionSpace, IN PKLDR_DATA_TABLE_ENTRY FoundDataTableEntry ) /*++ Routine Description: This routine loads the specified image into the kernel part of the address space. Arguments: InputSectionPointer - Supplies the section object for the image. This may be replaced by a pagefile-backed section (for protection purposes) for session images if it is determined that the image section is concurrently being accessed by a user app. ImageBaseAddress - Returns the address that the image header is at. ImageFileName - Supplies the full path name (including the image name) of the image to load. LoadInSessionSpace - Supplies nonzero to load this image in session space. Each session gets a different copy of this driver with pages shared as much as possible via copy on write. Supplies zero if this image should be loaded in global space. FoundDataTableEntry - Supplies the loader data table entry if the image has already been loaded. This can only happen for session space. It means this driver has already been loaded into a different session, so this session still needs to map it. Return Value: Status of the operation. --*/ { KAPC_STATE ApcState; PFN_NUMBER PagesRequired; PFN_NUMBER ActualPagesUsed; PSECTION SectionPointer; PSECTION NewSectionPointer; PVOID OpaqueSession; PMMPTE ProtoPte; PMMPTE FirstPte; PMMPTE LastPte; PMMPTE PointerPte; PEPROCESS Process; PEPROCESS TargetProcess; ULONG NumberOfPtes; MMPTE PteContents; MMPTE TempPte; PFN_NUMBER PageFrameIndex; PVOID UserVa; PVOID SystemVa; NTSTATUS Status; NTSTATUS ExceptionStatus; PVOID Base; ULONG_PTR ViewSize; LARGE_INTEGER SectionOffset; LOGICAL LoadSymbols; PVOID BaseAddress; PCONTROL_AREA ControlArea; PSUBSECTION Subsection; PAGED_CODE(); #if !DBG UNREFERENCED_PARAMETER (ImageFileName); #endif SectionPointer = *InputSectionPointer; NumberOfPtes = SectionPointer->Segment->TotalNumberOfPtes; if (LoadInSessionSpace != 0) { // // Allocate a unique systemwide session space virtual address for // the driver. // if (FoundDataTableEntry == NULL) { Status = MiSessionWideReserveImageAddress (SectionPointer, &BaseAddress, &NewSectionPointer); if (!NT_SUCCESS(Status)) { return Status; } if (NewSectionPointer != NULL) { SectionPointer = NewSectionPointer; *InputSectionPointer = NewSectionPointer; } } else { BaseAddress = FoundDataTableEntry->DllBase; } #if DBG if (NtGlobalFlag & FLG_SHOW_LDR_SNAPS) { DbgPrint ("MM: MiLoadImageSection: Image %wZ, BasedAddress 0x%p, Allocated Session BaseAddress 0x%p\n", ImageFileName, SectionPointer->Segment->BasedAddress, BaseAddress); } #endif // // Session images are mapped backed directly by the file image. // All pristine pages of the image will be shared across all // sessions, with each page treated as copy-on-write on first write. // // NOTE: This makes the file image "busy", a different behavior // as normal kernel drivers are backed by the paging file only. // Status = MiShareSessionImage (BaseAddress, SectionPointer); if (!NT_SUCCESS (Status)) { MiRemoveImageSessionWide (FoundDataTableEntry, BaseAddress, NumberOfPtes << PAGE_SHIFT); return Status; } *ImageBaseAddress = BaseAddress; return Status; } ASSERT (FoundDataTableEntry == NULL); // // Calculate the number of pages required to load this image. // // Start out by charging for everything and subtract out any gap // pages after the image loads successfully. // PagesRequired = NumberOfPtes; ActualPagesUsed = 0; // // See if ample pages exist to load this image. // if (MiChargeResidentAvailable (PagesRequired, MM_RESAVAIL_ALLOCATE_LOAD_SYSTEM_IMAGE) == FALSE) { return STATUS_INSUFFICIENT_RESOURCES; } // // Reserve the necessary system address space. // FirstPte = MiReserveSystemPtes (NumberOfPtes, SystemPteSpace); if (FirstPte == NULL) { MI_INCREMENT_RESIDENT_AVAILABLE (PagesRequired, MM_RESAVAIL_FREE_LOAD_SYSTEM_IMAGE1); return STATUS_INSUFFICIENT_RESOURCES; } PointerPte = FirstPte; SystemVa = MiGetVirtualAddressMappedByPte (PointerPte); if (MiChargeCommitment (PagesRequired, NULL) == FALSE) { MI_INCREMENT_RESIDENT_AVAILABLE (PagesRequired, MM_RESAVAIL_FREE_LOAD_SYSTEM_IMAGE1); MiReleaseSystemPtes (FirstPte, NumberOfPtes, SystemPteSpace); return STATUS_INSUFFICIENT_RESOURCES; } MM_TRACK_COMMIT (MM_DBG_COMMIT_DRIVER_PAGES, PagesRequired); InterlockedExchangeAdd ((PLONG)&MmDriverCommit, (LONG) PagesRequired); // // Map a view into the user portion of the address space. // Process = PsGetCurrentProcess (); // // Since callees are not always in the context of the system process, // attach here when necessary to guarantee the driver load occurs in a // known safe address space to prevent security holes. // OpaqueSession = NULL; KeStackAttachProcess (&PsInitialSystemProcess->Pcb, &ApcState); ZERO_LARGE (SectionOffset); Base = NULL; ViewSize = 0; if (NtGlobalFlag & FLG_ENABLE_KDEBUG_SYMBOL_LOAD) { LoadSymbols = TRUE; NtGlobalFlag &= ~FLG_ENABLE_KDEBUG_SYMBOL_LOAD; } else { LoadSymbols = FALSE; } TargetProcess = PsGetCurrentProcess (); Status = MmMapViewOfSection (SectionPointer, TargetProcess, &Base, 0, 0, &SectionOffset, &ViewSize, ViewUnmap, 0, PAGE_EXECUTE); if (LoadSymbols) { NtGlobalFlag |= FLG_ENABLE_KDEBUG_SYMBOL_LOAD; } if (Status == STATUS_IMAGE_MACHINE_TYPE_MISMATCH) { Status = STATUS_INVALID_IMAGE_FORMAT; } if (!NT_SUCCESS(Status)) { KeUnstackDetachProcess (&ApcState); MI_INCREMENT_RESIDENT_AVAILABLE (PagesRequired, MM_RESAVAIL_FREE_LOAD_SYSTEM_IMAGE2); MiReleaseSystemPtes (FirstPte, NumberOfPtes, SystemPteSpace); MiReturnCommitment (PagesRequired); return Status; } // // Allocate a physical page(s) and copy the image data. // Note for session drivers, the physical pages have already // been allocated and just data copying is done here. // ControlArea = SectionPointer->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); } ASSERT (Subsection->SubsectionBase != NULL); ProtoPte = Subsection->SubsectionBase; *ImageBaseAddress = SystemVa; UserVa = Base; TempPte = ValidKernelPte; TempPte.u.Long |= MM_PTE_EXECUTE; LastPte = ProtoPte + NumberOfPtes; ExceptionStatus = STATUS_SUCCESS; while (ProtoPte < LastPte) { PteContents = *ProtoPte; if ((PteContents.u.Hard.Valid == 1) || (PteContents.u.Soft.Protection != MM_NOACCESS)) { ActualPagesUsed += 1; PageFrameIndex = MiAllocatePfn (PointerPte, MM_EXECUTE); TempPte.u.Hard.PageFrameNumber = PageFrameIndex; MI_WRITE_VALID_PTE (PointerPte, TempPte); ASSERT (MI_PFN_ELEMENT (PageFrameIndex)->u1.WsIndex == 0); try { RtlCopyMemory (SystemVa, UserVa, PAGE_SIZE); } except (MiMapCacheExceptionFilter (&ExceptionStatus, GetExceptionInformation())) { // // An exception occurred, unmap the view and // return the error to the caller. // #if DBG DbgPrint("MiLoadImageSection: Exception 0x%x copying driver SystemVa 0x%p, UserVa 0x%p\n",ExceptionStatus,SystemVa,UserVa); #endif MiReturnFailedSessionPages (FirstPte, PointerPte); MI_INCREMENT_RESIDENT_AVAILABLE (PagesRequired, MM_RESAVAIL_FREE_LOAD_SYSTEM_IMAGE3); MiReleaseSystemPtes (FirstPte, NumberOfPtes, SystemPteSpace); MiReturnCommitment (PagesRequired); Status = MiUnmapViewOfSection (TargetProcess, Base, FALSE); ASSERT (NT_SUCCESS (Status)); // // Purge the section as we want these pages on the freelist // instead of at the tail of standby, as we're completely // done with the section. This is because other valuable // standby pages end up getting reused (especially during // bootup) when the section pages are the ones that really // will never be referenced again. // // Note this isn't done for session images as they're // inpaged directly from the filesystem via the section. // MmPurgeSection (ControlArea->FilePointer->SectionObjectPointer, NULL, 0, FALSE); KeUnstackDetachProcess (&ApcState); return ExceptionStatus; } } else { // // The PTE is no access. // MI_WRITE_INVALID_PTE (PointerPte, ZeroKernelPte); } ProtoPte += 1; PointerPte += 1; SystemVa = ((PCHAR)SystemVa + PAGE_SIZE); UserVa = ((PCHAR)UserVa + PAGE_SIZE); } Status = MiUnmapViewOfSection (TargetProcess, Base, FALSE); ASSERT (NT_SUCCESS (Status)); // // Purge the section as we want these pages on the freelist instead of // at the tail of standby, as we're completely done with the section. // This is because other valuable standby pages end up getting reused // (especially during bootup) when the section pages are the ones that // really will never be referenced again. // MmPurgeSection (ControlArea->FilePointer->SectionObjectPointer, NULL, 0, FALSE); KeUnstackDetachProcess (&ApcState); // // Return any excess resident available and commit. // if (PagesRequired != ActualPagesUsed) { ASSERT (PagesRequired > ActualPagesUsed); PagesRequired -= ActualPagesUsed; MI_INCREMENT_RESIDENT_AVAILABLE (PagesRequired, MM_RESAVAIL_FREE_LOAD_SYSTEM_IMAGE_EXCESS); MiReturnCommitment (PagesRequired); } return Status; } VOID MmFreeDriverInitialization ( IN PVOID ImageHandle ) /*++ Routine Description: This routine removes the pages that relocate and debug information from the address space of the driver. NOTE: This routine looks at the last sections defined in the image header and if that section is marked as DISCARDABLE in the characteristics, it is removed from the image. This means that all discardable sections at the end of the driver are deleted. Arguments: SectionObject - Supplies the section object for the image. Return Value: None. --*/ { PKLDR_DATA_TABLE_ENTRY DataTableEntry; PMMPTE PointerPte; PMMPTE LastPte; PFN_NUMBER NumberOfPtes; PVOID Base; PVOID StartVa; ULONG i; PIMAGE_NT_HEADERS NtHeaders; PIMAGE_SECTION_HEADER NtSection; PIMAGE_SECTION_HEADER FoundSection; PFN_NUMBER PagesDeleted; #if 0 PFN_NUMBER PageFrameIndex; PMMPFN Pfn1; KIRQL OldIrql; #endif DataTableEntry = (PKLDR_DATA_TABLE_ENTRY)ImageHandle; Base = DataTableEntry->DllBase; ASSERT (MI_IS_SESSION_ADDRESS (Base) == FALSE); NumberOfPtes = DataTableEntry->SizeOfImage >> PAGE_SHIFT; LastPte = MiGetPteAddress (Base) + NumberOfPtes; NtHeaders = (PIMAGE_NT_HEADERS) RtlImageNtHeader (Base); if (NtHeaders == NULL) { return; } NtSection = (PIMAGE_SECTION_HEADER)((PCHAR)NtHeaders + sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER) + NtHeaders->FileHeader.SizeOfOptionalHeader ); NtSection += NtHeaders->FileHeader.NumberOfSections; FoundSection = NULL; for (i = 0; i < NtHeaders->FileHeader.NumberOfSections; i += 1) { NtSection -= 1; if ((NtSection->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0) { FoundSection = NtSection; } else { // // There was a non discardable section between this // section and the last non discardable section, don't // discard this section and don't look any more. // break; } } if (FoundSection != NULL) { StartVa = (PVOID) (ROUND_TO_PAGES ( (PCHAR)Base + FoundSection->VirtualAddress)); PointerPte = MiGetPteAddress (StartVa); NumberOfPtes = (PFN_NUMBER)(LastPte - PointerPte); if (NumberOfPtes != 0) { if (MI_IS_PHYSICAL_ADDRESS (StartVa)) { // // Don't free the INIT code for a driver mapped by large pages // because if it unloads later, we'd have to deal with // discontiguous ranges of pages to free. // return; #if 0 PagesDeleted = NumberOfPtes; LOCK_PFN (OldIrql); while (NumberOfPtes != 0) { // // On certain architectures, virtual addresses // may be physical and hence have no corresponding PTE. // PageFrameIndex = MI_CONVERT_PHYSICAL_TO_PFN (StartVa); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); Pfn1->u2.ShareCount = 0; Pfn1->u3.e2.ReferenceCount = 0; MI_SET_PFN_DELETED (Pfn1); MiInsertPageInFreeList (PageFrameIndex); StartVa = (PVOID)((PUCHAR)StartVa + PAGE_SIZE); NumberOfPtes -= 1; } UNLOCK_PFN (OldIrql); #endif } else { PagesDeleted = MiDeleteSystemPagableVm (PointerPte, NumberOfPtes, ZeroKernelPte, FALSE, NULL); } MI_INCREMENT_RESIDENT_AVAILABLE (PagesDeleted, MM_RESAVAIL_FREE_DRIVER_INITIALIZATION); MiReturnCommitment (PagesDeleted); MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_DRIVER_INIT_CODE, PagesDeleted); InterlockedExchangeAdd ((PLONG)&MmDriverCommit, (LONG) (0 - PagesDeleted)); } } return; } LOGICAL MiChargeResidentAvailable ( IN PFN_NUMBER NumberOfPages, IN ULONG Id ) /*++ Routine Description: This routine is a nonpaged wrapper to charge resident available pages. Arguments: NumberOfPages - Supplies the number of pages to charge. Id - Supplies a tracking ID for debugging purposes. Return Value: TRUE if the pages were charged, FALSE if not. --*/ { KIRQL OldIrql; LOCK_PFN (OldIrql); if (MI_NONPAGABLE_MEMORY_AVAILABLE() <= (SPFN_NUMBER)NumberOfPages) { UNLOCK_PFN (OldIrql); return FALSE; } MI_DECREMENT_RESIDENT_AVAILABLE (NumberOfPages, Id); UNLOCK_PFN (OldIrql); return TRUE; } VOID MiFlushPteListFreePfns ( IN PMMPTE_FLUSH_LIST PteFlushList ) /*++ Routine Description: This routine flushes all the PTEs in the PTE flush list. If the list has overflowed, the entire TB is flushed. This routine also decrements the sharecounts on the relevant PFNs. Arguments: PteFlushList - Supplies a pointer to the list to be flushed. Return Value: None. Environment: Kernel mode, working set mutex held, APC_LEVEL. --*/ { ULONG i; PFN_NUMBER PageFrameIndex; PMMPFN Pfn; PMMPTE PointerPte; MMPTE TempPte; MMPTE PreviousPte; KIRQL OldIrql; ASSERT (KeAreAllApcsDisabled () == TRUE); ASSERT (PteFlushList->Count != 0); // // Put the PTEs in transition and decrement the number of // valid PTEs within the containing page table page. Note // that for a private page, the page table page is still // needed because the page is in transition. // LOCK_PFN (OldIrql); for (i = 0; i < PteFlushList->Count; i += 1) { PointerPte = MiGetPteAddress (PteFlushList->FlushVa[i]); // // If session space were allowed, we'd have to call // MI_FLUSH_ENTIRE_SESSION_TB (TRUE, TRUE) below because // Session space has no ASN - flush the entire TB. // ASSERT (MI_IS_SESSION_IMAGE_ADDRESS (MiGetVirtualAddressMappedByPte (PointerPte)) == FALSE); TempPte = *PointerPte; PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte); Pfn = MI_PFN_ELEMENT (PageFrameIndex); MI_MAKE_VALID_PTE_TRANSITION (TempPte, Pfn->OriginalPte.u.Soft.Protection); PreviousPte = *PointerPte; MI_WRITE_INVALID_PTE (PointerPte, TempPte); MI_CAPTURE_DIRTY_BIT_TO_PFN (&PreviousPte, Pfn); MiDecrementShareCount (Pfn, PageFrameIndex); } // // Flush the relevant entries from the translation buffer. // MiFlushPteList (PteFlushList, TRUE); UNLOCK_PFN (OldIrql); PteFlushList->Count = 0; return; } VOID MiEnablePagingOfDriver ( IN PVOID ImageHandle ) { ULONG Span; PKLDR_DATA_TABLE_ENTRY DataTableEntry; PMMPTE LastPte; PMMPTE PointerPte; PVOID Base; ULONG i; PIMAGE_NT_HEADERS NtHeaders; PIMAGE_SECTION_HEADER FoundSection; PIMAGE_OPTIONAL_HEADER OptionalHeader; // // Don't page kernel mode code if customer does not want it paged. // if (MmDisablePagingExecutive & MM_SYSTEM_CODE_LOCKED_DOWN) { return; } // // If the driver has pagable code, make it paged. // DataTableEntry = (PKLDR_DATA_TABLE_ENTRY) ImageHandle; Base = DataTableEntry->DllBase; NtHeaders = (PIMAGE_NT_HEADERS) RtlImageNtHeader (Base); if (NtHeaders == NULL) { return; } OptionalHeader = (PIMAGE_OPTIONAL_HEADER)((PCHAR)NtHeaders + #if defined (_WIN64) FIELD_OFFSET (IMAGE_NT_HEADERS64, OptionalHeader) #else FIELD_OFFSET (IMAGE_NT_HEADERS32, OptionalHeader) #endif ); FoundSection = IMAGE_FIRST_SECTION (NtHeaders); i = NtHeaders->FileHeader.NumberOfSections; PointerPte = NULL; // // Initializing LastPte is not needed for correctness, but without it // the compiler cannot compile this code W4 to check for use of // uninitialized variables. // LastPte = NULL; while (i > 0) { #if DBG if ((*(PULONG)FoundSection->Name == 'tini') || (*(PULONG)FoundSection->Name == 'egap')) { DbgPrint("driver %wZ has lower case sections (init or pagexxx)\n", &DataTableEntry->FullDllName); } #endif //DBG // // Mark as pagable any section which starts with the // first 4 characters PAGE or .eda (for the .edata section). // if ((*(PULONG)FoundSection->Name == 'EGAP') || (*(PULONG)FoundSection->Name == 'ade.')) { // // This section is pagable, save away the start and end. // if (PointerPte == NULL) { // // Previous section was NOT pagable, get the start address. // PointerPte = MiGetPteAddress ((PVOID)(ROUND_TO_PAGES ( (PCHAR)Base + FoundSection->VirtualAddress))); } // // Generally, SizeOfRawData is larger than VirtualSize for each // section because it includes the padding to get to the subsection // alignment boundary. However, if the image is linked with // subsection alignment == native page alignment, the linker will // have VirtualSize be much larger than SizeOfRawData because it // will account for all the bss. // Span = FoundSection->SizeOfRawData; if (Span < FoundSection->Misc.VirtualSize) { Span = FoundSection->Misc.VirtualSize; } LastPte = MiGetPteAddress ((PCHAR)Base + FoundSection->VirtualAddress + (OptionalHeader->SectionAlignment - 1) + Span - PAGE_SIZE); } else { // // This section is not pagable, if the previous section was // pagable, enable it. // if (PointerPte != NULL) { MiSetPagingOfDriver (PointerPte, LastPte); PointerPte = NULL; } } i -= 1; FoundSection += 1; } if (PointerPte != NULL) { MiSetPagingOfDriver (PointerPte, LastPte); } } VOID MiSetPagingOfDriver ( IN PMMPTE PointerPte, IN PMMPTE LastPte ) /*++ Routine Description: This routine marks the specified range of PTEs as pagable. Arguments: PointerPte - Supplies the starting PTE. LastPte - Supplies the ending PTE. Return Value: None. Environment: Kernel Mode, IRQL of APC_LEVEL or below. 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. --*/ { PVOID Base; PFN_NUMBER PageCount; PFN_NUMBER PageFrameIndex; PMMPFN Pfn; MMPTE_FLUSH_LIST PteFlushList; PAGED_CODE (); Base = MiGetVirtualAddressMappedByPte (PointerPte); if (MI_IS_PHYSICAL_ADDRESS (Base)) { // // No need to lock physical addresses. // return; } ASSERT (MI_IS_SESSION_IMAGE_ADDRESS (Base) == FALSE); PageCount = 0; PteFlushList.Count = 0; LOCK_WORKING_SET (&MmSystemCacheWs); while (PointerPte <= LastPte) { // // Check to make sure this PTE has not already been // made pagable (or deleted). It is pagable if it // is not valid, or if the PFN database wsindex element // is non zero. // if (PointerPte->u.Hard.Valid == 1) { PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte); Pfn = MI_PFN_ELEMENT (PageFrameIndex); ASSERT (Pfn->u2.ShareCount == 1); // // If the wsindex is nonzero, then this page is already pagable // and has a WSLE entry. Ignore it here and let the trimmer // take it if memory comes under pressure. // if (Pfn->u1.WsIndex == 0) { // // Original PTE may need to be set for drivers loaded // via ntldr. // if (Pfn->OriginalPte.u.Long == 0) { Pfn->OriginalPte.u.Long = MM_KERNEL_DEMAND_ZERO_PTE; Pfn->OriginalPte.u.Soft.Protection |= MM_EXECUTE; } PteFlushList.FlushVa[PteFlushList.Count] = Base; PteFlushList.Count += 1; if (PteFlushList.Count == MM_MAXIMUM_FLUSH_COUNT) { MiFlushPteListFreePfns (&PteFlushList); } PageCount += 1; } } Base = (PVOID)((PCHAR)Base + PAGE_SIZE); PointerPte += 1; } if (PteFlushList.Count != 0) { MiFlushPteListFreePfns (&PteFlushList); } UNLOCK_WORKING_SET (&MmSystemCacheWs); if (PageCount != 0) { InterlockedExchangeAdd (&MmTotalSystemDriverPages, (LONG) PageCount); MI_INCREMENT_RESIDENT_AVAILABLE (PageCount, MM_RESAVAIL_FREE_SET_DRIVER_PAGING); } } PVOID MmPageEntireDriver ( IN PVOID AddressWithinSection ) /*++ Routine Description: This routine allows a driver to page out all of its code and data regardless of the attributes of the various image sections. Note, this routine can be called multiple times with no intervening calls to MmResetDriverPaging. Arguments: AddressWithinSection - Supplies an address within the driver, e.g. DriverEntry. Return Value: Base address of driver. Environment: Kernel mode, APC_LEVEL or below. --*/ { PKLDR_DATA_TABLE_ENTRY DataTableEntry; PMMPTE FirstPte; PMMPTE LastPte; PVOID BaseAddress; PAGED_CODE(); DataTableEntry = MiLookupDataTableEntry (AddressWithinSection, FALSE); if (DataTableEntry == NULL) { return NULL; } // // Don't page kernel mode code if disabled via registry. // if (MmDisablePagingExecutive & MM_SYSTEM_CODE_LOCKED_DOWN) { return DataTableEntry->DllBase; } if (DataTableEntry->SectionPointer != NULL) { // // Driver is mapped as an image (ie: session space), this is always // pagable. // ASSERT (MI_IS_SESSION_IMAGE_ADDRESS (AddressWithinSection) == TRUE); return DataTableEntry->DllBase; } // // Force any active DPCs to clear the system before we page the driver. // KeFlushQueuedDpcs (); BaseAddress = DataTableEntry->DllBase; FirstPte = MiGetPteAddress (BaseAddress); LastPte = (FirstPte - 1) + (DataTableEntry->SizeOfImage >> PAGE_SHIFT); ASSERT (MI_IS_SESSION_IMAGE_ADDRESS (AddressWithinSection) == FALSE); MiSetPagingOfDriver (FirstPte, LastPte); return BaseAddress; } VOID MmResetDriverPaging ( IN PVOID AddressWithinSection ) /*++ Routine Description: This routines resets the driver paging to what the image specified. Hence image sections such as the IAT, .text, .data will be locked down in memory. Note, there is no requirement that MmPageEntireDriver was called. Arguments: AddressWithinSection - Supplies an address within the driver, e.g. DriverEntry. Return Value: None. Environment: Kernel mode, APC_LEVEL or below. --*/ { ULONG Span; PKLDR_DATA_TABLE_ENTRY DataTableEntry; PMMPTE LastPte; PMMPTE PointerPte; PVOID Base; ULONG i; PIMAGE_NT_HEADERS NtHeaders; PIMAGE_SECTION_HEADER FoundSection; PAGED_CODE(); // // Don't page kernel mode code if disabled via registry. // if (MmDisablePagingExecutive & MM_SYSTEM_CODE_LOCKED_DOWN) { return; } if (MI_IS_PHYSICAL_ADDRESS (AddressWithinSection)) { return; } // // If the driver has pagable code, make it paged. // DataTableEntry = MiLookupDataTableEntry (AddressWithinSection, FALSE); if (DataTableEntry->SectionPointer != NULL) { // // Driver is mapped by image hence already paged. // ASSERT (MI_IS_SESSION_IMAGE_ADDRESS (AddressWithinSection) == TRUE); return; } Base = DataTableEntry->DllBase; NtHeaders = (PIMAGE_NT_HEADERS) RtlImageNtHeader (Base); if (NtHeaders == NULL) { return; } FoundSection = (PIMAGE_SECTION_HEADER)((PCHAR)NtHeaders + sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER) + NtHeaders->FileHeader.SizeOfOptionalHeader ); i = NtHeaders->FileHeader.NumberOfSections; PointerPte = NULL; while (i > 0) { #if DBG if ((*(PULONG)FoundSection->Name == 'tini') || (*(PULONG)FoundSection->Name == 'egap')) { DbgPrint("driver %wZ has lower case sections (init or pagexxx)\n", &DataTableEntry->FullDllName); } #endif // // Don't lock down code for sections marked as discardable or // sections marked with the first 4 characters PAGE or .eda // (for the .edata section) or INIT. // if (((FoundSection->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0) || (*(PULONG)FoundSection->Name == 'EGAP') || (*(PULONG)FoundSection->Name == 'ade.') || (*(PULONG)FoundSection->Name == 'TINI')) { NOTHING; } else { // // This section is nonpagable. // PointerPte = MiGetPteAddress ( (PCHAR)Base + FoundSection->VirtualAddress); // // Generally, SizeOfRawData is larger than VirtualSize for each // section because it includes the padding to get to the subsection // alignment boundary. However, if the image is linked with // subsection alignment == native page alignment, the linker will // have VirtualSize be much larger than SizeOfRawData because it // will account for all the bss. // Span = FoundSection->SizeOfRawData; if (Span < FoundSection->Misc.VirtualSize) { Span = FoundSection->Misc.VirtualSize; } LastPte = MiGetPteAddress ((PCHAR)Base + FoundSection->VirtualAddress + (Span - 1)); ASSERT (PointerPte <= LastPte); MiLockCode (PointerPte, LastPte, MM_LOCK_BY_NONPAGE); } i -= 1; FoundSection += 1; } return; } VOID MiClearImports ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ) /*++ Routine Description: Free up the import list and clear the pointer. This stops the recursion performed in MiDereferenceImports(). Arguments: DataTableEntry - provided for the driver. Return Value: Status of the import list construction operation. --*/ { PAGED_CODE(); if (DataTableEntry->LoadedImports == (PVOID)LOADED_AT_BOOT) { return; } if (DataTableEntry->LoadedImports == (PVOID)NO_IMPORTS_USED) { NOTHING; } else if (SINGLE_ENTRY(DataTableEntry->LoadedImports)) { NOTHING; } else { // // free the memory // ExFreePool ((PVOID)DataTableEntry->LoadedImports); } // // stop the recursion // DataTableEntry->LoadedImports = (PVOID)LOADED_AT_BOOT; } VOID MiRememberUnloadedDriver ( IN PUNICODE_STRING DriverName, IN PVOID Address, IN ULONG Length ) /*++ Routine Description: This routine saves information about unloaded drivers so that ones that forget to delete lookaside lists or queues can be caught. Arguments: DriverName - Supplies a Unicode string containing the driver's name. Address - Supplies the address the driver was loaded at. Length - Supplies the number of bytes the driver load spanned. Return Value: None. --*/ { PUNLOADED_DRIVERS Entry; ULONG NumberOfBytes; if (DriverName->Length == 0) { // // This is an aborted load and the driver name hasn't been filled // in yet. No need to save it. // return; } // // Serialization is provided by the caller, so just update the list now. // Note the allocations are nonpaged so they can be searched at bugcheck // time. // if (MmUnloadedDrivers == NULL) { NumberOfBytes = MI_UNLOADED_DRIVERS * sizeof (UNLOADED_DRIVERS); MmUnloadedDrivers = (PUNLOADED_DRIVERS)ExAllocatePoolWithTag (NonPagedPool, NumberOfBytes, 'TDmM'); if (MmUnloadedDrivers == NULL) { return; } RtlZeroMemory (MmUnloadedDrivers, NumberOfBytes); MmLastUnloadedDriver = 0; } else if (MmLastUnloadedDriver >= MI_UNLOADED_DRIVERS) { MmLastUnloadedDriver = 0; } Entry = &MmUnloadedDrivers[MmLastUnloadedDriver]; // // Free the old entry as we recycle into the new. // RtlFreeUnicodeString (&Entry->Name); Entry->Name.Buffer = ExAllocatePoolWithTag (NonPagedPool, DriverName->Length, 'TDmM'); if (Entry->Name.Buffer == NULL) { Entry->Name.MaximumLength = 0; Entry->Name.Length = 0; MiUnloadsSkipped += 1; return; } RtlCopyMemory(Entry->Name.Buffer, DriverName->Buffer, DriverName->Length); Entry->Name.Length = DriverName->Length; Entry->Name.MaximumLength = DriverName->MaximumLength; Entry->StartAddress = Address; Entry->EndAddress = (PVOID)((PCHAR)Address + Length); KeQuerySystemTime (&Entry->CurrentTime); MiTotalUnloads += 1; MmLastUnloadedDriver += 1; } PUNICODE_STRING MmLocateUnloadedDriver ( IN PVOID VirtualAddress ) /*++ Routine Description: This routine attempts to find the specified virtual address in the unloaded driver list. Arguments: VirtualAddress - Supplies a virtual address that might be within a driver that has already unloaded. Return Value: A pointer to a Unicode string containing the unloaded driver's name. Environment: Kernel mode, bugcheck time. --*/ { PUNLOADED_DRIVERS Entry; ULONG i; ULONG Index; // // No serialization is needed because we've crashed. // if (MmUnloadedDrivers == NULL) { return NULL; } Index = MmLastUnloadedDriver - 1; for (i = 0; i < MI_UNLOADED_DRIVERS; i += 1) { if (Index >= MI_UNLOADED_DRIVERS) { Index = MI_UNLOADED_DRIVERS - 1; } Entry = &MmUnloadedDrivers[Index]; if (Entry->Name.Buffer != NULL) { if ((VirtualAddress >= Entry->StartAddress) && (VirtualAddress < Entry->EndAddress)) { return &Entry->Name; } } Index -= 1; } return NULL; } NTSTATUS MmUnloadSystemImage ( IN PVOID ImageHandle ) /*++ Routine Description: This routine unloads a previously loaded system image and returns the allocated resources. Arguments: ImageHandle - Supplies a pointer to the section object of the image to unload. Return Value: Various NTSTATUS codes. Environment: Kernel mode, APC_LEVEL or below, arbitrary process context. --*/ { PFN_NUMBER PageFrameIndex; PKLDR_DATA_TABLE_ENTRY DataTableEntry; PMMPTE LastPte; PFN_NUMBER PagesRequired; PFN_NUMBER ResidentPages; PMMPTE PointerPte; PFN_NUMBER NumberOfPtes; PFN_NUMBER RoundedNumberOfPtes; PVOID BasedAddress; SIZE_T NumberOfBytes; LOGICAL MustFree; SIZE_T CommittedPages; LOGICAL ViewDeleted; PIMAGE_ENTRY_IN_SESSION DriverImage; NTSTATUS Status; PSECTION SectionPointer; PKTHREAD CurrentThread; ViewDeleted = FALSE; DataTableEntry = (PKLDR_DATA_TABLE_ENTRY)ImageHandle; BasedAddress = DataTableEntry->DllBase; // // Arbitrary process context so prevent suspend APCs now. // CurrentThread = KeGetCurrentThread (); KeEnterCriticalRegionThread (CurrentThread); KeWaitForSingleObject (&MmSystemLoadLock, WrVirtualMemory, KernelMode, FALSE, (PLARGE_INTEGER)NULL); if (DataTableEntry->LoadedImports == (PVOID)LOADED_AT_BOOT) { // // Any driver loaded at boot that did not have its import list // and LoadCount reconstructed cannot be unloaded because we don't // know how many other drivers may be linked to it. // KeReleaseMutant (&MmSystemLoadLock, 1, FALSE, FALSE); KeLeaveCriticalRegionThread (CurrentThread); return STATUS_SUCCESS; } ASSERT (DataTableEntry->LoadCount != 0); if (MI_IS_SESSION_IMAGE_ADDRESS (BasedAddress)) { // // A printer driver may be referenced multiple times for the // same session space. Only unload the last reference. // DriverImage = MiSessionLookupImage (BasedAddress); ASSERT (DriverImage); ASSERT (DriverImage->ImageCountInThisSession); DriverImage->ImageCountInThisSession -= 1; if (DriverImage->ImageCountInThisSession != 0) { KeReleaseMutant (&MmSystemLoadLock, 1, FALSE, FALSE); KeLeaveCriticalRegionThread (CurrentThread); return STATUS_SUCCESS; } // // The reference count for this image has dropped to zero in this // session, so we can delete this session's view of the image. // NumberOfBytes = DataTableEntry->SizeOfImage; // // Free the session space taken up by the image, unmapping it from // the current VA space - note this does not remove page table pages // from the session PageTables[]. Each data page is only freed // if there are no other references to it (ie: from any other // sessions). // PointerPte = MiGetPteAddress (BasedAddress); LastPte = MiGetPteAddress ((PVOID)((ULONG_PTR)BasedAddress + NumberOfBytes)); PagesRequired = MiDeleteSystemPagableVm (PointerPte, (PFN_NUMBER)(LastPte - PointerPte), ZeroKernelPte, TRUE, &ResidentPages); // // Note resident available is returned here without waiting for load // count to reach zero because it is charged each time a session space // driver locks down its code or data regardless of whether it is really // the same copy-on-write backing page(s) that some other session has // already locked down. // MI_INCREMENT_RESIDENT_AVAILABLE (ResidentPages, MM_RESAVAIL_FREE_UNLOAD_SYSTEM_IMAGE); SectionPointer = (PSECTION) DataTableEntry->SectionPointer; ASSERT (SectionPointer != NULL); ASSERT (SectionPointer->Segment->u1.ImageCommitment != 0); if (BasedAddress != SectionPointer->Segment->BasedAddress) { CommittedPages = SectionPointer->Segment->TotalNumberOfPtes; } else { CommittedPages = SectionPointer->Segment->u1.ImageCommitment; } InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages, 0 - CommittedPages); MM_BUMP_SESS_COUNTER(MM_DBG_SESSION_COMMIT_IMAGE_UNLOAD, (ULONG)CommittedPages); ViewDeleted = TRUE; // // Return the commitment we took out on the pagefile when the // image was allocated. // MiReturnCommitment (CommittedPages); MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_DRIVER_UNLOAD, CommittedPages); // // Tell the session space image handler that we are releasing // our claim to the image. // ASSERT (DataTableEntry->LoadCount != 0); MiRemoveImageSessionWide (DataTableEntry, BasedAddress, DataTableEntry->SizeOfImage); ASSERT (MiSessionLookupImage (BasedAddress) == NULL); } ASSERT (DataTableEntry->LoadCount != 0); DataTableEntry->LoadCount -= 1; if (DataTableEntry->LoadCount != 0) { KeReleaseMutant (&MmSystemLoadLock, 1, FALSE, FALSE); KeLeaveCriticalRegionThread (CurrentThread); return STATUS_SUCCESS; } if (MmSnapUnloads) { #if 0 PVOID StillQueued; StillQueued = KeCheckForTimer (DataTableEntry->DllBase, DataTableEntry->SizeOfImage); if (StillQueued != NULL) { KeBugCheckEx (DRIVER_VERIFIER_DETECTED_VIOLATION, 0x18, (ULONG_PTR)StillQueued, (ULONG_PTR)-1, (ULONG_PTR)DataTableEntry->DllBase); } StillQueued = ExpCheckForResource (DataTableEntry->DllBase, DataTableEntry->SizeOfImage); if (StillQueued != NULL) { KeBugCheckEx (DRIVER_VERIFIER_DETECTED_VIOLATION, 0x19, (ULONG_PTR)StillQueued, (ULONG_PTR)-1, (ULONG_PTR)DataTableEntry->DllBase); } #endif } if (MmVerifierData.Level & DRIVER_VERIFIER_DEADLOCK_DETECTION) { VerifierDeadlockFreePool (DataTableEntry->DllBase, DataTableEntry->SizeOfImage); } if (DataTableEntry->Flags & LDRP_IMAGE_VERIFYING) { MiVerifyingDriverUnloading (DataTableEntry); } if (MiActiveVerifierThunks != 0) { MiVerifierCheckThunks (DataTableEntry); } // // Unload symbols from debugger. // if (DataTableEntry->Flags & LDRP_DEBUG_SYMBOLS_LOADED) { // // TEMP TEMP TEMP rip out when debugger converted // ANSI_STRING AnsiName; Status = RtlUnicodeStringToAnsiString (&AnsiName, &DataTableEntry->BaseDllName, TRUE); if (NT_SUCCESS (Status)) { DbgUnLoadImageSymbols (&AnsiName, BasedAddress, (ULONG)-1); RtlFreeAnsiString (&AnsiName); } } // // No unload can happen till after Mm has finished Phase 1 initialization. // Therefore, large pages are already in effect (if this platform supports // it). // if (ViewDeleted == FALSE) { NumberOfPtes = DataTableEntry->SizeOfImage >> PAGE_SHIFT; if (MmSnapUnloads) { MiRememberUnloadedDriver (&DataTableEntry->BaseDllName, BasedAddress, (ULONG)(NumberOfPtes << PAGE_SHIFT)); } if (DataTableEntry->Flags & LDRP_SYSTEM_MAPPED) { if (MI_PDE_MAPS_LARGE_PAGE (MiGetPdeAddress (BasedAddress))) { PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (MiGetPdeAddress (BasedAddress)) + MiGetPteOffset (BasedAddress); RoundedNumberOfPtes = MI_ROUND_TO_SIZE (NumberOfPtes, MM_MINIMUM_VA_FOR_LARGE_PAGE >> PAGE_SHIFT); MiUnmapLargePages (BasedAddress, RoundedNumberOfPtes << PAGE_SHIFT); // // MiFreeContiguousPages is going to return commitment // and resident available so don't do it here. // MiRemoveCachedRange (PageFrameIndex, PageFrameIndex + RoundedNumberOfPtes - 1); MiFreeContiguousPages (PageFrameIndex, NumberOfPtes); PagesRequired = NumberOfPtes; } else { PointerPte = MiGetPteAddress (BasedAddress); PagesRequired = MiDeleteSystemPagableVm (PointerPte, NumberOfPtes, ZeroKernelPte, FALSE, &ResidentPages); // // Note that drivers loaded at boot that have not been relocated // have no system PTEs or commit charged. // MiReleaseSystemPtes (PointerPte, (ULONG)NumberOfPtes, SystemPteSpace); MI_INCREMENT_RESIDENT_AVAILABLE (ResidentPages, MM_RESAVAIL_FREE_UNLOAD_SYSTEM_IMAGE1); MiReturnCommitment (PagesRequired); MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_DRIVER_UNLOAD1, PagesRequired); InterlockedExchangeAdd (&MmTotalSystemDriverPages, 0 - (ULONG)(PagesRequired - ResidentPages)); } if (DataTableEntry->SectionPointer != NULL) { InterlockedExchangeAdd ((PLONG)&MmDriverCommit, (LONG) (0 - PagesRequired)); } } else { // // This must be a boot driver that was not relocated into // system PTEs. If large or super pages are enabled, the // image pages must be freed without referencing the // non-existent page table pages. If large/super pages are // not enabled, note that system PTEs were not used to map the // image and thus, cannot be freed. // // This is further complicated by the fact that the INIT and/or // discardable portions of these images may have already been freed. // MI_INCREMENT_RESIDENT_AVAILABLE (NumberOfPtes, MM_RESAVAIL_FREE_UNLOAD_SYSTEM_IMAGE1); MiReturnCommitment (NumberOfPtes); MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_DRIVER_UNLOAD1, NumberOfPtes); } } // // Search the loaded module list for the data table entry that describes // the DLL that was just unloaded. It is possible an entry is not in the // list if a failure occurred at a point in loading the DLL just before // the data table entry was generated. // if (DataTableEntry->InLoadOrderLinks.Flink != NULL) { MiProcessLoaderEntry (DataTableEntry, FALSE); MustFree = TRUE; } else { MustFree = FALSE; } // // Handle unloading of any dependent DLLs that we loaded automatically // for this image. // MiDereferenceImports ((PLOAD_IMPORTS)DataTableEntry->LoadedImports); MiClearImports (DataTableEntry); // // Free this loader entry. // if (MustFree == TRUE) { if (DataTableEntry->FullDllName.Buffer != NULL) { ExFreePool (DataTableEntry->FullDllName.Buffer); } // // Dereference the section object (session images only). // if (DataTableEntry->SectionPointer != NULL) { ObDereferenceObject (DataTableEntry->SectionPointer); } if (DataTableEntry->PatchInformation) { MiRundownHotpatchList (DataTableEntry->PatchInformation); } ExFreePool (DataTableEntry); } KeReleaseMutant (&MmSystemLoadLock, 1, FALSE, FALSE); KeLeaveCriticalRegionThread (CurrentThread); PERFINFO_IMAGE_UNLOAD(BasedAddress); return STATUS_SUCCESS; } NTSTATUS MiBuildImportsForBootDrivers ( VOID ) /*++ Routine Description: Construct an import list chain for boot-loaded drivers. If this cannot be done for an entry, its chain is set to LOADED_AT_BOOT. If a chain can be successfully built, then this driver's DLLs will be automatically unloaded if this driver goes away (provided no other driver is also using them). Otherwise, on driver unload, its dependent DLLs would have to be explicitly unloaded. Note that the incoming LoadCount values are not correct and thus, they are reinitialized here. Arguments: None. Return Value: Various NTSTATUS codes. --*/ { PKLDR_DATA_TABLE_ENTRY DataTableEntry; PLIST_ENTRY NextEntry; PKLDR_DATA_TABLE_ENTRY DataTableEntry2; PLIST_ENTRY NextEntry2; ULONG i; ULONG j; ULONG ImageCount; PVOID *ImageReferences; PVOID LastImageReference; PULONG_PTR ImportThunk; ULONG_PTR BaseAddress; ULONG_PTR LastAddress; ULONG ImportSize; ULONG ImportListSize; PLOAD_IMPORTS ImportList; LOGICAL UndoEverything; PKLDR_DATA_TABLE_ENTRY KernelDataTableEntry; PKLDR_DATA_TABLE_ENTRY HalDataTableEntry; UNICODE_STRING KernelString; UNICODE_STRING HalString; PAGED_CODE(); ImageCount = 0; KernelDataTableEntry = NULL; HalDataTableEntry = NULL; #define KERNEL_NAME L"ntoskrnl.exe" KernelString.Buffer = (const PUSHORT) KERNEL_NAME; KernelString.Length = sizeof (KERNEL_NAME) - sizeof (WCHAR); KernelString.MaximumLength = sizeof KERNEL_NAME; #define HAL_NAME L"hal.dll" HalString.Buffer = (const PUSHORT) HAL_NAME; HalString.Length = sizeof (HAL_NAME) - sizeof (WCHAR); HalString.MaximumLength = sizeof HAL_NAME; NextEntry = PsLoadedModuleList.Flink; while (NextEntry != &PsLoadedModuleList) { DataTableEntry = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (RtlEqualUnicodeString (&KernelString, &DataTableEntry->BaseDllName, TRUE)) { KernelDataTableEntry = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); } else if (RtlEqualUnicodeString (&HalString, &DataTableEntry->BaseDllName, TRUE)) { HalDataTableEntry = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); } // // Initialize these properly so error recovery is simplified. // if (DataTableEntry->Flags & LDRP_DRIVER_DEPENDENT_DLL) { if ((DataTableEntry == HalDataTableEntry) || (DataTableEntry == KernelDataTableEntry)) { DataTableEntry->LoadCount = 1; } else { DataTableEntry->LoadCount = 0; } } else { DataTableEntry->LoadCount = 1; } DataTableEntry->LoadedImports = (PVOID)LOADED_AT_BOOT; ImageCount += 1; NextEntry = NextEntry->Flink; } if (KernelDataTableEntry == NULL || HalDataTableEntry == NULL) { return STATUS_NOT_FOUND; } ImageReferences = (PVOID *) ExAllocatePoolWithTag (PagedPool | POOL_COLD_ALLOCATION, ImageCount * sizeof (PVOID), 'TDmM'); if (ImageReferences == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } UndoEverything = FALSE; NextEntry = PsLoadedModuleList.Flink; for ( ; NextEntry != &PsLoadedModuleList; NextEntry = NextEntry->Flink) { DataTableEntry = CONTAINING_RECORD (NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); ImportThunk = (PULONG_PTR)RtlImageDirectoryEntryToData ( DataTableEntry->DllBase, TRUE, IMAGE_DIRECTORY_ENTRY_IAT, &ImportSize); if (ImportThunk == NULL) { DataTableEntry->LoadedImports = NO_IMPORTS_USED; continue; } RtlZeroMemory (ImageReferences, ImageCount * sizeof (PVOID)); ImportSize /= sizeof(PULONG_PTR); BaseAddress = 0; // // Initializing these locals is not needed for correctness, but // without it the compiler cannot compile this code W4 to check // for use of uninitialized variables. // j = 0; LastAddress = 0; for (i = 0; i < ImportSize; i += 1, ImportThunk += 1) { // // Check the hint first. // if (BaseAddress != 0) { if (*ImportThunk >= BaseAddress && *ImportThunk < LastAddress) { ASSERT (ImageReferences[j]); continue; } } j = 0; NextEntry2 = PsLoadedModuleList.Flink; while (NextEntry2 != &PsLoadedModuleList) { DataTableEntry2 = CONTAINING_RECORD(NextEntry2, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); BaseAddress = (ULONG_PTR) DataTableEntry2->DllBase; LastAddress = BaseAddress + DataTableEntry2->SizeOfImage; if (*ImportThunk >= BaseAddress && *ImportThunk < LastAddress) { ImageReferences[j] = DataTableEntry2; break; } NextEntry2 = NextEntry2->Flink; j += 1; } if (*ImportThunk < BaseAddress || *ImportThunk >= LastAddress) { if (*ImportThunk) { #if DBG DbgPrint ("MM: broken import linkage %p %p %p\n", DataTableEntry, ImportThunk, *ImportThunk); DbgBreakPoint (); #endif UndoEverything = TRUE; goto finished; } BaseAddress = 0; } } ImportSize = 0; // // Initializing LastImageReference is not needed for correctness, but // without it the compiler cannot compile this code W4 to check // for use of uninitialized variables. // LastImageReference = NULL; for (i = 0; i < ImageCount; i += 1) { if ((ImageReferences[i] != NULL) && (ImageReferences[i] != KernelDataTableEntry) && (ImageReferences[i] != HalDataTableEntry)) { LastImageReference = ImageReferences[i]; ImportSize += 1; } } if (ImportSize == 0) { DataTableEntry->LoadedImports = NO_IMPORTS_USED; } else if (ImportSize == 1) { #if DBG_SYSLOAD DbgPrint("driver %wZ imports %wZ\n", &DataTableEntry->FullDllName, &((PKLDR_DATA_TABLE_ENTRY)LastImageReference)->FullDllName); #endif DataTableEntry->LoadedImports = POINTER_TO_SINGLE_ENTRY (LastImageReference); ((PKLDR_DATA_TABLE_ENTRY)LastImageReference)->LoadCount += 1; } else { #if DBG_SYSLOAD DbgPrint("driver %wZ imports many\n", &DataTableEntry->FullDllName); #endif ImportListSize = ImportSize * sizeof(PVOID) + sizeof(SIZE_T); ImportList = (PLOAD_IMPORTS) ExAllocatePoolWithTag (PagedPool | POOL_COLD_ALLOCATION, ImportListSize, 'TDmM'); if (ImportList == NULL) { UndoEverything = TRUE; break; } ImportList->Count = ImportSize; j = 0; for (i = 0; i < ImageCount; i += 1) { if ((ImageReferences[i] != NULL) && (ImageReferences[i] != KernelDataTableEntry) && (ImageReferences[i] != HalDataTableEntry)) { #if DBG_SYSLOAD DbgPrint("driver %wZ imports %wZ\n", &DataTableEntry->FullDllName, &((PKLDR_DATA_TABLE_ENTRY)ImageReferences[i])->FullDllName); #endif ImportList->Entry[j] = ImageReferences[i]; ((PKLDR_DATA_TABLE_ENTRY)ImageReferences[i])->LoadCount += 1; j += 1; } } ASSERT (j == ImportSize); DataTableEntry->LoadedImports = ImportList; } #if DBG_SYSLOAD DbgPrint("\n"); #endif } finished: ExFreePool ((PVOID)ImageReferences); // // The kernel and HAL are never unloaded. // if ((KernelDataTableEntry->LoadedImports != NO_IMPORTS_USED) && (!POINTER_TO_SINGLE_ENTRY(KernelDataTableEntry->LoadedImports))) { ExFreePool ((PVOID)KernelDataTableEntry->LoadedImports); } if ((HalDataTableEntry->LoadedImports != NO_IMPORTS_USED) && (!POINTER_TO_SINGLE_ENTRY(HalDataTableEntry->LoadedImports))) { ExFreePool ((PVOID)HalDataTableEntry->LoadedImports); } KernelDataTableEntry->LoadedImports = (PVOID)LOADED_AT_BOOT; HalDataTableEntry->LoadedImports = (PVOID)LOADED_AT_BOOT; if (UndoEverything == TRUE) { #if DBG_SYSLOAD DbgPrint("driver %wZ import rebuild failed\n", &DataTableEntry->FullDllName); DbgBreakPoint(); #endif // // An error occurred and this is an all or nothing operation so // roll everything back. // NextEntry = PsLoadedModuleList.Flink; while (NextEntry != &PsLoadedModuleList) { DataTableEntry = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); ImportList = DataTableEntry->LoadedImports; if (ImportList == LOADED_AT_BOOT || ImportList == NO_IMPORTS_USED || SINGLE_ENTRY(ImportList)) { NOTHING; } else { ExFreePool (ImportList); } DataTableEntry->LoadedImports = (PVOID)LOADED_AT_BOOT; DataTableEntry->LoadCount = 1; NextEntry = NextEntry->Flink; } return STATUS_INSUFFICIENT_RESOURCES; } return STATUS_SUCCESS; } LOGICAL MiCallDllUnloadAndUnloadDll( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ) /*++ Routine Description: All the references from other drivers to this DLL have been cleared. The only remaining issue is that this DLL must support being unloaded. This means having no outstanding DPCs, allocated pool, etc. If the DLL has an unload routine that returns SUCCESS, then we clean it up and free up its memory now. Note this routine is NEVER called for drivers - only for DLLs that were loaded due to import references from various drivers. Arguments: DataTableEntry - provided for the DLL. Return Value: TRUE if the DLL was successfully unloaded, FALSE if not. --*/ { PMM_DLL_UNLOAD Func; NTSTATUS Status; LOGICAL Unloaded; PAGED_CODE(); Unloaded = FALSE; Func = (PMM_DLL_UNLOAD) (ULONG_PTR) MiLocateExportName (DataTableEntry->DllBase, "DllUnload"); if (Func) { // // The unload function was found in the DLL so unload it now. // Status = Func(); if (NT_SUCCESS(Status)) { // // Set up the reference count so the import DLL looks like a regular // driver image is being unloaded. // ASSERT (DataTableEntry->LoadCount == 0); DataTableEntry->LoadCount = 1; MmUnloadSystemImage ((PVOID)DataTableEntry); Unloaded = TRUE; } } return Unloaded; } PVOID MiLocateExportName ( IN PVOID DllBase, IN PCHAR FunctionName ) /*++ Routine Description: This function is invoked to locate a function name in an export directory. Arguments: DllBase - Supplies the image base. FunctionName - Supplies the the name to be located. Return Value: The address of the located function or NULL. --*/ { PVOID Func; PULONG NameTableBase; PUSHORT NameOrdinalTableBase; PIMAGE_EXPORT_DIRECTORY ExportDirectory; PULONG Addr; ULONG ExportSize; ULONG Low; ULONG Middle; ULONG High; LONG Result; USHORT OrdinalNumber; PAGED_CODE(); Func = NULL; // // Locate the DLL's export directory. // ExportDirectory = (PIMAGE_EXPORT_DIRECTORY) RtlImageDirectoryEntryToData ( DllBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ExportSize); if (ExportDirectory) { NameTableBase = (PULONG)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfNames); NameOrdinalTableBase = (PUSHORT)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfNameOrdinals); // // Look in the export name table for the specified function name. // Low = 0; Middle = 0; High = ExportDirectory->NumberOfNames - 1; while (High >= Low && (LONG)High >= 0) { // // Compute the next probe index and compare the export name entry // with the specified function name. // Middle = (Low + High) >> 1; Result = strcmp(FunctionName, (PCHAR)((PCHAR)DllBase + NameTableBase[Middle])); if (Result < 0) { High = Middle - 1; } else if (Result > 0) { Low = Middle + 1; } else { break; } } // // If the high index is less than the low index, then a matching table // entry was not found. Otherwise, get the ordinal number from the // ordinal table and location the function address. // if ((LONG)High >= (LONG)Low) { OrdinalNumber = NameOrdinalTableBase[Middle]; Addr = (PULONG)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfFunctions); Func = (PVOID)((ULONG_PTR)DllBase + Addr[OrdinalNumber]); // // If the function address is w/in range of the export directory, // then the function is forwarded, which is not allowed, so ignore // it. // if ((ULONG_PTR)Func > (ULONG_PTR)ExportDirectory && (ULONG_PTR)Func < ((ULONG_PTR)ExportDirectory + ExportSize)) { Func = NULL; } } } return Func; } NTSTATUS MiDereferenceImports ( IN PLOAD_IMPORTS ImportList ) /*++ Routine Description: Decrement the reference count on each DLL specified in the image import list. If any DLL's reference count reaches zero, then free the DLL. No locks may be held on entry as MmUnloadSystemImage may be called. The parameter list is freed here as well. Arguments: ImportList - Supplies the list of DLLs to dereference. Return Value: Status of the dereference operation. --*/ { ULONG i; LOGICAL Unloaded; PVOID SavedImports; LOAD_IMPORTS SingleTableEntry; PKLDR_DATA_TABLE_ENTRY ImportTableEntry; PAGED_CODE(); if (ImportList == LOADED_AT_BOOT || ImportList == NO_IMPORTS_USED) { return STATUS_SUCCESS; } if (SINGLE_ENTRY(ImportList)) { SingleTableEntry.Count = 1; SingleTableEntry.Entry[0] = SINGLE_ENTRY_TO_POINTER(ImportList); ImportList = &SingleTableEntry; } for (i = 0; i < ImportList->Count && ImportList->Entry[i]; i += 1) { ImportTableEntry = ImportList->Entry[i]; if (ImportTableEntry->LoadedImports == (PVOID)LOADED_AT_BOOT) { // // Skip this one - it was loaded by ntldr. // continue; } #if DBG { ULONG ImageCount; PLIST_ENTRY NextEntry; PKLDR_DATA_TABLE_ENTRY DataTableEntry; // // Assert that the first 2 entries are never dereferenced as // unloading the kernel or HAL would be fatal. // NextEntry = PsLoadedModuleList.Flink; ImageCount = 0; while (NextEntry != &PsLoadedModuleList && ImageCount < 2) { DataTableEntry = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); ASSERT (ImportTableEntry != DataTableEntry); ASSERT (DataTableEntry->LoadCount == 1); NextEntry = NextEntry->Flink; ImageCount += 1; } } #endif ASSERT (ImportTableEntry->LoadCount >= 1); ImportTableEntry->LoadCount -= 1; if (ImportTableEntry->LoadCount == 0) { // // Unload this dependent DLL - we only do this to non-referenced // non-boot-loaded drivers. Stop the import list recursion prior // to unloading - we know we're done at this point. // // Note we can continue on afterwards without restarting // regardless of which locks get released and reacquired // because this chain is private. // SavedImports = ImportTableEntry->LoadedImports; ImportTableEntry->LoadedImports = (PVOID)NO_IMPORTS_USED; Unloaded = MiCallDllUnloadAndUnloadDll ((PVOID)ImportTableEntry); if (Unloaded == TRUE) { // // This DLL was unloaded so recurse through its imports and // attempt to unload all of those too. // MiDereferenceImports ((PLOAD_IMPORTS)SavedImports); if ((SavedImports != (PVOID)LOADED_AT_BOOT) && (SavedImports != (PVOID)NO_IMPORTS_USED) && (!SINGLE_ENTRY(SavedImports))) { ExFreePool (SavedImports); } } else { ImportTableEntry->LoadedImports = SavedImports; } } } return STATUS_SUCCESS; } NTSTATUS MiResolveImageReferences ( PVOID ImageBase, IN PUNICODE_STRING ImageFileDirectory, IN PUNICODE_STRING NamePrefix OPTIONAL, OUT PCHAR *MissingProcedureName, OUT PWSTR *MissingDriverName, OUT PLOAD_IMPORTS *LoadedImports ) /*++ Routine Description: This routine resolves the references from the newly loaded driver to the kernel, HAL and other drivers. Arguments: ImageBase - Supplies the address of which the image header resides. ImageFileDirectory - Supplies the directory to load referenced DLLs. Return Value: Status of the image reference resolution. --*/ { PCHAR MissingProcedureStorageArea; PVOID ImportBase; ULONG ImportSize; ULONG ImportListSize; ULONG Count; ULONG i; PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor; PIMAGE_IMPORT_DESCRIPTOR Imp; NTSTATUS st; ULONG ExportSize; PIMAGE_EXPORT_DIRECTORY ExportDirectory; PIMAGE_THUNK_DATA NameThunk; PIMAGE_THUNK_DATA AddrThunk; PSZ ImportName; PLIST_ENTRY NextEntry; PKLDR_DATA_TABLE_ENTRY DataTableEntry; PKLDR_DATA_TABLE_ENTRY SingleEntry; ANSI_STRING AnsiString; UNICODE_STRING ImportName_U; UNICODE_STRING ImportDescriptorName_U; UNICODE_STRING DllToLoad; UNICODE_STRING DllToLoad2; PVOID Section; PVOID BaseAddress; LOGICAL PrefixedNameAllocated; LOGICAL ReferenceImport; ULONG LinkWin32k = 0; ULONG LinkNonWin32k = 0; PLOAD_IMPORTS ImportList; PLOAD_IMPORTS CompactedImportList; LOGICAL Loaded; UNICODE_STRING DriverDirectory; PAGED_CODE(); *LoadedImports = NO_IMPORTS_USED; MissingProcedureStorageArea = *MissingProcedureName; ImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR) RtlImageDirectoryEntryToData ( ImageBase, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ImportSize); if (ImportDescriptor == NULL) { return STATUS_SUCCESS; } // Count the number of imports so we can allocate enough room to // store them all chained off this module's LDR_DATA_TABLE_ENTRY. // Count = 0; for (Imp = ImportDescriptor; Imp->Name && Imp->OriginalFirstThunk; Imp += 1) { Count += 1; } if (Count != 0) { ImportListSize = Count * sizeof(PVOID) + sizeof(SIZE_T); ImportList = (PLOAD_IMPORTS) ExAllocatePoolWithTag (PagedPool | POOL_COLD_ALLOCATION, ImportListSize, 'TDmM'); // // Zero it so we can recover gracefully if we fail in the middle. // If the allocation failed, just don't build the import list. // if (ImportList != NULL) { RtlZeroMemory (ImportList, ImportListSize); ImportList->Count = Count; } } else { ImportList = NULL; } Count = 0; while (ImportDescriptor->Name && ImportDescriptor->OriginalFirstThunk) { ImportName = (PSZ)((PCHAR)ImageBase + ImportDescriptor->Name); // // A driver can link with win32k.sys if and only if it is a GDI // driver. // Also display drivers can only link to win32k.sys (and lego ...). // // So if we get a driver that links to win32k.sys and has more // than one set of imports, we will fail to load it. // LinkWin32k = LinkWin32k | (!_strnicmp(ImportName, "win32k", sizeof("win32k") - 1)); // // We don't want to count coverage, win32k and irt (lego) since // display drivers CAN link against these. // LinkNonWin32k = LinkNonWin32k | ((_strnicmp(ImportName, "win32k", sizeof("win32k") - 1)) && (_strnicmp(ImportName, "dxapi", sizeof("dxapi") - 1)) && (_strnicmp(ImportName, "coverage", sizeof("coverage") - 1)) && (_strnicmp(ImportName, "irt", sizeof("irt") - 1))); if (LinkNonWin32k && LinkWin32k) { MiDereferenceImports (ImportList); if (ImportList) { ExFreePool (ImportList); } return STATUS_PROCEDURE_NOT_FOUND; } if ((!_strnicmp(ImportName, "ntdll", sizeof("ntdll") - 1)) || (!_strnicmp(ImportName, "winsrv", sizeof("winsrv") - 1)) || (!_strnicmp(ImportName, "advapi32", sizeof("advapi32") - 1)) || (!_strnicmp(ImportName, "kernel32", sizeof("kernel32") - 1)) || (!_strnicmp(ImportName, "user32", sizeof("user32") - 1)) || (!_strnicmp(ImportName, "gdi32", sizeof("gdi32") - 1)) ) { MiDereferenceImports (ImportList); if (ImportList) { ExFreePool (ImportList); } return STATUS_PROCEDURE_NOT_FOUND; } if ((!_strnicmp(ImportName, "ntoskrnl", sizeof("ntoskrnl") - 1)) || (!_strnicmp(ImportName, "win32k", sizeof("win32k") - 1)) || (!_strnicmp(ImportName, "hal", sizeof("hal") - 1))) { // // These imports don't get refcounted because we don't // ever want to unload them. // ReferenceImport = FALSE; } else { ReferenceImport = TRUE; } RtlInitAnsiString (&AnsiString, ImportName); st = RtlAnsiStringToUnicodeString (&ImportName_U, &AnsiString, TRUE); if (!NT_SUCCESS(st)) { MiDereferenceImports (ImportList); if (ImportList != NULL) { ExFreePool (ImportList); } return st; } if (NamePrefix && (_strnicmp(ImportName, "ntoskrnl", sizeof("ntoskrnl") - 1) && _strnicmp(ImportName, "hal", sizeof("hal") - 1))) { ImportDescriptorName_U.MaximumLength = (USHORT)(ImportName_U.Length + NamePrefix->Length); ImportDescriptorName_U.Buffer = ExAllocatePoolWithTag (NonPagedPool, ImportDescriptorName_U.MaximumLength, 'TDmM'); if (!ImportDescriptorName_U.Buffer) { RtlFreeUnicodeString (&ImportName_U); MiDereferenceImports (ImportList); if (ImportList != NULL) { ExFreePool (ImportList); } return STATUS_INSUFFICIENT_RESOURCES; } ImportDescriptorName_U.Length = 0; RtlAppendUnicodeStringToString(&ImportDescriptorName_U, NamePrefix); RtlAppendUnicodeStringToString(&ImportDescriptorName_U, &ImportName_U); PrefixedNameAllocated = TRUE; } else { ImportDescriptorName_U = ImportName_U; PrefixedNameAllocated = FALSE; } Loaded = FALSE; ReCheck: NextEntry = PsLoadedModuleList.Flink; ImportBase = NULL; // // Initializing DataTableEntry is not needed for correctness // but without it the compiler cannot compile this code // W4 to check for use of uninitialized variables. // DataTableEntry = NULL; while (NextEntry != &PsLoadedModuleList) { DataTableEntry = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (RtlEqualUnicodeString (&ImportDescriptorName_U, &DataTableEntry->BaseDllName, TRUE)) { ImportBase = DataTableEntry->DllBase; // // Only bump the LoadCount if this thread did not initiate // the load below. If this thread initiated the load, then // the LoadCount has already been bumped as part of the // load - we only want to increment it here if we are // "attaching" to a previously loaded DLL. // if ((Loaded == FALSE) && (ReferenceImport == TRUE)) { // // Only increment the load count on the import if it is not // circular (ie: the import is not from the original // caller). // if ((DataTableEntry->Flags & LDRP_LOAD_IN_PROGRESS) == 0) { DataTableEntry->LoadCount += 1; } } break; } NextEntry = NextEntry->Flink; } if (ImportBase == NULL) { // // The DLL name was not located, attempt to load this dll. // DllToLoad.MaximumLength = (USHORT)(ImportName_U.Length + ImageFileDirectory->Length + sizeof(WCHAR)); DllToLoad.Buffer = ExAllocatePoolWithTag (NonPagedPool, DllToLoad.MaximumLength, 'TDmM'); if (DllToLoad.Buffer) { DllToLoad.Length = ImageFileDirectory->Length; RtlCopyMemory (DllToLoad.Buffer, ImageFileDirectory->Buffer, ImageFileDirectory->Length); RtlAppendStringToString ((PSTRING)&DllToLoad, (PSTRING)&ImportName_U); // // Add NULL termination in case the load fails so the name // can be returned as the PWSTR MissingDriverName. // DllToLoad.Buffer[(DllToLoad.MaximumLength - 1) / sizeof (WCHAR)] = UNICODE_NULL; st = MmLoadSystemImage (&DllToLoad, NamePrefix, NULL, FALSE, &Section, &BaseAddress); if (NT_SUCCESS(st)) { // // No need to keep the temporary name buffer around now // that there is a loaded module list entry for this DLL. // ExFreePool (DllToLoad.Buffer); } else { if ((st == STATUS_OBJECT_NAME_NOT_FOUND) && (NamePrefix == NULL) && (MI_IS_SESSION_ADDRESS (ImageBase))) { #define DRIVERS_SUBDIR_NAME L"drivers\\" DriverDirectory.Buffer = (const PUSHORT) DRIVERS_SUBDIR_NAME; DriverDirectory.Length = sizeof (DRIVERS_SUBDIR_NAME) - sizeof (WCHAR); DriverDirectory.MaximumLength = sizeof DRIVERS_SUBDIR_NAME; // // The DLL file was not located, attempt to load it // from the drivers subdirectory. This makes it // possible for drivers like win32k.sys to link to // drivers that reside in the drivers subdirectory // (like dxapi.sys). // DllToLoad2.MaximumLength = (USHORT)(ImportName_U.Length + DriverDirectory.Length + ImageFileDirectory->Length + sizeof(WCHAR)); DllToLoad2.Buffer = ExAllocatePoolWithTag (NonPagedPool, DllToLoad2.MaximumLength, 'TDmM'); if (DllToLoad2.Buffer) { DllToLoad2.Length = ImageFileDirectory->Length; RtlCopyMemory (DllToLoad2.Buffer, ImageFileDirectory->Buffer, ImageFileDirectory->Length); RtlAppendStringToString ((PSTRING)&DllToLoad2, (PSTRING)&DriverDirectory); RtlAppendStringToString ((PSTRING)&DllToLoad2, (PSTRING)&ImportName_U); // // Add NULL termination in case the load fails // so the name can be returned as the PWSTR // MissingDriverName. // DllToLoad2.Buffer[(DllToLoad2.MaximumLength - 1) / sizeof (WCHAR)] = UNICODE_NULL; st = MmLoadSystemImage (&DllToLoad2, NULL, NULL, FALSE, &Section, &BaseAddress); ExFreePool (DllToLoad.Buffer); DllToLoad.Buffer = DllToLoad2.Buffer; DllToLoad.Length = DllToLoad2.Length; DllToLoad.MaximumLength = DllToLoad2.MaximumLength; if (NT_SUCCESS(st)) { ExFreePool (DllToLoad.Buffer); goto LoadFinished; } } else { Section = NULL; BaseAddress = NULL; st = STATUS_INSUFFICIENT_RESOURCES; goto LoadFinished; } } // // Return the temporary name buffer to our caller so // the name of the DLL that failed to load can be displayed. // Set the low bit of the pointer so our caller knows to // free this buffer when he's done displaying it (as opposed // to loaded module list entries which should not be freed). // *MissingDriverName = DllToLoad.Buffer; *(PULONG)MissingDriverName |= 0x1; // // Set this to NULL so the hard error prints properly. // *MissingProcedureName = NULL; } } else { // // Initializing Section and BaseAddress is not needed for // correctness but without it the compiler cannot compile // this code W4 to check for use of uninitialized variables. // Section = NULL; BaseAddress = NULL; st = STATUS_INSUFFICIENT_RESOURCES; } LoadFinished: // // Call any needed DLL initialization now. // if (NT_SUCCESS(st)) { #if DBG PLIST_ENTRY Entry; #endif PKLDR_DATA_TABLE_ENTRY TableEntry; Loaded = TRUE; TableEntry = (PKLDR_DATA_TABLE_ENTRY) Section; ASSERT (BaseAddress == TableEntry->DllBase); #if DBG // // Lookup the dll's table entry in the loaded module list. // This is expected to always succeed. // Entry = PsLoadedModuleList.Blink; while (Entry != &PsLoadedModuleList) { TableEntry = CONTAINING_RECORD (Entry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (BaseAddress == TableEntry->DllBase) { ASSERT (TableEntry == (PKLDR_DATA_TABLE_ENTRY) Section); break; } ASSERT (TableEntry != (PKLDR_DATA_TABLE_ENTRY) Section); Entry = Entry->Blink; } ASSERT (Entry != &PsLoadedModuleList); #endif // // Call the dll's initialization routine if it has // one. This routine will reapply verifier thunks to // any modules that link to this one if necessary. // st = MmCallDllInitialize (TableEntry, &PsLoadedModuleList); // // If the module could not be properly initialized, // unload it. // if (!NT_SUCCESS(st)) { MmUnloadSystemImage ((PVOID)TableEntry); Loaded = FALSE; } } if (!NT_SUCCESS(st)) { RtlFreeUnicodeString (&ImportName_U); if (PrefixedNameAllocated == TRUE) { ExFreePool (ImportDescriptorName_U.Buffer); } MiDereferenceImports (ImportList); if (ImportList != NULL) { ExFreePool (ImportList); } return st; } goto ReCheck; } if ((ReferenceImport == TRUE) && (ImportList)) { // // Only add the image providing satisfying our imports to the // import list if the reference is not circular (ie: the import // is not from the original caller). // if ((DataTableEntry->Flags & LDRP_LOAD_IN_PROGRESS) == 0) { ImportList->Entry[Count] = DataTableEntry; Count += 1; } } RtlFreeUnicodeString (&ImportName_U); if (PrefixedNameAllocated) { ExFreePool (ImportDescriptorName_U.Buffer); } *MissingDriverName = DataTableEntry->BaseDllName.Buffer; ExportDirectory = (PIMAGE_EXPORT_DIRECTORY) RtlImageDirectoryEntryToData ( ImportBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ExportSize); if (!ExportDirectory) { MiDereferenceImports (ImportList); if (ImportList) { ExFreePool (ImportList); } return STATUS_DRIVER_ENTRYPOINT_NOT_FOUND; } // // Walk through the IAT and snap all the thunks. // if (ImportDescriptor->OriginalFirstThunk) { NameThunk = (PIMAGE_THUNK_DATA)((PCHAR)ImageBase + (ULONG)ImportDescriptor->OriginalFirstThunk); AddrThunk = (PIMAGE_THUNK_DATA)((PCHAR)ImageBase + (ULONG)ImportDescriptor->FirstThunk); while (NameThunk->u1.AddressOfData) { st = MiSnapThunk (ImportBase, ImageBase, NameThunk++, AddrThunk++, ExportDirectory, ExportSize, FALSE, MissingProcedureName); if (!NT_SUCCESS(st) ) { MiDereferenceImports (ImportList); if (ImportList) { ExFreePool (ImportList); } return st; } *MissingProcedureName = MissingProcedureStorageArea; } } ImportDescriptor += 1; } // // All the imports are successfully loaded so establish and compact // the import unload list. // if (ImportList) { // // Blank entries occur for things like the kernel, HAL & win32k.sys // that we never want to unload. Especially for things like // win32k.sys where the reference count can really hit 0. // // // Initializing SingleEntry is not needed for correctness // but without it the compiler cannot compile this code // W4 to check for use of uninitialized variables. // SingleEntry = NULL; Count = 0; for (i = 0; i < ImportList->Count; i += 1) { if (ImportList->Entry[i]) { SingleEntry = POINTER_TO_SINGLE_ENTRY(ImportList->Entry[i]); Count += 1; } } if (Count == 0) { ExFreePool(ImportList); ImportList = NO_IMPORTS_USED; } else if (Count == 1) { ExFreePool(ImportList); ImportList = (PLOAD_IMPORTS)SingleEntry; } else if (Count != ImportList->Count) { ImportListSize = Count * sizeof(PVOID) + sizeof(SIZE_T); CompactedImportList = (PLOAD_IMPORTS) ExAllocatePoolWithTag (PagedPool | POOL_COLD_ALLOCATION, ImportListSize, 'TDmM'); if (CompactedImportList) { CompactedImportList->Count = Count; Count = 0; for (i = 0; i < ImportList->Count; i += 1) { if (ImportList->Entry[i]) { CompactedImportList->Entry[Count] = ImportList->Entry[i]; Count += 1; } } ExFreePool(ImportList); ImportList = CompactedImportList; } } *LoadedImports = ImportList; } return STATUS_SUCCESS; } NTSTATUS MiSnapThunk( IN PVOID DllBase, IN PVOID ImageBase, IN PIMAGE_THUNK_DATA NameThunk, OUT PIMAGE_THUNK_DATA AddrThunk, IN PIMAGE_EXPORT_DIRECTORY ExportDirectory, IN ULONG ExportSize, IN LOGICAL SnapForwarder, OUT PCHAR *MissingProcedureName ) /*++ Routine Description: This function snaps a thunk using the specified Export Section data. If the section data does not support the thunk, then the thunk is partially snapped (Dll field is still non-null, but snap address is set). Arguments: DllBase - Base of DLL being snapped to. ImageBase - Base of image that contains the thunks to snap. Thunk - On input, supplies the thunk to snap. When successfully snapped, the function field is set to point to the address in the DLL, and the DLL field is set to NULL. ExportDirectory - Supplies the Export Section data from a DLL. SnapForwarder - Supplies TRUE if the snap is for a forwarder, and therefore Address of Data is already setup. Return Value: STATUS_SUCCESS or STATUS_DRIVER_ENTRYPOINT_NOT_FOUND or STATUS_DRIVER_ORDINAL_NOT_FOUND --*/ { BOOLEAN Ordinal; USHORT OrdinalNumber; PULONG NameTableBase; PUSHORT NameOrdinalTableBase; PULONG Addr; USHORT HintIndex; ULONG High; ULONG Low; ULONG Middle; LONG Result; NTSTATUS Status; PCHAR MissingProcedureName2; CHAR NameBuffer[ MAXIMUM_FILENAME_LENGTH ]; PAGED_CODE(); // // Determine if snap is by name, or by ordinal // Ordinal = (BOOLEAN)IMAGE_SNAP_BY_ORDINAL(NameThunk->u1.Ordinal); if (Ordinal && !SnapForwarder) { OrdinalNumber = (USHORT)(IMAGE_ORDINAL(NameThunk->u1.Ordinal) - ExportDirectory->Base); *MissingProcedureName = (PCHAR)(ULONG_PTR)OrdinalNumber; } else { // // Change AddressOfData from an RVA to a VA. // if (!SnapForwarder) { NameThunk->u1.AddressOfData = (ULONG_PTR)ImageBase + NameThunk->u1.AddressOfData; } strncpy (*MissingProcedureName, (const PCHAR)&((PIMAGE_IMPORT_BY_NAME)NameThunk->u1.AddressOfData)->Name[0], MAXIMUM_FILENAME_LENGTH - 1); // // Lookup Name in NameTable // NameTableBase = (PULONG)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfNames); NameOrdinalTableBase = (PUSHORT)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfNameOrdinals); // // Before dropping into binary search, see if // the hint index results in a successful // match. If the hint index is zero, then // drop into binary search. // HintIndex = ((PIMAGE_IMPORT_BY_NAME)NameThunk->u1.AddressOfData)->Hint; if ((ULONG)HintIndex < ExportDirectory->NumberOfNames && !strcmp((PSZ)((PIMAGE_IMPORT_BY_NAME)NameThunk->u1.AddressOfData)->Name, (PSZ)((PCHAR)DllBase + NameTableBase[HintIndex]))) { OrdinalNumber = NameOrdinalTableBase[HintIndex]; } else { // // Lookup the import name in the name table using a binary search. // Low = 0; Middle = 0; High = ExportDirectory->NumberOfNames - 1; while (High >= Low) { // // Compute the next probe index and compare the import name // with the export name entry. // Middle = (Low + High) >> 1; Result = strcmp((const PCHAR)&((PIMAGE_IMPORT_BY_NAME)NameThunk->u1.AddressOfData)->Name[0], (PCHAR)((PCHAR)DllBase + NameTableBase[Middle])); if (Result < 0) { High = Middle - 1; } else if (Result > 0) { Low = Middle + 1; } else { break; } } // // If the high index is less than the low index, then a matching // table entry was not found. Otherwise, get the ordinal number // from the ordinal table. // if ((LONG)High < (LONG)Low) { return STATUS_DRIVER_ENTRYPOINT_NOT_FOUND; } else { OrdinalNumber = NameOrdinalTableBase[Middle]; } } } // // If OrdinalNumber is not within the Export Address Table, // then DLL does not implement function. Snap to LDRP_BAD_DLL. // if ((ULONG)OrdinalNumber >= ExportDirectory->NumberOfFunctions) { Status = STATUS_DRIVER_ORDINAL_NOT_FOUND; } else { MissingProcedureName2 = NameBuffer; Addr = (PULONG)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfFunctions); *(PULONG_PTR)&AddrThunk->u1.Function = (ULONG_PTR)DllBase + Addr[OrdinalNumber]; // AddrThunk s/b used from here on. Status = STATUS_SUCCESS; if (((ULONG_PTR)AddrThunk->u1.Function > (ULONG_PTR)ExportDirectory) && ((ULONG_PTR)AddrThunk->u1.Function < ((ULONG_PTR)ExportDirectory + ExportSize)) ) { UNICODE_STRING UnicodeString; ANSI_STRING ForwardDllName; PLIST_ENTRY NextEntry; PKLDR_DATA_TABLE_ENTRY DataTableEntry; ULONG LocalExportSize; PIMAGE_EXPORT_DIRECTORY LocalExportDirectory; Status = STATUS_DRIVER_ENTRYPOINT_NOT_FOUND; // // Include the dot in the length so we can do prefix later on. // ForwardDllName.Buffer = (PCHAR)AddrThunk->u1.Function; ForwardDllName.Length = (USHORT)(strchr(ForwardDllName.Buffer, '.') - ForwardDllName.Buffer + 1); ForwardDllName.MaximumLength = ForwardDllName.Length; if (NT_SUCCESS(RtlAnsiStringToUnicodeString(&UnicodeString, &ForwardDllName, TRUE))) { NextEntry = PsLoadedModuleList.Flink; while (NextEntry != &PsLoadedModuleList) { DataTableEntry = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); // // We have to do a case INSENSITIVE comparison for // forwarder because the linker just took what is in the // def file, as opposed to looking in the exporting // image for the name. // we also use the prefix function to ignore the .exe or // .sys or .dll at the end. // if (RtlPrefixString((PSTRING)&UnicodeString, (PSTRING)&DataTableEntry->BaseDllName, TRUE)) { LocalExportDirectory = (PIMAGE_EXPORT_DIRECTORY) RtlImageDirectoryEntryToData (DataTableEntry->DllBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &LocalExportSize); if (LocalExportDirectory != NULL) { IMAGE_THUNK_DATA thunkData; PIMAGE_IMPORT_BY_NAME addressOfData; SIZE_T length; // // One extra byte for NULL termination. // length = strlen(ForwardDllName.Buffer + ForwardDllName.Length) + 1; addressOfData = (PIMAGE_IMPORT_BY_NAME) ExAllocatePoolWithTag (PagedPool, length + sizeof(IMAGE_IMPORT_BY_NAME), ' mM'); if (addressOfData) { RtlCopyMemory(&(addressOfData->Name[0]), ForwardDllName.Buffer + ForwardDllName.Length, length); addressOfData->Hint = 0; *(PULONG_PTR)&thunkData.u1.AddressOfData = (ULONG_PTR)addressOfData; Status = MiSnapThunk (DataTableEntry->DllBase, ImageBase, &thunkData, &thunkData, LocalExportDirectory, LocalExportSize, TRUE, &MissingProcedureName2); ExFreePool (addressOfData); AddrThunk->u1 = thunkData.u1; } } break; } NextEntry = NextEntry->Flink; } RtlFreeUnicodeString (&UnicodeString); } } } return Status; } NTSTATUS MmCheckSystemImage ( IN HANDLE ImageFileHandle, IN LOGICAL PurgeSection ) /*++ Routine Description: This function ensures the checksum for a system image is correct and matches the data in the image. Arguments: ImageFileHandle - Supplies the file handle of the image. This is a kernel handle (ie: cannot be tampered with by the user). PurgeSection - Supplies TRUE if the data section mapping the image should be purged prior to returning. Note that the first page could be used to speed up subsequent image section creation, but generally the cost of useless data pages sitting in transition is costly. Better to put the pages immediately on the free list to preserve the transition cache for more useful pages. Return Value: Status value. --*/ { NTSTATUS Status; NTSTATUS Status2; HANDLE Section; PVOID ViewBase; SIZE_T ViewSize; IO_STATUS_BLOCK IoStatusBlock; PIMAGE_FILE_HEADER FileHeader; PIMAGE_NT_HEADERS NtHeaders; FILE_STANDARD_INFORMATION StandardInfo; PSECTION SectionPointer; OBJECT_ATTRIBUTES ObjectAttributes; KAPC_STATE ApcState; PAGED_CODE(); InitializeObjectAttributes (&ObjectAttributes, NULL, (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE), NULL, NULL); Status = ZwCreateSection (&Section, SECTION_MAP_EXECUTE, &ObjectAttributes, NULL, PAGE_EXECUTE, SEC_COMMIT, ImageFileHandle); if (!NT_SUCCESS (Status)) { return Status; } ViewBase = NULL; ViewSize = 0; // // Since callees are not always in the context of the system process, // attach here when necessary to guarantee the driver load occurs in a // known safe address space to prevent security holes. // KeStackAttachProcess (&PsInitialSystemProcess->Pcb, &ApcState); Status = ZwMapViewOfSection (Section, NtCurrentProcess (), (PVOID *)&ViewBase, 0L, 0L, NULL, &ViewSize, ViewShare, 0L, PAGE_EXECUTE); if (!NT_SUCCESS(Status)) { KeUnstackDetachProcess (&ApcState); ZwClose (Section); return Status; } // // Now the image is mapped as a data file... Calculate its size and then // check its checksum. // Status = ZwQueryInformationFile (ImageFileHandle, &IoStatusBlock, &StandardInfo, sizeof(StandardInfo), FileStandardInformation); if (NT_SUCCESS(Status)) { try { if (!LdrVerifyMappedImageMatchesChecksum (ViewBase, StandardInfo.EndOfFile.LowPart)) { Status = STATUS_IMAGE_CHECKSUM_MISMATCH; goto out; } NtHeaders = RtlImageNtHeader (ViewBase); if (NtHeaders == NULL) { Status = STATUS_IMAGE_CHECKSUM_MISMATCH; goto out; } FileHeader = &NtHeaders->FileHeader; // // Detect configurations inadvertently trying to load 32-bit // drivers on NT64 or mismatched platform architectures, etc. // if ((FileHeader->Machine != IMAGE_FILE_MACHINE_NATIVE) || (NtHeaders->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)) { Status = STATUS_INVALID_IMAGE_PROTECT; goto out; } #if !defined(NT_UP) if (!MmVerifyImageIsOkForMpUse (ViewBase)) { Status = STATUS_IMAGE_MP_UP_MISMATCH; goto out; } #endif } except (EXCEPTION_EXECUTE_HANDLER) { Status = STATUS_IMAGE_CHECKSUM_MISMATCH; } } out: ZwUnmapViewOfSection (NtCurrentProcess (), ViewBase); KeUnstackDetachProcess (&ApcState); if (PurgeSection == TRUE) { Status2 = ObReferenceObjectByHandle (Section, SECTION_MAP_EXECUTE, MmSectionObjectType, KernelMode, (PVOID *) &SectionPointer, (POBJECT_HANDLE_INFORMATION) NULL); if (NT_SUCCESS (Status2)) { MmPurgeSection (SectionPointer->Segment->ControlArea->FilePointer->SectionObjectPointer, NULL, 0, FALSE); ObDereferenceObject (SectionPointer); } } ZwClose (Section); return Status; } #if !defined(NT_UP) BOOLEAN MmVerifyImageIsOkForMpUse ( IN PVOID BaseAddress ) { PIMAGE_NT_HEADERS NtHeaders; PAGED_CODE(); NtHeaders = RtlImageNtHeader (BaseAddress); if ((NtHeaders != NULL) && (KeNumberProcessors > 1) && (NtHeaders->FileHeader.Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY)) { return FALSE; } return TRUE; } #endif PFN_NUMBER MiDeleteSystemPagableVm ( IN PMMPTE PointerPte, IN PFN_NUMBER NumberOfPtes, IN MMPTE NewPteValue, IN LOGICAL SessionAllocation, OUT PPFN_NUMBER ResidentPages OPTIONAL ) /*++ Routine Description: This function deletes pagable system address space (paged pool or driver pagable sections). Arguments: PointerPte - Supplies the start of the PTE range to delete. NumberOfPtes - Supplies the number of PTEs in the range. NewPteValue - Supplies the new value for the PTE. SessionAllocation - Supplies TRUE if this is a range in session space. If TRUE is specified, it is assumed that the caller has already attached to the relevant session. If FALSE is supplied, then it is assumed that the range is in the systemwide global space instead. ResidentPages - If not NULL, the number of resident pages freed is returned here. Return Value: Returns the number of pages actually freed. --*/ { PMMSUPPORT Ws; PVOID VirtualAddress; PFN_NUMBER PageFrameIndex; MMPTE PteContents; PMMPFN Pfn1; PMMPFN Pfn2; PFN_NUMBER ValidPages; PFN_NUMBER PagesRequired; MMPTE NewContents; WSLE_NUMBER WsIndex; KIRQL OldIrql; MMPTE_FLUSH_LIST PteFlushList; MMWSLENTRY Locked; PFN_NUMBER PageTableFrameIndex; LOGICAL WsHeld; ASSERT (KeGetCurrentIrql() <= APC_LEVEL); ValidPages = 0; PagesRequired = 0; PteFlushList.Count = 0; WsHeld = FALSE; NewContents = NewPteValue; if (SessionAllocation == TRUE) { Ws = &MmSessionSpace->GlobalVirtualAddress->Vm; } else { Ws = &MmSystemCacheWs; } while (NumberOfPtes != 0) { PteContents = *PointerPte; if (PteContents.u.Long != ZeroKernelPte.u.Long) { if (PteContents.u.Hard.Valid == 1) { // // Once the working set mutex is acquired, it is deliberately // held until all the pages have been freed. This is because // when paged pool is running low on large servers, we need the // segment dereference thread to be able to free large amounts // quickly. Typically this thread will free 64k chunks and we // don't want to have to contend for the mutex 16 times to do // this as there may be thousands of other threads also trying // for it. // if (WsHeld == FALSE) { WsHeld = TRUE; LOCK_WORKING_SET (Ws); } PteContents = *PointerPte; if (PteContents.u.Hard.Valid == 0) { continue; } // // Delete the page. // PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (&PteContents); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); // // Check to see if this is a pagable page in which // case it needs to be removed from the working set list. // WsIndex = Pfn1->u1.WsIndex; if (WsIndex == 0) { ValidPages += 1; if (SessionAllocation == TRUE) { MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_DELVA, 1); InterlockedExchangeAddSizeT (&MmSessionSpace->NonPagablePages, -1); } } else { if (SessionAllocation == FALSE) { MiRemoveWsle (WsIndex, MmSystemCacheWorkingSetList); MiReleaseWsle (WsIndex, &MmSystemCacheWs); } else { VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte); WsIndex = MiLocateWsle (VirtualAddress, MmSessionSpace->Vm.VmWorkingSetList, WsIndex); ASSERT (WsIndex != WSLE_NULL_INDEX); // // Check to see if this entry is locked in // the working set or locked in memory. // Locked = MmSessionSpace->Wsle[WsIndex].u1.e1; MiRemoveWsle (WsIndex, MmSessionSpace->Vm.VmWorkingSetList); MiReleaseWsle (WsIndex, &MmSessionSpace->Vm); if (Locked.LockedInWs == 1 || Locked.LockedInMemory == 1) { // // This entry is locked. // MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_DELVA, 1); InterlockedExchangeAddSizeT (&MmSessionSpace->NonPagablePages, -1); ValidPages += 1; ASSERT (WsIndex < MmSessionSpace->Vm.VmWorkingSetList->FirstDynamic); MmSessionSpace->Vm.VmWorkingSetList->FirstDynamic -= 1; if (WsIndex != MmSessionSpace->Vm.VmWorkingSetList->FirstDynamic) { WSLE_NUMBER Entry; PVOID SwapVa; Entry = MmSessionSpace->Vm.VmWorkingSetList->FirstDynamic; ASSERT (MmSessionSpace->Wsle[Entry].u1.e1.Valid); SwapVa = MmSessionSpace->Wsle[Entry].u1.VirtualAddress; SwapVa = PAGE_ALIGN (SwapVa); MiSwapWslEntries (Entry, WsIndex, &MmSessionSpace->Vm, FALSE); } } else { ASSERT (WsIndex >= MmSessionSpace->Vm.VmWorkingSetList->FirstDynamic); } } } LOCK_PFN (OldIrql); #if DBG0 if ((Pfn1->u3.e2.ReferenceCount > 1) && (Pfn1->u3.e1.WriteInProgress == 0)) { DbgPrint ("MM:SYSLOAD - deleting pool locked for I/O PTE %p, pfn %p, share=%x, refcount=%x, wsindex=%x\n", PointerPte, PageFrameIndex, Pfn1->u2.ShareCount, Pfn1->u3.e2.ReferenceCount, Pfn1->u1.WsIndex); // // This case is valid only if the page being deleted // contained a lookaside free list entry that wasn't mapped // and multiple threads faulted on it and waited together. // Some of the faulted threads are still on the ready // list but haven't run yet, and so still have references // to this page that they picked up during the fault. // But this current thread has already allocated the // lookaside entry and is now freeing the entire page. // // BUT - if it is NOT the above case, we really should // trap here. However, we don't have a good way to // distinguish between the two cases. Note // that this complication was inserted when we went to // cmpxchg8 because using locks would prevent anyone from // accessing the lookaside freelist flinks like this. // // So, the ASSERT below comes out, but we leave the print // above in (with more data added) for the case where it's // not a lookaside contender with the reference count, but // is instead a truly bad reference that needs to be // debugged. The system should crash shortly thereafter // and we'll at least have the above print to help us out. // // ASSERT (Pfn1->u3.e2.ReferenceCount == 1); } #endif //DBG // // Check if this is a prototype PTE. // if (Pfn1->u3.e1.PrototypePte == 1) { PMMPTE PointerPde; ASSERT (SessionAllocation == TRUE); // // Capture the state of the modified bit for this // PTE. // MI_CAPTURE_DIRTY_BIT_TO_PFN (PointerPte, Pfn1); // // Decrement the share and valid counts of the page table // page which maps this PTE. // PointerPde = MiGetPteAddress (PointerPte); if (PointerPde->u.Hard.Valid == 0) { #if (_MI_PAGING_LEVELS < 3) if (!NT_SUCCESS(MiCheckPdeForPagedPool (PointerPte))) { #endif KeBugCheckEx (MEMORY_MANAGEMENT, 0x61940, (ULONG_PTR)PointerPte, (ULONG_PTR)PointerPde->u.Long, (ULONG_PTR)MiGetVirtualAddressMappedByPte(PointerPte)); #if (_MI_PAGING_LEVELS < 3) } #endif } PageTableFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPde); Pfn2 = MI_PFN_ELEMENT (PageTableFrameIndex); MiDecrementShareCountInline (Pfn2, PageTableFrameIndex); // // Decrement the share count for the physical page. // MiDecrementShareCount (Pfn1, PageFrameIndex); // // No need to worry about fork prototype PTEs // for kernel addresses. // ASSERT (PointerPte > MiHighestUserPte); } else { PageTableFrameIndex = Pfn1->u4.PteFrame; Pfn2 = MI_PFN_ELEMENT (PageTableFrameIndex); MiDecrementShareCountInline (Pfn2, PageTableFrameIndex); MI_SET_PFN_DELETED (Pfn1); MiDecrementShareCount (Pfn1, PageFrameIndex); } MI_WRITE_INVALID_PTE (PointerPte, NewContents); UNLOCK_PFN (OldIrql); // // Flush the TB for this virtual address. // if (PteFlushList.Count != MM_MAXIMUM_FLUSH_COUNT) { PteFlushList.FlushVa[PteFlushList.Count] = MiGetVirtualAddressMappedByPte (PointerPte); PteFlushList.Count += 1; } } else if (PteContents.u.Soft.Prototype) { ASSERT (SessionAllocation == TRUE); // // No need to worry about fork prototype PTEs // for kernel addresses. // ASSERT (PointerPte >= MiHighestUserPte); MI_WRITE_INVALID_PTE (PointerPte, NewContents); // // We currently commit for all prototype kernel mappings since // we could copy-on-write. // } else if (PteContents.u.Soft.Transition == 1) { LOCK_PFN (OldIrql); PteContents = *PointerPte; if (PteContents.u.Soft.Transition == 0) { UNLOCK_PFN (OldIrql); continue; } // // Transition, release page. // PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents); // // Set the pointer to PTE as empty so the page // is deleted when the reference count goes to zero. // Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); MI_SET_PFN_DELETED (Pfn1); PageTableFrameIndex = Pfn1->u4.PteFrame; Pfn2 = MI_PFN_ELEMENT (PageTableFrameIndex); MiDecrementShareCountInline (Pfn2, PageTableFrameIndex); // // Check the reference count for the page, if the reference // count is zero, move the page to the free list, if the // reference count is not zero, ignore this page. When the // reference count goes to zero, it will be placed on the // free list. // if (Pfn1->u3.e2.ReferenceCount == 0) { MiUnlinkPageFromList (Pfn1); MiReleasePageFileSpace (Pfn1->OriginalPte); MiInsertPageInFreeList (PageFrameIndex); } #if 0 // // This assert is not valid since pool may now be the deferred // MmUnlockPages queue in which case the reference count // will be nonzero with no write in progress pending. // if ((Pfn1->u3.e2.ReferenceCount > 1) && (Pfn1->u3.e1.WriteInProgress == 0)) { DbgPrint ("MM:SYSLOAD - deleting pool locked for I/O %p\n", PageFrameIndex); DbgBreakPoint(); } #endif //DBG MI_WRITE_INVALID_PTE (PointerPte, NewContents); UNLOCK_PFN (OldIrql); } else { // // Demand zero, release page file space. // if (PteContents.u.Soft.PageFileHigh != 0) { LOCK_PFN (OldIrql); MiReleasePageFileSpace (PteContents); UNLOCK_PFN (OldIrql); } MI_WRITE_INVALID_PTE (PointerPte, NewContents); } PagesRequired += 1; } NumberOfPtes -= 1; PointerPte += 1; } if (WsHeld == TRUE) { UNLOCK_WORKING_SET (Ws); } if (PteFlushList.Count != 0) { if (SessionAllocation == TRUE) { // // Session space has no ASN - flush the entire TB. // MI_FLUSH_ENTIRE_SESSION_TB (TRUE, TRUE); } MiFlushPteList (&PteFlushList, TRUE); } if (ARGUMENT_PRESENT(ResidentPages)) { *ResidentPages = ValidPages; } return PagesRequired; } VOID MiMarkSectionWritable ( IN PIMAGE_SECTION_HEADER SectionTableEntry ) /*++ Routine Description: This function is a nonpaged helper routine that updates the characteristics field of the argument section table entry and marks the page dirty so that subsequent session loads share the same copy. Arguments: SectionTableEntry - Supplies the relevant section table entry. Return Value: None. --*/ { PEPROCESS Process; PMMPTE PointerPte; ULONG FreeBit; PMMPFN Pfn1; PFN_NUMBER PageFrameIndex; KIRQL OldIrql; PULONG Characteristics; // // Modify the PE header through hyperspace and mark the header page // dirty so subsequent sections pick up the same copy. // // Note this makes the entire .rdata (.sdata on IA64) writable // instead of just the import tables. // Process = PsGetCurrentProcess (); PointerPte = MiGetPteAddress (&SectionTableEntry->Characteristics); LOCK_PFN (OldIrql); MiMakeSystemAddressValidPfn (&SectionTableEntry->Characteristics, OldIrql); ASSERT (PointerPte->u.Hard.Valid == 1); PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte); Characteristics = MiMapPageInHyperSpaceAtDpc (Process, PageFrameIndex); Characteristics = (PULONG)((PCHAR)Characteristics + MiGetByteOffset (&SectionTableEntry->Characteristics)); *Characteristics |= IMAGE_SCN_MEM_WRITE; MiUnmapPageInHyperSpaceFromDpc (Process, Characteristics); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); MI_SET_MODIFIED (Pfn1, 1, 0x7); if ((Pfn1->OriginalPte.u.Soft.Prototype == 0) && (Pfn1->u3.e1.WriteInProgress == 0)) { FreeBit = GET_PAGING_FILE_OFFSET (Pfn1->OriginalPte); if ((FreeBit != 0) && (FreeBit != MI_PTE_LOOKUP_NEEDED)) { MiReleaseConfirmedPageFileSpace (Pfn1->OriginalPte); } Pfn1->OriginalPte.u.Soft.PageFileHigh = 0; } UNLOCK_PFN (OldIrql); return; } VOID MiMakeEntireImageCopyOnWrite ( IN PSUBSECTION Subsection ) /*++ Routine Description: This function sets the protection of all prototype PTEs to copy on write. Arguments: Subsection - Supplies the base subsection for the entire image. Return Value: None. --*/ { PMMPTE PointerPte; PMMPTE ProtoPte; PMMPTE LastProtoPte; MMPTE PteContents; // // Note this is only called for image control areas that have at least // PAGE_SIZE subsection alignment, and so the first // subsection which maps the header can always be skipped. // while (Subsection = Subsection->NextSubsection) { // // Don't mark global subsections as copy on write even when the // image is relocated. This is easily distinguishable because // it is the only subsection that is marked readwrite. // if (Subsection->u.SubsectionFlags.Protection == MM_READWRITE) { continue; } ProtoPte = Subsection->SubsectionBase; LastProtoPte = Subsection->SubsectionBase + Subsection->PtesInSubsection; PointerPte = ProtoPte; MmLockPagedPool (ProtoPte, Subsection->PtesInSubsection * sizeof (MMPTE)); do { PteContents = *PointerPte; ASSERT (PteContents.u.Hard.Valid == 0); if (PteContents.u.Long != ZeroPte.u.Long) { if ((PteContents.u.Soft.Prototype == 0) && (PteContents.u.Soft.Transition == 1)) { if (MiSetProtectionOnTransitionPte (PointerPte, MM_EXECUTE_WRITECOPY)) { continue; } } else { PointerPte->u.Soft.Protection = MM_EXECUTE_WRITECOPY; } } PointerPte += 1; } while (PointerPte < LastProtoPte); MmUnlockPagedPool (ProtoPte, Subsection->PtesInSubsection * sizeof (MMPTE)); Subsection->u.SubsectionFlags.Protection = MM_EXECUTE_WRITECOPY; } return; } VOID MiSetSystemCodeProtection ( IN PMMPTE FirstPte, IN PMMPTE LastPte, IN ULONG ProtectionMask ) /*++ Routine Description: This function sets the protection of system code to read only. Arguments: FirstPte - Supplies the starting PTE. LastPte - Supplies the ending PTE. ProtectionMask - Supplies the desired protection mask. Return Value: None. Environment: Kernel Mode, APC_LEVEL or below. --*/ { KIRQL OldIrql; MMPTE PteContents; MMPTE TempPte; PMMPTE PointerPte; PMMPTE PointerPde; PMMPTE PointerProtoPte; PMMPFN Pfn1; LOGICAL SessionAddress; PVOID VirtualAddress; MMPTE_FLUSH_LIST PteFlushList; PETHREAD CurrentThread; PMMSUPPORT Ws; ASSERT (KeGetCurrentIrql () <= APC_LEVEL); PteFlushList.Count = 0; #if defined(_X86_) ASSERT (MI_IS_PHYSICAL_ADDRESS(MiGetVirtualAddressMappedByPte(FirstPte)) == 0); #endif CurrentThread = PsGetCurrentThread (); PointerPte = FirstPte; if (MI_IS_SESSION_ADDRESS (MiGetVirtualAddressMappedByPte(FirstPte))) { Ws = &MmSessionSpace->GlobalVirtualAddress->Vm; SessionAddress = TRUE; } else { Ws = &MmSystemCacheWs; SessionAddress = FALSE; } LOCK_WORKING_SET (Ws); // // Set these PTEs to the specified protection. // // Note that the write bit may already be off (in the valid PTE) if the // page has already been inpaged from the paging file and has not since // been dirtied. // LOCK_PFN (OldIrql); while (PointerPte <= LastPte) { PteContents = *PointerPte; if ((PteContents.u.Long == 0) || ((!*MiPteStr) && ((ProtectionMask == MM_READONLY) || (ProtectionMask == MM_EXECUTE_READ)))) { PointerPte += 1; continue; } if (PteContents.u.Hard.Valid == 1) { Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber); if (Pfn1->u3.e1.PrototypePte == 1) { // // This must be a session address. The prototype PTE contains // the protection that is pushed out to the real PTE after // it's been trimmed so update that too. // PointerProtoPte = Pfn1->PteAddress; PointerPde = MiGetPteAddress (PointerProtoPte); if (PointerPde->u.Hard.Valid == 0) { if (SessionAddress == TRUE) { // // Unlock the session working set and lock the // system working set as we need to make the backing // prototype PTE valid. // UNLOCK_PFN (OldIrql); UNLOCK_WORKING_SET (Ws); LOCK_WORKING_SET (&MmSystemCacheWs); LOCK_PFN (OldIrql); } MiMakeSystemAddressValidPfnSystemWs (PointerProtoPte, OldIrql); if (SessionAddress == TRUE) { // // Unlock the system working set and lock the // session working set as we have made the backing // prototype PTE valid and can now handle the // original session PTE. // UNLOCK_PFN (OldIrql); UNLOCK_WORKING_SET (&MmSystemCacheWs); LOCK_WORKING_SET (Ws); LOCK_PFN (OldIrql); } // // The world may have changed while we waited. // continue; } } else { PointerProtoPte = NULL; } Pfn1->OriginalPte.u.Soft.Protection = ProtectionMask; MI_MAKE_VALID_PTE (TempPte, PteContents.u.Hard.PageFrameNumber, ProtectionMask, PointerPte); // // Note the dirty and write bits get turned off here. // Any existing pagefile addresses for clean pages are preserved. // if (MI_IS_PTE_DIRTY (PteContents)) { MI_CAPTURE_DIRTY_BIT_TO_PFN (&PteContents, Pfn1); } MI_WRITE_VALID_PTE_NEW_PROTECTION (PointerPte, TempPte); if (PointerProtoPte != NULL) { MI_WRITE_VALID_PTE_NEW_PROTECTION (PointerProtoPte, TempPte); } if (PteFlushList.Count < MM_MAXIMUM_FLUSH_COUNT) { VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte); PteFlushList.FlushVa[PteFlushList.Count] = VirtualAddress; PteFlushList.Count += 1; } } else if (PteContents.u.Soft.Prototype == 1) { // // WITH REGARDS TO SESSION SPACE : // // Nothing needs to be done if the image was linked with // greater than or equal to PAGE_SIZE subsection alignment // because image section creation assigned proper protections // to each subsection. // // However, if the image had less than PAGE_SIZE subsection // alignment, then image creation uses a single copyonwrite // subsection to control the entire image, so individual // protections need to be applied now. Note well - this must // only be done *ONCE* when the image is first loaded - subsequent // loads of this image in other sessions do not need to update // the common prototype PTEs. // PointerProtoPte = MiPteToProto (PointerPte); ASSERT (!MI_IS_PHYSICAL_ADDRESS (PointerProtoPte)); PointerPde = MiGetPteAddress (PointerProtoPte); if (PointerPde->u.Hard.Valid == 0) { if (SessionAddress == TRUE) { UNLOCK_PFN (OldIrql); UNLOCK_WORKING_SET (Ws); LOCK_WORKING_SET (&MmSystemCacheWs); LOCK_PFN (OldIrql); } MiMakeSystemAddressValidPfnSystemWs (PointerProtoPte, OldIrql); if (SessionAddress == TRUE) { UNLOCK_PFN (OldIrql); UNLOCK_WORKING_SET (&MmSystemCacheWs); LOCK_WORKING_SET (Ws); LOCK_PFN (OldIrql); } // // The world may have changed while we waited. // continue; } PteContents = *PointerProtoPte; if (PteContents.u.Long != ZeroPte.u.Long) { ASSERT (PteContents.u.Hard.Valid == 0); PointerProtoPte->u.Soft.Protection = ProtectionMask; if ((PteContents.u.Soft.Prototype == 0) && (PteContents.u.Soft.Transition == 1)) { Pfn1 = MI_PFN_ELEMENT (PteContents.u.Trans.PageFrameNumber); Pfn1->OriginalPte.u.Soft.Protection = ProtectionMask; } } } else if (PteContents.u.Soft.Transition == 1) { Pfn1 = MI_PFN_ELEMENT (PteContents.u.Trans.PageFrameNumber); Pfn1->OriginalPte.u.Soft.Protection = ProtectionMask; PointerPte->u.Soft.Protection = ProtectionMask; } else { // // Must be page file space or demand zero. // PointerPte->u.Soft.Protection = ProtectionMask; } PointerPte += 1; } if (PteFlushList.Count != 0) { if (SessionAddress == TRUE) { // // Session space has no ASN - flush the entire TB. // MI_FLUSH_ENTIRE_SESSION_TB (TRUE, TRUE); } MiFlushPteList (&PteFlushList, TRUE); } UNLOCK_PFN (OldIrql); UNLOCK_WORKING_SET (Ws); return; } VOID MiWriteProtectSystemImage ( IN PVOID DllBase ) /*++ Routine Description: This function sets the protection of a system component to read only. Arguments: DllBase - Supplies the base address of the system component. Return Value: None. --*/ { ULONG SectionProtection; ULONG NumberOfSubsections; ULONG SectionVirtualSize; ULONG OffsetToSectionTable; PFN_NUMBER NumberOfPtes; ULONG_PTR VirtualAddress; PVOID LastVirtualAddress; PMMPTE PointerPte; PMMPTE FirstPte; PMMPTE LastPte; PMMPTE LastImagePte; PMMPTE WritablePte; PIMAGE_NT_HEADERS NtHeader; PIMAGE_FILE_HEADER FileHeader; PIMAGE_SECTION_HEADER SectionTableEntry; LOGICAL SessionAddress; PAGED_CODE(); if (MI_IS_PHYSICAL_ADDRESS (DllBase)) { return; } NtHeader = RtlImageNtHeader (DllBase); if (NtHeader == NULL) { return; } // // All session drivers must be one way or the other - no mixing is allowed // within multiple copy-on-write drivers. // if (MI_IS_SESSION_ADDRESS (DllBase) == 0) { // // Images prior to Win2000 were not protected from stepping all over // their (and others) code and readonly data. Here we somewhat // preserve that behavior, but don't allow them to step on anyone else. // if (NtHeader->OptionalHeader.MajorOperatingSystemVersion < 5) { return; } if (NtHeader->OptionalHeader.MajorImageVersion < 5) { return; } SessionAddress = FALSE; } else { SessionAddress = TRUE; } // // If the image has section alignment of at least PAGE_SIZE, then // the image section was created with individual subsections and // proper permissions already applied to the prototype PTEs. However, // our caller may have been changing the individual PTE protections // in order to relocate the image, so march on regardless of section // alignment. // NumberOfPtes = BYTES_TO_PAGES (NtHeader->OptionalHeader.SizeOfImage); FileHeader = &NtHeader->FileHeader; NumberOfSubsections = FileHeader->NumberOfSections; ASSERT (NumberOfSubsections != 0); OffsetToSectionTable = sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER) + FileHeader->SizeOfOptionalHeader; SectionTableEntry = (PIMAGE_SECTION_HEADER)((PCHAR)NtHeader + OffsetToSectionTable); // // Verify the image contains subsections ordered by increasing virtual // address and that there are no overlaps. // FirstPte = NULL; LastVirtualAddress = DllBase; for ( ; NumberOfSubsections > 0; NumberOfSubsections -= 1, SectionTableEntry += 1) { if (SectionTableEntry->Misc.VirtualSize == 0) { SectionVirtualSize = SectionTableEntry->SizeOfRawData; } else { SectionVirtualSize = SectionTableEntry->Misc.VirtualSize; } VirtualAddress = (ULONG_PTR)DllBase + SectionTableEntry->VirtualAddress; if ((PVOID)VirtualAddress <= LastVirtualAddress) { // // Subsections are not in an increasing virtual address ordering. // No protection is provided for such a poorly linked image. // KdPrint (("MM:sysload - Image at %p is badly linked\n", DllBase)); return; } LastVirtualAddress = (PVOID)((PCHAR)VirtualAddress + SectionVirtualSize - 1); } NumberOfSubsections = FileHeader->NumberOfSections; ASSERT (NumberOfSubsections != 0); SectionTableEntry = (PIMAGE_SECTION_HEADER)((PCHAR)NtHeader + OffsetToSectionTable); LastVirtualAddress = NULL; // // Set writable PTE here so the image headers are excluded. This is // needed so that locking down of sections can continue to edit the // image headers for counts. // WritablePte = MiGetPteAddress ((PVOID)((ULONG_PTR)(SectionTableEntry + NumberOfSubsections) - 1)); LastImagePte = MiGetPteAddress(DllBase) + NumberOfPtes; for ( ; NumberOfSubsections > 0; NumberOfSubsections -= 1, SectionTableEntry += 1) { if (SectionTableEntry->Misc.VirtualSize == 0) { SectionVirtualSize = SectionTableEntry->SizeOfRawData; } else { SectionVirtualSize = SectionTableEntry->Misc.VirtualSize; } VirtualAddress = (ULONG_PTR)DllBase + SectionTableEntry->VirtualAddress; PointerPte = MiGetPteAddress ((PVOID)VirtualAddress); if (PointerPte >= LastImagePte) { // // Skip relocation subsections (which aren't given VA space). // break; } SectionProtection = (SectionTableEntry->Characteristics & (IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE)); if (SectionProtection & IMAGE_SCN_MEM_WRITE) { // // This is a writable subsection, skip it. Make sure if it's // sharing a PTE (and update the linker so this doesn't happen // for the kernel at least) that the last PTE isn't made // read only. // WritablePte = MiGetPteAddress ((PVOID)(VirtualAddress + SectionVirtualSize - 1)); if (LastVirtualAddress != NULL) { LastPte = (PVOID) MiGetPteAddress (LastVirtualAddress); if (LastPte == PointerPte) { LastPte -= 1; } if (FirstPte <= LastPte) { ASSERT (PointerPte < LastImagePte); if (LastPte >= LastImagePte) { LastPte = LastImagePte - 1; } MiSetSystemCodeProtection (FirstPte, LastPte, MM_EXECUTE_READ); } LastVirtualAddress = NULL; } continue; } if (LastVirtualAddress == NULL) { // // There is no previous subsection or the previous // subsection was writable. Thus the current starting PTE // could be mapping both a readonly and a readwrite // subsection if the image alignment is less than PAGE_SIZE. // These cases (in either order) are handled here. // if (PointerPte == WritablePte) { LastPte = MiGetPteAddress ((PVOID)(VirtualAddress + SectionVirtualSize - 1)); if (PointerPte == LastPte) { // // Nothing can be protected in this subsection // due to the image alignment specified for the executable. // continue; } PointerPte += 1; } FirstPte = PointerPte; } LastVirtualAddress = (PVOID)((PCHAR)VirtualAddress + SectionVirtualSize - 1); } if (LastVirtualAddress != NULL) { LastPte = (PVOID) MiGetPteAddress (LastVirtualAddress); if ((FirstPte <= LastPte) && (FirstPte < LastImagePte)) { if (LastPte >= LastImagePte) { LastPte = LastImagePte - 1; } MiSetSystemCodeProtection (FirstPte, LastPte, MM_EXECUTE_READ); } } return; } VOID MiSessionProcessGlobalSubsections ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ) /*++ Routine Description: This function sets the protection of a session driver's subsections to globally shared if their PE header specifies them as such. Arguments: DataTableEntry - Supplies the loaded module list entry for the driver. Return Value: None. --*/ { PVOID DllBase; PSUBSECTION Subsection; PMMPTE RealPteBase; PMMPTE PrototypePteBase; PCONTROL_AREA ControlArea; PIMAGE_NT_HEADERS NtHeader; PMMPTE PointerPte; PMMPTE LastPte; LOGICAL GlobalSubsectionSupport; PIMAGE_ENTRY_IN_SESSION Image; ULONG Count; PAGED_CODE(); Image = MiSessionLookupImage (DataTableEntry->DllBase); if (Image != NULL) { ASSERT (MmSessionSpace->ImageLoadingCount >= 0); if (Image->ImageLoading == TRUE) { Image->ImageLoading = FALSE; ASSERT (MmSessionSpace->ImageLoadingCount > 0); InterlockedDecrement (&MmSessionSpace->ImageLoadingCount); } } else { ASSERT (FALSE); } DllBase = DataTableEntry->DllBase; ControlArea = ((PSECTION)DataTableEntry->SectionPointer)->Segment->ControlArea; ASSERT (MI_IS_PHYSICAL_ADDRESS(DllBase) == FALSE); ASSERT (MI_IS_SESSION_ADDRESS(DllBase)); NtHeader = RtlImageNtHeader (DllBase); ASSERT (NtHeader); if (NtHeader->OptionalHeader.SectionAlignment < PAGE_SIZE) { if (Image->GlobalSubs != NULL) { ExFreePool (Image->GlobalSubs); Image->GlobalSubs = NULL; } return; } // // Win XP and Win2000 did not support global shared subsections // for session images. To ensure backwards compatibility for existing // drivers, ensure that only newer ones get this feature. // GlobalSubsectionSupport = FALSE; if (NtHeader->OptionalHeader.MajorOperatingSystemVersion > 5) { GlobalSubsectionSupport = TRUE; } else if (NtHeader->OptionalHeader.MajorOperatingSystemVersion == 5) { if (NtHeader->OptionalHeader.MinorOperatingSystemVersion > 1) { GlobalSubsectionSupport = TRUE; } else if (NtHeader->OptionalHeader.MinorOperatingSystemVersion == 1) { if (NtHeader->OptionalHeader.MajorImageVersion > 5) { GlobalSubsectionSupport = TRUE; } else if (NtHeader->OptionalHeader.MajorImageVersion == 5) { if (NtHeader->OptionalHeader.MinorImageVersion > 1) { GlobalSubsectionSupport = TRUE; } else if (NtHeader->OptionalHeader.MinorImageVersion == 1) { if (NtHeader->OptionalHeader.MajorSubsystemVersion > 5) { GlobalSubsectionSupport = TRUE; } else if (NtHeader->OptionalHeader.MajorSubsystemVersion == 5) { if (NtHeader->OptionalHeader.MinorSubsystemVersion >= 2) { GlobalSubsectionSupport = TRUE; } } } } } } if (GlobalSubsectionSupport == FALSE) { if (Image->GlobalSubs != NULL) { ExFreePool (Image->GlobalSubs); Image->GlobalSubs = NULL; } return; } if ((ControlArea->u.Flags.GlobalOnlyPerSession == 0) && (ControlArea->u.Flags.Rom == 0)) { Subsection = (PSUBSECTION)(ControlArea + 1); } else { Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1); } RealPteBase = MiGetPteAddress (DllBase); PrototypePteBase = Subsection->SubsectionBase; // // Loop through all the subsections. // if (ControlArea->u.Flags.Image == 1) { do { if (Subsection->u.SubsectionFlags.GlobalMemory == 1) { PointerPte = RealPteBase + (Subsection->SubsectionBase - PrototypePteBase); LastPte = PointerPte + Subsection->PtesInSubsection - 1; MiSetSystemCodeProtection (PointerPte, LastPte, Subsection->u.SubsectionFlags.Protection); } Subsection = Subsection->NextSubsection; } while (Subsection != NULL); } else if (Image->GlobalSubs != NULL) { Count = 0; ASSERT (Subsection->NextSubsection == NULL); while (Image->GlobalSubs[Count].PteCount != 0) { PointerPte = RealPteBase + Image->GlobalSubs[Count].PteIndex; LastPte = PointerPte + Image->GlobalSubs[Count].PteCount - 1; MiSetSystemCodeProtection (PointerPte, LastPte, Image->GlobalSubs[Count].Protection); Count += 1; } ExFreePool (Image->GlobalSubs); Image->GlobalSubs = NULL; } return; } VOID MiUpdateThunks ( IN PLOADER_PARAMETER_BLOCK LoaderBlock, IN PVOID OldAddress, IN PVOID NewAddress, IN ULONG NumberOfBytes ) /*++ Routine Description: This function updates the IATs of all the loaded modules in the system to handle a newly relocated image. Arguments: LoaderBlock - Supplies a pointer to the system loader block. OldAddress - Supplies the old address of the DLL which was just relocated. NewAddress - Supplies the new address of the DLL which was just relocated. NumberOfBytes - Supplies the number of bytes spanned by the DLL. Return Value: None. --*/ { PULONG_PTR ImportThunk; ULONG_PTR OldAddressHigh; ULONG_PTR AddressDifference; PKLDR_DATA_TABLE_ENTRY DataTableEntry; PLIST_ENTRY NextEntry; ULONG_PTR i; ULONG ImportSize; // // Note this routine must not call any modules outside the kernel. // This is because that module may itself be the one being relocated right // now. // OldAddressHigh = (ULONG_PTR)((PCHAR)OldAddress + NumberOfBytes - 1); AddressDifference = (ULONG_PTR)NewAddress - (ULONG_PTR)OldAddress; NextEntry = LoaderBlock->LoadOrderListHead.Flink; for ( ; NextEntry != &LoaderBlock->LoadOrderListHead; NextEntry = NextEntry->Flink) { DataTableEntry = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); ImportThunk = (PULONG_PTR) RtlImageDirectoryEntryToData ( DataTableEntry->DllBase, TRUE, IMAGE_DIRECTORY_ENTRY_IAT, &ImportSize); if (ImportThunk == NULL) { continue; } ImportSize /= sizeof(PULONG_PTR); for (i = 0; i < ImportSize; i += 1, ImportThunk += 1) { if (*ImportThunk >= (ULONG_PTR)OldAddress && *ImportThunk <= OldAddressHigh) { *ImportThunk += AddressDifference; } } } } VOID MiReloadBootLoadedDrivers ( IN PLOADER_PARAMETER_BLOCK LoaderBlock ) /*++ Routine Description: The kernel, HAL and boot drivers are relocated by the loader. All the boot drivers are then relocated again here. This function relocates osloader-loaded images into system PTEs. This gives these images the benefits that all other drivers already enjoy, including : 1. Paging of the drivers (this is more than 500K today). 2. Write-protection of their text sections. 3. Automatic unload of drivers on last dereference. Note care must be taken when processing HIGHADJ relocations more than once. Arguments: LoaderBlock - Supplies a pointer to the system loader block. Return Value: None. Environment: Kernel mode, Phase 0 Initialization. --*/ { LOGICAL UsedLargePage; LOGICAL HasRelocations; PKLDR_DATA_TABLE_ENTRY DataTableEntry; PLIST_ENTRY NextEntry; PIMAGE_FILE_HEADER FileHeader; PIMAGE_NT_HEADERS NtHeader; PIMAGE_DATA_DIRECTORY DataDirectory; ULONG_PTR i; ULONG RoundedNumberOfPtes; ULONG NumberOfPtes; ULONG NumberOfLoaderPtes; PMMPTE PointerPte; PMMPTE LastPte; PMMPTE LoaderPte; MMPTE PteContents; MMPTE TempPte; PVOID LoaderImageAddress; PVOID NewImageAddress; NTSTATUS Status; PFN_NUMBER PageFrameIndex; PFN_NUMBER PteFramePage; PMMPTE PteFramePointer; PMMPFN Pfn1; PMMPFN Pfn2; KIRQL OldIrql; PCHAR RelocatedVa; PCHAR NonRelocatedVa; LOGICAL StopMoving; #if !defined (_X86_) // // Only try to preserve low memory on x86 machines. // MmMakeLowMemory = FALSE; #endif StopMoving = FALSE; i = 0; NextEntry = LoaderBlock->LoadOrderListHead.Flink; for ( ; NextEntry != &LoaderBlock->LoadOrderListHead; NextEntry = NextEntry->Flink) { // // Skip the kernel and the HAL. Note their relocation sections will // be automatically reclaimed. // i += 1; if (i <= 2) { continue; } DataTableEntry = CONTAINING_RECORD (NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); NtHeader = RtlImageNtHeader (DataTableEntry->DllBase); // // Ensure that the relocation section exists and that the loader // hasn't freed it already. // if (NtHeader == NULL) { continue; } FileHeader = &NtHeader->FileHeader; if (FileHeader->Characteristics & IMAGE_FILE_RELOCS_STRIPPED) { continue; } if (IMAGE_DIRECTORY_ENTRY_BASERELOC >= NtHeader->OptionalHeader.NumberOfRvaAndSizes) { continue; } DataDirectory = &NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; if (DataDirectory->VirtualAddress == 0) { HasRelocations = FALSE; } else { if (DataDirectory->VirtualAddress + DataDirectory->Size > DataTableEntry->SizeOfImage) { // // The relocation section has already been freed, the user must // be using an old loader that didn't save the relocations. // continue; } HasRelocations = TRUE; } LoaderImageAddress = DataTableEntry->DllBase; LoaderPte = MiGetPteAddress(DataTableEntry->DllBase); NumberOfLoaderPtes = (ULONG)((ROUND_TO_PAGES(DataTableEntry->SizeOfImage)) >> PAGE_SHIFT); LOCK_PFN (OldIrql); PointerPte = LoaderPte; LastPte = PointerPte + NumberOfLoaderPtes; while (PointerPte < LastPte) { ASSERT (PointerPte->u.Hard.Valid == 1); PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); // // Mark the page as modified so boot drivers that call // MmPageEntireDriver don't lose their unmodified data ! // MI_SET_MODIFIED (Pfn1, 1, 0x14); PointerPte += 1; } UNLOCK_PFN (OldIrql); NumberOfPtes = NumberOfLoaderPtes; NewImageAddress = LoaderImageAddress; UsedLargePage = MiUseLargeDriverPage (NumberOfPtes, &NewImageAddress, &DataTableEntry->BaseDllName, 0); if (UsedLargePage == TRUE) { // // This image has been loaded into a large page mapping. // RelocatedVa = NewImageAddress; NonRelocatedVa = (PCHAR) DataTableEntry->DllBase; PointerPte -= NumberOfPtes; goto Fixup; } // // Extra PTEs are allocated here to map the relocation section at the // new address so the image can be relocated. // PointerPte = MiReserveSystemPtes (NumberOfPtes, SystemPteSpace); if (PointerPte == NULL) { continue; } LastPte = PointerPte + NumberOfPtes; NewImageAddress = MiGetVirtualAddressMappedByPte (PointerPte); #if DBG_SYSLOAD DbgPrint ("Relocating %wZ from %p to %p, %x bytes\n", &DataTableEntry->FullDllName, DataTableEntry->DllBase, NewImageAddress, DataTableEntry->SizeOfImage ); #endif // // This assert is important because the assumption is made that PTEs // (not superpages) are mapping these drivers. // ASSERT (InitializationPhase == 0); // // If the system is configured to make low memory available for ISA // type drivers, then copy the boot loaded drivers now. Otherwise // only PTE adjustment is done. Presumably some day when ISA goes // away this code can be removed. // RelocatedVa = NewImageAddress; NonRelocatedVa = (PCHAR) DataTableEntry->DllBase; while (PointerPte < LastPte) { PteContents = *LoaderPte; ASSERT (PteContents.u.Hard.Valid == 1); if (MmMakeLowMemory == TRUE) { #if DBG PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (LoaderPte); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); ASSERT (Pfn1->u1.WsIndex == 0); #endif LOCK_PFN (OldIrql); if (MmAvailablePages < MM_HIGH_LIMIT) { MiEnsureAvailablePageOrWait (NULL, NULL, OldIrql); } PageFrameIndex = MiRemoveAnyPage( MI_GET_PAGE_COLOR_FROM_PTE (PointerPte)); if (PageFrameIndex < (16*1024*1024)/PAGE_SIZE) { // // If the frames cannot be replaced with high pages // then stop copying. // #if defined (_MI_MORE_THAN_4GB_) if (MiNoLowMemory == 0) #endif StopMoving = TRUE; } MI_MAKE_VALID_PTE (TempPte, PageFrameIndex, MM_EXECUTE_READWRITE, PointerPte); MI_SET_PTE_DIRTY (TempPte); MI_SET_ACCESSED_IN_PTE (&TempPte, 1); MI_WRITE_VALID_PTE (PointerPte, TempPte); MiInitializePfn (PageFrameIndex, PointerPte, 1); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); MI_SET_MODIFIED (Pfn1, 1, 0x15); // // Initialize the WsIndex just like the original page had it. // Pfn1->u1.WsIndex = 0; UNLOCK_PFN (OldIrql); RtlCopyMemory (RelocatedVa, NonRelocatedVa, PAGE_SIZE); RelocatedVa += PAGE_SIZE; NonRelocatedVa += PAGE_SIZE; } else { MI_MAKE_VALID_PTE (TempPte, PteContents.u.Hard.PageFrameNumber, MM_EXECUTE_READWRITE, PointerPte); MI_SET_PTE_DIRTY (TempPte); MI_WRITE_VALID_PTE (PointerPte, TempPte); } PointerPte += 1; LoaderPte += 1; } PointerPte -= NumberOfPtes; Fixup: ASSERT (*(PULONG)NewImageAddress == *(PULONG)LoaderImageAddress); // // Image is now mapped at the new address. Relocate it (again). // NtHeader->OptionalHeader.ImageBase = (ULONG_PTR)LoaderImageAddress; if ((MmMakeLowMemory == TRUE) || (UsedLargePage == TRUE)) { PIMAGE_NT_HEADERS NtHeader2; NtHeader2 = (PIMAGE_NT_HEADERS)((PCHAR)NtHeader + (RelocatedVa - NonRelocatedVa)); NtHeader2->OptionalHeader.ImageBase = (ULONG_PTR)LoaderImageAddress; } if (HasRelocations == TRUE) { Status = (NTSTATUS)LdrRelocateImage(NewImageAddress, (CONST PCHAR)"SYSLDR", (ULONG)STATUS_SUCCESS, (ULONG)STATUS_CONFLICTING_ADDRESSES, (ULONG)STATUS_INVALID_IMAGE_FORMAT ); if (!NT_SUCCESS(Status)) { if (UsedLargePage == TRUE) { ASSERT (MI_PDE_MAPS_LARGE_PAGE (MiGetPdeAddress (NewImageAddress))); PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (MiGetPdeAddress (NewImageAddress)) + MiGetPteOffset (NewImageAddress); RoundedNumberOfPtes = MI_ROUND_TO_SIZE (NumberOfPtes, MM_MINIMUM_VA_FOR_LARGE_PAGE >> PAGE_SHIFT); MiUnmapLargePages (NewImageAddress, RoundedNumberOfPtes << PAGE_SHIFT); MiRemoveCachedRange (PageFrameIndex, PageFrameIndex + RoundedNumberOfPtes - 1); MiFreeContiguousPages (PageFrameIndex, NumberOfPtes); } if (MmMakeLowMemory == TRUE) { while (PointerPte < LastPte) { PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); Pfn2 = MI_PFN_ELEMENT (Pfn1->u4.PteFrame); MiDecrementShareCount (Pfn2, Pfn1->u4.PteFrame); MI_SET_PFN_DELETED (Pfn1); MiDecrementShareCount (Pfn1, PageFrameIndex); PointerPte += 1; } PointerPte -= NumberOfPtes; } MiReleaseSystemPtes (PointerPte, NumberOfPtes, SystemPteSpace); if (StopMoving == TRUE) { MmMakeLowMemory = FALSE; } continue; } } // // Update the IATs for all other loaded modules that reference this one. // NonRelocatedVa = (PCHAR) DataTableEntry->DllBase; DataTableEntry->DllBase = NewImageAddress; MiUpdateThunks (LoaderBlock, LoaderImageAddress, NewImageAddress, DataTableEntry->SizeOfImage); // // Update the loaded module list entry. // DataTableEntry->Flags |= LDRP_SYSTEM_MAPPED; DataTableEntry->DllBase = NewImageAddress; DataTableEntry->EntryPoint = (PVOID)((PCHAR)NewImageAddress + NtHeader->OptionalHeader.AddressOfEntryPoint); DataTableEntry->SizeOfImage = NumberOfPtes << PAGE_SHIFT; // // Update the exception table data info // MiCaptureImageExceptionValues (DataTableEntry); // // Update the PFNs of the image to support trimming. // Note that the loader addresses are freed now so no references // to it are permitted after this point. // LoaderPte = MiGetPteAddress (NonRelocatedVa); LOCK_PFN (OldIrql); while (PointerPte < LastPte) { ASSERT ((UsedLargePage == TRUE) || (PointerPte->u.Hard.Valid == 1)); if ((MmMakeLowMemory == TRUE) || (UsedLargePage == TRUE)) { ASSERT (LoaderPte->u.Hard.Valid == 1); PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (LoaderPte); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); Pfn2 = MI_PFN_ELEMENT (Pfn1->u4.PteFrame); // // Decrement the share count on the original page table // page so it can be freed. // MiDecrementShareCount (Pfn2, Pfn1->u4.PteFrame); MI_SET_PFN_DELETED (Pfn1); MiDecrementShareCount (Pfn1, PageFrameIndex); LoaderPte += 1; } else { PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); Pfn2 = MI_PFN_ELEMENT (Pfn1->u4.PteFrame); // // Decrement the share count on the original page table // page so it can be freed. // MiDecrementShareCount (Pfn2, Pfn1->u4.PteFrame); *Pfn1->PteAddress = ZeroPte; // // Chain the PFN entry to its new page table. // PteFramePointer = MiGetPteAddress(PointerPte); PteFramePage = MI_GET_PAGE_FRAME_FROM_PTE (PteFramePointer); Pfn1->u4.PteFrame = PteFramePage; Pfn1->PteAddress = PointerPte; // // Increment the share count for the page table page that now // contains the PTE that was copied. // Pfn2 = MI_PFN_ELEMENT (PteFramePage); Pfn2->u2.ShareCount += 1; } PointerPte += 1; } UNLOCK_PFN (OldIrql); // // The physical pages mapping the relocation section are freed // later with the rest of the initialization code spanned by the // DataTableEntry->SizeOfImage. // if (StopMoving == TRUE) { MmMakeLowMemory = FALSE; } } } #if defined(_X86_) || defined(_AMD64_) PMMPTE MiKernelResourceStartPte; PMMPTE MiKernelResourceEndPte; #endif VOID MiLocateKernelSections ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ) /*++ Routine Description: This function locates the resource section in the kernel so it can be made readwrite if we bugcheck later, as the bugcheck code will write into it. Arguments: DataTableEntry - Supplies the kernel's data table entry. Return Value: None. Environment: Kernel mode, Phase 0 Initialization. --*/ { ULONG Span; PVOID CurrentBase; PIMAGE_NT_HEADERS NtHeader; PIMAGE_SECTION_HEADER SectionTableEntry; LONG i; PMMPTE PointerPte; PVOID SectionBaseAddress; CurrentBase = (PVOID) DataTableEntry->DllBase; NtHeader = RtlImageNtHeader (CurrentBase); SectionTableEntry = (PIMAGE_SECTION_HEADER)((PCHAR)NtHeader + sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER) + NtHeader->FileHeader.SizeOfOptionalHeader); // // From the image header, locate the section named '.rsrc'. // i = NtHeader->FileHeader.NumberOfSections; PointerPte = NULL; while (i > 0) { SectionBaseAddress = SECTION_BASE_ADDRESS(SectionTableEntry); // // Generally, SizeOfRawData is larger than VirtualSize for each // section because it includes the padding to get to the subsection // alignment boundary. However, if the image is linked with // subsection alignment == native page alignment, the linker will // have VirtualSize be much larger than SizeOfRawData because it // will account for all the bss. // Span = SectionTableEntry->SizeOfRawData; if (Span < SectionTableEntry->Misc.VirtualSize) { Span = SectionTableEntry->Misc.VirtualSize; } #if defined(_X86_) || defined(_AMD64_) if (*(PULONG)SectionTableEntry->Name == 'rsr.') { MiKernelResourceStartPte = MiGetPteAddress ((ULONG_PTR)CurrentBase + SectionTableEntry->VirtualAddress); MiKernelResourceEndPte = MiGetPteAddress (ROUND_TO_PAGES((ULONG_PTR)CurrentBase + SectionTableEntry->VirtualAddress + Span)); break; } #endif if (*(PULONG)SectionTableEntry->Name == 'LOOP') { if (*(PULONG)&SectionTableEntry->Name[4] == 'EDOC') { ExPoolCodeStart = (PVOID)((ULONG_PTR)CurrentBase + SectionTableEntry->VirtualAddress); ExPoolCodeEnd = (PVOID)((ULONG_PTR)CurrentBase + SectionTableEntry->VirtualAddress + Span); } else if (*(PUSHORT)&SectionTableEntry->Name[4] == 'IM') { MmPoolCodeStart = (PVOID)((ULONG_PTR)CurrentBase + SectionTableEntry->VirtualAddress); MmPoolCodeEnd = (PVOID)((ULONG_PTR)CurrentBase + SectionTableEntry->VirtualAddress + Span); } } else if ((*(PULONG)SectionTableEntry->Name == 'YSIM') && (*(PULONG)&SectionTableEntry->Name[4] == 'ETPS')) { MmPteCodeStart = (PVOID)((ULONG_PTR)CurrentBase + SectionTableEntry->VirtualAddress); MmPteCodeEnd = (PVOID)((ULONG_PTR)CurrentBase + SectionTableEntry->VirtualAddress + Span); } i -= 1; SectionTableEntry += 1; } } VOID MmMakeKernelResourceSectionWritable ( VOID ) /*++ Routine Description: This function makes the kernel's resource section readwrite so the bugcheck code can write into it. Arguments: None. Return Value: None. Environment: Kernel mode. Any IRQL. --*/ { #if defined(_X86_) || defined(_AMD64_) MMPTE TempPte; MMPTE PteContents; PMMPTE PointerPte; if (MiKernelResourceStartPte == NULL) { return; } PointerPte = MiKernelResourceStartPte; if (MI_IS_PHYSICAL_ADDRESS (MiGetVirtualAddressMappedByPte (PointerPte))) { // // Mapped physically, doesn't need to be made readwrite. // return; } // // Since the entry state and IRQL are unknown, just go through the // PTEs without a lock and make them all readwrite. // do { PteContents = *PointerPte; #if defined(NT_UP) if (PteContents.u.Hard.Write == 0) #else if (PteContents.u.Hard.Writable == 0) #endif { MI_MAKE_VALID_PTE (TempPte, PteContents.u.Hard.PageFrameNumber, MM_READWRITE, PointerPte); #if !defined(NT_UP) TempPte.u.Hard.Writable = 1; #endif MI_WRITE_VALID_PTE_NEW_PROTECTION (PointerPte, TempPte); } PointerPte += 1; } while (PointerPte < MiKernelResourceEndPte); // // Don't do this more than once. // MiKernelResourceStartPte = NULL; // // Only flush this processor as the state of the others is unknown. // KeFlushCurrentTb (); #endif } #ifdef i386 PVOID PsNtosImageBase = (PVOID)0x80100000; #else PVOID PsNtosImageBase; #endif #if DBG PVOID PsNtosImageEnd; #endif #if defined (_WIN64) INVERTED_FUNCTION_TABLE PsInvertedFunctionTable = { 0, MAXIMUM_INVERTED_FUNCTION_TABLE_SIZE, FALSE}; #endif LIST_ENTRY PsLoadedModuleList; ERESOURCE PsLoadedModuleResource; LOGICAL MiInitializeLoadedModuleList ( IN PLOADER_PARAMETER_BLOCK LoaderBlock ) /*++ Routine Description: This function initializes the loaded module list based on the LoaderBlock. Arguments: LoaderBlock - Supplies a pointer to the system loader block. Return Value: None. Environment: Kernel mode, Phase 0 Initialization. --*/ { SIZE_T CommittedPages; SIZE_T DataTableEntrySize; PLIST_ENTRY NextEntry; PLIST_ENTRY NextEntryEnd; PKLDR_DATA_TABLE_ENTRY DataTableEntry1; PKLDR_DATA_TABLE_ENTRY DataTableEntry2; CommittedPages = 0; // // Initialize the loaded module list executive resource and spin lock. // ExInitializeResourceLite (&PsLoadedModuleResource); KeInitializeSpinLock (&PsLoadedModuleSpinLock); InitializeListHead (&PsLoadedModuleList); // // Scan the loaded module list and allocate and initialize a data table // entry for each module. The data table entry is inserted in the loaded // module list and the initialization order list in the order specified // in the loader parameter block. The data table entry is inserted in the // memory order list in memory order. // NextEntry = LoaderBlock->LoadOrderListHead.Flink; NextEntryEnd = &LoaderBlock->LoadOrderListHead; DataTableEntry2 = CONTAINING_RECORD (NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); PsNtosImageBase = DataTableEntry2->DllBase; #if DBG PsNtosImageEnd = (PVOID) ((ULONG_PTR) DataTableEntry2->DllBase + DataTableEntry2->SizeOfImage); #endif MiLocateKernelSections (DataTableEntry2); #if defined (_IA64_) ExamineList: #endif while (NextEntry != NextEntryEnd) { DataTableEntry2 = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); // // Allocate a data table entry. // DataTableEntrySize = sizeof (KLDR_DATA_TABLE_ENTRY) + DataTableEntry2->BaseDllName.MaximumLength + sizeof(UNICODE_NULL); DataTableEntry1 = ExAllocatePoolWithTag (NonPagedPool, DataTableEntrySize, 'dLmM'); if (DataTableEntry1 == NULL) { return FALSE; } // // Copy the data table entry. // *DataTableEntry1 = *DataTableEntry2; // // Clear fields we may use later so they don't inherit irrelevant // loader values. // ((PKLDR_DATA_TABLE_ENTRY)DataTableEntry1)->NonPagedDebugInfo = NULL; DataTableEntry1->PatchInformation = NULL; DataTableEntry1->FullDllName.Buffer = ExAllocatePoolWithTag (PagedPool, DataTableEntry2->FullDllName.MaximumLength + sizeof(UNICODE_NULL), 'TDmM'); if (DataTableEntry1->FullDllName.Buffer == NULL) { ExFreePool (DataTableEntry1); return FALSE; } DataTableEntry1->BaseDllName.Buffer = (PWSTR)((ULONG_PTR)DataTableEntry1 + sizeof (KLDR_DATA_TABLE_ENTRY)); // // Copy the strings. // RtlCopyMemory (DataTableEntry1->FullDllName.Buffer, DataTableEntry2->FullDllName.Buffer, DataTableEntry1->FullDllName.MaximumLength); RtlCopyMemory (DataTableEntry1->BaseDllName.Buffer, DataTableEntry2->BaseDllName.Buffer, DataTableEntry1->BaseDllName.MaximumLength); DataTableEntry1->BaseDllName.Buffer[DataTableEntry1->BaseDllName.Length/sizeof(WCHAR)] = UNICODE_NULL; // // Always charge commitment regardless of whether we were able to // relocate the driver, use large pages, etc. // CommittedPages += (DataTableEntry1->SizeOfImage >> PAGE_SHIFT); #if defined (_IA64_) // // Don't calculate exception values for IA64 firmware modules. // if (NextEntryEnd != &LoaderBlock->Extension->FirmwareDescriptorListHead) #endif // // Calculate exception pointers // MiCaptureImageExceptionValues(DataTableEntry1); // // Insert the data table entry in the load order list in the order // they are specified. // InsertTailList (&PsLoadedModuleList, &DataTableEntry1->InLoadOrderLinks); #if defined (_WIN64) #if defined (_IA64_) // // Don't add IA64 firmware modules to the exception handling list. // if (NextEntryEnd != &LoaderBlock->Extension->FirmwareDescriptorListHead) #endif RtlInsertInvertedFunctionTable (&PsInvertedFunctionTable, DataTableEntry1->DllBase, DataTableEntry1->SizeOfImage); #endif NextEntry = NextEntry->Flink; } #if defined (_IA64_) // // Go pick up the firmware modules if we haven't already. // if (NextEntryEnd != &LoaderBlock->Extension->FirmwareDescriptorListHead) { NextEntry = LoaderBlock->Extension->FirmwareDescriptorListHead.Flink; NextEntryEnd = &LoaderBlock->Extension->FirmwareDescriptorListHead; goto ExamineList; } #endif // // Charge commitment for each boot loaded driver so that if unloads // later, the return will balance. Note that the actual number of // free pages is not changing now so the commit limits need to be // bumped by the same amount. // // Resident available does not need to be charged here because it // has been already (by virtue of being snapped from available pages). // MM_TRACK_COMMIT (MM_DBG_COMMIT_LOAD_SYSTEM_IMAGE_TEMP, CommittedPages); MmTotalCommittedPages += CommittedPages; MmTotalCommitLimit += CommittedPages; MmTotalCommitLimitMaximum += CommittedPages; MiBuildImportsForBootDrivers (); return TRUE; } NTSTATUS MmCallDllInitialize ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry, IN PLIST_ENTRY ModuleListHead ) /*++ Routine Description: This function calls the DLL's initialize routine. Arguments: DataTableEntry - Supplies the kernel's data table entry. Return Value: Various NTSTATUS error codes. Environment: Kernel mode. --*/ { NTSTATUS st; PWCHAR Dot; PMM_DLL_INITIALIZE Func; UNICODE_STRING RegistryPath; UNICODE_STRING ImportName; ULONG ThunksAdded; Func = (PMM_DLL_INITIALIZE)(ULONG_PTR)MiLocateExportName (DataTableEntry->DllBase, "DllInitialize"); if (!Func) { return STATUS_SUCCESS; } ImportName.MaximumLength = DataTableEntry->BaseDllName.Length; ImportName.Buffer = ExAllocatePoolWithTag (NonPagedPool, ImportName.MaximumLength, 'TDmM'); if (ImportName.Buffer == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } ImportName.Length = DataTableEntry->BaseDllName.Length; RtlCopyMemory (ImportName.Buffer, DataTableEntry->BaseDllName.Buffer, ImportName.Length); RegistryPath.MaximumLength = (USHORT)(CmRegistryMachineSystemCurrentControlSetServices.Length + ImportName.Length + 2*sizeof(WCHAR)); RegistryPath.Buffer = ExAllocatePoolWithTag (NonPagedPool, RegistryPath.MaximumLength, 'TDmM'); if (RegistryPath.Buffer == NULL) { ExFreePool (ImportName.Buffer); return STATUS_INSUFFICIENT_RESOURCES; } RegistryPath.Length = CmRegistryMachineSystemCurrentControlSetServices.Length; RtlCopyMemory (RegistryPath.Buffer, CmRegistryMachineSystemCurrentControlSetServices.Buffer, CmRegistryMachineSystemCurrentControlSetServices.Length); RtlAppendUnicodeToString (&RegistryPath, (const PUSHORT)L"\\"); Dot = wcschr (ImportName.Buffer, L'.'); if (Dot) { ImportName.Length = (USHORT)((Dot - ImportName.Buffer) * sizeof(WCHAR)); } RtlAppendUnicodeStringToString (&RegistryPath, &ImportName); ExFreePool (ImportName.Buffer); // // Save the number of verifier thunks currently added so we know // if this activation adds any. To extend the thunk list, the module // performs an NtSetSystemInformation call which calls back to the // verifier's MmAddVerifierThunks, which increments MiVerifierThunksAdded. // ThunksAdded = MiVerifierThunksAdded; // // Invoke the DLL's initialization routine. // st = Func (&RegistryPath); ExFreePool (RegistryPath.Buffer); // // If the module's initialization routine succeeded, and if it extended // the verifier thunk list, and this is boot time, reapply the verifier // to the loaded modules. // // Note that boot time is the special case because after boot time, Mm // loads all the DLLs itself and a DLL initialize is thus guaranteed to // complete and add its thunks before the importing driver load finishes. // Since the importing driver is only thunked after its load finishes, // ordering implicitly guarantees that all DLL-registered thunks are // properly factored in to the importing driver. // // Boot time is special because the loader (not the Mm) already loaded // the DLLs *AND* the importing drivers so we have to look over our // shoulder and make it all right after the fact. // if ((NT_SUCCESS(st)) && (MiFirstDriverLoadEver == 0) && (MiVerifierThunksAdded != ThunksAdded)) { MiReApplyVerifierToLoadedModules (ModuleListHead); } return st; } NTKERNELAPI PVOID MmGetSystemRoutineAddress ( IN PUNICODE_STRING SystemRoutineName ) /*++ Routine Description: This function returns the address of the argument function pointer if it is in the kernel or HAL, NULL if it is not. Arguments: SystemRoutineName - Supplies the name of the desired routine. Return Value: Non-NULL function pointer if successful. NULL if not. Environment: Kernel mode, PASSIVE_LEVEL, arbitrary process context. --*/ { PKTHREAD CurrentThread; NTSTATUS Status; PKLDR_DATA_TABLE_ENTRY DataTableEntry; ANSI_STRING AnsiString; PLIST_ENTRY NextEntry; UNICODE_STRING KernelString; UNICODE_STRING HalString; PVOID FunctionAddress; LOGICAL Found; ULONG EntriesChecked; ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL); EntriesChecked = 0; FunctionAddress = NULL; KernelString.Buffer = (const PUSHORT) KERNEL_NAME; KernelString.Length = sizeof (KERNEL_NAME) - sizeof (WCHAR); KernelString.MaximumLength = sizeof KERNEL_NAME; HalString.Buffer = (const PUSHORT) HAL_NAME; HalString.Length = sizeof (HAL_NAME) - sizeof (WCHAR); HalString.MaximumLength = sizeof HAL_NAME; do { Status = RtlUnicodeStringToAnsiString (&AnsiString, SystemRoutineName, TRUE); if (NT_SUCCESS (Status)) { break; } KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmShortTime); } while (TRUE); // // Arbitrary process context so prevent suspend APCs now. // CurrentThread = KeGetCurrentThread (); KeEnterCriticalRegionThread (CurrentThread); ExAcquireResourceSharedLite (&PsLoadedModuleResource, TRUE); // // Check only the kernel and the HAL for exports. // NextEntry = PsLoadedModuleList.Flink; while (NextEntry != &PsLoadedModuleList) { Found = FALSE; DataTableEntry = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (RtlEqualUnicodeString (&KernelString, &DataTableEntry->BaseDllName, TRUE)) { Found = TRUE; EntriesChecked += 1; } else if (RtlEqualUnicodeString (&HalString, &DataTableEntry->BaseDllName, TRUE)) { Found = TRUE; EntriesChecked += 1; } if (Found == TRUE) { FunctionAddress = MiFindExportedRoutineByName (DataTableEntry->DllBase, &AnsiString); if (FunctionAddress != NULL) { break; } if (EntriesChecked == 2) { break; } } NextEntry = NextEntry->Flink; } ExReleaseResourceLite (&PsLoadedModuleResource); KeLeaveCriticalRegionThread (CurrentThread); RtlFreeAnsiString (&AnsiString); return FunctionAddress; } PVOID MiFindExportedRoutineByName ( IN PVOID DllBase, IN PANSI_STRING AnsiImageRoutineName ) /*++ Routine Description: This function searches the argument module looking for the requested exported function name. Arguments: DllBase - Supplies the base address of the requested module. AnsiImageRoutineName - Supplies the ANSI routine name being searched for. Return Value: The virtual address of the requested routine or NULL if not found. --*/ { USHORT OrdinalNumber; PULONG NameTableBase; PUSHORT NameOrdinalTableBase; PULONG Addr; ULONG High; ULONG Low; ULONG Middle; LONG Result; ULONG ExportSize; PVOID FunctionAddress; PIMAGE_EXPORT_DIRECTORY ExportDirectory; PAGED_CODE(); ExportDirectory = (PIMAGE_EXPORT_DIRECTORY) RtlImageDirectoryEntryToData ( DllBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ExportSize); if (ExportDirectory == NULL) { return NULL; } // // Initialize the pointer to the array of RVA-based ansi export strings. // NameTableBase = (PULONG)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfNames); // // Initialize the pointer to the array of USHORT ordinal numbers. // NameOrdinalTableBase = (PUSHORT)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfNameOrdinals); // // Lookup the desired name in the name table using a binary search. // Low = 0; Middle = 0; High = ExportDirectory->NumberOfNames - 1; while (High >= Low) { // // Compute the next probe index and compare the import name // with the export name entry. // Middle = (Low + High) >> 1; Result = strcmp (AnsiImageRoutineName->Buffer, (PCHAR)DllBase + NameTableBase[Middle]); if (Result < 0) { High = Middle - 1; } else if (Result > 0) { Low = Middle + 1; } else { break; } } // // If the high index is less than the low index, then a matching // table entry was not found. Otherwise, get the ordinal number // from the ordinal table. // if ((LONG)High < (LONG)Low) { return NULL; } OrdinalNumber = NameOrdinalTableBase[Middle]; // // If the OrdinalNumber is not within the Export Address Table, // then this image does not implement the function. Return not found. // if ((ULONG)OrdinalNumber >= ExportDirectory->NumberOfFunctions) { return NULL; } // // Index into the array of RVA export addresses by ordinal number. // Addr = (PULONG)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfFunctions); FunctionAddress = (PVOID)((PCHAR)DllBase + Addr[OrdinalNumber]); // // Forwarders are not used by the kernel and HAL to each other. // ASSERT ((FunctionAddress <= (PVOID)ExportDirectory) || (FunctionAddress >= (PVOID)((PCHAR)ExportDirectory + ExportSize))); return FunctionAddress; } #if _MI_DEBUG_RONLY PMMPTE MiSessionDataStartPte; PMMPTE MiSessionDataEndPte; VOID MiAssertNotSessionData ( IN PMMPTE PointerPte ) { if (MI_IS_SESSION_IMAGE_PTE (PointerPte)) { if ((PointerPte >= MiSessionDataStartPte) && (PointerPte <= MiSessionDataEndPte)) { KeBugCheckEx (MEMORY_MANAGEMENT, 0x41287, (ULONG_PTR) PointerPte, 0, 0); } } } VOID MiLogSessionDataStart ( IN PKLDR_DATA_TABLE_ENTRY DataTableEntry ) { LONG i; PVOID CurrentBase; PIMAGE_NT_HEADERS NtHeader; PIMAGE_SECTION_HEADER SectionTableEntry; PVOID DataStart; PVOID DataEnd; if (MiSessionDataStartPte != NULL) { return; } // // Crack the image header to mark the data. // CurrentBase = (PVOID)DataTableEntry->DllBase; NtHeader = RtlImageNtHeader (CurrentBase); SectionTableEntry = (PIMAGE_SECTION_HEADER)((PCHAR)NtHeader + sizeof(ULONG) + sizeof(IMAGE_FILE_HEADER) + NtHeader->FileHeader.SizeOfOptionalHeader); i = NtHeader->FileHeader.NumberOfSections; while (i > 0) { // // Save the start and end of the data section. // if ((*(PULONG)SectionTableEntry->Name == 0x7461642e) && (*(PULONG)&SectionTableEntry->Name[4] == 0x61)) { DataStart = (PVOID)((PCHAR)CurrentBase + SectionTableEntry->VirtualAddress); // // Generally, SizeOfRawData is larger than VirtualSize for each // section because it includes the padding to get to the subsection // alignment boundary. However, if the image is linked with // subsection alignment == native page alignment, the linker will // have VirtualSize be much larger than SizeOfRawData because it // will account for all the bss. // Span = SectionTableEntry->SizeOfRawData; if (Span < SectionTableEntry->Misc.VirtualSize) { Span = SectionTableEntry->Misc.VirtualSize; } DataEnd = (PVOID)((PCHAR)DataStart + Span - 1); MiSessionDataStartPte = MiGetPteAddress (DataStart); MiSessionDataEndPte = MiGetPteAddress (DataEnd); break; } i -= 1; SectionTableEntry += 1; } } #endif