1282 lines
31 KiB
C
1282 lines
31 KiB
C
/*++
|
||
|
||
Copyright (c) 1997 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
sessload.c
|
||
|
||
Abstract:
|
||
|
||
This module contains the routines which implement the loading of
|
||
session space drivers.
|
||
|
||
Author:
|
||
|
||
Landy Wang (landyw) 05-Dec-1997
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "mi.h"
|
||
|
||
//
|
||
// This tracks allocated group virtual addresses. The term SESSIONWIDE is used
|
||
// to denote data that is the same across all sessions (as opposed to
|
||
// per-session data which can vary from session to session).
|
||
//
|
||
// Since each driver loaded into a session space is linked and fixed up
|
||
// against the system image, it must remain at the same virtual address
|
||
// across the system regardless of the session.
|
||
//
|
||
// A list is maintained by the group allocator of which virtual
|
||
// addresses are in use and by which DLL.
|
||
//
|
||
// The reference count tracks the number of sessions that have loaded
|
||
// this image.
|
||
//
|
||
// Access to this structure is guarded by the MmSystemLoadLock.
|
||
//
|
||
//
|
||
// typedef struct _SESSIONWIDE_DRIVER_ADDRESS {
|
||
// LIST_ENTRY Link;
|
||
// ULONG ReferenceCount;
|
||
// PVOID Address;
|
||
// ULONG_PTR Size;
|
||
// ULONG_PTR WritablePages;
|
||
// UNICODE_STRING FullDllName;
|
||
// } SESSIONWIDE_DRIVER_ADDRESS, *PSESSIONWIDE_DRIVER_ADDRESS;
|
||
//
|
||
|
||
LIST_ENTRY MmSessionWideAddressList;
|
||
|
||
//
|
||
// External function references
|
||
//
|
||
|
||
ULONG
|
||
MiSetProtectionOnTransitionPte (
|
||
IN PMMPTE PointerPte,
|
||
IN ULONG ProtectionMask
|
||
);
|
||
|
||
NTSTATUS
|
||
MiSessionInsertImage (
|
||
IN PVOID BaseAddress
|
||
);
|
||
|
||
NTSTATUS
|
||
MiSessionRemoveImage (
|
||
IN PVOID BaseAddress
|
||
);
|
||
|
||
NTSTATUS
|
||
MiSessionWideInsertImageAddress (
|
||
IN PVOID BaseAddress,
|
||
IN ULONG_PTR Size,
|
||
IN ULONG WritablePages,
|
||
IN PUNICODE_STRING ImageName,
|
||
IN LOGICAL AtPreferredAddress
|
||
);
|
||
|
||
NTSTATUS
|
||
MiSessionWideDereferenceImage (
|
||
IN PVOID BaseAddress
|
||
);
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(INIT, MiSessionWideInitializeAddresses)
|
||
|
||
#pragma alloc_text(PAGE, MiSessionWideInsertImageAddress)
|
||
#pragma alloc_text(PAGE, MiSessionWideDereferenceImage)
|
||
#pragma alloc_text(PAGE, MiSessionWideGetImageSize)
|
||
#pragma alloc_text(PAGE, MiSessionWideReserveImageAddress)
|
||
#pragma alloc_text(PAGE, MiRemoveImageSessionWide)
|
||
#pragma alloc_text(PAGE, MiShareSessionImage)
|
||
|
||
#pragma alloc_text(PAGE, MiSessionInsertImage)
|
||
#pragma alloc_text(PAGE, MiSessionRemoveImage)
|
||
#pragma alloc_text(PAGE, MiSessionLookupImage)
|
||
#pragma alloc_text(PAGE, MiSessionUnloadAllImages)
|
||
#endif
|
||
|
||
|
||
LOGICAL
|
||
MiMarkControlAreaInSystemSpace (
|
||
IN PCONTROL_AREA ControlArea
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Nonpaged wrapper to mark the argument control area properly.
|
||
|
||
Arguments:
|
||
|
||
ControlArea - Supplies a control area to mark as mapped in system space.
|
||
|
||
Return Value:
|
||
|
||
Returns TRUE if this was the first system space mapping of this
|
||
control area. FALSE otherwise.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL and below, MmSystemLoadLock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL OldIrql;
|
||
LOGICAL FirstMapped;
|
||
|
||
FirstMapped = FALSE;
|
||
|
||
LOCK_PFN (OldIrql);
|
||
if (ControlArea->u.Flags.ImageMappedInSystemSpace == 0) {
|
||
FirstMapped = TRUE;
|
||
ControlArea->u.Flags.ImageMappedInSystemSpace = 1;
|
||
}
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
return FirstMapped;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
MiShareSessionImage (
|
||
IN PSECTION Section,
|
||
IN OUT PSIZE_T ViewSize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine maps the given image into the current session space.
|
||
This allows the image to be executed backed by the image file in the
|
||
filesystem and allow code and read-only data to be shared.
|
||
|
||
Arguments:
|
||
|
||
Section - Supplies a pointer to a section.
|
||
|
||
ViewSize - Supplies the size in bytes of the view desired.
|
||
|
||
Return Value:
|
||
|
||
Returns STATUS_SUCCESS on success, various NTSTATUS codes on failure.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL and below, MmSystemLoadLock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL WsIrql;
|
||
PSUBSECTION Subsection;
|
||
PCONTROL_AREA ControlArea;
|
||
ULONG NumberOfPtes;
|
||
PMMPTE StartPte;
|
||
PMMPTE EndPte;
|
||
PVOID AllocationStart;
|
||
SIZE_T AllocationSize;
|
||
NTSTATUS Status;
|
||
LOGICAL FirstMapped;
|
||
PVOID MappedBase;
|
||
SIZE_T CommittedPages;
|
||
PIMAGE_ENTRY_IN_SESSION DriverImage;
|
||
|
||
PAGED_CODE();
|
||
|
||
SYSLOAD_LOCK_OWNED_BY_ME ();
|
||
|
||
if (*ViewSize == 0) {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
ASSERT (MmIsAddressValid (MmSessionSpace) == TRUE);
|
||
|
||
MappedBase = Section->Segment->BasedAddress;
|
||
|
||
ASSERT (((ULONG_PTR)MappedBase % PAGE_SIZE) == 0);
|
||
ASSERT ((*ViewSize % PAGE_SIZE) == 0);
|
||
|
||
//
|
||
// Check to see if a purge operation is in progress and if so, wait
|
||
// for the purge to complete. In addition, up the count of mapped
|
||
// views for this control area.
|
||
//
|
||
|
||
ControlArea = Section->Segment->ControlArea;
|
||
|
||
ASSERT (ControlArea->u.Flags.GlobalOnlyPerSession == 0);
|
||
|
||
if ((ControlArea->u.Flags.GlobalOnlyPerSession == 0) &&
|
||
(ControlArea->u.Flags.Rom == 0)) {
|
||
Subsection = (PSUBSECTION)(ControlArea + 1);
|
||
}
|
||
else {
|
||
Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1);
|
||
}
|
||
|
||
if (MiCheckPurgeAndUpMapCount (ControlArea) == FALSE) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
if (*ViewSize == 0) {
|
||
|
||
*ViewSize = Section->SizeOfSection.LowPart;
|
||
|
||
}
|
||
else if (*ViewSize > Section->SizeOfSection.LowPart) {
|
||
|
||
//
|
||
// Section offset or view size past size of section.
|
||
//
|
||
|
||
MiDereferenceControlArea (ControlArea);
|
||
return STATUS_INVALID_VIEW_SIZE;
|
||
}
|
||
|
||
AllocationStart = MappedBase;
|
||
|
||
AllocationSize = *ViewSize;
|
||
|
||
//
|
||
// Calculate the PTE ranges and amount.
|
||
//
|
||
|
||
StartPte = MiGetPteAddress (AllocationStart);
|
||
|
||
EndPte = MiGetPteAddress ((PCHAR)AllocationStart + AllocationSize);
|
||
|
||
NumberOfPtes = BYTES_TO_PAGES (AllocationSize);
|
||
|
||
Status = MiSessionWideGetImageSize (MappedBase, NULL, &CommittedPages);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
CommittedPages = NumberOfPtes;
|
||
}
|
||
|
||
if (MiChargeCommitment (CommittedPages, NULL) == FALSE) {
|
||
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_COMMIT);
|
||
|
||
//
|
||
// Don't bother releasing the page tables or their commit here, another
|
||
// load will happen shortly or the whole session will go away. On
|
||
// session exit everything will be released automatically.
|
||
//
|
||
|
||
MiDereferenceControlArea (ControlArea);
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages,
|
||
CommittedPages);
|
||
|
||
LOCK_SESSION_SPACE_WS (WsIrql, PsGetCurrentThread ());
|
||
|
||
//
|
||
// Make sure we have page tables for the PTE
|
||
// entries we must fill in the session space structure.
|
||
//
|
||
|
||
Status = MiSessionCommitPageTables (AllocationStart,
|
||
(PVOID)((PCHAR)AllocationStart + AllocationSize));
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
UNLOCK_SESSION_SPACE_WS (WsIrql);
|
||
|
||
Status = STATUS_NO_MEMORY;
|
||
bail:
|
||
InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages,
|
||
0 - CommittedPages);
|
||
|
||
MiDereferenceControlArea (ControlArea);
|
||
MiReturnCommitment (CommittedPages);
|
||
|
||
return Status;
|
||
}
|
||
|
||
#if DBG
|
||
while (StartPte < EndPte) {
|
||
ASSERT (StartPte->u.Long == 0);
|
||
StartPte += 1;
|
||
}
|
||
StartPte = MiGetPteAddress (AllocationStart);
|
||
#endif
|
||
|
||
//
|
||
// Flag that the image is mapped into system space.
|
||
//
|
||
|
||
if (Section->u.Flags.Image) {
|
||
|
||
FirstMapped = MiMarkControlAreaInSystemSpace (ControlArea);
|
||
|
||
//
|
||
// Initialize all of the prototype PTEs as read only - later, copy
|
||
// on write protections will be set on the actual PTEs mapping the
|
||
// data (but not code) pages.
|
||
//
|
||
|
||
if (FirstMapped == TRUE) {
|
||
MiSetImageProtect (Section->Segment, MM_EXECUTE_READ);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Initialize the PTEs to point at the prototype PTEs.
|
||
//
|
||
|
||
Status = MiAddMappedPtes (StartPte, NumberOfPtes, ControlArea);
|
||
|
||
UNLOCK_SESSION_SPACE_WS (WsIrql);
|
||
|
||
if (!NT_SUCCESS (Status)) {
|
||
|
||
//
|
||
// Regardless of whether the PTEs were mapped, leave the control area
|
||
// marked as mapped in system space so user applications cannot map the
|
||
// file as an image as clearly the intent is to run it as a driver.
|
||
//
|
||
|
||
goto bail;
|
||
}
|
||
|
||
MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_SHARED_IMAGE, CommittedPages);
|
||
|
||
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_SYSMAPPED_PAGES_COMMITTED, (ULONG)CommittedPages);
|
||
|
||
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_SYSMAPPED_PAGES_ALLOC, NumberOfPtes);
|
||
|
||
//
|
||
// No session space image faults may be taken until these fields of the
|
||
// image entry are initialized.
|
||
//
|
||
|
||
DriverImage = MiSessionLookupImage (AllocationStart);
|
||
ASSERT (DriverImage);
|
||
|
||
DriverImage->LastAddress = (PVOID)((PCHAR)AllocationStart + AllocationSize - 1);
|
||
DriverImage->PrototypePtes = Subsection->SubsectionBase;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
MiSessionInsertImage (
|
||
IN PVOID BaseAddress
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine allocates an image entry for the specified address in the
|
||
current session space.
|
||
|
||
Arguments:
|
||
|
||
BaseAddress - Supplies the base address for the executable image.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS or various NTSTATUS error codes on failure.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL and below, MmSystemLoadLock held.
|
||
|
||
Note both the system load resource and the session working set
|
||
mutex must be held to modify the list of images in this session.
|
||
Either may be held to safely walk the list.
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL OldIrql;
|
||
PLIST_ENTRY NextEntry;
|
||
PIMAGE_ENTRY_IN_SESSION Image;
|
||
PIMAGE_ENTRY_IN_SESSION NewImage;
|
||
|
||
PAGED_CODE();
|
||
|
||
SYSLOAD_LOCK_OWNED_BY_ME ();
|
||
|
||
//
|
||
// Create and initialize a new image entry prior to acquiring the session
|
||
// space ws mutex. This is to reduce the amount of time the mutex is held.
|
||
// If an existing entry is found this allocation is just discarded.
|
||
//
|
||
|
||
NewImage = ExAllocatePoolWithTag (NonPagedPool,
|
||
sizeof(IMAGE_ENTRY_IN_SESSION),
|
||
'iHmM');
|
||
|
||
if (NewImage == NULL) {
|
||
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_NONPAGED_POOL);
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
RtlZeroMemory (NewImage, sizeof(IMAGE_ENTRY_IN_SESSION));
|
||
|
||
NewImage->Address = BaseAddress;
|
||
NewImage->ImageCountInThisSession = 1;
|
||
|
||
//
|
||
// Check to see if the address is already loaded.
|
||
//
|
||
|
||
LOCK_SESSION_SPACE_WS (OldIrql, PsGetCurrentThread ());
|
||
|
||
NextEntry = MmSessionSpace->ImageList.Flink;
|
||
|
||
while (NextEntry != &MmSessionSpace->ImageList) {
|
||
Image = CONTAINING_RECORD (NextEntry, IMAGE_ENTRY_IN_SESSION, Link);
|
||
|
||
if (Image->Address == BaseAddress) {
|
||
Image->ImageCountInThisSession += 1;
|
||
UNLOCK_SESSION_SPACE_WS (OldIrql);
|
||
ExFreePool (NewImage);
|
||
return STATUS_ALREADY_COMMITTED;
|
||
}
|
||
NextEntry = NextEntry->Flink;
|
||
}
|
||
|
||
//
|
||
// Insert the image entry into the session space structure.
|
||
//
|
||
|
||
InsertTailList (&MmSessionSpace->ImageList, &NewImage->Link);
|
||
|
||
UNLOCK_SESSION_SPACE_WS (OldIrql);
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
MiSessionRemoveImage (
|
||
PVOID BaseAddr
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine removes the given image entry from the current session space.
|
||
|
||
Arguments:
|
||
|
||
BaseAddress - Supplies the base address for the executable image.
|
||
|
||
Return Value:
|
||
|
||
Returns STATUS_SUCCESS on success, STATUS_NOT_FOUND if the image is not
|
||
in the current session space.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL and below.
|
||
|
||
Note both the system load resource and the session working set
|
||
mutex must be held to modify the list of images in this session.
|
||
Either may be held to safely walk the list.
|
||
|
||
--*/
|
||
|
||
{
|
||
PLIST_ENTRY NextEntry;
|
||
PIMAGE_ENTRY_IN_SESSION Image;
|
||
KIRQL OldIrql;
|
||
|
||
PAGED_CODE();
|
||
|
||
SYSLOAD_LOCK_OWNED_BY_ME ();
|
||
|
||
LOCK_SESSION_SPACE_WS (OldIrql, PsGetCurrentThread ());
|
||
NextEntry = MmSessionSpace->ImageList.Flink;
|
||
|
||
while (NextEntry != &MmSessionSpace->ImageList) {
|
||
|
||
Image = CONTAINING_RECORD(NextEntry, IMAGE_ENTRY_IN_SESSION, Link);
|
||
|
||
if (Image->Address == BaseAddr) {
|
||
RemoveEntryList (NextEntry);
|
||
UNLOCK_SESSION_SPACE_WS (OldIrql);
|
||
ExFreePool (Image);
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NextEntry = NextEntry->Flink;
|
||
}
|
||
|
||
UNLOCK_SESSION_SPACE_WS (OldIrql);
|
||
return STATUS_NOT_FOUND;
|
||
}
|
||
|
||
|
||
PIMAGE_ENTRY_IN_SESSION
|
||
MiSessionLookupImage (
|
||
IN PVOID BaseAddress
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine looks up the image entry within the current session by the
|
||
specified base address.
|
||
|
||
Arguments:
|
||
|
||
BaseAddress - Supplies the base address for the executable image.
|
||
|
||
Return Value:
|
||
|
||
The image entry within this session on success or NULL on failure.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL and below, MmSystemLoadLock held.
|
||
|
||
Note both the system load resource and the session working set
|
||
mutex must be held to modify the list of images in this session.
|
||
Either may be held to safely walk the list.
|
||
|
||
--*/
|
||
|
||
{
|
||
PLIST_ENTRY NextEntry;
|
||
PIMAGE_ENTRY_IN_SESSION Image;
|
||
|
||
SYSLOAD_LOCK_OWNED_BY_ME ();
|
||
|
||
NextEntry = MmSessionSpace->ImageList.Flink;
|
||
|
||
while (NextEntry != &MmSessionSpace->ImageList) {
|
||
|
||
Image = CONTAINING_RECORD(NextEntry, IMAGE_ENTRY_IN_SESSION, Link);
|
||
|
||
if (Image->Address == BaseAddress) {
|
||
return Image;
|
||
}
|
||
|
||
NextEntry = NextEntry->Flink;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
VOID
|
||
MiSessionUnloadAllImages (
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine dereferences each image that has been loaded in the
|
||
current session space.
|
||
|
||
As each image is dereferenced, checks are made:
|
||
|
||
If this session's reference count to the image reaches zero, the VA
|
||
range in this session is deleted. If the reference count to the image
|
||
in the SESSIONWIDE list drops to zero, then the SESSIONWIDE's VA
|
||
reservation is removed and the address space is made available to any
|
||
new image.
|
||
|
||
If this is the last systemwide reference to the driver then the driver
|
||
is deleted from memory.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode. This is called in one of two contexts:
|
||
1. the last thread in the last process of the current session space.
|
||
2. or by any thread in the SMSS process.
|
||
|
||
Note both the system load resource and the session working set
|
||
mutex must be held to modify the list of images in this session.
|
||
Either may be held to safely walk the list.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
PLIST_ENTRY NextEntry;
|
||
PIMAGE_ENTRY_IN_SESSION Module;
|
||
PKLDR_DATA_TABLE_ENTRY ImageHandle;
|
||
|
||
ASSERT (MmSessionSpace->ReferenceCount == 0);
|
||
|
||
//
|
||
// The session's working set lock does not need to be acquired here since
|
||
// no thread can be faulting on these addresses.
|
||
//
|
||
|
||
NextEntry = MmSessionSpace->ImageList.Flink;
|
||
|
||
while (NextEntry != &MmSessionSpace->ImageList) {
|
||
|
||
Module = CONTAINING_RECORD(NextEntry, IMAGE_ENTRY_IN_SESSION, Link);
|
||
|
||
//
|
||
// Lookup the image entry in the system PsLoadedModuleList,
|
||
// unload the image and delete it.
|
||
//
|
||
|
||
ImageHandle = MiLookupDataTableEntry (Module->Address, FALSE);
|
||
|
||
ASSERT (ImageHandle);
|
||
|
||
Status = MmUnloadSystemImage (ImageHandle);
|
||
|
||
//
|
||
// Restart the search at the beginning since the entry has been deleted.
|
||
//
|
||
|
||
ASSERT (MmSessionSpace->ReferenceCount == 0);
|
||
|
||
NextEntry = MmSessionSpace->ImageList.Flink;
|
||
}
|
||
}
|
||
|
||
|
||
VOID
|
||
MiSessionWideInitializeAddresses (
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called at system initialization to set up the group
|
||
address list.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode.
|
||
|
||
--*/
|
||
|
||
{
|
||
InitializeListHead (&MmSessionWideAddressList);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
MiSessionWideInsertImageAddress (
|
||
IN PVOID BaseAddress,
|
||
IN ULONG_PTR NumberOfBytes,
|
||
IN ULONG WritablePages,
|
||
IN PUNICODE_STRING ImageName,
|
||
IN LOGICAL AtPreferredAddress
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Allocate and add a SessionWide Entry reference to the global address
|
||
allocation list for the current process' session space.
|
||
|
||
Arguments:
|
||
|
||
BaseAddress - Supplies the base address to allocate an entry for.
|
||
|
||
NumberOfBytes - Supplies the number of bytes the entry spans.
|
||
|
||
WritablePages - Supplies the number of pages to charge commit for.
|
||
|
||
ImageName - Supplies the name of the image in the PsLoadedModuleList
|
||
that is represented by the virtual region.
|
||
|
||
AtPreferredAddress - Supplies TRUE if the image is based at its preferred
|
||
address.
|
||
|
||
Return Value:
|
||
|
||
Returns STATUS_SUCCESS on success, STATUS_NO_MEMORY on failure.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL and below, MmSystemLoadLock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
PVOID LastAddress;
|
||
PLIST_ENTRY NextEntry;
|
||
PSESSIONWIDE_DRIVER_ADDRESS Vaddr;
|
||
PSESSIONWIDE_DRIVER_ADDRESS New;
|
||
PWCHAR NewName;
|
||
|
||
SYSLOAD_LOCK_OWNED_BY_ME ();
|
||
|
||
New = ExAllocatePoolWithTag (NonPagedPool,
|
||
sizeof(SESSIONWIDE_DRIVER_ADDRESS),
|
||
'vHmM');
|
||
|
||
if (New == NULL) {
|
||
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_NONPAGED_POOL);
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
RtlZeroMemory (New, sizeof(SESSIONWIDE_DRIVER_ADDRESS));
|
||
|
||
New->ReferenceCount = 1;
|
||
New->Address = BaseAddress;
|
||
New->Size = NumberOfBytes;
|
||
if (AtPreferredAddress == TRUE) {
|
||
New->WritablePages = WritablePages;
|
||
}
|
||
else {
|
||
New->WritablePages = (MI_ROUND_TO_SIZE (NumberOfBytes, PAGE_SIZE)) >> PAGE_SHIFT;
|
||
}
|
||
|
||
ASSERT (ImageName != NULL);
|
||
|
||
NewName = (PWCHAR) ExAllocatePoolWithTag (PagedPool | POOL_COLD_ALLOCATION,
|
||
ImageName->Length + sizeof(UNICODE_NULL),
|
||
'nHmM');
|
||
|
||
if (NewName == NULL) {
|
||
ExFreePool (New);
|
||
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_PAGED_POOL);
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
RtlCopyMemory (NewName, ImageName->Buffer, ImageName->Length);
|
||
NewName [ImageName->Length / sizeof(WCHAR)] = UNICODE_NULL;
|
||
|
||
New->FullDllName.Buffer = NewName;
|
||
New->FullDllName.Length = ImageName->Length;
|
||
New->FullDllName.MaximumLength = ImageName->Length;
|
||
|
||
//
|
||
// Insert the entry in the memory-ordered list.
|
||
//
|
||
|
||
LastAddress = NULL;
|
||
NextEntry = MmSessionWideAddressList.Flink;
|
||
|
||
while (NextEntry != &MmSessionWideAddressList) {
|
||
|
||
Vaddr = CONTAINING_RECORD (NextEntry,
|
||
SESSIONWIDE_DRIVER_ADDRESS,
|
||
Link);
|
||
|
||
if (LastAddress < Vaddr->Address && Vaddr->Address > New->Address) {
|
||
break;
|
||
}
|
||
|
||
LastAddress = Vaddr->Address;
|
||
NextEntry = NextEntry->Flink;
|
||
}
|
||
|
||
InsertTailList (NextEntry, &New->Link);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NTSTATUS
|
||
MiSessionWideDereferenceImage (
|
||
IN PVOID BaseAddress
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Dereference the SessionWide entry for the specified address, potentially
|
||
resulting in a deletion of the image.
|
||
|
||
Arguments:
|
||
|
||
BaseAddress - Supplies the address for the driver to dereference.
|
||
|
||
Return Value:
|
||
|
||
Returns STATUS_SUCCESS on success, STATUS_NOT_FOUND on failure.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL and below, MmSystemLoadLock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
PLIST_ENTRY NextEntry;
|
||
PSESSIONWIDE_DRIVER_ADDRESS SessionWideImageEntry;
|
||
|
||
SYSLOAD_LOCK_OWNED_BY_ME ();
|
||
|
||
ASSERT (BaseAddress);
|
||
|
||
NextEntry = MmSessionWideAddressList.Flink;
|
||
|
||
while (NextEntry != &MmSessionWideAddressList) {
|
||
|
||
SessionWideImageEntry = CONTAINING_RECORD (NextEntry,
|
||
SESSIONWIDE_DRIVER_ADDRESS,
|
||
Link);
|
||
|
||
if (BaseAddress == SessionWideImageEntry->Address) {
|
||
|
||
SessionWideImageEntry->ReferenceCount -= 1;
|
||
|
||
//
|
||
// If reference count is 0, delete the node.
|
||
//
|
||
|
||
if (SessionWideImageEntry->ReferenceCount == 0) {
|
||
RemoveEntryList (NextEntry);
|
||
ASSERT (SessionWideImageEntry->FullDllName.Buffer != NULL);
|
||
ExFreePool (SessionWideImageEntry->FullDllName.Buffer);
|
||
ExFreePool (SessionWideImageEntry);
|
||
}
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NextEntry = NextEntry->Flink;
|
||
}
|
||
|
||
return STATUS_NOT_FOUND;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
MiSessionWideGetImageSize (
|
||
IN PVOID BaseAddress,
|
||
OUT PSIZE_T NumberOfBytes OPTIONAL,
|
||
OUT PSIZE_T CommitPages OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Lookup the size allocated and committed for the image at the base address.
|
||
This ensures that we free every page that may have been allocated due
|
||
to rounding up.
|
||
|
||
Arguments:
|
||
|
||
BaseAddress - Supplies the preferred address that the driver has
|
||
been linked (rebased) at. If this address is available,
|
||
the driver will require no relocation.
|
||
|
||
NumberOfBytes - Supplies a pointer to store the image size into.
|
||
|
||
CommitPages - Supplies a pointer to store the number of committed pages
|
||
that were charged for this image.
|
||
|
||
Return Value:
|
||
|
||
Returns STATUS_SUCCESS on success, STATUS_NOT_FOUND on failure.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL and below, MmSystemLoadLock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
PLIST_ENTRY NextEntry;
|
||
PSESSIONWIDE_DRIVER_ADDRESS SessionWideEntry;
|
||
|
||
SYSLOAD_LOCK_OWNED_BY_ME ();
|
||
|
||
NextEntry = MmSessionWideAddressList.Flink;
|
||
|
||
while (NextEntry != &MmSessionWideAddressList) {
|
||
|
||
SessionWideEntry = CONTAINING_RECORD (NextEntry,
|
||
SESSIONWIDE_DRIVER_ADDRESS,
|
||
Link);
|
||
|
||
if (BaseAddress == SessionWideEntry->Address) {
|
||
|
||
if (ARGUMENT_PRESENT (NumberOfBytes)) {
|
||
*NumberOfBytes = SessionWideEntry->Size;
|
||
}
|
||
|
||
if (ARGUMENT_PRESENT (CommitPages)) {
|
||
*CommitPages = SessionWideEntry->WritablePages;
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NextEntry = NextEntry->Flink;
|
||
}
|
||
|
||
return STATUS_NOT_FOUND;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
MiSessionWideReserveImageAddress (
|
||
IN PUNICODE_STRING ImageName,
|
||
IN PSECTION Section,
|
||
IN ULONG_PTR Alignment,
|
||
OUT PVOID *AssignedAddress,
|
||
OUT PLOGICAL AlreadyLoaded
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine allocates a range of virtual address space within
|
||
session space. This address range is unique system-wide and in this
|
||
manner, code and pristine data of session drivers can be shared across
|
||
multiple sessions.
|
||
|
||
This routine does not actually commit pages, but reserves the virtual
|
||
address region for the named image. An entry is created here and attached
|
||
to the current session space to track the loaded image. Thus if all
|
||
the references to a given range go away, the range can then be reused.
|
||
|
||
Arguments:
|
||
|
||
ImageName - Supplies the name of the driver that will be loaded into
|
||
the allocated space.
|
||
|
||
Section - Supplies the section (and thus, the preferred address that the
|
||
driver has been linked (rebased) at. If this address is
|
||
available, the driver will require no relocation. The section
|
||
is also used to derive the number of bytes to reserve.
|
||
|
||
Alignment - Supplies the virtual address alignment for the address.
|
||
|
||
AssignedAddress - Supplies a pointer to a variable that receives the
|
||
allocated address if the routine succeeds.
|
||
|
||
AlreadyLoaded - Supplies a pointer to a variable that receives TRUE if the
|
||
specified image name has already been loaded.
|
||
|
||
Return Value:
|
||
|
||
Returns STATUS_SUCCESS on success, various NTSTATUS codes on failure.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL and below, MmSystemLoadLock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
PLIST_ENTRY NextEntry;
|
||
PSESSIONWIDE_DRIVER_ADDRESS Vaddr;
|
||
NTSTATUS Status;
|
||
PWCHAR pName;
|
||
PVOID NewAddress;
|
||
ULONG_PTR AvailableAddress;
|
||
ULONG_PTR SessionSpaceEnd;
|
||
PVOID PreferredAddress;
|
||
ULONG_PTR NumberOfBytes;
|
||
ULONG WritablePages;
|
||
LOGICAL AtPreferredAddress;
|
||
|
||
PAGED_CODE();
|
||
|
||
SYSLOAD_LOCK_OWNED_BY_ME ();
|
||
|
||
ASSERT (PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION);
|
||
ASSERT (MmIsAddressValid (MmSessionSpace) == TRUE);
|
||
|
||
pName = NULL;
|
||
*AlreadyLoaded = FALSE;
|
||
PreferredAddress = Section->Segment->BasedAddress;
|
||
NumberOfBytes = Section->Segment->TotalNumberOfPtes << PAGE_SHIFT;
|
||
|
||
AvailableAddress = MiSessionImageStart;
|
||
NumberOfBytes = MI_ROUND_TO_SIZE (NumberOfBytes, Alignment);
|
||
SessionSpaceEnd = MiSessionImageEnd;
|
||
|
||
Status = MiGetWritablePagesInSection(Section, &WritablePages);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
WritablePages = Section->Segment->TotalNumberOfPtes;
|
||
}
|
||
|
||
//
|
||
// If the requested address is not properly aligned or not in the session
|
||
// space region, pick an address for it. This image will not be shared.
|
||
//
|
||
|
||
if ((ULONG_PTR)PreferredAddress & (Alignment - 1)) {
|
||
|
||
#if DBG
|
||
DbgPrint("MiSessionWideReserveImageAddress: Bad alignment 0x%x for PreferredAddress 0x%x\n",
|
||
Alignment,
|
||
PreferredAddress);
|
||
#endif
|
||
|
||
PreferredAddress = NULL;
|
||
}
|
||
else if ((ULONG_PTR)PreferredAddress < AvailableAddress ||
|
||
((ULONG_PTR)PreferredAddress + NumberOfBytes >= SessionSpaceEnd)) {
|
||
|
||
#if DBG
|
||
if (MmDebug & MM_DBG_SESSIONS) {
|
||
DbgPrint ("MiSessionWideReserveImageAddress: PreferredAddress 0x%x not in session space\n", PreferredAddress);
|
||
}
|
||
#endif
|
||
|
||
PreferredAddress = NULL;
|
||
}
|
||
|
||
//
|
||
// Check the system wide session space image list to see if the
|
||
// image name has already been given a slot.
|
||
//
|
||
|
||
NextEntry = MmSessionWideAddressList.Flink;
|
||
|
||
while (NextEntry != &MmSessionWideAddressList) {
|
||
|
||
Vaddr = CONTAINING_RECORD (NextEntry,
|
||
SESSIONWIDE_DRIVER_ADDRESS,
|
||
Link);
|
||
|
||
if (Vaddr->FullDllName.Buffer != NULL) {
|
||
|
||
if (RtlEqualUnicodeString(ImageName, &Vaddr->FullDllName, TRUE)) {
|
||
|
||
//
|
||
// The size requested should be the same.
|
||
//
|
||
|
||
if (Vaddr->Size < NumberOfBytes) {
|
||
#if DBG
|
||
DbgPrint ("MiSessionWideReserveImageAddress: Size %d Larger than Entry %d, DLL %wZ\n",
|
||
NumberOfBytes,
|
||
Vaddr->Size,
|
||
ImageName);
|
||
#endif
|
||
|
||
return STATUS_CONFLICTING_ADDRESSES;
|
||
}
|
||
|
||
//
|
||
// 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 (Vaddr->Address);
|
||
|
||
if (Status == STATUS_ALREADY_COMMITTED) {
|
||
|
||
*AlreadyLoaded = TRUE;
|
||
*AssignedAddress = Vaddr->Address;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
if (!NT_SUCCESS (Status)) {
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Bump the reference count as this is a new entry.
|
||
//
|
||
|
||
Vaddr->ReferenceCount += 1;
|
||
|
||
*AssignedAddress = Vaddr->Address;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Note this list must be sorted by ascending address.
|
||
// See if the PreferredAddress and size collide with any entries.
|
||
//
|
||
|
||
if (PreferredAddress) {
|
||
|
||
if ((PreferredAddress >= Vaddr->Address) &&
|
||
(PreferredAddress < (PVOID)((ULONG_PTR)Vaddr->Address + Vaddr->Size))) {
|
||
PreferredAddress = NULL;
|
||
}
|
||
else if ((PreferredAddress < Vaddr->Address) &&
|
||
((PVOID)((ULONG_PTR)PreferredAddress + NumberOfBytes) > Vaddr->Address)) {
|
||
PreferredAddress = NULL;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Check for an available general allocation slot.
|
||
//
|
||
|
||
if (((PVOID)AvailableAddress >= Vaddr->Address) &&
|
||
(AvailableAddress <= (ULONG_PTR)Vaddr->Address + Vaddr->Size)) {
|
||
|
||
AvailableAddress = (ULONG_PTR)Vaddr->Address + Vaddr->Size;
|
||
|
||
if (AvailableAddress & (Alignment - 1)) {
|
||
AvailableAddress = MI_ROUND_TO_SIZE (AvailableAddress, Alignment);
|
||
}
|
||
}
|
||
else if (AvailableAddress + NumberOfBytes > (ULONG_PTR)Vaddr->Address) {
|
||
|
||
AvailableAddress = (ULONG_PTR)Vaddr->Address + Vaddr->Size;
|
||
|
||
if (AvailableAddress & (Alignment - 1)) {
|
||
AvailableAddress = MI_ROUND_TO_SIZE (AvailableAddress, Alignment);
|
||
}
|
||
}
|
||
|
||
NextEntry = NextEntry->Flink;
|
||
}
|
||
|
||
if ((PreferredAddress == NULL) &&
|
||
(AvailableAddress + NumberOfBytes > MiSessionImageEnd)) {
|
||
|
||
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_IMAGE_VA_SPACE);
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
//
|
||
// Try to put the module into its requested address so it can be shared.
|
||
//
|
||
|
||
if (PreferredAddress) {
|
||
|
||
#if DBG
|
||
if (MmDebug & MM_DBG_SESSIONS) {
|
||
DbgPrint ("MiSessionWideReserveImageAddress: Code Sharing on %wZ, Address 0x%x\n",ImageName,PreferredAddress);
|
||
}
|
||
#endif
|
||
|
||
NewAddress = PreferredAddress;
|
||
}
|
||
else {
|
||
ASSERT (AvailableAddress != 0);
|
||
ASSERT ((AvailableAddress & (Alignment - 1)) == 0);
|
||
|
||
#if DBG
|
||
DbgPrint ("MiSessionWideReserveImageAddress: NO Code Sharing on %wZ, Address 0x%x\n",ImageName,AvailableAddress);
|
||
#endif
|
||
|
||
NewAddress = (PVOID)AvailableAddress;
|
||
}
|
||
|
||
//
|
||
// Create a new node entry for the address range.
|
||
//
|
||
|
||
if (NewAddress == PreferredAddress) {
|
||
AtPreferredAddress = TRUE;
|
||
}
|
||
else {
|
||
AtPreferredAddress = FALSE;
|
||
}
|
||
|
||
Status = MiSessionWideInsertImageAddress (NewAddress,
|
||
NumberOfBytes,
|
||
WritablePages,
|
||
ImageName,
|
||
AtPreferredAddress);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Create an entry for this image in the current session space.
|
||
//
|
||
|
||
Status = MiSessionInsertImage (NewAddress);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
MiSessionWideDereferenceImage (NewAddress);
|
||
return Status;
|
||
}
|
||
|
||
*AssignedAddress = NewAddress;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NTSTATUS
|
||
MiRemoveImageSessionWide (
|
||
IN PVOID BaseAddress
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Delete the image space region from the current session space.
|
||
This dereferences the globally allocated SessionWide region.
|
||
|
||
The SessionWide region will be deleted if the reference count goes to zero.
|
||
|
||
Arguments:
|
||
|
||
BaseAddress - Supplies the address the driver is loaded at.
|
||
|
||
Return Value:
|
||
|
||
Returns STATUS_SUCCESS on success, STATUS_NOT_FOUND on failure.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL and below, MmSystemLoadLock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
PAGED_CODE();
|
||
|
||
SYSLOAD_LOCK_OWNED_BY_ME ();
|
||
|
||
ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE);
|
||
|
||
Status = MiSessionWideDereferenceImage (BaseAddress);
|
||
|
||
ASSERT (NT_SUCCESS(Status));
|
||
|
||
//
|
||
// Remove the image reference from the current session space.
|
||
//
|
||
|
||
MiSessionRemoveImage (BaseAddress);
|
||
|
||
return Status;
|
||
}
|