You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8515 lines
257 KiB
8515 lines
257 KiB
/*++
|
|
|
|
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
|