/*++ 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); }