mirror of https://github.com/tongzx/nt5src
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.
4321 lines
107 KiB
4321 lines
107 KiB
/*++
|
|
|
|
Copyright (c) 1997 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
session.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the routines which implement the creation and
|
|
deletion of session spaces along with associated support routines.
|
|
|
|
Author:
|
|
|
|
Landy Wang (landyw) 05-Dec-1997
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "mi.h"
|
|
|
|
ULONG MiSessionCount;
|
|
|
|
LONG MmSessionDataPages;
|
|
|
|
#define MM_MAXIMUM_CONCURRENT_SESSIONS 16384
|
|
|
|
FAST_MUTEX MiSessionIdMutex;
|
|
|
|
PRTL_BITMAP MiSessionIdBitmap;
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
|
|
#if defined(_AMD64_)
|
|
#define MI_SESSION_COMMIT_CHARGE 5
|
|
|
|
#elif defined(_IA64_)
|
|
#define MI_SESSION_COMMIT_CHARGE 5
|
|
|
|
extern REGION_MAP_INFO MmSessionMapInfo;
|
|
extern PFN_NUMBER MmSessionParentTablePage;
|
|
|
|
#else
|
|
#define MI_SESSION_COMMIT_CHARGE 4
|
|
#endif
|
|
|
|
#else
|
|
#define MI_SESSION_COMMIT_CHARGE 3
|
|
#endif
|
|
|
|
VOID
|
|
MiSessionAddProcess (
|
|
PEPROCESS NewProcess
|
|
);
|
|
|
|
VOID
|
|
MiSessionRemoveProcess (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
MiInitializeSessionIds (
|
|
VOID
|
|
);
|
|
|
|
NTSTATUS
|
|
MiSessionCreateInternal (
|
|
OUT PULONG SessionId
|
|
);
|
|
|
|
NTSTATUS
|
|
MiSessionCommitPageTables (
|
|
IN PVOID StartVa,
|
|
IN PVOID EndVa
|
|
);
|
|
|
|
VOID
|
|
MiDereferenceSession (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
MiSessionDeletePde (
|
|
IN PMMPTE Pde,
|
|
IN LOGICAL WorkingSetInitialized,
|
|
IN PMMPTE SelfMapPde
|
|
);
|
|
|
|
VOID
|
|
MiDereferenceSessionFinal (
|
|
VOID
|
|
);
|
|
|
|
#if DBG
|
|
VOID
|
|
MiCheckSessionVirtualSpace (
|
|
IN PVOID VirtualAddress,
|
|
IN SIZE_T NumberOfBytes
|
|
);
|
|
#endif
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(INIT,MiInitializeSessionIds)
|
|
|
|
#pragma alloc_text(PAGE, MmSessionSetUnloadAddress)
|
|
#pragma alloc_text(PAGE, MmSessionCreate)
|
|
#pragma alloc_text(PAGE, MmSessionDelete)
|
|
#pragma alloc_text(PAGE, MmGetSessionLocaleId)
|
|
#pragma alloc_text(PAGE, MmSetSessionLocaleId)
|
|
#pragma alloc_text(PAGE, MmQuitNextSession)
|
|
#pragma alloc_text(PAGE, MiDereferenceSession)
|
|
#pragma alloc_text(PAGE, MiDetachFromSecureProcessInSession)
|
|
#if DBG
|
|
#pragma alloc_text(PAGE, MiCheckSessionVirtualSpace)
|
|
#endif
|
|
|
|
#pragma alloc_text(PAGELK, MiSessionCreateInternal)
|
|
#pragma alloc_text(PAGELK, MiDereferenceSessionFinal)
|
|
|
|
#endif
|
|
|
|
VOID
|
|
MiSessionLeader (
|
|
IN PEPROCESS Process
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Mark the argument process as having the ability to create or delete session
|
|
spaces. This is only granted to the session manager process.
|
|
|
|
Arguments:
|
|
|
|
Process - Supplies a pointer to the privileged process.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
Process->Vm.Flags.SessionLeader = 1;
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
}
|
|
|
|
|
|
VOID
|
|
MmSessionSetUnloadAddress (
|
|
IN PDRIVER_OBJECT pWin32KDevice
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Copy the win32k.sys driver object to the session structure for use during
|
|
unload.
|
|
|
|
Arguments:
|
|
|
|
NewProcess - Supplies a pointer to the win32k driver object.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
PDRIVER_OBJECT Destination;
|
|
|
|
ASSERT (PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION);
|
|
ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE);
|
|
|
|
Destination = &MmSessionSpace->Win32KDriverObject;
|
|
|
|
RtlCopyMemory (Destination, pWin32KDevice, sizeof(DRIVER_OBJECT));
|
|
}
|
|
|
|
VOID
|
|
MiSessionAddProcess (
|
|
PEPROCESS NewProcess
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Add the new process to the current session space.
|
|
|
|
Arguments:
|
|
|
|
NewProcess - Supplies a pointer to the process being created.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APCs disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
|
|
//
|
|
// If the calling process has no session, then the new process won't get
|
|
// one either.
|
|
//
|
|
|
|
if ((PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION)== 0) {
|
|
return;
|
|
}
|
|
|
|
ASSERT (MmIsAddressValid (MmSessionSpace) == TRUE);
|
|
|
|
SessionGlobal = SESSION_GLOBAL(MmSessionSpace);
|
|
|
|
InterlockedIncrement ((PLONG)&SessionGlobal->ReferenceCount);
|
|
|
|
InterlockedIncrement (&SessionGlobal->ProcessReferenceToSession);
|
|
|
|
//
|
|
// Once the Session pointer in the EPROCESS is set it can never
|
|
// be cleared because it is accessed lock-free.
|
|
//
|
|
|
|
ASSERT (NewProcess->Session == NULL);
|
|
NewProcess->Session = (PVOID) SessionGlobal;
|
|
|
|
#if defined(_IA64_)
|
|
KeAddSessionSpace(&NewProcess->Pcb,
|
|
&SessionGlobal->SessionMapInfo,
|
|
SessionGlobal->PageDirectoryParentPage);
|
|
#endif
|
|
|
|
//
|
|
// Link the process entry into the session space and WSL structures.
|
|
//
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
if (IsListEmpty(&SessionGlobal->ProcessList)) {
|
|
|
|
if (MmSessionSpace->Vm.Flags.AllowWorkingSetAdjustment == FALSE) {
|
|
|
|
ASSERT (MmSessionSpace->u.Flags.WorkingSetInserted == 0);
|
|
|
|
MmSessionSpace->Vm.Flags.AllowWorkingSetAdjustment = TRUE;
|
|
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&SessionGlobal->Vm.WorkingSetExpansionLinks);
|
|
|
|
MmSessionSpace->u.Flags.WorkingSetInserted = 1;
|
|
}
|
|
}
|
|
|
|
InsertTailList (&SessionGlobal->ProcessList, &NewProcess->SessionProcessLinks);
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
PS_SET_BITS (&NewProcess->Flags, PS_PROCESS_FLAGS_IN_SESSION);
|
|
}
|
|
|
|
|
|
VOID
|
|
MiSessionRemoveProcess (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine removes the current process from the current session space.
|
|
This may trigger a substantial round of dereferencing and resource freeing
|
|
if it is also the last process in the session, (holding the last image
|
|
in the group, etc).
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APC_LEVEL and below, but queueing of APCs to this thread has
|
|
been permanently disabled. This is the last thread in the process
|
|
being deleted. The caller has ensured that this process is not
|
|
on the expansion list and therefore there can be no races in regards to
|
|
trimming.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PEPROCESS CurrentProcess;
|
|
#if DBG
|
|
ULONG Found;
|
|
PEPROCESS Process;
|
|
PLIST_ENTRY NextEntry;
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
#endif
|
|
|
|
CurrentProcess = PsGetCurrentProcess();
|
|
|
|
if (((CurrentProcess->Flags & PS_PROCESS_FLAGS_IN_SESSION) == 0) ||
|
|
(CurrentProcess->Vm.Flags.SessionLeader == 1)) {
|
|
return;
|
|
}
|
|
|
|
ASSERT (MmIsAddressValid (MmSessionSpace) == TRUE);
|
|
|
|
//
|
|
// Remove this process from the list of processes in the current session.
|
|
//
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
#if DBG
|
|
|
|
SessionGlobal = SESSION_GLOBAL(MmSessionSpace);
|
|
|
|
Found = 0;
|
|
NextEntry = SessionGlobal->ProcessList.Flink;
|
|
|
|
while (NextEntry != &SessionGlobal->ProcessList) {
|
|
Process = CONTAINING_RECORD (NextEntry, EPROCESS, SessionProcessLinks);
|
|
|
|
if (Process == CurrentProcess) {
|
|
Found = 1;
|
|
}
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
ASSERT (Found == 1);
|
|
|
|
#endif
|
|
|
|
RemoveEntryList (&CurrentProcess->SessionProcessLinks);
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
//
|
|
// Decrement this process' reference count to the session. If this
|
|
// is the last reference, then the entire session will be destroyed
|
|
// upon return. This includes unloading drivers, unmapping pools,
|
|
// freeing page tables, etc.
|
|
//
|
|
|
|
MiDereferenceSession ();
|
|
}
|
|
|
|
LCID
|
|
MmGetSessionLocaleId (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine gets the locale ID for the current session.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
The locale ID for the current session.
|
|
|
|
Environment:
|
|
|
|
PASSIVE_LEVEL, the caller must supply any desired synchronization.
|
|
|
|
--*/
|
|
|
|
{
|
|
PEPROCESS Process;
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
|
|
PAGED_CODE ();
|
|
|
|
Process = PsGetCurrentProcess ();
|
|
|
|
if (Process->Vm.Flags.SessionLeader == 1) {
|
|
|
|
//
|
|
// smss may transiently have a session space but that's of no interest
|
|
// to our caller.
|
|
//
|
|
|
|
return PsDefaultThreadLocaleId;
|
|
}
|
|
|
|
//
|
|
// The Session field of the EPROCESS is never cleared once set so these
|
|
// checks can be done lock free.
|
|
//
|
|
|
|
SessionGlobal = (PMM_SESSION_SPACE) Process->Session;
|
|
|
|
if (SessionGlobal == NULL) {
|
|
|
|
//
|
|
// The system process has no session space.
|
|
//
|
|
|
|
return PsDefaultThreadLocaleId;
|
|
}
|
|
|
|
SessionGlobal = (PMM_SESSION_SPACE) Process->Session;
|
|
|
|
return SessionGlobal->LocaleId;
|
|
}
|
|
|
|
VOID
|
|
MmSetSessionLocaleId (
|
|
IN LCID LocaleId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets the locale ID for the current session.
|
|
|
|
Arguments:
|
|
|
|
LocaleId - Supplies the desired locale ID.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
PASSIVE_LEVEL, the caller must supply any desired synchronization.
|
|
|
|
--*/
|
|
|
|
{
|
|
PEPROCESS Process;
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
|
|
PAGED_CODE ();
|
|
|
|
Process = PsGetCurrentProcess ();
|
|
|
|
if (Process->Vm.Flags.SessionLeader == 1) {
|
|
|
|
//
|
|
// smss may transiently have a session space but that's of no interest
|
|
// to our caller.
|
|
//
|
|
|
|
PsDefaultThreadLocaleId = LocaleId;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The Session field of the EPROCESS is never cleared once set so these
|
|
// checks can be done lock free.
|
|
//
|
|
|
|
SessionGlobal = (PMM_SESSION_SPACE) Process->Session;
|
|
|
|
if (SessionGlobal == NULL) {
|
|
|
|
//
|
|
// The system process has no session space.
|
|
//
|
|
|
|
PsDefaultThreadLocaleId = LocaleId;
|
|
return;
|
|
}
|
|
|
|
SessionGlobal = (PMM_SESSION_SPACE) Process->Session;
|
|
|
|
SessionGlobal->LocaleId = LocaleId;
|
|
}
|
|
|
|
VOID
|
|
MiInitializeSessionIds (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates and initializes session ID allocation/deallocation.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// If this ever grows beyond 2x the page size, both the allocation and
|
|
// deletion code will need to be updated.
|
|
//
|
|
|
|
ASSERT (sizeof(MM_SESSION_SPACE) <= 2 * PAGE_SIZE);
|
|
|
|
ExInitializeFastMutex (&MiSessionIdMutex);
|
|
|
|
MiCreateBitMap (&MiSessionIdBitmap,
|
|
MM_MAXIMUM_CONCURRENT_SESSIONS,
|
|
PagedPool);
|
|
|
|
if (MiSessionIdBitmap == NULL) {
|
|
MiCreateBitMap (&MiSessionIdBitmap,
|
|
MM_MAXIMUM_CONCURRENT_SESSIONS,
|
|
NonPagedPoolMustSucceed);
|
|
}
|
|
|
|
RtlClearAllBits (MiSessionIdBitmap);
|
|
}
|
|
|
|
NTSTATUS
|
|
MiSessionCreateInternal (
|
|
OUT PULONG SessionId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates the data structure that describes and maintains
|
|
the session space. It resides at the beginning of the session space.
|
|
Carefully construct the first page mapping to bootstrap the fault
|
|
handler which relies on the session space data structure being
|
|
present and valid.
|
|
|
|
In NT32, this initial mapping for the portion of session space
|
|
mapped by the first PDE will automatically be inherited by all child
|
|
processes when the system copies the system portion of the page
|
|
directory for new address spaces. Additional entries are faulted
|
|
in by the session space fault handler, which references this structure.
|
|
|
|
For NT64, everything is automatically inherited.
|
|
|
|
This routine commits virtual memory within the current session space with
|
|
backing pages. The virtual addresses within session space are
|
|
allocated with a separate facility in the image management facility.
|
|
This is because images must be at a unique system wide virtual address.
|
|
|
|
Arguments:
|
|
|
|
SessionId - Supplies a pointer to place the new session ID into.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if all went well, various failure status codes
|
|
if the session was not created.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, no mutexes held.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PMMPTE PointerPde;
|
|
PMMPTE PointerPte;
|
|
PMMPTE GlobalMappingPte;
|
|
NTSTATUS Status;
|
|
PMM_SESSION_SPACE SessionSpace;
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
PFN_NUMBER ResidentPages;
|
|
LOGICAL GotCommit;
|
|
LOGICAL GotPages;
|
|
LOGICAL GotUnmappedDataPage;
|
|
LOGICAL PoolInitialized;
|
|
PMMPFN Pfn1;
|
|
MMPTE TempPte;
|
|
MMPTE TempPte2;
|
|
ULONG_PTR Va;
|
|
PFN_NUMBER DataPage;
|
|
PFN_NUMBER DataPage2;
|
|
PFN_NUMBER PageTablePage;
|
|
ULONG PageColor;
|
|
ULONG ProcessFlags;
|
|
ULONG NewProcessFlags;
|
|
PEPROCESS Process;
|
|
#if (_MI_PAGING_LEVELS < 3)
|
|
SIZE_T PageTableBytes;
|
|
PMMPTE PageTables;
|
|
#else
|
|
PMMPTE PointerPpe;
|
|
PFN_NUMBER PageDirectoryPage;
|
|
PFN_NUMBER PageDirectoryParentPage;
|
|
#endif
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
PMMPTE PointerPxe;
|
|
#endif
|
|
|
|
GotCommit = FALSE;
|
|
GotPages = FALSE;
|
|
GotUnmappedDataPage = FALSE;
|
|
GlobalMappingPte = NULL;
|
|
PoolInitialized = FALSE;
|
|
|
|
//
|
|
// Initializing these are not needed for correctness
|
|
// but without it the compiler cannot compile this code
|
|
// W4 to check for use of uninitialized variables.
|
|
//
|
|
|
|
DataPage = 0;
|
|
DataPage2 = 0;
|
|
PageTablePage = 0;
|
|
PointerPte = NULL;
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
PageDirectoryPage = 0;
|
|
PageDirectoryParentPage = 0;
|
|
#endif
|
|
|
|
Process = PsGetCurrentProcess();
|
|
|
|
#if defined(_IA64_)
|
|
ASSERT (MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)(&Process->Pcb.SessionParentBase)) == MmSessionParentTablePage);
|
|
#else
|
|
ASSERT (MmIsAddressValid(MmSessionSpace) == FALSE);
|
|
#endif
|
|
|
|
|
|
|
|
//
|
|
// Check for concurrent session creation attempts.
|
|
//
|
|
|
|
|
|
ProcessFlags = Process->Flags;
|
|
|
|
while (TRUE) {
|
|
|
|
if (ProcessFlags & PS_PROCESS_FLAGS_CREATING_SESSION) {
|
|
return STATUS_ALREADY_COMMITTED;
|
|
}
|
|
|
|
NewProcessFlags = (ProcessFlags | PS_PROCESS_FLAGS_CREATING_SESSION);
|
|
|
|
NewProcessFlags = InterlockedCompareExchange ((PLONG)&Process->Flags,
|
|
(LONG)NewProcessFlags,
|
|
(LONG)ProcessFlags);
|
|
|
|
if (NewProcessFlags == ProcessFlags) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// The structure changed beneath us. Use the return value from the
|
|
// exchange and try it all again.
|
|
//
|
|
|
|
ProcessFlags = NewProcessFlags;
|
|
}
|
|
|
|
ASSERT (Process->Flags & PS_PROCESS_FLAGS_CREATING_SESSION);
|
|
|
|
#if (_MI_PAGING_LEVELS < 3)
|
|
|
|
PageTableBytes = MI_SESSION_SPACE_MAXIMUM_PAGE_TABLES * sizeof (MMPTE);
|
|
|
|
PageTables = (PMMPTE) ExAllocatePoolWithTag (NonPagedPool,
|
|
PageTableBytes,
|
|
'tHmM');
|
|
|
|
if (PageTables == NULL) {
|
|
|
|
ASSERT (Process->Flags & PS_PROCESS_FLAGS_CREATING_SESSION);
|
|
PS_CLEAR_BITS (&Process->Flags, PS_PROCESS_FLAGS_CREATING_SESSION);
|
|
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_NONPAGED_POOL);
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlZeroMemory (PageTables, PageTableBytes);
|
|
|
|
#endif
|
|
|
|
//
|
|
// Select a free session ID.
|
|
//
|
|
|
|
|
|
|
|
ExAcquireFastMutex (&MiSessionIdMutex);
|
|
|
|
*SessionId = RtlFindClearBitsAndSet (MiSessionIdBitmap, 1, 0);
|
|
|
|
ExReleaseFastMutex (&MiSessionIdMutex);
|
|
|
|
if (*SessionId == NO_BITS_FOUND) {
|
|
|
|
ASSERT (Process->Flags & PS_PROCESS_FLAGS_CREATING_SESSION);
|
|
PS_CLEAR_BITS (&Process->Flags, PS_PROCESS_FLAGS_CREATING_SESSION);
|
|
|
|
#if (_MI_PAGING_LEVELS < 3)
|
|
ExFreePool (PageTables);
|
|
#endif
|
|
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_IDS);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
|
|
//
|
|
// Lock down this routine in preparation for the PFN lock acquisition.
|
|
// Note this is done prior to the commitment charges just to simplify
|
|
// error handling.
|
|
//
|
|
|
|
MmLockPagableSectionByHandle (ExPageLockHandle);
|
|
|
|
//
|
|
// Charge commitment.
|
|
//
|
|
|
|
|
|
ResidentPages = MI_SESSION_COMMIT_CHARGE;
|
|
|
|
if (MiChargeCommitment (ResidentPages, NULL) == FALSE) {
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_COMMIT);
|
|
goto Failure;
|
|
}
|
|
|
|
GotCommit = TRUE;
|
|
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_CREATE, ResidentPages);
|
|
|
|
|
|
|
|
//
|
|
// Reserve global system PTEs to map the data pages with.
|
|
//
|
|
|
|
|
|
|
|
GlobalMappingPte = MiReserveSystemPtes (2, SystemPteSpace);
|
|
|
|
if (GlobalMappingPte == NULL) {
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_SYSPTES);
|
|
goto Failure;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// Ensure the resident physical pages are available.
|
|
//
|
|
|
|
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
if ((SPFN_NUMBER)(ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM) > MI_NONPAGABLE_MEMORY_AVAILABLE()) {
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_RESIDENT);
|
|
goto Failure;
|
|
}
|
|
|
|
GotPages = TRUE;
|
|
|
|
MmResidentAvailablePages -= (ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
|
|
|
|
MM_BUMP_COUNTER(40, ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
|
|
|
|
|
|
|
|
//
|
|
// Allocate both session space data pages first as on some architectures
|
|
// a region ID will be used immediately for the TB references as the
|
|
// PTE mappings are initialized.
|
|
//
|
|
|
|
|
|
|
|
TempPte.u.Long = ValidKernelPte.u.Long;
|
|
|
|
MiEnsureAvailablePageOrWait (NULL, NULL);
|
|
|
|
PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
|
|
|
|
DataPage = MiRemoveZeroPageMayReleaseLocks (PageColor, OldIrql);
|
|
|
|
TempPte.u.Hard.PageFrameNumber = DataPage;
|
|
|
|
|
|
|
|
TempPte2.u.Long = ValidKernelPte.u.Long;
|
|
|
|
MiEnsureAvailablePageOrWait (NULL, NULL);
|
|
|
|
PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
|
|
|
|
DataPage2 = MiRemoveZeroPageMayReleaseLocks (PageColor, OldIrql);
|
|
|
|
TempPte2.u.Hard.PageFrameNumber = DataPage2;
|
|
|
|
GotUnmappedDataPage = TRUE;
|
|
|
|
|
|
|
|
//
|
|
// Map the data pages immediately in global space. Some architectures
|
|
// use a region ID which is used immediately for the TB references after
|
|
// the PTE mappings are initialized.
|
|
//
|
|
//
|
|
// The global bit can be left on for the global mappings (unlike the
|
|
// session space mapping which must be have the global bit off since
|
|
// we need to make sure the TB entry is flushed when we switch to
|
|
// a process in a different session space).
|
|
//
|
|
|
|
MI_WRITE_VALID_PTE (GlobalMappingPte, TempPte);
|
|
MI_WRITE_VALID_PTE (GlobalMappingPte + 1, TempPte2);
|
|
|
|
SessionGlobal = (PMM_SESSION_SPACE) MiGetVirtualAddressMappedByPte (GlobalMappingPte);
|
|
|
|
|
|
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
|
|
//
|
|
// Initialize the page directory parent page.
|
|
//
|
|
|
|
MiEnsureAvailablePageOrWait (NULL, NULL);
|
|
|
|
PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
|
|
|
|
PageDirectoryParentPage = MiRemoveZeroPageMayReleaseLocks (PageColor, OldIrql);
|
|
|
|
TempPte.u.Long = ValidKernelPdeLocal.u.Long;
|
|
TempPte.u.Hard.PageFrameNumber = PageDirectoryParentPage;
|
|
|
|
#if defined(_IA64_)
|
|
|
|
ASSERT (MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)(&Process->Pcb.SessionParentBase)) == MmSessionParentTablePage);
|
|
|
|
|
|
//
|
|
// In order to prevent races with threads on other processors context
|
|
// switching into this process, initialize the region registers and
|
|
// translation registers stored in the KPROCESS now. Otherwise
|
|
// access to the top level parent can go away which would be fatal.
|
|
//
|
|
// Note this could not be done until both the top level page and the
|
|
// session data page were acquired.
|
|
//
|
|
// The top level session entry is mapped with a translation register.
|
|
// Replacing a current TR entry requires a purge first if the virtual
|
|
// address and RID are the same in the new and old entries.
|
|
//
|
|
|
|
KeEnableSessionSharing (&SessionGlobal->SessionMapInfo,
|
|
PageDirectoryParentPage);
|
|
|
|
//
|
|
// Install the selfmap entry for this session space.
|
|
//
|
|
|
|
PointerPpe = KSEG_ADDRESS (PageDirectoryParentPage);
|
|
|
|
PointerPpe[MiGetPpeOffset(PDE_STBASE)] = TempPte;
|
|
|
|
PointerPpe = MiGetPpeAddress ((PVOID)MmSessionSpace);
|
|
|
|
MiInitializePfnForOtherProcess (PageDirectoryParentPage, PointerPpe, 0);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageDirectoryParentPage);
|
|
Pfn1->u4.PteFrame = PageDirectoryParentPage;
|
|
|
|
#else
|
|
|
|
//
|
|
// The global bit is masked off since we need to make sure the TB entry
|
|
// is flushed when we switch to a process in a different session space.
|
|
//
|
|
|
|
TempPte.u.Long = ValidKernelPdeLocal.u.Long;
|
|
TempPte.u.Hard.PageFrameNumber = PageDirectoryParentPage;
|
|
|
|
PointerPxe = MiGetPxeAddress ((PVOID)MmSessionSpace);
|
|
|
|
ASSERT (PointerPxe->u.Long == 0);
|
|
|
|
*PointerPxe = TempPte;
|
|
|
|
//
|
|
// Do not reference the top level parent page as it belongs to the
|
|
// current process (SMSS).
|
|
//
|
|
|
|
MiInitializePfnForOtherProcess (PageDirectoryParentPage, PointerPxe, 0);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageDirectoryParentPage);
|
|
Pfn1->u4.PteFrame = 0;
|
|
|
|
KeFillEntryTb ((PHARDWARE_PTE) PointerPxe,
|
|
MiGetVirtualAddressMappedByPte (PointerPxe),
|
|
FALSE);
|
|
#endif
|
|
|
|
ASSERT (MI_PFN_ELEMENT(PageDirectoryParentPage)->u1.WsIndex == 0);
|
|
|
|
//
|
|
// Initialize the page directory page.
|
|
//
|
|
|
|
MiEnsureAvailablePageOrWait (NULL, NULL);
|
|
|
|
PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
|
|
|
|
PageDirectoryPage = MiRemoveZeroPageMayReleaseLocks (PageColor, OldIrql);
|
|
|
|
//
|
|
// The global bit is masked off since we need to make sure the TB entry
|
|
// is flushed when we switch to a process in a different session space.
|
|
//
|
|
|
|
TempPte.u.Long = ValidKernelPdeLocal.u.Long;
|
|
TempPte.u.Hard.PageFrameNumber = PageDirectoryPage;
|
|
|
|
PointerPpe = MiGetPpeAddress ((PVOID)MmSessionSpace);
|
|
|
|
ASSERT (PointerPpe->u.Long == 0);
|
|
|
|
*PointerPpe = TempPte;
|
|
|
|
#if defined(_IA64_)
|
|
//
|
|
// IA64 can reference the top level parent page here because a unique
|
|
// one is allocated per process.
|
|
//
|
|
MiInitializePfnForOtherProcess (PageDirectoryPage, PointerPpe, PageDirectoryParentPage);
|
|
#else
|
|
//
|
|
// Do not reference the top level parent page as it belongs to the
|
|
// current process (SMSS).
|
|
//
|
|
MiInitializePfnForOtherProcess (PageDirectoryPage, PointerPpe, 0);
|
|
Pfn1 = MI_PFN_ELEMENT (PageDirectoryPage);
|
|
Pfn1->u4.PteFrame = 0;
|
|
#endif
|
|
|
|
ASSERT (MI_PFN_ELEMENT(PageDirectoryPage)->u1.WsIndex == 0);
|
|
|
|
KeFillEntryTb ((PHARDWARE_PTE) PointerPpe,
|
|
MiGetVirtualAddressMappedByPte (PointerPpe),
|
|
FALSE);
|
|
#endif
|
|
|
|
//
|
|
// Initialize the page table page.
|
|
//
|
|
|
|
MiEnsureAvailablePageOrWait (NULL, NULL);
|
|
|
|
PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
|
|
|
|
PageTablePage = MiRemoveZeroPageMayReleaseLocks (PageColor, OldIrql);
|
|
|
|
//
|
|
// The global bit is masked off since we need to make sure the TB entry
|
|
// is flushed when we switch to a process in a different session space.
|
|
//
|
|
|
|
TempPte.u.Long = ValidKernelPdeLocal.u.Long;
|
|
TempPte.u.Hard.PageFrameNumber = PageTablePage;
|
|
|
|
PointerPde = MiGetPdeAddress ((PVOID)MmSessionSpace);
|
|
|
|
ASSERT (PointerPde->u.Long == 0);
|
|
|
|
MI_WRITE_VALID_PTE (PointerPde, TempPte);
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
MiInitializePfnForOtherProcess (PageTablePage, PointerPde, PageDirectoryPage);
|
|
#else
|
|
//
|
|
// This page frame references itself instead of the current (SMSS.EXE)
|
|
// page directory as its PteFrame. This allows the current process to
|
|
// appear more normal (at least on 32-bit NT). It just means we have
|
|
// to treat this page specially during teardown.
|
|
//
|
|
|
|
MiInitializePfnForOtherProcess (PageTablePage, PointerPde, PageTablePage);
|
|
#endif
|
|
|
|
//
|
|
// This page is never paged, ensure that its WsIndex stays clear so the
|
|
// release of the page is handled correctly.
|
|
//
|
|
|
|
ASSERT (MI_PFN_ELEMENT(PageTablePage)->u1.WsIndex == 0);
|
|
|
|
Va = (ULONG_PTR)MiGetPteAddress (MmSessionSpace);
|
|
|
|
KeFillEntryTb ((PHARDWARE_PTE) PointerPde, (PMMPTE)Va, FALSE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// The global bit is masked off since we need to make sure the TB entry
|
|
// is flushed when we switch to a process in a different session space.
|
|
//
|
|
|
|
TempPte.u.Long = ValidKernelPteLocal.u.Long;
|
|
TempPte.u.Hard.PageFrameNumber = DataPage;
|
|
|
|
PointerPte = MiGetPteAddress (MmSessionSpace);
|
|
|
|
MI_WRITE_VALID_PTE (PointerPte, TempPte);
|
|
|
|
MiInitializePfn (DataPage, PointerPte, 1);
|
|
|
|
//
|
|
// Now do the second data page.
|
|
//
|
|
|
|
TempPte.u.Hard.PageFrameNumber = DataPage2;
|
|
|
|
MI_WRITE_VALID_PTE (PointerPte + 1, TempPte);
|
|
|
|
MiInitializePfn (DataPage2, PointerPte + 1, 1);
|
|
|
|
//
|
|
// Now that the data page is mapped, it can be freed as full hierarchy
|
|
// teardown in the event of any future failures encountered by this routine.
|
|
//
|
|
|
|
GotUnmappedDataPage = FALSE;
|
|
|
|
ASSERT (MI_PFN_ELEMENT(DataPage)->u1.WsIndex == 0);
|
|
ASSERT (MI_PFN_ELEMENT(DataPage2)->u1.WsIndex == 0);
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
KeFillEntryTb ((PHARDWARE_PTE) PointerPte,
|
|
(PMMPTE)MmSessionSpace,
|
|
FALSE);
|
|
|
|
KeFillEntryTb ((PHARDWARE_PTE) PointerPte + 1,
|
|
(PMMPTE)((PCHAR)MmSessionSpace + PAGE_SIZE),
|
|
FALSE);
|
|
|
|
//
|
|
// Initialize the new session space data structure.
|
|
//
|
|
|
|
SessionSpace = MmSessionSpace;
|
|
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGETABLE_ALLOC, 1);
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGE_ALLOC, 1);
|
|
|
|
SessionSpace->GlobalPteEntry = GlobalMappingPte;
|
|
SessionSpace->GlobalVirtualAddress = SessionGlobal;
|
|
|
|
#if defined(_IA64_)
|
|
SessionSpace->PageDirectoryParentPage = PageDirectoryParentPage;
|
|
#endif
|
|
SessionSpace->ReferenceCount = 1;
|
|
SessionSpace->u.LongFlags = 0;
|
|
SessionSpace->SessionId = *SessionId;
|
|
SessionSpace->LocaleId = PsDefaultSystemLocaleId;
|
|
SessionSpace->SessionPageDirectoryIndex = PageTablePage;
|
|
|
|
SessionSpace->Color = PageColor;
|
|
|
|
//
|
|
// Track the page table page and the data page.
|
|
//
|
|
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_SESSION_CREATE, (ULONG)ResidentPages);
|
|
SessionSpace->NonPagablePages = ResidentPages;
|
|
SessionSpace->CommittedPages = ResidentPages;
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
|
|
//
|
|
// Initialize the session data page directory entry so trimmers can attach.
|
|
//
|
|
|
|
#if defined(_AMD64_)
|
|
PointerPpe = MiGetPxeAddress ((PVOID)MmSessionSpace);
|
|
#else
|
|
PointerPpe = MiGetPpeAddress ((PVOID)MmSessionSpace);
|
|
#endif
|
|
SessionSpace->PageDirectory = *PointerPpe;
|
|
|
|
#else
|
|
|
|
SessionSpace->PageTables = PageTables;
|
|
|
|
//
|
|
// Load the session data page table entry so that other processes
|
|
// can fault in the mapping.
|
|
//
|
|
|
|
SessionSpace->PageTables[PointerPde - MiGetPdeAddress (MmSessionBase)] = *PointerPde;
|
|
|
|
#endif
|
|
|
|
//
|
|
// This list entry is only referenced while within the
|
|
// session space and has session space (not global) addresses.
|
|
//
|
|
|
|
InitializeListHead (&SessionSpace->ImageList);
|
|
|
|
//
|
|
// Initialize the session space pool.
|
|
//
|
|
|
|
Status = MiInitializeSessionPool ();
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto Failure;
|
|
}
|
|
|
|
PoolInitialized = TRUE;
|
|
|
|
//
|
|
// Initialize the view mapping support - note this must happen after
|
|
// initializing session pool.
|
|
//
|
|
|
|
if (MiInitializeSystemSpaceMap (&SessionGlobal->Session) == FALSE) {
|
|
goto Failure;
|
|
}
|
|
|
|
MmUnlockPagableImageSection (ExPageLockHandle);
|
|
|
|
#if defined (_WIN64)
|
|
MiInitializeSpecialPool (PagedPoolSession);
|
|
#endif
|
|
|
|
//
|
|
// Use the global virtual address rather than the session space virtual
|
|
// address to set up fields that need to be globally accessible.
|
|
//
|
|
|
|
InitializeListHead (&SessionGlobal->WsListEntry);
|
|
|
|
InitializeListHead (&SessionGlobal->ProcessList);
|
|
|
|
ASSERT (Process->Flags & PS_PROCESS_FLAGS_CREATING_SESSION);
|
|
PS_CLEAR_BITS (&Process->Flags, PS_PROCESS_FLAGS_CREATING_SESSION);
|
|
|
|
ASSERT (Process->Session == NULL);
|
|
|
|
ASSERT (SessionGlobal->ProcessReferenceToSession == 0);
|
|
SessionGlobal->ProcessReferenceToSession = 1;
|
|
|
|
InterlockedIncrement (&MmSessionDataPages);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
Failure:
|
|
|
|
#if (_MI_PAGING_LEVELS < 3)
|
|
ExFreePool (PageTables);
|
|
#endif
|
|
|
|
if (GotCommit == TRUE) {
|
|
MiReturnCommitment (ResidentPages);
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SESSION_CREATE_FAILURE,
|
|
ResidentPages);
|
|
}
|
|
|
|
if (GotPages == TRUE) {
|
|
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
PointerPxe = MiGetPxeAddress ((PVOID)MmSessionSpace);
|
|
ASSERT (PointerPxe->u.Hard.Valid != 0);
|
|
#endif
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
PointerPpe = MiGetPpeAddress ((PVOID)MmSessionSpace);
|
|
ASSERT (PointerPpe->u.Hard.Valid != 0);
|
|
#endif
|
|
|
|
PointerPde = MiGetPdeAddress (MmSessionSpace);
|
|
ASSERT (PointerPde->u.Hard.Valid != 0);
|
|
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGE_FREE_FAIL1, 1);
|
|
MM_BUMP_COUNTER (49, ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
|
|
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGETABLE_FREE_FAIL1, 1);
|
|
|
|
|
|
//
|
|
// Do not call MiFreeSessionSpaceMap () as the maps cannot have been
|
|
// initialized if we are in this path.
|
|
//
|
|
|
|
//
|
|
// Free the initial page table page that was allocated for the
|
|
// paged pool range (if it has been allocated at this point).
|
|
//
|
|
|
|
MiFreeSessionPoolBitMaps ();
|
|
|
|
//
|
|
// Capture all needed session information now as after sharing
|
|
// is disabled below, no references to session space can be made.
|
|
//
|
|
|
|
#if defined(_IA64_)
|
|
KeDetachSessionSpace (&MmSessionMapInfo, MmSessionParentTablePage);
|
|
#else
|
|
MI_WRITE_INVALID_PTE (PointerPte, ZeroKernelPte);
|
|
MI_WRITE_INVALID_PTE (PointerPte + 1, ZeroKernelPte);
|
|
MI_WRITE_INVALID_PTE (PointerPde, ZeroKernelPte);
|
|
#if defined(_AMD64_)
|
|
MI_WRITE_INVALID_PTE (PointerPpe, ZeroKernelPte);
|
|
MI_WRITE_INVALID_PTE (PointerPxe, ZeroKernelPte);
|
|
#endif
|
|
|
|
#endif
|
|
|
|
MI_FLUSH_SESSION_TB ();
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Free the session data structure pages.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (DataPage);
|
|
MiDecrementShareAndValidCount (Pfn1->u4.PteFrame);
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (DataPage);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (DataPage2);
|
|
MiDecrementShareAndValidCount (Pfn1->u4.PteFrame);
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (DataPage2);
|
|
|
|
//
|
|
// Free the page table page.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageTablePage);
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
|
|
if (PoolInitialized == TRUE) {
|
|
ASSERT (Pfn1->u2.ShareCount == 2);
|
|
Pfn1->u2.ShareCount -= 1;
|
|
}
|
|
ASSERT (Pfn1->u2.ShareCount == 1);
|
|
MiDecrementShareAndValidCount (Pfn1->u4.PteFrame);
|
|
|
|
#else
|
|
|
|
ASSERT (PageTablePage == Pfn1->u4.PteFrame);
|
|
|
|
if (PoolInitialized == TRUE) {
|
|
ASSERT (Pfn1->u2.ShareCount == 3);
|
|
Pfn1->u2.ShareCount -= 2;
|
|
}
|
|
else {
|
|
ASSERT (Pfn1->u2.ShareCount == 2);
|
|
Pfn1->u2.ShareCount -= 1;
|
|
}
|
|
|
|
#endif
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (PageTablePage);
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
|
|
//
|
|
// Free the page directory page.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageDirectoryPage);
|
|
MiDecrementShareAndValidCount (Pfn1->u4.PteFrame);
|
|
ASSERT (Pfn1->u2.ShareCount == 1);
|
|
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (PageDirectoryPage);
|
|
|
|
//
|
|
// Free the page directory parent page.
|
|
//
|
|
|
|
ASSERT (Pfn1->u4.PteFrame == PageDirectoryParentPage);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageDirectoryParentPage);
|
|
|
|
ASSERT (Pfn1->u2.ShareCount == 1);
|
|
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (PageDirectoryParentPage);
|
|
|
|
#endif
|
|
|
|
MmResidentAvailablePages += (ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
}
|
|
|
|
if (GlobalMappingPte != NULL) {
|
|
MiReleaseSystemPtes (GlobalMappingPte, 2, SystemPteSpace);
|
|
}
|
|
|
|
if (GotUnmappedDataPage == TRUE) {
|
|
LOCK_PFN (OldIrql);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (DataPage);
|
|
ASSERT (Pfn1->u2.ShareCount == 0);
|
|
ASSERT (Pfn1->u3.e2.ReferenceCount == 0);
|
|
Pfn1->u3.e2.ReferenceCount = 1;
|
|
Pfn1->OriginalPte.u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
#if DBG
|
|
Pfn1->u3.e1.PageLocation = StandbyPageList;
|
|
#endif
|
|
MiDecrementReferenceCount (DataPage);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (DataPage2);
|
|
ASSERT (Pfn1->u2.ShareCount == 0);
|
|
ASSERT (Pfn1->u3.e2.ReferenceCount == 0);
|
|
Pfn1->u3.e2.ReferenceCount = 1;
|
|
Pfn1->OriginalPte.u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
#if DBG
|
|
Pfn1->u3.e1.PageLocation = StandbyPageList;
|
|
#endif
|
|
|
|
MiDecrementReferenceCount (DataPage2);
|
|
UNLOCK_PFN (OldIrql);
|
|
}
|
|
|
|
MmUnlockPagableImageSection (ExPageLockHandle);
|
|
|
|
ExAcquireFastMutex (&MiSessionIdMutex);
|
|
|
|
ASSERT (RtlCheckBit (MiSessionIdBitmap, *SessionId));
|
|
RtlClearBit (MiSessionIdBitmap, *SessionId);
|
|
|
|
ExReleaseFastMutex (&MiSessionIdMutex);
|
|
|
|
ASSERT (Process->Flags & PS_PROCESS_FLAGS_CREATING_SESSION);
|
|
PS_CLEAR_BITS (&Process->Flags, PS_PROCESS_FLAGS_CREATING_SESSION);
|
|
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiIncrementSessionCount (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Nonpaged wrapper to increment the session count. A spinlock is used to
|
|
provide serialization with win32k callouts.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
MiSessionCount += 1;
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
}
|
|
|
|
LONG MiSessionLeaderExists;
|
|
|
|
|
|
NTSTATUS
|
|
MmSessionCreate (
|
|
OUT PULONG SessionId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called from NtSetSystemInformation() to create a session space
|
|
in the calling process with the specified SessionId. An error is returned
|
|
if the calling process already has a session space.
|
|
|
|
Arguments:
|
|
|
|
SessionId - Supplies a pointer to place the resulting session id in.
|
|
|
|
Return Value:
|
|
|
|
Various NTSTATUS error codes.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, no mutexes held.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG SessionLeaderExists;
|
|
PKTHREAD CurrentThread;
|
|
NTSTATUS Status;
|
|
PEPROCESS CurrentProcess;
|
|
#if DBG && (_MI_PAGING_LEVELS < 3)
|
|
PMMPTE StartPde;
|
|
PMMPTE EndPde;
|
|
#endif
|
|
|
|
CurrentThread = KeGetCurrentThread ();
|
|
ASSERT ((PETHREAD)CurrentThread == PsGetCurrentThread ());
|
|
CurrentProcess = PsGetCurrentProcessByThread ((PETHREAD)CurrentThread);
|
|
|
|
//
|
|
// A simple check to see if the calling process already has a session space.
|
|
// No need to go through all this if it does. Creation races are caught
|
|
// below and recovered from regardless.
|
|
//
|
|
|
|
if (CurrentProcess->Flags & PS_PROCESS_FLAGS_IN_SESSION) {
|
|
return STATUS_ALREADY_COMMITTED;
|
|
}
|
|
|
|
if (CurrentProcess->Vm.Flags.SessionLeader == 0) {
|
|
|
|
//
|
|
// Only the session manager can create a session. Make the current
|
|
// process the session leader if this is the first session creation
|
|
// ever.
|
|
//
|
|
// Make sure the add is only done once as this is called multiple times.
|
|
//
|
|
|
|
SessionLeaderExists = InterlockedCompareExchange (&MiSessionLeaderExists, 1, 0);
|
|
|
|
if (SessionLeaderExists != 0) {
|
|
return STATUS_INVALID_SYSTEM_SERVICE;
|
|
}
|
|
|
|
MiSessionLeader (CurrentProcess);
|
|
}
|
|
|
|
ASSERT (MmIsAddressValid(MmSessionSpace) == FALSE);
|
|
|
|
#if defined (_AMD64_)
|
|
ASSERT ((MiGetPxeAddress(MmSessionBase))->u.Long == ZeroKernelPte.u.Long);
|
|
#endif
|
|
|
|
#if (_MI_PAGING_LEVELS < 3)
|
|
|
|
#if DBG
|
|
StartPde = MiGetPdeAddress (MmSessionBase);
|
|
EndPde = MiGetPdeAddress (MiSessionSpaceEnd);
|
|
|
|
while (StartPde < EndPde) {
|
|
ASSERT (StartPde->u.Long == ZeroKernelPte.u.Long);
|
|
StartPde += 1;
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|
|
KeEnterCriticalRegionThread (CurrentThread);
|
|
|
|
Status = MiSessionCreateInternal (SessionId);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
KeLeaveCriticalRegionThread (CurrentThread);
|
|
return Status;
|
|
}
|
|
|
|
MiIncrementSessionCount ();
|
|
|
|
//
|
|
// Add the session space to the working set list.
|
|
//
|
|
|
|
Status = MiSessionInitializeWorkingSetList ();
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
MiDereferenceSession ();
|
|
KeLeaveCriticalRegionThread (CurrentThread);
|
|
return Status;
|
|
}
|
|
|
|
KeLeaveCriticalRegionThread (CurrentThread);
|
|
|
|
MmSessionSpace->u.Flags.Initialized = 1;
|
|
|
|
PS_SET_BITS (&CurrentProcess->Flags, PS_PROCESS_FLAGS_IN_SESSION);
|
|
|
|
if (MiSessionLeaderExists == 1) {
|
|
InterlockedCompareExchange (&MiSessionLeaderExists, 2, 1);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
MmSessionDelete (
|
|
ULONG SessionId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called from NtSetSystemInformation() to detach from an existing
|
|
session space in the calling process. An error is returned
|
|
if the calling process has no session space.
|
|
|
|
Arguments:
|
|
|
|
SessionId - Supplies the session id to delete.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS on success, STATUS_UNABLE_TO_FREE_VM on failure.
|
|
|
|
This process will not be able to access session space anymore upon
|
|
a successful return. If this is the last process in the session then
|
|
the entire session is torn down.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, no mutexes held.
|
|
|
|
--*/
|
|
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PEPROCESS CurrentProcess;
|
|
|
|
CurrentThread = KeGetCurrentThread ();
|
|
ASSERT ((PETHREAD)CurrentThread == PsGetCurrentThread ());
|
|
CurrentProcess = PsGetCurrentProcessByThread ((PETHREAD)CurrentThread);
|
|
|
|
//
|
|
// See if the calling process has a session space - this must be
|
|
// checked since we can be called via a system service.
|
|
//
|
|
|
|
if ((CurrentProcess->Flags & PS_PROCESS_FLAGS_IN_SESSION) == 0) {
|
|
#if DBG
|
|
DbgPrint ("MmSessionDelete: Process %p not in a session\n",
|
|
CurrentProcess);
|
|
DbgBreakPoint();
|
|
#endif
|
|
return STATUS_UNABLE_TO_FREE_VM;
|
|
}
|
|
|
|
if (CurrentProcess->Vm.Flags.SessionLeader == 0) {
|
|
|
|
//
|
|
// Only the session manager can delete a session. This is because
|
|
// it affects the virtual mappings for all threads within the process
|
|
// when this address space is deleted. This is different from normal
|
|
// VAD clearing because win32k and other drivers rely on this space.
|
|
//
|
|
|
|
return STATUS_UNABLE_TO_FREE_VM;
|
|
}
|
|
|
|
ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE);
|
|
|
|
if (MmSessionSpace->SessionId != SessionId) {
|
|
#if DBG
|
|
DbgPrint("MmSessionDelete: Wrong SessionId! Own %d, Ask %d\n",
|
|
MmSessionSpace->SessionId,
|
|
SessionId);
|
|
DbgBreakPoint();
|
|
#endif
|
|
return STATUS_UNABLE_TO_FREE_VM;
|
|
}
|
|
|
|
KeEnterCriticalRegionThread (CurrentThread);
|
|
|
|
MiDereferenceSession ();
|
|
|
|
KeLeaveCriticalRegionThread (CurrentThread);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiAttachSession (
|
|
IN PMM_SESSION_SPACE SessionGlobal
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Attaches to the specified session space.
|
|
|
|
Arguments:
|
|
|
|
SessionGlobal - Supplies a pointer to the session to attach to.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. No locks held. Current process must not have a session
|
|
space - ie: the caller should be the system process or smss.exe.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE PointerPde;
|
|
|
|
ASSERT ((PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION) == 0);
|
|
|
|
#if defined (_AMD64_)
|
|
|
|
PointerPde = MiGetPxeAddress (MmSessionBase);
|
|
ASSERT (PointerPde->u.Long == ZeroKernelPte.u.Long);
|
|
MI_WRITE_VALID_PTE (PointerPde, SessionGlobal->PageDirectory);
|
|
|
|
#elif defined(_IA64_)
|
|
|
|
PointerPde = (PMMPTE) (&PsGetCurrentProcess()->Pcb.SessionParentBase);
|
|
|
|
ASSERT (MI_GET_PAGE_FRAME_FROM_PTE(PointerPde) == MmSessionParentTablePage);
|
|
|
|
KeAttachSessionSpace (&SessionGlobal->SessionMapInfo,
|
|
SessionGlobal->PageDirectoryParentPage);
|
|
|
|
#else
|
|
|
|
PointerPde = MiGetPdeAddress (MmSessionBase);
|
|
|
|
ASSERT (RtlCompareMemoryUlong (PointerPde,
|
|
MiSessionSpacePageTables * sizeof (MMPTE),
|
|
0) == MiSessionSpacePageTables * sizeof (MMPTE));
|
|
|
|
RtlCopyMemory (PointerPde,
|
|
&SessionGlobal->PageTables[0],
|
|
MiSessionSpacePageTables * sizeof (MMPTE));
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
VOID
|
|
MiDetachSession (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Detaches from the specified session space.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. No locks held. Current process must not have a session
|
|
space to return to - ie: this should be the system process.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE PointerPde;
|
|
|
|
ASSERT ((PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION) == 0);
|
|
ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE);
|
|
|
|
#if defined (_AMD64_)
|
|
|
|
PointerPde = MiGetPxeAddress (MmSessionBase);
|
|
PointerPde->u.Long = ZeroKernelPte.u.Long;
|
|
|
|
#elif defined(_IA64_)
|
|
|
|
PointerPde = (PMMPTE) (&PsGetCurrentProcess()->Pcb.SessionParentBase);
|
|
|
|
ASSERT (MI_GET_PAGE_FRAME_FROM_PTE(PointerPde) == MmSessionSpace->PageDirectoryParentPage);
|
|
|
|
KeDetachSessionSpace (&MmSessionMapInfo, MmSessionParentTablePage);
|
|
|
|
#else
|
|
|
|
PointerPde = MiGetPdeAddress (MmSessionBase);
|
|
|
|
RtlZeroMemory (PointerPde, MiSessionSpacePageTables * sizeof (MMPTE));
|
|
|
|
#endif
|
|
|
|
MI_FLUSH_SESSION_TB ();
|
|
}
|
|
|
|
#if DBG
|
|
|
|
VOID
|
|
MiCheckSessionVirtualSpace (
|
|
IN PVOID VirtualAddress,
|
|
IN SIZE_T NumberOfBytes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Used to verify that no drivers fail to clean up their session allocations.
|
|
|
|
Arguments:
|
|
|
|
VirtualAddress - Supplies the starting virtual address to check.
|
|
|
|
NumberOfBytes - Supplies the number of bytes to check.
|
|
|
|
Return Value:
|
|
|
|
TRUE if all the PTEs have been freed, FALSE if not.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. APCs disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE StartPde;
|
|
PMMPTE EndPde;
|
|
PMMPTE StartPte;
|
|
PMMPTE EndPte;
|
|
ULONG Index;
|
|
|
|
//
|
|
// Check the specified region. Everything should have been cleaned up
|
|
// already.
|
|
//
|
|
|
|
#if defined (_AMD64_)
|
|
ASSERT64 (MiGetPxeAddress (VirtualAddress)->u.Hard.Valid == 1);
|
|
#endif
|
|
|
|
ASSERT64 (MiGetPpeAddress (VirtualAddress)->u.Hard.Valid == 1);
|
|
|
|
StartPde = MiGetPdeAddress (VirtualAddress);
|
|
EndPde = MiGetPdeAddress ((PVOID)((PCHAR)VirtualAddress + NumberOfBytes - 1));
|
|
|
|
StartPte = MiGetPteAddress (VirtualAddress);
|
|
EndPte = MiGetPteAddress ((PVOID)((PCHAR)VirtualAddress + NumberOfBytes - 1));
|
|
|
|
Index = (ULONG)(StartPde - MiGetPdeAddress ((PVOID)MmSessionBase));
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
while (StartPde <= EndPde && StartPde->u.Long == 0)
|
|
#else
|
|
while (StartPde <= EndPde && MmSessionSpace->PageTables[Index].u.Long == 0)
|
|
#endif
|
|
{
|
|
StartPde += 1;
|
|
Index += 1;
|
|
StartPte = MiGetVirtualAddressMappedByPte (StartPde);
|
|
}
|
|
|
|
while (StartPte <= EndPte) {
|
|
|
|
if (MiIsPteOnPdeBoundary(StartPte)) {
|
|
|
|
StartPde = MiGetPteAddress (StartPte);
|
|
Index = (ULONG)(StartPde - MiGetPdeAddress ((PVOID)MmSessionBase));
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
while (StartPde <= EndPde && StartPde->u.Long == 0)
|
|
#else
|
|
while (StartPde <= EndPde && MmSessionSpace->PageTables[Index].u.Long == 0)
|
|
#endif
|
|
{
|
|
Index += 1;
|
|
StartPde += 1;
|
|
StartPte = MiGetVirtualAddressMappedByPte (StartPde);
|
|
}
|
|
if (StartPde > EndPde) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (StartPte->u.Long != 0 && StartPte->u.Long != MM_KERNEL_NOACCESS_PTE) {
|
|
DbgPrint("MiCheckSessionVirtualSpace: StartPte 0x%p is still valid! 0x%p, VA 0x%p\n",
|
|
StartPte,
|
|
StartPte->u.Long,
|
|
MiGetVirtualAddressMappedByPte(StartPte));
|
|
|
|
DbgBreakPoint();
|
|
}
|
|
StartPte += 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
VOID
|
|
MiSessionDeletePde (
|
|
IN PMMPTE Pde,
|
|
IN LOGICAL WorkingSetInitialized,
|
|
IN PMMPTE SelfMapPde
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Used to delete a page directory entry from a session space.
|
|
|
|
Arguments:
|
|
|
|
Pde - Supplies the page directory entry to delete.
|
|
|
|
WorkingSetInitialized - Supplies TRUE if the working set has been
|
|
initialized.
|
|
|
|
SelfMapPde - Supplies the page directory entry that contains the self map
|
|
session page.
|
|
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. PFN lock held.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPFN Pfn1;
|
|
PFN_NUMBER PageFrameIndex;
|
|
LOGICAL SelfMapPage;
|
|
|
|
#if DBG
|
|
PMMPFN Pfn2;
|
|
#else
|
|
UNREFERENCED_PARAMETER (WorkingSetInitialized);
|
|
#endif
|
|
|
|
if (Pde->u.Long == ZeroKernelPte.u.Long) {
|
|
return;
|
|
}
|
|
|
|
SelfMapPage = (Pde == SelfMapPde ? TRUE : FALSE);
|
|
|
|
ASSERT (Pde->u.Hard.Valid == 1);
|
|
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (Pde);
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
|
|
#if DBG
|
|
|
|
ASSERT (PageFrameIndex <= MmHighestPhysicalPage);
|
|
|
|
if (WorkingSetInitialized == TRUE) {
|
|
ASSERT (Pfn1->u1.WsIndex);
|
|
}
|
|
|
|
ASSERT (Pfn1->u3.e1.PrototypePte == 0);
|
|
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
|
|
ASSERT (Pfn1->u4.PteFrame <= MmHighestPhysicalPage);
|
|
|
|
Pfn2 = MI_PFN_ELEMENT (Pfn1->u4.PteFrame);
|
|
|
|
//
|
|
// Verify the containing page table is still the page
|
|
// table page mapping the session data structure.
|
|
//
|
|
|
|
if (SelfMapPage == FALSE) {
|
|
|
|
//
|
|
// Note these ASSERTs will fail if win32k leaks pool.
|
|
//
|
|
|
|
ASSERT (Pfn1->u2.ShareCount == 1);
|
|
|
|
//
|
|
// NT32 points the additional page tables at the master.
|
|
// NT64 doesn't need to use this trick as there is always
|
|
// an additional hierarchy level.
|
|
//
|
|
|
|
ASSERT32 (Pfn1->u4.PteFrame == MI_GET_PAGE_FRAME_FROM_PTE (SelfMapPde));
|
|
ASSERT32 (Pfn2->u2.ShareCount >= 2);
|
|
}
|
|
else {
|
|
ASSERT32 (Pfn1 == Pfn2);
|
|
ASSERT32 (Pfn1->u2.ShareCount == 2);
|
|
|
|
ASSERT64 (Pfn1->u2.ShareCount == 1);
|
|
}
|
|
|
|
#endif // DBG
|
|
|
|
if (SelfMapPage == FALSE) {
|
|
MiDecrementShareAndValidCount (Pfn1->u4.PteFrame);
|
|
}
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (PageFrameIndex);
|
|
}
|
|
|
|
|
|
VOID
|
|
MiReleaseProcessReferenceToSessionDataPage (
|
|
PMM_SESSION_SPACE SessionGlobal
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Decrement this process' session reference. The session itself may have
|
|
already been deleted. If this is the last reference to the session,
|
|
then the session data page and its mapping PTE (if any) will be destroyed
|
|
upon return.
|
|
|
|
Arguments:
|
|
|
|
SessionGlobal - Supplies the global session space pointer being
|
|
dereferenced. The caller has already verified that this
|
|
process is a member of the target session.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, no mutexes held, APCs disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG SessionId;
|
|
PMMPTE GlobalPteEntrySave;
|
|
PFN_NUMBER PageFrameIndex;
|
|
PFN_NUMBER PageFrameIndex2;
|
|
PMMPFN Pfn1;
|
|
PMMPFN Pfn2;
|
|
KIRQL OldIrql;
|
|
|
|
if (InterlockedDecrement (&SessionGlobal->ProcessReferenceToSession) != 0) {
|
|
return;
|
|
}
|
|
|
|
SessionId = SessionGlobal->SessionId;
|
|
|
|
//
|
|
// Free the datapages & self-map PTE now since this is the last
|
|
// process reference and KeDetach has returned.
|
|
//
|
|
|
|
#if (_MI_PAGING_LEVELS < 3)
|
|
ExFreePool (SessionGlobal->PageTables);
|
|
#endif
|
|
|
|
GlobalPteEntrySave = SessionGlobal->GlobalPteEntry;
|
|
|
|
ASSERT (!MI_IS_PHYSICAL_ADDRESS(SessionGlobal));
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (GlobalPteEntrySave);
|
|
PageFrameIndex2 = MI_GET_PAGE_FRAME_FROM_PTE (GlobalPteEntrySave + 1);
|
|
MiReleaseSystemPtes (GlobalPteEntrySave, 2, SystemPteSpace);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
Pfn2 = MI_PFN_ELEMENT (PageFrameIndex2);
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
ASSERT (Pfn1->u2.ShareCount == 1);
|
|
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (PageFrameIndex);
|
|
|
|
//
|
|
// Free the second datapage.
|
|
//
|
|
|
|
ASSERT (Pfn2->u2.ShareCount == 1);
|
|
ASSERT (Pfn2->u3.e2.ReferenceCount == 1);
|
|
|
|
MI_SET_PFN_DELETED (Pfn2);
|
|
MiDecrementShareCountOnly (PageFrameIndex2);
|
|
|
|
MmResidentAvailablePages += 2;
|
|
MM_BUMP_COUNTER(52, 2);
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
InterlockedDecrement (&MmSessionDataPages);
|
|
|
|
//
|
|
// Return commitment for the datapages.
|
|
//
|
|
|
|
MiReturnCommitment (2);
|
|
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SESSION_DATAPAGE, 2);
|
|
|
|
//
|
|
// Release the session ID so it can be recycled.
|
|
//
|
|
|
|
ExAcquireFastMutex (&MiSessionIdMutex);
|
|
|
|
ASSERT (RtlCheckBit (MiSessionIdBitmap, SessionId));
|
|
RtlClearBit (MiSessionIdBitmap, SessionId);
|
|
|
|
ExReleaseFastMutex (&MiSessionIdMutex);
|
|
}
|
|
|
|
|
|
VOID
|
|
MiDereferenceSession (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Decrement this process' reference count to the session, unmapping access
|
|
to the session for the current process. If this is the last process
|
|
reference to this session, then the entire session will be destroyed upon
|
|
return. This includes unloading drivers, unmapping pools, freeing
|
|
page tables, etc.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, no mutexes held, APCs disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
#if !defined(_IA64_)
|
|
PMMPTE StartPde;
|
|
#endif
|
|
ULONG SessionId;
|
|
PEPROCESS Process;
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
|
|
ASSERT ((PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION) ||
|
|
((MmSessionSpace->u.Flags.Initialized == 0) && (PsGetCurrentProcess()->Vm.Flags.SessionLeader == 1) && (MmSessionSpace->ReferenceCount == 1)));
|
|
|
|
SessionId = MmSessionSpace->SessionId;
|
|
|
|
ASSERT (RtlCheckBit (MiSessionIdBitmap, SessionId));
|
|
|
|
if (InterlockedDecrement ((PLONG)&MmSessionSpace->ReferenceCount) != 0) {
|
|
|
|
Process = PsGetCurrentProcess ();
|
|
|
|
PS_CLEAR_BITS (&Process->Flags, PS_PROCESS_FLAGS_IN_SESSION);
|
|
|
|
//
|
|
// Don't delete any non-smss session space mappings here. Let them
|
|
// live on through process death. This handles the case where
|
|
// MmDispatchWin32Callout picks csrss - csrss has exited as it's not
|
|
// the last process (smss is). smss is simultaneously detaching from
|
|
// the session and since it is the last process, it's waiting on
|
|
// the AttachCount below. The dispatch callout ends up in csrss but
|
|
// has no way to synchronize against csrss exiting through this path
|
|
// as the object reference count doesn't stop it. So leave the
|
|
// session space mappings alive so the callout can execute through
|
|
// the remains of csrss.
|
|
//
|
|
// Note that when smss detaches, the address space must get cleared
|
|
// here so that subsequent session creations by smss will succeed.
|
|
//
|
|
|
|
if (Process->Vm.Flags.SessionLeader == 1) {
|
|
|
|
SessionGlobal = SESSION_GLOBAL (MmSessionSpace);
|
|
|
|
#if defined(_IA64_)
|
|
|
|
//
|
|
// Revert back to the pre-session dummy top level page.
|
|
//
|
|
|
|
KeDetachSessionSpace (&MmSessionMapInfo, MmSessionParentTablePage);
|
|
|
|
#elif defined (_AMD64_)
|
|
|
|
StartPde = MiGetPxeAddress (MmSessionBase);
|
|
StartPde->u.Long = ZeroKernelPte.u.Long;
|
|
|
|
#else
|
|
|
|
StartPde = MiGetPdeAddress (MmSessionBase);
|
|
RtlZeroMemory (StartPde, MiSessionSpacePageTables * sizeof(MMPTE));
|
|
|
|
#endif
|
|
|
|
MI_FLUSH_SESSION_TB ();
|
|
|
|
//
|
|
// This process' reference to the session must be NULL as the
|
|
// KeDetach has completed so no swap context referencing the
|
|
// earlier session page can occur from here on. This is also
|
|
// needed because during clean shutdowns, the final dereference
|
|
// of this process (smss) object will trigger an
|
|
// MmDeleteProcessAddressSpace - this routine will dereference
|
|
// the (no-longer existing) session space if this
|
|
// bit is not cleared properly.
|
|
//
|
|
|
|
ASSERT (Process->Session == NULL);
|
|
|
|
//
|
|
// Another process may have won the race and exited the session
|
|
// as this process is executing here. Hence the reference count
|
|
// is carefully checked here to ensure no leaks occur.
|
|
//
|
|
|
|
MiReleaseProcessReferenceToSessionDataPage (SessionGlobal);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//
|
|
// This is the final process in the session so the entire session must
|
|
// be dereferenced now.
|
|
//
|
|
|
|
MiDereferenceSessionFinal ();
|
|
}
|
|
|
|
|
|
VOID
|
|
MiDereferenceSessionFinal (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Decrement this process' reference count to the session, unmapping access
|
|
to the session for the current process. If this is the last process
|
|
reference to this session, then the entire session will be destroyed upon
|
|
return. This includes unloading drivers, unmapping pools, freeing
|
|
page tables, etc.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, no mutexes held, APCs disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
ULONG Index;
|
|
ULONG_PTR CountReleased;
|
|
ULONG_PTR CountReleased2;
|
|
ULONG SessionId;
|
|
PFN_NUMBER PageFrameIndex;
|
|
PFN_NUMBER PageFrameIndex2;
|
|
ULONG SessionDataPdeIndex;
|
|
KEVENT Event;
|
|
PMMPFN Pfn1;
|
|
PMMPFN Pfn2;
|
|
PMMPTE PointerPte;
|
|
PMMPTE EndPte;
|
|
PMMPTE GlobalPteEntrySave;
|
|
PMMPTE StartPde;
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
LOGICAL WorkingSetWasInitialized;
|
|
ULONG AttachCount;
|
|
PEPROCESS Process;
|
|
PKTHREAD CurrentThread;
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
PFN_NUMBER PageDirectoryFrame;
|
|
PFN_NUMBER PageParentFrame;
|
|
MMPTE SavePageTables[MI_SESSION_SPACE_MAXIMUM_PAGE_TABLES];
|
|
#endif
|
|
|
|
Process = PsGetCurrentProcess();
|
|
|
|
ASSERT ((Process->Flags & PS_PROCESS_FLAGS_IN_SESSION) ||
|
|
((MmSessionSpace->u.Flags.Initialized == 0) && (Process->Vm.Flags.SessionLeader == 1) && (MmSessionSpace->ReferenceCount == 0)));
|
|
|
|
SessionId = MmSessionSpace->SessionId;
|
|
|
|
ASSERT (RtlCheckBit (MiSessionIdBitmap, SessionId));
|
|
|
|
//
|
|
// This is the final dereference. We could be any process
|
|
// including SMSS when a session space load fails. Note also that
|
|
// processes can terminate in any order as well.
|
|
//
|
|
|
|
SessionGlobal = SESSION_GLOBAL (MmSessionSpace);
|
|
|
|
MmLockPagableSectionByHandle (ExPageLockHandle);
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
//
|
|
// Wait for any cross-session process attaches to detach. Refuse
|
|
// subsequent attempts to cross-session attach so the address invalidation
|
|
// code doesn't surprise an ongoing or subsequent attachee.
|
|
//
|
|
|
|
ASSERT (MmSessionSpace->u.Flags.DeletePending == 0);
|
|
|
|
MmSessionSpace->u.Flags.DeletePending = 1;
|
|
|
|
AttachCount = MmSessionSpace->AttachCount;
|
|
|
|
if (AttachCount) {
|
|
|
|
KeInitializeEvent (&SessionGlobal->AttachEvent,
|
|
NotificationEvent,
|
|
FALSE);
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
KeWaitForSingleObject( &SessionGlobal->AttachEvent,
|
|
WrVirtualMemory,
|
|
KernelMode,
|
|
FALSE,
|
|
(PLARGE_INTEGER)NULL);
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
ASSERT (MmSessionSpace->u.Flags.DeletePending == 1);
|
|
ASSERT (MmSessionSpace->AttachCount == 0);
|
|
}
|
|
|
|
if (MmSessionSpace->Vm.Flags.BeingTrimmed) {
|
|
|
|
//
|
|
// Initialize an event and put the event address
|
|
// in the VmSupport. When the trimming is complete,
|
|
// this event will be set.
|
|
//
|
|
|
|
KeInitializeEvent(&Event, NotificationEvent, FALSE);
|
|
|
|
MmSessionSpace->Vm.WorkingSetExpansionLinks.Blink = (PLIST_ENTRY)&Event;
|
|
|
|
//
|
|
// Release the mutex and wait for the event.
|
|
//
|
|
|
|
CurrentThread = KeGetCurrentThread ();
|
|
KeEnterCriticalRegionThread (CurrentThread);
|
|
UNLOCK_EXPANSION_AND_THEN_WAIT (OldIrql);
|
|
|
|
KeWaitForSingleObject(&Event,
|
|
WrVirtualMemory,
|
|
KernelMode,
|
|
FALSE,
|
|
(PLARGE_INTEGER)NULL);
|
|
KeLeaveCriticalRegionThread (CurrentThread);
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
}
|
|
else if (MmSessionSpace->u.Flags.WorkingSetInserted == 1) {
|
|
|
|
//
|
|
// Remove this session from the session list and the working
|
|
// set list.
|
|
//
|
|
|
|
RemoveEntryList (&SessionGlobal->Vm.WorkingSetExpansionLinks);
|
|
|
|
MmSessionSpace->u.Flags.WorkingSetInserted = 0;
|
|
}
|
|
|
|
if (MmSessionSpace->u.Flags.SessionListInserted == 1) {
|
|
|
|
RemoveEntryList (&SessionGlobal->WsListEntry);
|
|
|
|
MmSessionSpace->u.Flags.SessionListInserted = 0;
|
|
}
|
|
|
|
MiSessionCount -= 1;
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
#if DBG
|
|
if (Process->Vm.Flags.SessionLeader == 0) {
|
|
ASSERT (MmSessionSpace->ProcessOutSwapCount == 0);
|
|
ASSERT (MmSessionSpace->ReferenceCount == 0);
|
|
}
|
|
#endif
|
|
|
|
MM_SNAP_SESS_MEMORY_COUNTERS(0);
|
|
|
|
//
|
|
// If an unload function has been registered for WIN32K.SYS,
|
|
// call it now before we force an unload on any modules. WIN32K.SYS
|
|
// is responsible for calling any other loaded modules that have
|
|
// unload routines to be run. Another option is to have the other
|
|
// session drivers register a DLL initialize/uninitialize pair on load.
|
|
//
|
|
|
|
if (MmSessionSpace->Win32KDriverObject.DriverUnload) {
|
|
MmSessionSpace->Win32KDriverObject.DriverUnload (&MmSessionSpace->Win32KDriverObject);
|
|
}
|
|
|
|
//
|
|
// Complete all deferred pool block deallocations.
|
|
//
|
|
|
|
ExDeferredFreePool (&MmSessionSpace->PagedPool);
|
|
|
|
//
|
|
// Now that all modules have had their unload routine(s)
|
|
// called, check for pool leaks before unloading the images.
|
|
//
|
|
|
|
MiCheckSessionPoolAllocations ();
|
|
|
|
ASSERT (MmSessionSpace->ReferenceCount == 0);
|
|
|
|
#if defined (_WIN64)
|
|
if (MmSessionSpecialPoolStart != 0) {
|
|
MiDeleteSessionSpecialPool ();
|
|
}
|
|
#endif
|
|
|
|
MM_SNAP_SESS_MEMORY_COUNTERS(1);
|
|
|
|
//
|
|
// Destroy the view mapping structures.
|
|
//
|
|
|
|
MiFreeSessionSpaceMap ();
|
|
|
|
MM_SNAP_SESS_MEMORY_COUNTERS(2);
|
|
|
|
//
|
|
// Walk down the list of modules we have loaded dereferencing them.
|
|
//
|
|
// This allows us to force an unload of any kernel images loaded by
|
|
// the session so we do not have any virtual space and paging
|
|
// file leaks.
|
|
//
|
|
|
|
MiSessionUnloadAllImages ();
|
|
|
|
MM_SNAP_SESS_MEMORY_COUNTERS(3);
|
|
|
|
//
|
|
// Destroy the session space bitmap structure
|
|
//
|
|
|
|
MiFreeSessionPoolBitMaps ();
|
|
|
|
MM_SNAP_SESS_MEMORY_COUNTERS(4);
|
|
|
|
//
|
|
// Reference the session space structure using its global
|
|
// kernel PTE based address. This is to avoid deleting it out
|
|
// from underneath ourselves.
|
|
//
|
|
|
|
GlobalPteEntrySave = MmSessionSpace->GlobalPteEntry;
|
|
ASSERT (GlobalPteEntrySave != NULL);
|
|
|
|
//
|
|
// Sweep the individual regions in their proper order.
|
|
//
|
|
|
|
#if DBG
|
|
|
|
//
|
|
// Check the executable image region. All images
|
|
// should have been unloaded by the image handler.
|
|
//
|
|
|
|
MiCheckSessionVirtualSpace ((PVOID) MiSessionImageStart,
|
|
MiSessionImageEnd - MiSessionImageStart);
|
|
#endif
|
|
|
|
MM_SNAP_SESS_MEMORY_COUNTERS(5);
|
|
|
|
#if DBG
|
|
|
|
//
|
|
// Check the view region. All views should have been cleaned up already.
|
|
//
|
|
|
|
MiCheckSessionVirtualSpace ((PVOID) MiSessionViewStart, MmSessionViewSize);
|
|
#endif
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
RtlCopyMemory (SavePageTables,
|
|
MiGetPdeAddress ((PVOID)MmSessionBase),
|
|
MiSessionSpacePageTables * sizeof (MMPTE));
|
|
#endif
|
|
|
|
MM_SNAP_SESS_MEMORY_COUNTERS(6);
|
|
|
|
#if DBG
|
|
//
|
|
// Check everything possible before the remaining virtual address space
|
|
// is torn down. In this way if anything is amiss, the data can be
|
|
// more easily examined.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (MmSessionSpace->SessionPageDirectoryIndex);
|
|
|
|
//
|
|
// This should be greater than 1 because working set page tables are
|
|
// using this as their parent as well.
|
|
//
|
|
|
|
ASSERT (Pfn1->u2.ShareCount > 1);
|
|
#endif
|
|
|
|
CountReleased = 0;
|
|
|
|
if (MmSessionSpace->u.Flags.HasWsLock == 1) {
|
|
|
|
PointerPte = MiGetPteAddress ((PVOID)MiSessionSpaceWs);
|
|
EndPte = MiGetPteAddress (MmSessionSpace->Vm.VmWorkingSetList->HighestPermittedHashAddress);
|
|
|
|
for ( ; PointerPte < EndPte; PointerPte += 1) {
|
|
|
|
if (PointerPte->u.Long) {
|
|
|
|
ASSERT (PointerPte->u.Hard.Valid == 1);
|
|
|
|
CountReleased += 1;
|
|
}
|
|
}
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_WS_PAGE_FREE, (ULONG) CountReleased);
|
|
|
|
InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages,
|
|
0 - CountReleased);
|
|
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_WS_PAGE_FREE, (ULONG) CountReleased);
|
|
MmSessionSpace->NonPagablePages -= CountReleased;
|
|
|
|
WorkingSetWasInitialized = TRUE;
|
|
MmSessionSpace->u.Flags.HasWsLock = 0;
|
|
}
|
|
else {
|
|
WorkingSetWasInitialized = FALSE;
|
|
}
|
|
|
|
//
|
|
// Account for the session data structure data page. For NT64, the page
|
|
// directory page is also accounted for here.
|
|
//
|
|
// Note CountReleased is deliberately incremented by one less than the
|
|
// CommittedPages/NonPagablePages is incremented by.
|
|
// This is because the data page (and its commitment) can only be returned
|
|
// after the last process has been reaped (not just exited).
|
|
//
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGE_FREE, 4);
|
|
InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages, -4);
|
|
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_SESSION_DESTROY, 4);
|
|
MmSessionSpace->NonPagablePages -= 4;
|
|
|
|
CountReleased += 3;
|
|
#else
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGE_FREE, 2);
|
|
InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages, -2);
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_SESSION_DESTROY, 2);
|
|
MmSessionSpace->NonPagablePages -= 2;
|
|
#endif
|
|
|
|
//
|
|
// Account for any needed session space page tables. Note the common
|
|
// structure (not the local PDEs) must be examined as any page tables
|
|
// that were dynamically materialized in the context of a different
|
|
// process may not be in the current process' page directory (ie: the
|
|
// current process has never accessed the materialized VAs) !
|
|
//
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
StartPde = MiGetPdeAddress ((PVOID)MmSessionBase);
|
|
#else
|
|
StartPde = &MmSessionSpace->PageTables[0];
|
|
#endif
|
|
|
|
CountReleased2 = 0;
|
|
for (Index = 0; Index < MiSessionSpacePageTables; Index += 1) {
|
|
|
|
if (StartPde->u.Long != ZeroKernelPte.u.Long) {
|
|
CountReleased2 += 1;
|
|
}
|
|
|
|
StartPde += 1;
|
|
}
|
|
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_PAGETABLE_FREE, (ULONG) CountReleased2);
|
|
InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages,
|
|
0 - CountReleased2);
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_SESSION_PTDESTROY, (ULONG) CountReleased2);
|
|
MmSessionSpace->NonPagablePages -= CountReleased2;
|
|
CountReleased += CountReleased2;
|
|
|
|
ASSERT (MmSessionSpace->NonPagablePages == 0);
|
|
|
|
//
|
|
// Note that whenever win32k or drivers loaded by it leak pool, the
|
|
// ASSERT below will be triggered.
|
|
//
|
|
|
|
ASSERT (MmSessionSpace->CommittedPages == 0);
|
|
|
|
MiReturnCommitment (CountReleased);
|
|
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SESSION_DEREFERENCE, CountReleased);
|
|
|
|
//
|
|
// Sweep the working set entries.
|
|
// No more accesses to the working set or its lock are allowed.
|
|
//
|
|
|
|
if (WorkingSetWasInitialized == TRUE) {
|
|
ExDeleteResourceLite (&SessionGlobal->WsLock);
|
|
|
|
PointerPte = MiGetPteAddress ((PVOID)MiSessionSpaceWs);
|
|
EndPte = MiGetPteAddress (MmSessionSpace->Vm.VmWorkingSetList->HighestPermittedHashAddress);
|
|
|
|
for ( ; PointerPte < EndPte; PointerPte += 1) {
|
|
|
|
if (PointerPte->u.Long) {
|
|
|
|
ASSERT (PointerPte->u.Hard.Valid == 1);
|
|
|
|
//
|
|
// Delete the page.
|
|
//
|
|
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
|
|
//
|
|
// Each page should still be locked in the session working set.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
|
|
|
|
MiDecrementShareAndValidCount (Pfn1->u4.PteFrame);
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (PageFrameIndex);
|
|
MI_WRITE_INVALID_PTE (PointerPte, ZeroKernelPte);
|
|
|
|
//
|
|
// Don't return the resident available pages charge here
|
|
// as it's going to be returned in one chunk below as part of
|
|
// CountReleased.
|
|
//
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT (!MI_IS_PHYSICAL_ADDRESS(SessionGlobal));
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (GlobalPteEntrySave);
|
|
PageFrameIndex2 = MI_GET_PAGE_FRAME_FROM_PTE (GlobalPteEntrySave + 1);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
Pfn2 = MI_PFN_ELEMENT (PageFrameIndex2);
|
|
|
|
ASSERT (Pfn1->u4.PteFrame == MmSessionSpace->SessionPageDirectoryIndex);
|
|
ASSERT (Pfn2->u4.PteFrame == MmSessionSpace->SessionPageDirectoryIndex);
|
|
|
|
//
|
|
// Make sure the data pages are still locked.
|
|
//
|
|
|
|
ASSERT (Pfn1->u1.WsIndex == 0);
|
|
ASSERT (Pfn2->u1.WsIndex == 1);
|
|
|
|
#if defined(_IA64_)
|
|
KeDetachSessionSpace (&MmSessionMapInfo, MmSessionParentTablePage);
|
|
#elif defined (_AMD64_)
|
|
StartPde = MiGetPxeAddress (MmSessionBase);
|
|
StartPde->u.Long = ZeroKernelPte.u.Long;
|
|
#else
|
|
StartPde = MiGetPdeAddress (MmSessionBase);
|
|
RtlZeroMemory (StartPde, MiSessionSpacePageTables * sizeof(MMPTE));
|
|
#endif
|
|
|
|
//
|
|
// Delete the VA space - no more accesses to MmSessionSpace at this point.
|
|
//
|
|
// Cut off the pagetable reference as the local page table is going to be
|
|
// freed now. Any needed references must go through the global PTE
|
|
// space or superpages but never through the local session VA. The
|
|
// actual session data pages are not freed until the very last process
|
|
// of the session receives its very last object dereference.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
|
|
ASSERT (Pfn2->u3.e2.ReferenceCount == 1);
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
PageDirectoryFrame = MI_PFN_ELEMENT(Pfn1->u4.PteFrame)->u4.PteFrame;
|
|
PageParentFrame = MI_PFN_ELEMENT(PageDirectoryFrame)->u4.PteFrame;
|
|
|
|
ASSERT (PageDirectoryFrame == MI_PFN_ELEMENT(Pfn2->u4.PteFrame)->u4.PteFrame);
|
|
#endif
|
|
|
|
MiDecrementShareAndValidCount (Pfn1->u4.PteFrame);
|
|
MiDecrementShareAndValidCount (Pfn2->u4.PteFrame);
|
|
|
|
//
|
|
// N.B. Pfn1/2 and PageFrameIndex/2 cannot be referenced from here on out
|
|
// as the interlocked decrement has been done and another process
|
|
// may be racing through MmDeleteProcessAddressSpace.
|
|
//
|
|
|
|
MmResidentAvailablePages += CountReleased;
|
|
MM_BUMP_COUNTER(53, CountReleased);
|
|
|
|
MmResidentAvailablePages += MI_SESSION_SPACE_WORKING_SET_MINIMUM;
|
|
MM_BUMP_COUNTER(56, MI_SESSION_SPACE_WORKING_SET_MINIMUM);
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
|
|
//
|
|
// Delete the session page directory and top level pages.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageDirectoryFrame);
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (PageDirectoryFrame);
|
|
MiDecrementShareCountOnly (PageDirectoryFrame);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageParentFrame);
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (PageParentFrame);
|
|
MiDecrementShareCountOnly (PageParentFrame);
|
|
|
|
#endif
|
|
|
|
//
|
|
// At this point everything has been deleted except the data pages.
|
|
//
|
|
// Delete page table pages. Note that the page table page mapping the
|
|
// session space data structure is done last so that we can apply
|
|
// various ASSERTs in the DeletePde routine.
|
|
//
|
|
|
|
SessionDataPdeIndex = MiGetPdeSessionIndex (MmSessionSpace);
|
|
|
|
for (Index = 0; Index < MiSessionSpacePageTables; Index += 1) {
|
|
|
|
if (Index == SessionDataPdeIndex) {
|
|
|
|
//
|
|
// The self map entry must be done last.
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
MiSessionDeletePde (&SavePageTables[Index],
|
|
WorkingSetWasInitialized,
|
|
&SavePageTables[SessionDataPdeIndex]);
|
|
#else
|
|
MiSessionDeletePde (&SessionGlobal->PageTables[Index],
|
|
WorkingSetWasInitialized,
|
|
&SessionGlobal->PageTables[SessionDataPdeIndex]);
|
|
#endif
|
|
}
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
MiSessionDeletePde (&SavePageTables[SessionDataPdeIndex],
|
|
WorkingSetWasInitialized,
|
|
&SavePageTables[SessionDataPdeIndex]);
|
|
#else
|
|
MiSessionDeletePde (&SessionGlobal->PageTables[SessionDataPdeIndex],
|
|
WorkingSetWasInitialized,
|
|
&SessionGlobal->PageTables[SessionDataPdeIndex]);
|
|
#endif
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Flush the session space TB entries.
|
|
//
|
|
|
|
MI_FLUSH_SESSION_TB ();
|
|
|
|
PS_CLEAR_BITS (&Process->Flags, PS_PROCESS_FLAGS_IN_SESSION);
|
|
|
|
//
|
|
// The session space has been deleted and all TB flushing is complete.
|
|
//
|
|
|
|
MmUnlockPagableImageSection (ExPageLockHandle);
|
|
|
|
return;
|
|
}
|
|
|
|
NTSTATUS
|
|
MiSessionCommitImagePages (
|
|
IN PVOID VirtualAddress,
|
|
IN SIZE_T NumberOfBytes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine commits virtual memory within the current session space with
|
|
backing pages. The virtual addresses within session space are
|
|
allocated with a separate facility in the image management facility.
|
|
This is because images must be at a unique systemwide virtual address.
|
|
|
|
Arguments:
|
|
|
|
VirtualAddress - Supplies the first virtual address to commit.
|
|
|
|
NumberOfBytes - Supplies the number of bytes to commit.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if all went well, STATUS_NO_MEMORY if the current process
|
|
has no session.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, MmSystemLoadLock held.
|
|
|
|
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.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL WsIrql;
|
|
KIRQL OldIrql;
|
|
ULONG Color;
|
|
PFN_NUMBER SizeInPages;
|
|
PMMPFN Pfn1;
|
|
ULONG_PTR AllocationStart;
|
|
PFN_NUMBER PageFrameIndex;
|
|
NTSTATUS Status;
|
|
PMMPTE StartPte, EndPte;
|
|
MMPTE TempPte;
|
|
|
|
SYSLOAD_LOCK_OWNED_BY_ME ();
|
|
|
|
if (NumberOfBytes == 0) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (MmIsAddressValid(MmSessionSpace) == FALSE) {
|
|
#if DBG
|
|
DbgPrint ("MiSessionCommitImagePages: No session space!\n");
|
|
#endif
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
ASSERT (((ULONG_PTR)VirtualAddress % PAGE_SIZE) == 0);
|
|
ASSERT ((NumberOfBytes % PAGE_SIZE) == 0);
|
|
|
|
SizeInPages = (PFN_NUMBER)(NumberOfBytes >> PAGE_SHIFT);
|
|
|
|
if (MiChargeCommitment (SizeInPages, NULL) == FALSE) {
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_COMMIT);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
//
|
|
// Calculate pages needed.
|
|
//
|
|
|
|
AllocationStart = (ULONG_PTR)VirtualAddress;
|
|
|
|
StartPte = MiGetPteAddress ((PVOID)AllocationStart);
|
|
EndPte = MiGetPteAddress ((PVOID)(AllocationStart + NumberOfBytes));
|
|
|
|
TempPte = ValidKernelPteLocal;
|
|
|
|
TempPte.u.Long |= MM_PTE_EXECUTE;
|
|
|
|
//
|
|
// Lock the session space working set.
|
|
//
|
|
|
|
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 ((PVOID)AllocationStart,
|
|
(PVOID)(AllocationStart + NumberOfBytes));
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
UNLOCK_SESSION_SPACE_WS(WsIrql);
|
|
MiReturnCommitment (SizeInPages);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
//
|
|
// Loop allocating them and placing them into the page tables.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Check to make sure the physical pages are available.
|
|
//
|
|
|
|
if ((SPFN_NUMBER)SizeInPages > MI_NONPAGABLE_MEMORY_AVAILABLE() - 20) {
|
|
UNLOCK_PFN (OldIrql);
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_RESIDENT);
|
|
UNLOCK_SESSION_SPACE_WS(WsIrql);
|
|
MiReturnCommitment (SizeInPages);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
//
|
|
// Check to make sure the actual pages are currently available. Normally
|
|
// it would be fine to skip this check and use MiEnsureAvailablePageOrWait
|
|
// calls in the loop below which could release the session space mutex
|
|
// and wait - but this code has not been modified to handle rechecking
|
|
// all the state, so it's easier to just check once up front.
|
|
// The hardcoded 50 better always be higher than MM_HIGH_LIMIT.
|
|
//
|
|
|
|
if ((PFN_COUNT)SizeInPages + MM_HIGH_LIMIT + 50 > MmAvailablePages) {
|
|
UNLOCK_PFN (OldIrql);
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_AVAILABLE);
|
|
UNLOCK_SESSION_SPACE_WS(WsIrql);
|
|
MiReturnCommitment (SizeInPages);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_IMAGE_PAGES, SizeInPages);
|
|
|
|
MmResidentAvailablePages -= SizeInPages;
|
|
|
|
MM_BUMP_COUNTER(45, SizeInPages);
|
|
|
|
while (StartPte < EndPte) {
|
|
|
|
ASSERT (StartPte->u.Long == ZeroKernelPte.u.Long);
|
|
|
|
#if 0
|
|
//
|
|
// If MiEnsureAvailablePageOrWait released the session space mutex
|
|
// while waiting for a page all this code would need to be modified
|
|
// to handle rechecking all the state. Instead the check was made
|
|
// once up front for enough currently available pages and the code
|
|
// here removed.
|
|
//
|
|
|
|
Waited = MiEnsureAvailablePageOrWait (HYDRA_PROCESS, NULL);
|
|
|
|
ASSERT (Waited == FALSE);
|
|
#endif
|
|
|
|
Color = MI_GET_PAGE_COLOR_FROM_SESSION (MmSessionSpace);
|
|
|
|
PageFrameIndex = MiRemoveZeroPage (Color);
|
|
|
|
TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
|
|
MI_WRITE_VALID_PTE (StartPte, TempPte);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
|
|
ASSERT (Pfn1->u1.WsIndex == 0);
|
|
|
|
MiInitializePfn (PageFrameIndex, StartPte, 1);
|
|
|
|
KeFillEntryTb ((PHARDWARE_PTE) StartPte, (PMMPTE)AllocationStart, FALSE);
|
|
|
|
StartPte += 1;
|
|
AllocationStart += PAGE_SIZE;
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
MmSessionSpace->NonPagablePages += SizeInPages;
|
|
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_DRIVER_PAGES_LOCKED, (ULONG)SizeInPages);
|
|
|
|
UNLOCK_SESSION_SPACE_WS(WsIrql);
|
|
|
|
InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages, SizeInPages);
|
|
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_COMMIT_IMAGE, (ULONG)SizeInPages);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
MiSessionCommitPageTables (
|
|
IN PVOID StartVa,
|
|
IN PVOID EndVa
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Fill in page tables covering the specified virtual address range.
|
|
|
|
Arguments:
|
|
|
|
StartVa - Supplies a starting virtual address.
|
|
|
|
EndVa - Supplies an ending virtual address.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS on success, STATUS_NO_MEMORY on failure.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. Session space working set mutex held.
|
|
|
|
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.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
ULONG Color;
|
|
ULONG Index;
|
|
PMMPTE StartPde;
|
|
PMMPTE EndPde;
|
|
MMPTE TempPte;
|
|
PMMPFN Pfn1;
|
|
WSLE_NUMBER Entry;
|
|
WSLE_NUMBER SwapEntry;
|
|
PFN_NUMBER SizeInPages;
|
|
PFN_NUMBER PageTablePage;
|
|
PVOID SessionPte;
|
|
PMMWSL WorkingSetList;
|
|
ULONG AllocatedPageTables[(MI_SESSION_SPACE_MAXIMUM_PAGE_TABLES + sizeof(ULONG)*8-1)/(sizeof(ULONG)*8)];
|
|
|
|
ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE);
|
|
|
|
MM_SESSION_SPACE_WS_LOCK_ASSERT();
|
|
|
|
ASSERT (StartVa >= (PVOID)MmSessionBase);
|
|
ASSERT (EndVa < (PVOID)MiSessionSpaceEnd);
|
|
|
|
//
|
|
// Allocate the page table pages, loading them
|
|
// into the current process's page directory.
|
|
//
|
|
|
|
StartPde = MiGetPdeAddress (StartVa);
|
|
EndPde = MiGetPdeAddress (EndVa);
|
|
Index = MiGetPdeSessionIndex (StartVa);
|
|
|
|
SizeInPages = 0;
|
|
|
|
while (StartPde <= EndPde) {
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
if (StartPde->u.Long == ZeroKernelPte.u.Long)
|
|
#else
|
|
if (MmSessionSpace->PageTables[Index].u.Long == ZeroKernelPte.u.Long)
|
|
#endif
|
|
{
|
|
SizeInPages += 1;
|
|
}
|
|
StartPde += 1;
|
|
Index += 1;
|
|
}
|
|
|
|
if (SizeInPages == 0) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (MiChargeCommitment (SizeInPages, NULL) == FALSE) {
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_COMMIT);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
RtlZeroMemory (AllocatedPageTables, sizeof (AllocatedPageTables));
|
|
|
|
WorkingSetList = MmSessionSpace->Vm.VmWorkingSetList;
|
|
|
|
StartPde = MiGetPdeAddress (StartVa);
|
|
Index = MiGetPdeSessionIndex (StartVa);
|
|
|
|
TempPte = ValidKernelPdeLocal;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Check to make sure the physical pages are available.
|
|
//
|
|
|
|
if ((SPFN_NUMBER)SizeInPages > MI_NONPAGABLE_MEMORY_AVAILABLE() - 20) {
|
|
UNLOCK_PFN (OldIrql);
|
|
MiReturnCommitment (SizeInPages);
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_RESIDENT);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
//
|
|
// Check to make sure the actual pages are currently available. Normally
|
|
// it would be fine to skip this check and use MiEnsureAvailablePageOrWait
|
|
// calls in the loop below which could release the session space mutex
|
|
// and wait - but this code has not been modified to handle rechecking
|
|
// all the state, so it's easier to just check once up front.
|
|
// The hardcoded 50 better always be higher than MM_HIGH_LIMIT.
|
|
//
|
|
|
|
if ((PFN_COUNT)SizeInPages + 50 > MmAvailablePages) {
|
|
UNLOCK_PFN (OldIrql);
|
|
MiReturnCommitment (SizeInPages);
|
|
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_AVAILABLE);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_PAGETABLE_PAGES, SizeInPages);
|
|
|
|
MmResidentAvailablePages -= SizeInPages;
|
|
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_PAGETABLE_ALLOC, (ULONG)SizeInPages);
|
|
|
|
MM_BUMP_COUNTER(44, SizeInPages);
|
|
|
|
while (StartPde <= EndPde) {
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
if (StartPde->u.Long == ZeroKernelPte.u.Long)
|
|
#else
|
|
if (MmSessionSpace->PageTables[Index].u.Long == ZeroKernelPte.u.Long)
|
|
#endif
|
|
{
|
|
|
|
ASSERT (StartPde->u.Hard.Valid == 0);
|
|
|
|
MI_SET_BIT (AllocatedPageTables, Index);
|
|
|
|
#if 0
|
|
//
|
|
// If MiEnsureAvailablePageOrWait released the session space mutex
|
|
// while waiting for a page all this code would need to be modified
|
|
// to handle rechecking all the state. Instead the check was made
|
|
// once up front for enough currently available pages and the code
|
|
// here removed.
|
|
//
|
|
|
|
Waited = MiEnsureAvailablePageOrWait (HYDRA_PROCESS, NULL);
|
|
|
|
ASSERT (Waited == FALSE);
|
|
#endif
|
|
|
|
Color = MI_GET_PAGE_COLOR_FROM_SESSION (MmSessionSpace);
|
|
|
|
PageTablePage = MiRemoveZeroPage (Color);
|
|
|
|
TempPte.u.Hard.PageFrameNumber = PageTablePage;
|
|
MI_WRITE_VALID_PTE (StartPde, TempPte);
|
|
|
|
#if (_MI_PAGING_LEVELS < 3)
|
|
MmSessionSpace->PageTables[Index] = TempPte;
|
|
#endif
|
|
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_NP_COMMIT_IMAGE_PT, 1);
|
|
MmSessionSpace->NonPagablePages += 1;
|
|
InterlockedExchangeAddSizeT (&MmSessionSpace->CommittedPages, 1);
|
|
|
|
MiInitializePfnForOtherProcess (PageTablePage,
|
|
StartPde,
|
|
MmSessionSpace->SessionPageDirectoryIndex);
|
|
}
|
|
|
|
StartPde += 1;
|
|
Index += 1;
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
StartPde = MiGetPdeAddress (StartVa);
|
|
Index = MiGetPdeSessionIndex (StartVa);
|
|
|
|
while (StartPde <= EndPde) {
|
|
|
|
if (MI_CHECK_BIT(AllocatedPageTables, Index)) {
|
|
|
|
ASSERT (StartPde->u.Hard.Valid == 1);
|
|
|
|
PageTablePage = MI_GET_PAGE_FRAME_FROM_PTE (StartPde);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageTablePage);
|
|
|
|
ASSERT (Pfn1->u1.Event == NULL);
|
|
Pfn1->u1.Event = (PVOID)PsGetCurrentThread ();
|
|
|
|
SessionPte = MiGetVirtualAddressMappedByPte (StartPde);
|
|
|
|
MiAddValidPageToWorkingSet (SessionPte,
|
|
StartPde,
|
|
Pfn1,
|
|
0);
|
|
|
|
Entry = MiLocateWsle (SessionPte,
|
|
MmSessionSpace->Vm.VmWorkingSetList,
|
|
Pfn1->u1.WsIndex);
|
|
|
|
if (Entry >= WorkingSetList->FirstDynamic) {
|
|
|
|
SwapEntry = WorkingSetList->FirstDynamic;
|
|
|
|
if (Entry != WorkingSetList->FirstDynamic) {
|
|
|
|
//
|
|
// Swap this entry with the one at first dynamic.
|
|
//
|
|
|
|
MiSwapWslEntries (Entry, SwapEntry, &MmSessionSpace->Vm);
|
|
}
|
|
|
|
WorkingSetList->FirstDynamic += 1;
|
|
}
|
|
else {
|
|
SwapEntry = Entry;
|
|
}
|
|
|
|
//
|
|
// Indicate that the page is locked.
|
|
//
|
|
|
|
MmSessionSpace->Wsle[SwapEntry].u1.e1.LockedInWs = 1;
|
|
}
|
|
|
|
StartPde += 1;
|
|
Index += 1;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
#if DBG
|
|
typedef struct _MISWAP {
|
|
ULONG Flag;
|
|
ULONG OutSwapCount;
|
|
PEPROCESS Process;
|
|
PMM_SESSION_SPACE Session;
|
|
} MISWAP, *PMISWAP;
|
|
|
|
ULONG MiSessionInfo[4];
|
|
MISWAP MiSessionSwap[0x100];
|
|
ULONG MiSwapIndex;
|
|
#endif
|
|
|
|
|
|
VOID
|
|
MiSessionOutSwapProcess (
|
|
IN PEPROCESS Process
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine notifies the containing session that the specified process is
|
|
being outswapped. When all the processes within a session have been
|
|
outswapped, the containing session undergoes a heavy trim.
|
|
|
|
Arguments:
|
|
|
|
Process - Supplies a pointer to the process that is swapped out of memory.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. This routine must not enter a wait state for memory resources
|
|
or the system will deadlock.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
#if DBG
|
|
ULONG InCount;
|
|
ULONG OutCount;
|
|
PLIST_ENTRY NextEntry;
|
|
#endif
|
|
|
|
ASSERT (Process->Flags & PS_PROCESS_FLAGS_IN_SESSION);
|
|
|
|
//
|
|
// smss doesn't count when we swap it before it has detached from the
|
|
// session it is currently creating.
|
|
//
|
|
|
|
if (Process->Vm.Flags.SessionLeader == 1) {
|
|
return;
|
|
}
|
|
|
|
SessionGlobal = (PMM_SESSION_SPACE) Process->Session;
|
|
ASSERT (SessionGlobal != NULL);
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
SessionGlobal->ProcessOutSwapCount += 1;
|
|
|
|
#if DBG
|
|
ASSERT ((LONG)SessionGlobal->ProcessOutSwapCount > 0);
|
|
|
|
InCount = 0;
|
|
OutCount = 0;
|
|
NextEntry = SessionGlobal->ProcessList.Flink;
|
|
|
|
while (NextEntry != &SessionGlobal->ProcessList) {
|
|
Process = CONTAINING_RECORD (NextEntry, EPROCESS, SessionProcessLinks);
|
|
|
|
if (Process->Flags & PS_PROCESS_FLAGS_OUTSWAP_ENABLED) {
|
|
OutCount += 1;
|
|
}
|
|
else {
|
|
InCount += 1;
|
|
}
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
if (InCount + OutCount > SessionGlobal->ReferenceCount) {
|
|
DbgPrint ("MiSessionOutSwapProcess : process count mismatch %p %x %x %x\n",
|
|
SessionGlobal,
|
|
SessionGlobal->ReferenceCount,
|
|
InCount,
|
|
OutCount);
|
|
DbgBreakPoint ();
|
|
}
|
|
|
|
if (SessionGlobal->ProcessOutSwapCount != OutCount) {
|
|
DbgPrint ("MiSessionOutSwapProcess : out count mismatch %p %x %x %x %x\n",
|
|
SessionGlobal,
|
|
SessionGlobal->ReferenceCount,
|
|
SessionGlobal->ProcessOutSwapCount,
|
|
InCount,
|
|
OutCount);
|
|
DbgBreakPoint ();
|
|
}
|
|
|
|
ASSERT (SessionGlobal->ProcessOutSwapCount <= SessionGlobal->ReferenceCount);
|
|
|
|
MiSessionSwap[MiSwapIndex].Flag = 1;
|
|
MiSessionSwap[MiSwapIndex].Process = Process;
|
|
MiSessionSwap[MiSwapIndex].Session = SessionGlobal;
|
|
MiSessionSwap[MiSwapIndex].OutSwapCount = SessionGlobal->ProcessOutSwapCount;
|
|
MiSwapIndex += 1;
|
|
if (MiSwapIndex == 0x100) {
|
|
MiSwapIndex = 0;
|
|
}
|
|
#endif
|
|
|
|
if (SessionGlobal->ProcessOutSwapCount == SessionGlobal->ReferenceCount) {
|
|
SessionGlobal->Vm.Flags.TrimHard = 1;
|
|
#if DBG
|
|
if (MmDebug & MM_DBG_SESSIONS) {
|
|
DbgPrint ("Mm: Last process (%d total) just swapped out for session %d, %d pages\n",
|
|
SessionGlobal->ProcessOutSwapCount,
|
|
SessionGlobal->SessionId,
|
|
SessionGlobal->Vm.WorkingSetSize);
|
|
}
|
|
MiSessionInfo[0] += 1;
|
|
#endif
|
|
KeQuerySystemTime (&SessionGlobal->LastProcessSwappedOutTime);
|
|
}
|
|
#if DBG
|
|
else {
|
|
MiSessionInfo[1] += 1;
|
|
}
|
|
#endif
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
}
|
|
|
|
|
|
VOID
|
|
MiSessionInSwapProcess (
|
|
IN PEPROCESS Process
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine in swaps the specified process.
|
|
|
|
Arguments:
|
|
|
|
Process - Supplies a pointer to the process that is to be swapped
|
|
into memory.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. This routine must not enter a wait state for memory resources
|
|
or the system will deadlock.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
#if DBG
|
|
ULONG InCount;
|
|
ULONG OutCount;
|
|
PLIST_ENTRY NextEntry;
|
|
#endif
|
|
|
|
ASSERT (Process->Flags & PS_PROCESS_FLAGS_IN_SESSION);
|
|
|
|
//
|
|
// smss doesn't count when we swap it before it has detached from the
|
|
// session it is currently creating.
|
|
//
|
|
|
|
if (Process->Vm.Flags.SessionLeader == 1) {
|
|
return;
|
|
}
|
|
|
|
SessionGlobal = (PMM_SESSION_SPACE) Process->Session;
|
|
ASSERT (SessionGlobal != NULL);
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
#if DBG
|
|
ASSERT ((LONG)SessionGlobal->ProcessOutSwapCount > 0);
|
|
|
|
InCount = 0;
|
|
OutCount = 0;
|
|
NextEntry = SessionGlobal->ProcessList.Flink;
|
|
|
|
while (NextEntry != &SessionGlobal->ProcessList) {
|
|
Process = CONTAINING_RECORD (NextEntry, EPROCESS, SessionProcessLinks);
|
|
|
|
if (Process->Flags & PS_PROCESS_FLAGS_OUTSWAP_ENABLED) {
|
|
OutCount += 1;
|
|
}
|
|
else {
|
|
InCount += 1;
|
|
}
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
if (InCount + OutCount > SessionGlobal->ReferenceCount) {
|
|
DbgPrint ("MiSessionInSwapProcess : count mismatch %p %x %x %x\n",
|
|
SessionGlobal,
|
|
SessionGlobal->ReferenceCount,
|
|
InCount,
|
|
OutCount);
|
|
DbgBreakPoint ();
|
|
}
|
|
|
|
if (SessionGlobal->ProcessOutSwapCount != OutCount) {
|
|
DbgPrint ("MiSessionInSwapProcess : out count mismatch %p %x %x %x %x\n",
|
|
SessionGlobal,
|
|
SessionGlobal->ReferenceCount,
|
|
SessionGlobal->ProcessOutSwapCount,
|
|
InCount,
|
|
OutCount);
|
|
DbgBreakPoint ();
|
|
}
|
|
|
|
ASSERT (SessionGlobal->ProcessOutSwapCount <= SessionGlobal->ReferenceCount);
|
|
|
|
MiSessionSwap[MiSwapIndex].Flag = 2;
|
|
MiSessionSwap[MiSwapIndex].Process = Process;
|
|
MiSessionSwap[MiSwapIndex].Session = SessionGlobal;
|
|
MiSessionSwap[MiSwapIndex].OutSwapCount = SessionGlobal->ProcessOutSwapCount;
|
|
MiSwapIndex += 1;
|
|
if (MiSwapIndex == 0x100) {
|
|
MiSwapIndex = 0;
|
|
}
|
|
#endif
|
|
|
|
if (SessionGlobal->ProcessOutSwapCount == SessionGlobal->ReferenceCount) {
|
|
#if DBG
|
|
MiSessionInfo[2] += 1;
|
|
if (MmDebug & MM_DBG_SESSIONS) {
|
|
DbgPrint ("Mm: First process (%d total) just swapped back in for session %d, %d pages\n",
|
|
SessionGlobal->ProcessOutSwapCount,
|
|
SessionGlobal->SessionId,
|
|
SessionGlobal->Vm.WorkingSetSize);
|
|
}
|
|
#endif
|
|
SessionGlobal->Vm.Flags.TrimHard = 0;
|
|
}
|
|
#if DBG
|
|
else {
|
|
MiSessionInfo[3] += 1;
|
|
}
|
|
#endif
|
|
|
|
SessionGlobal->ProcessOutSwapCount -= 1;
|
|
|
|
ASSERT ((LONG)SessionGlobal->ProcessOutSwapCount >= 0);
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
}
|
|
|
|
|
|
ULONG
|
|
MmGetSessionId (
|
|
IN PEPROCESS Process
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the session ID of the specified process.
|
|
|
|
Arguments:
|
|
|
|
Process - Supplies a pointer to the process whose session ID is desired.
|
|
|
|
Return Value:
|
|
|
|
The session ID. Note these are recycled when sessions exit, hence the
|
|
caller must use proper object referencing on the specified process.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. PASSIVE_LEVEL.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
|
|
if (Process->Vm.Flags.SessionLeader == 1) {
|
|
|
|
//
|
|
// smss may transiently have a session space but that's of no interest
|
|
// to our caller.
|
|
//
|
|
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// The Session field of the EPROCESS is never cleared once set so these
|
|
// checks can be done lock free.
|
|
//
|
|
|
|
SessionGlobal = (PMM_SESSION_SPACE) Process->Session;
|
|
|
|
if (SessionGlobal == NULL) {
|
|
|
|
//
|
|
// The system process has no session space.
|
|
//
|
|
|
|
return 0;
|
|
}
|
|
|
|
SessionGlobal = (PMM_SESSION_SPACE) Process->Session;
|
|
|
|
return SessionGlobal->SessionId;
|
|
}
|
|
|
|
PVOID
|
|
MmGetNextSession (
|
|
IN PVOID OpaqueSession
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function allows code to enumerate all the sessions in the system.
|
|
The first session (if OpaqueSession is NULL) or subsequent session
|
|
(if session is not NULL) is returned on each call.
|
|
|
|
If OpaqueSession is not NULL then this session must have previously
|
|
been obtained by a call to MmGetNextSession.
|
|
|
|
Enumeration may be terminated early by calling MmQuitNextSession on
|
|
the last non-NULL session returned by MmGetNextSession.
|
|
|
|
Sessions may be referenced in this manner and used later safely.
|
|
|
|
For example, to enumerate all sessions in a loop use this code fragment:
|
|
|
|
for (OpaqueSession = MmGetNextSession (NULL);
|
|
OpaqueSession != NULL;
|
|
OpaqueSession = MmGetNextSession (OpaqueSession)) {
|
|
|
|
...
|
|
...
|
|
|
|
//
|
|
// Checking for a specific session (if needed) is handled like this:
|
|
//
|
|
|
|
if (MmGetSessionId (OpaqueSession) == DesiredId) {
|
|
|
|
//
|
|
// Attach to session now to perform operations...
|
|
//
|
|
|
|
KAPC_STATE ApcState;
|
|
|
|
if (NT_SUCCESS (MmAttachSession (OpaqueSession, &ApcState))) {
|
|
|
|
//
|
|
// Session hasn't exited yet, so call interesting work
|
|
// functions that need session context ...
|
|
//
|
|
|
|
...
|
|
|
|
//
|
|
// Detach from session.
|
|
//
|
|
|
|
MmDetachSession (OpaqueSession, &ApcState);
|
|
}
|
|
|
|
//
|
|
// If the interesting work functions failed and error recovery
|
|
// (ie: walk back through all the sessions already operated on
|
|
// and try to undo the actions), then do this. Note you must add
|
|
// similar checks to the above if the operations were only done
|
|
// to specifically requested session IDs.
|
|
//
|
|
|
|
if (ErrorRecoveryNeeded) {
|
|
|
|
for (OpaqueSession = MmGetPreviousSession (OpaqueSession);
|
|
OpaqueSession != NULL;
|
|
OpaqueSession = MmGetPreviousSession (OpaqueSession)) {
|
|
|
|
//
|
|
// MmAttachSession/DetachSession as needed to obtain
|
|
// context, etc.
|
|
//
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Bail if only this session was of interest.
|
|
//
|
|
|
|
MmQuitNextSession (OpaqueSession);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Early terminating conditions are handled like this:
|
|
//
|
|
|
|
if (NeedToBreakOutEarly) {
|
|
MmQuitNextSession (OpaqueSession);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
Arguments:
|
|
|
|
OpaqueSession - Supplies the session to get the next session from
|
|
or NULL for the first session.
|
|
|
|
Return Value:
|
|
|
|
Next session or NULL if no more sessions exist.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PLIST_ENTRY NextEntry;
|
|
PMM_SESSION_SPACE Session;
|
|
PMM_SESSION_SPACE EntrySession;
|
|
PLIST_ENTRY NextProcessEntry;
|
|
PEPROCESS Process;
|
|
PVOID OpaqueNextSession;
|
|
PEPROCESS EntryProcess;
|
|
|
|
ASSERT (KeGetCurrentIrql () <= APC_LEVEL);
|
|
|
|
OpaqueNextSession = NULL;
|
|
|
|
EntryProcess = (PEPROCESS) OpaqueSession;
|
|
|
|
if (EntryProcess == NULL) {
|
|
EntrySession = NULL;
|
|
}
|
|
else {
|
|
ASSERT (EntryProcess->Vm.Flags.SessionLeader == 0);
|
|
|
|
//
|
|
// The Session field of the EPROCESS is never cleared once set so this
|
|
// field can be used lock free.
|
|
//
|
|
|
|
EntrySession = (PMM_SESSION_SPACE) EntryProcess->Session;
|
|
|
|
ASSERT (EntrySession != NULL);
|
|
}
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
if (EntrySession == NULL) {
|
|
NextEntry = MiSessionWsList.Flink;
|
|
}
|
|
else {
|
|
NextEntry = EntrySession->WsListEntry.Flink;
|
|
}
|
|
|
|
while (NextEntry != &MiSessionWsList) {
|
|
|
|
Session = CONTAINING_RECORD (NextEntry, MM_SESSION_SPACE, WsListEntry);
|
|
|
|
NextProcessEntry = Session->ProcessList.Flink;
|
|
|
|
if ((Session->u.Flags.DeletePending == 0) &&
|
|
(NextProcessEntry != &Session->ProcessList)) {
|
|
|
|
Process = CONTAINING_RECORD (NextProcessEntry,
|
|
EPROCESS,
|
|
SessionProcessLinks);
|
|
|
|
if (Process->Vm.Flags.SessionLeader == 1) {
|
|
|
|
//
|
|
// If session manager is still the first process (ie: smss
|
|
// hasn't detached yet), then don't bother delivering to this
|
|
// session this early in its lifetime. And since smss is
|
|
// serialized, it can't be creating another session yet so
|
|
// just bail now as we must be at the end of the list.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If the process has finished rudimentary initialization, then
|
|
// select it as an attach can be performed safely. If this first
|
|
// process has not finished initializing there can be no others
|
|
// in this session, so just march on to the next session.
|
|
//
|
|
// Note the VmWorkingSetList is used instead of the
|
|
// AddressSpaceInitialized field because the VmWorkingSetList is
|
|
// never cleared so we can never see an exiting process (whose
|
|
// AddressSpaceInitialized field gets zeroed) and incorrectly
|
|
// decide the list must be empty.
|
|
//
|
|
|
|
if (Process->Vm.VmWorkingSetList != NULL) {
|
|
|
|
//
|
|
// Reference any process in the session so that the session
|
|
// cannot be completely deleted once the expansion lock is
|
|
// released (note this does NOT prevent the session from being
|
|
// cleaned).
|
|
//
|
|
|
|
ObReferenceObject (Process);
|
|
OpaqueNextSession = (PVOID) Process;
|
|
break;
|
|
}
|
|
}
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
//
|
|
// Regardless of whether a next session is returned, if a starting one
|
|
// was passed in, it must be dereferenced now.
|
|
//
|
|
|
|
if (EntryProcess != NULL) {
|
|
ObDereferenceObject (EntryProcess);
|
|
}
|
|
|
|
return OpaqueNextSession;
|
|
}
|
|
|
|
PVOID
|
|
MmGetPreviousSession (
|
|
IN PVOID OpaqueSession
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function allows code to reverse-enumerate all the sessions in
|
|
the system. This is typically used for error recovery - ie: to walk
|
|
backwards undoing work done by MmGetNextSession semantics.
|
|
|
|
The first session (if OpaqueSession is NULL) or subsequent session
|
|
(if session is not NULL) is returned on each call.
|
|
|
|
If OpaqueSession is not NULL then this session must have previously
|
|
been obtained by a call to MmGetNextSession.
|
|
|
|
Arguments:
|
|
|
|
OpaqueSession - Supplies the session to get the next session from
|
|
or NULL for the first session.
|
|
|
|
Return Value:
|
|
|
|
Next session or NULL if no more sessions exist.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PLIST_ENTRY NextEntry;
|
|
PMM_SESSION_SPACE Session;
|
|
PMM_SESSION_SPACE EntrySession;
|
|
PLIST_ENTRY NextProcessEntry;
|
|
PEPROCESS Process;
|
|
PVOID OpaqueNextSession;
|
|
PEPROCESS EntryProcess;
|
|
|
|
ASSERT (KeGetCurrentIrql () <= APC_LEVEL);
|
|
|
|
OpaqueNextSession = NULL;
|
|
|
|
EntryProcess = (PEPROCESS) OpaqueSession;
|
|
|
|
if (EntryProcess == NULL) {
|
|
EntrySession = NULL;
|
|
}
|
|
else {
|
|
ASSERT (EntryProcess->Vm.Flags.SessionLeader == 0);
|
|
|
|
//
|
|
// The Session field of the EPROCESS is never cleared once set so this
|
|
// field can be used lock free.
|
|
//
|
|
|
|
EntrySession = (PMM_SESSION_SPACE) EntryProcess->Session;
|
|
|
|
ASSERT (EntrySession != NULL);
|
|
}
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
if (EntrySession == NULL) {
|
|
NextEntry = MiSessionWsList.Blink;
|
|
}
|
|
else {
|
|
NextEntry = EntrySession->WsListEntry.Blink;
|
|
}
|
|
|
|
while (NextEntry != &MiSessionWsList) {
|
|
|
|
Session = CONTAINING_RECORD (NextEntry, MM_SESSION_SPACE, WsListEntry);
|
|
|
|
NextProcessEntry = Session->ProcessList.Flink;
|
|
|
|
if ((Session->u.Flags.DeletePending == 0) &&
|
|
(NextProcessEntry != &Session->ProcessList)) {
|
|
|
|
Process = CONTAINING_RECORD (NextProcessEntry,
|
|
EPROCESS,
|
|
SessionProcessLinks);
|
|
|
|
ASSERT (Process->Vm.Flags.SessionLeader == 0);
|
|
|
|
//
|
|
// Reference any process in the session so that the session
|
|
// cannot be completely deleted once the expansion lock is
|
|
// released (note this does NOT prevent the session from being
|
|
// cleaned).
|
|
//
|
|
|
|
ObReferenceObject (Process);
|
|
OpaqueNextSession = (PVOID) Process;
|
|
break;
|
|
}
|
|
NextEntry = NextEntry->Blink;
|
|
}
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
//
|
|
// Regardless of whether a next session is returned, if a starting one
|
|
// was passed in, it must be dereferenced now.
|
|
//
|
|
|
|
if (EntryProcess != NULL) {
|
|
ObDereferenceObject (EntryProcess);
|
|
}
|
|
|
|
return OpaqueNextSession;
|
|
}
|
|
|
|
NTSTATUS
|
|
MmQuitNextSession (
|
|
IN PVOID OpaqueSession
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is used to prematurely terminate a session enumeration
|
|
that began using MmGetNextSession.
|
|
|
|
Arguments:
|
|
|
|
OpaqueSession - Supplies a non-NULL session previously obtained by
|
|
a call to MmGetNextSession.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS.
|
|
|
|
--*/
|
|
|
|
{
|
|
PEPROCESS EntryProcess;
|
|
|
|
ASSERT (KeGetCurrentIrql () <= APC_LEVEL);
|
|
|
|
EntryProcess = (PEPROCESS) OpaqueSession;
|
|
|
|
ASSERT (EntryProcess->Vm.Flags.SessionLeader == 0);
|
|
|
|
//
|
|
// The Session field of the EPROCESS is never cleared once set so this
|
|
// field can be used lock free.
|
|
//
|
|
|
|
ASSERT (EntryProcess->Session != NULL);
|
|
|
|
ObDereferenceObject (EntryProcess);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
MmAttachSession (
|
|
IN PVOID OpaqueSession,
|
|
OUT PRKAPC_STATE ApcState
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function attaches the calling thread to a referenced session
|
|
previously obtained via MmGetNextSession.
|
|
|
|
Arguments:
|
|
|
|
OpaqueSession - Supplies a non-NULL session previously obtained by
|
|
a call to MmGetNextSession.
|
|
|
|
ApcState - Supplies APC state storage for the subsequent detach.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS. If successful then we are attached on return. The caller is
|
|
responsible for calling MmDetachSession when done.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PEPROCESS EntryProcess;
|
|
PMM_SESSION_SPACE EntrySession;
|
|
PEPROCESS CurrentProcess;
|
|
PMM_SESSION_SPACE CurrentSession;
|
|
|
|
ASSERT (KeGetCurrentIrql () <= APC_LEVEL);
|
|
|
|
EntryProcess = (PEPROCESS) OpaqueSession;
|
|
|
|
ASSERT (EntryProcess->Vm.Flags.SessionLeader == 0);
|
|
|
|
//
|
|
// The Session field of the EPROCESS is never cleared once set so this
|
|
// field can be used lock free.
|
|
//
|
|
|
|
EntrySession = (PMM_SESSION_SPACE) EntryProcess->Session;
|
|
|
|
ASSERT (EntrySession != NULL);
|
|
|
|
CurrentProcess = PsGetCurrentProcess ();
|
|
|
|
CurrentSession = (PMM_SESSION_SPACE) CurrentProcess->Session;
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
if (EntrySession->u.Flags.DeletePending == 1) {
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
return STATUS_PROCESS_IS_TERMINATING;
|
|
}
|
|
|
|
EntrySession->AttachCount += 1;
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
if ((CurrentProcess->Vm.Flags.SessionLeader == 0) &&
|
|
(CurrentSession != NULL)) {
|
|
|
|
//
|
|
// smss may transiently have a session space but that's of
|
|
// no interest to our caller.
|
|
//
|
|
|
|
if (CurrentSession == EntrySession) {
|
|
|
|
ASSERT (CurrentSession->SessionId == EntrySession->SessionId);
|
|
|
|
//
|
|
// The current and target sessions match so an attach is not needed.
|
|
// Call KeStackAttach anyway (this has the overhead of an extra
|
|
// dispatcher lock acquire and release) so that callers can always
|
|
// use MmDetachSession to detach. This is a very infrequent path so
|
|
// the extra lock acquire and release is not significant.
|
|
//
|
|
// Note that by resetting EntryProcess below, an attach will not
|
|
// actually occur.
|
|
//
|
|
|
|
EntryProcess = CurrentProcess;
|
|
}
|
|
else {
|
|
ASSERT (CurrentSession->SessionId != EntrySession->SessionId);
|
|
}
|
|
}
|
|
|
|
KeStackAttachProcess (&EntryProcess->Pcb, ApcState);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
MmDetachSession (
|
|
IN PVOID OpaqueSession,
|
|
IN PRKAPC_STATE ApcState
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function detaches the calling thread from the referenced session
|
|
previously attached to via MmAttachSession.
|
|
|
|
Arguments:
|
|
|
|
OpaqueSession - Supplies a non-NULL session previously obtained by
|
|
a call to MmGetNextSession.
|
|
|
|
ApcState - Supplies APC state storage information for the detach.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS. If successful then we are detached on return. The caller is
|
|
responsible for eventually calling MmQuitNextSession on return.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PEPROCESS EntryProcess;
|
|
PMM_SESSION_SPACE EntrySession;
|
|
|
|
ASSERT (KeGetCurrentIrql () <= APC_LEVEL);
|
|
|
|
EntryProcess = (PEPROCESS) OpaqueSession;
|
|
|
|
ASSERT (EntryProcess->Vm.Flags.SessionLeader == 0);
|
|
|
|
//
|
|
// The Session field of the EPROCESS is never cleared once set so this
|
|
// field can be used lock free.
|
|
//
|
|
|
|
EntrySession = (PMM_SESSION_SPACE) EntryProcess->Session;
|
|
|
|
ASSERT (EntrySession != NULL);
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
ASSERT (EntrySession->AttachCount >= 1);
|
|
|
|
EntrySession->AttachCount -= 1;
|
|
|
|
if ((EntrySession->u.Flags.DeletePending == 0) ||
|
|
(EntrySession->AttachCount != 0)) {
|
|
|
|
EntrySession = NULL;
|
|
}
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
KeUnstackDetachProcess (ApcState);
|
|
|
|
if (EntrySession != NULL) {
|
|
KeSetEvent (&EntrySession->AttachEvent, 0, FALSE);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
PVOID
|
|
MmGetSessionById (
|
|
IN ULONG SessionId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function allows callers to obtain a reference to a specific session.
|
|
The caller can then MmAttachSession, MmDetachSession & MmQuitNextSession
|
|
to complete the proper sequence so reference counting and address context
|
|
operate properly.
|
|
|
|
Arguments:
|
|
|
|
SessionId - Supplies the session ID of the desired session.
|
|
|
|
Return Value:
|
|
|
|
An opaque session token or NULL if the session cannot be found.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, the caller must guarantee the session cannot exit or the ID
|
|
becomes meaningless as it can be reused.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PLIST_ENTRY NextEntry;
|
|
PMM_SESSION_SPACE Session;
|
|
PLIST_ENTRY NextProcessEntry;
|
|
PEPROCESS Process;
|
|
PVOID OpaqueSession;
|
|
|
|
ASSERT (KeGetCurrentIrql () <= APC_LEVEL);
|
|
|
|
OpaqueSession = NULL;
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
NextEntry = MiSessionWsList.Flink;
|
|
|
|
while (NextEntry != &MiSessionWsList) {
|
|
|
|
Session = CONTAINING_RECORD (NextEntry, MM_SESSION_SPACE, WsListEntry);
|
|
|
|
NextProcessEntry = Session->ProcessList.Flink;
|
|
|
|
if (Session->SessionId == SessionId) {
|
|
|
|
if ((Session->u.Flags.DeletePending != 0) ||
|
|
(NextProcessEntry == &Session->ProcessList)) {
|
|
|
|
//
|
|
// Session is empty or exiting so return failure to the caller.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
Process = CONTAINING_RECORD (NextProcessEntry,
|
|
EPROCESS,
|
|
SessionProcessLinks);
|
|
|
|
if (Process->Vm.Flags.SessionLeader == 1) {
|
|
|
|
//
|
|
// Session manager is still the first process (ie: smss
|
|
// hasn't detached yet), don't bother delivering to this
|
|
// session this early in its lifetime. And since smss is
|
|
// serialized, it can't be creating another session yet so
|
|
// just bail now as we must be at the end of the list.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Reference any process in the session so that the session
|
|
// cannot be completely deleted once the expansion lock is
|
|
// released (note this does NOT prevent the session from being
|
|
// cleaned).
|
|
//
|
|
|
|
ObReferenceObject (Process);
|
|
OpaqueSession = (PVOID) Process;
|
|
break;
|
|
}
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
return OpaqueSession;
|
|
}
|
|
|
|
PVOID
|
|
MiAttachToSecureProcessInSession (
|
|
IN PRKAPC_STATE ApcState
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function allows callers to attach to a secure process
|
|
in the current session. This is the first process created in it.
|
|
|
|
Arguments:
|
|
|
|
ApcState - Supplies APC state storage information for the attach.
|
|
|
|
Return Value:
|
|
|
|
An opaque session token or NULL if the attach could not be performed.
|
|
If non-null, then the process attach been performed on return.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PLIST_ENTRY NextProcessEntry;
|
|
PEPROCESS TargetProcess;
|
|
PMM_SESSION_SPACE SessionGlobal;
|
|
|
|
SessionGlobal = SESSION_GLOBAL(MmSessionSpace);
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
NextProcessEntry = SessionGlobal->ProcessList.Flink;
|
|
|
|
if ((SessionGlobal->u.Flags.DeletePending == 0) &&
|
|
(NextProcessEntry != &SessionGlobal->ProcessList)) {
|
|
|
|
TargetProcess = CONTAINING_RECORD (NextProcessEntry,
|
|
EPROCESS,
|
|
SessionProcessLinks);
|
|
|
|
ObReferenceObject (TargetProcess);
|
|
}
|
|
else {
|
|
TargetProcess = NULL;
|
|
}
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
if (TargetProcess != NULL) {
|
|
KeStackAttachProcess (&TargetProcess->Pcb, ApcState);
|
|
}
|
|
|
|
return TargetProcess;
|
|
}
|
|
|
|
VOID
|
|
MiDetachFromSecureProcessInSession (
|
|
IN PVOID OpaqueSession,
|
|
IN PRKAPC_STATE ApcState
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function allows callers to detach from the currently secure process
|
|
in the current session (and dereference the secure process object).
|
|
|
|
Arguments:
|
|
|
|
OpaqueSession - Supplies the opaque session value returned by
|
|
MiAttachToSecureProcessInSession.
|
|
|
|
ApcState - Supplies APC state storage information for the detach.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APC_LEVEL or below.
|
|
|
|
--*/
|
|
|
|
{
|
|
PEPROCESS TargetProcess;
|
|
|
|
TargetProcess = (PEPROCESS) OpaqueSession;
|
|
|
|
KeUnstackDetachProcess (ApcState);
|
|
|
|
ObDereferenceObject (TargetProcess);
|
|
}
|