/**************************** Module Header ********************************\ * Module Name: exitwin.c * * Copyright (c) 1985 - 1999, Microsoft Corporation * * History: * 07-23-92 ScottLu Created. \***************************************************************************/ #include "precomp.h" #pragma hdrstop #include #include #include #define BEGIN_LPC_RECV(API) \ P##API##MSG a = (P##API##MSG)&m->u.ApiMessageData; \ PCSR_THREAD pcsrt; \ PTEB Teb = NtCurrentTeb(); \ NTSTATUS Status = STATUS_SUCCESS; \ UNREFERENCED_PARAMETER(ReplyStatus); \ \ Teb->LastErrorValue = 0; \ pcsrt = CSR_SERVER_QUERYCLIENTTHREAD(); #define END_LPC_RECV() \ a->dwLastError = Teb->LastErrorValue; \ return Status; #define CCHBODYMAX 512 #define CSR_THREAD_SHUTDOWNSKIP 0x00000008 #define CSR_THREAD_HANGREPORTED 0x00000010 /* * SrvRecordShutdownReason globals */ ULONG g_ShutdownState; ULONG g_DirtyShutdownMax = 1; HMODULE g_SnapShotDllHandle; LONG g_SnapShot = 1; /* * Snapshot dll entry function. */ typedef ULONG (*SNAPSHOTFUNC)(DWORD dwStrings, LPCTSTR *lpStrings, PLONG MaxBuffSize, LPTSTR SnapShotBuff); BOOL WowExitTask(PCSR_THREAD pcsrt); NTSTATUS UserClientShutdown(PCSR_PROCESS pcsrp, ULONG dwFlags, BOOLEAN fFirstPass); BOOL CancelExitWindows(VOID); /***************************************************************************\ * _ExitWindowsEx * * Determines whether shutdown is allowed, and if so calls CSR to start * shutting down processes. If this succeeds all the way through, tell winlogon * so it'll either logoff or reboot the system. Shuts down the processes in * the caller's sid. * * History * 07-23-92 ScottLu Created. \***************************************************************************/ NTSTATUS _ExitWindowsEx( PCSR_THREAD pcsrt, UINT dwFlags) { LUID luidCaller; NTSTATUS Status; if ((dwFlags & EWX_REBOOT) || (dwFlags & EWX_POWEROFF)) { dwFlags |= EWX_SHUTDOWN; } // // Only winlogon gets to set the high flags: // if ((dwFlags & ~EWX_VALID) != 0) { if (HandleToUlong(pcsrt->ClientId.UniqueProcess) != gIdLogon) { RIPMSG2(RIP_WARNING, "Process 0x%x tried to call ExitWindowsEx with flags 0x%x", pcsrt->ClientId.UniqueProcess, dwFlags); return STATUS_ACCESS_DENIED; } } /* * Find out the callers sid. Only want to shutdown processes in the * callers sid. */ if (!CsrImpersonateClient(NULL)) { return STATUS_BAD_IMPERSONATION_LEVEL; } Status = CsrGetProcessLuid(NULL, &luidCaller); if (!NT_SUCCESS(Status)) { CsrRevertToSelf(); return Status; } /* * Loop until we can do the shutdown. If we cannot do it, we'll go to * fastexit and bail. */ while (TRUE) { Status = NtUserSetInformationThread(pcsrt->ThreadHandle, UserThreadInitiateShutdown, &dwFlags, sizeof(dwFlags)); switch (Status) { case STATUS_PENDING: /* * The logoff/shutdown is in progress and nothing more needs to * be done. */ goto fastexit; case STATUS_RETRY: if (!CancelExitWindows()) { Status = STATUS_PENDING; goto fastexit; } else { continue; } case STATUS_CANT_WAIT: /* * There is no notify window and the calling thread has * windows that prevent this request from succeeding. * The client handles this by starting another thread * to recall ExitWindowsEx. */ goto fastexit; default: if (!NT_SUCCESS(Status)) { SetLastError(RtlNtStatusToDosError(Status)); goto fastexit; } } break; } /* * This thread is doing the shutdown */ EnterCrit(); UserAssert(gdwThreadEndSession == 0); gdwThreadEndSession = HandleToUlong(pcsrt->ClientId.UniqueThread); LeaveCrit(); /* * Call csr to loop through the processes shutting them down. */ Status = CsrShutdownProcesses(&luidCaller, dwFlags); if (Status == STATUS_CANCELLED && IsSETEnabled()) { SHUTDOWN_REASON sr; sr.cbSize = sizeof(SHUTDOWN_REASON); sr.uFlags = dwFlags; sr.dwReasonCode = 0; sr.fShutdownCancelled = TRUE; sr.dwEventType = SR_EVENT_EXITWINDOWS; sr.lpszComment = NULL; /* * Record the fact that the shutdown was cancelled. */ RecordShutdownReason(&sr); } /* * Tell win32k.sys we're done. */ NtUserSetInformationThread(pcsrt->ThreadHandle, UserThreadEndShutdown, &Status, sizeof(Status)); EnterCrit(); gdwThreadEndSession = 0; NtSetEvent(gheventCancelled, NULL); LeaveCrit(); fastexit: CsrRevertToSelf(); return Status; } /***************************************************************************\ * UserClientShutdown * * This gets called from CSR. If we recognize the application (i.e., it has a * top level window), then send queryend/end session messages to it. Otherwise * say we don't recognize it. * * 07-23-92 ScottLu Created. \***************************************************************************/ NTSTATUS UserClientShutdown( PCSR_PROCESS pcsrp, ULONG dwFlags, BOOLEAN fFirstPass) { PLIST_ENTRY ListHead, ListNext; PCSR_PROCESS Process; PCSR_THREAD Thread; USERTHREAD_SHUTDOWN_INFORMATION ShutdownInfo; BOOL fNoMsgs; BOOL fNoMsgsEver = TRUE; BOOL Forced = FALSE; BOOL bDoBlock; BOOL fNoRetry; DWORD cmd = 0, dwClientFlags; NTSTATUS Status; NTSTATUS TerminateStatus = STATUS_ACCESS_DENIED; UINT cThreads; BOOL fSendEndSession = FALSE; #if DBG DWORD dwLocalThreadEndSession = gdwThreadEndSession; #endif /* * If this is a logoff and the process does not belong to * the account doing the logoff and is not LocalSystem, * do not send end-session messages. Console will notify * the app of the logoff. */ if (!(dwFlags & EWX_SHUTDOWN) && (pcsrp->ShutdownFlags & SHUTDOWN_OTHERCONTEXT)) { Status = SHUTDOWN_UNKNOWN_PROCESS; goto CleanupAndExit; } /* * Calculate whether to allow exit and force-exit this process before * we unlock pcsrp. */ fNoRetry = (pcsrp->ShutdownFlags & SHUTDOWN_NORETRY) || (dwFlags & EWX_FORCE); /* * Setup flags for WM_CLIENTSHUTDOWN * -Assume the process is going to OK the WM_QUERYENDSESSION (WMCS_EXIT) * -NT's shutdown always starts with a logoff. * -Shutdown or logoff? (WMCS_SHUTDOWN) * -Should display dialog for hung apps? (WMCS_NODLGIFHUNG) * -is this process in the context being logged off? (WMCS_CONTEXTLOGOFF) */ dwClientFlags = WMCS_EXIT | (fNoRetry ? WMCS_NORETRY : 0); /* * Check the flags originally passed by the ExitWindows caller to see if we're * really just logging off. */ if (!(dwFlags & (EWX_WINLOGON_OLD_REBOOT | EWX_WINLOGON_OLD_SHUTDOWN))) { dwClientFlags |= WMCS_LOGOFF; } if (dwFlags & EWX_FORCEIFHUNG) { dwClientFlags |= WMCS_NODLGIFHUNG; } if (!(pcsrp->ShutdownFlags & (SHUTDOWN_SYSTEMCONTEXT | SHUTDOWN_OTHERCONTEXT))) { dwClientFlags |= WMCS_CONTEXTLOGOFF; } /* * Lock the process while we walk the thread list. We know * that the process is valid and therefore do not need to * check the return status. */ CsrLockProcessByClientId(pcsrp->ClientId.UniqueProcess, &Process); ShutdownInfo.StatusShutdown = SHUTDOWN_UNKNOWN_PROCESS; /* * Go through the thread list and mark them as not * shutdown yet. */ ListHead = &pcsrp->ThreadList; ListNext = ListHead->Flink; while (ListNext != ListHead) { Thread = CONTAINING_RECORD( ListNext, CSR_THREAD, Link ); Thread->Flags &= ~(CSR_THREAD_SHUTDOWNSKIP | CSR_THREAD_HANGREPORTED); ListNext = ListNext->Flink; } /* * Perform the proper shutdown operation on each thread. Keep * a count of the number of gui threads found. */ cThreads = 0; ShutdownInfo.drdRestore.pdeskRestore = NULL; ShutdownInfo.drdRestore.hdeskNew = NULL; while (TRUE) { ListNext = ListHead->Flink; while (ListNext != ListHead) { Thread = CONTAINING_RECORD( ListNext, CSR_THREAD, Link ); /* * Skip the thread doing the shutdown. Assume that it's * ready. * gdwThreadEndSession shouldn't change while the shutdown * is in progress; so this should be thread safe. */ UserAssert(gdwThreadEndSession == dwLocalThreadEndSession); if (HandleToUlong(Thread->ClientId.UniqueThread) == gdwThreadEndSession) { Thread->Flags |= CSR_THREAD_SHUTDOWNSKIP; } if (!(Thread->Flags & (CSR_THREAD_DESTROYED | CSR_THREAD_SHUTDOWNSKIP))) { break; } ListNext = ListNext->Flink; } if (ListNext == ListHead) { break; } Thread->Flags |= CSR_THREAD_SHUTDOWNSKIP; ShutdownInfo.dwFlags = dwClientFlags; CsrReferenceThread(Thread); CsrUnlockProcess(Process); Status = NtUserQueryInformationThread(Thread->ThreadHandle, UserThreadShutdownInformation, &ShutdownInfo, sizeof(ShutdownInfo), NULL); CsrLockProcessByClientId(pcsrp->ClientId.UniqueProcess, &Process); /* * When we release the process structure lock, Thread can go away. * Since we already ref'ed it, we're safe, but once we deref here * its ref count can go to zero and the memory will be freed, in * which case we must be careful not to touch it again. */ if (CsrDereferenceThread(Thread) == 0) { continue; } if (!NT_SUCCESS(Status)) { continue; } if (ShutdownInfo.StatusShutdown == SHUTDOWN_UNKNOWN_PROCESS) { continue; } if (ShutdownInfo.StatusShutdown == SHUTDOWN_KNOWN_PROCESS) { CsrUnlockProcess(Process); Status = SHUTDOWN_KNOWN_PROCESS; goto RestoreDesktop; } /* * If this process is not in the account being logged off and it * is not on the windowstation being logged off, don't send * the end session messages. */ if (!(dwClientFlags & WMCS_CONTEXTLOGOFF) && (ShutdownInfo.hwndDesktop == NULL)) { /* * This process is not in the context being logged off. Do * not terminate it and let console send an event to the process. */ ShutdownInfo.StatusShutdown = SHUTDOWN_UNKNOWN_PROCESS; continue; } /* * Shut down this process. */ cThreads++; if (ISTS()) { Forced = (dwFlags & EWX_FORCE); fNoMsgs = (pcsrp->ShutdownFlags & SHUTDOWN_NORETRY) || !(ShutdownInfo.dwFlags & USER_THREAD_GUI); fNoMsgsEver &= fNoMsgs; if (Forced && (!(dwFlags & EWX_NONOTIFY) || (gSessionId != 0))) { dwClientFlags &= ~WMCS_LOGOFF; // WinStation Reset or Shutdown. Don't do this for console session. } if (fNoMsgs || Forced) { BoostHardError((ULONG_PTR)Thread->ClientId.UniqueProcess, BHE_FORCE); } bDoBlock = (fNoMsgs == FALSE); } else { if (fNoRetry || !(ShutdownInfo.dwFlags & USER_THREAD_GUI)) { /* * Dispose of any hard errors. */ BoostHardError((ULONG_PTR)Thread->ClientId.UniqueProcess, BHE_FORCE); bDoBlock = FALSE; } else { bDoBlock = TRUE; } } if (bDoBlock) { CsrReferenceThread(Thread); CsrUnlockProcess(Process); /* * There are problems in changing shutdown to send all the * QUERYENDSESSIONs at once before doing any ENDSESSIONs, like * Windows does. The whole machine needs to be modal if you do this. * If it isn't modal, then you have this problem. Imagine app 1 and 2. * 1 gets the queryendsession, no problem. 2 gets it and brings up a * dialog. Now being a simple user, you decide you need to change the * document in app 1. Now you switch back to app 2, hit ok, and * everything goes away - including app 1 without saving its changes. * Also, apps expect that once they've received the QUERYENDSESSION, * they are not going to get anything else of any particular interest * (unless it is a WM_ENDSESSION with FALSE). We had bugs pre 511 where * apps were blowing up because of this. * If this change is made, the entire system must be modal * while this is going on. - ScottLu 6/30/94 */ cmd = ThreadShutdownNotify(dwClientFlags | WMCS_QUERYEND, (ULONG_PTR)Thread, 0); CsrLockProcessByClientId(pcsrp->ClientId.UniqueProcess, &Process); CsrDereferenceThread(Thread); /* * If shutdown has been cancelled, let csr know about it. */ switch (cmd) { case TSN_USERSAYSCANCEL: case TSN_APPSAYSNOTOK: if (!Forced) { dwClientFlags &= ~WMCS_EXIT; } /* * Fall through. */ case TSN_APPSAYSOK: fSendEndSession = TRUE; break; case TSN_USERSAYSKILL: /* * Since we cannot just kill one thread, the whole process * is going down. Hence, there is no point on continuing * checking other threads. Also, the user wants it killed * so we won't waste any time sending more messages */ dwClientFlags |= WMCS_EXIT; goto KillIt; case TSN_NOWINDOW: /* * Did this process have a window? * If this is the second pass we terminate the process even if it did * not have any windows in case the app was just starting up. * WOW hits this often because it takes so long to start up. * Logon (with WOW auto-starting) then logoff WOW won't die but will * lock some files open so you can't logon next time. */ if (fFirstPass) { cThreads--; } break; } } } /* * If end session message need to be sent, do it now. */ if (fSendEndSession) { /* * Go through the thread list and mark them as not * shutdown yet. */ ListNext = ListHead->Flink; while (ListNext != ListHead) { Thread = CONTAINING_RECORD( ListNext, CSR_THREAD, Link ); Thread->Flags &= ~(CSR_THREAD_SHUTDOWNSKIP | CSR_THREAD_HANGREPORTED); ListNext = ListNext->Flink; } /* * Perform the proper shutdown operation on each thread. */ while (TRUE) { ListHead = &pcsrp->ThreadList; ListNext = ListHead->Flink; while (ListNext != ListHead) { Thread = CONTAINING_RECORD( ListNext, CSR_THREAD, Link ); if (!(Thread->Flags & (CSR_THREAD_DESTROYED | CSR_THREAD_SHUTDOWNSKIP))) { break; } ListNext = ListNext->Flink; } if (ListNext == ListHead) break; CsrReferenceThread(Thread); CsrUnlockProcess(Process); Thread->Flags |= CSR_THREAD_SHUTDOWNSKIP; ShutdownInfo.dwFlags = dwClientFlags; Status = NtUserQueryInformationThread(Thread->ThreadHandle, UserThreadShutdownInformation, &ShutdownInfo, sizeof(ShutdownInfo), NULL); if (!NT_SUCCESS(Status)) goto SkipThread; if (ShutdownInfo.StatusShutdown == SHUTDOWN_UNKNOWN_PROCESS || !(ShutdownInfo.dwFlags & USER_THREAD_GUI)) goto SkipThread; /* * Send the end session messages to the thread. */ /* * If the user says kill it, the user wants it to go away now * no matter what. If the user didn't say kill, then call again * because we need to send WM_ENDSESSION messages. */ ThreadShutdownNotify(dwClientFlags, (ULONG_PTR)Thread, 0); SkipThread: CsrLockProcessByClientId(pcsrp->ClientId.UniqueProcess, &Process); CsrDereferenceThread(Thread); } } KillIt: CsrUnlockProcess(Process); if (ISTS()) { bDoBlock = (!fNoMsgsEver && !(dwClientFlags & WMCS_EXIT)); } else { bDoBlock = (!fNoRetry && !(dwClientFlags & WMCS_EXIT)); } if (bDoBlock) { Status = SHUTDOWN_CANCEL; goto RestoreDesktop; } /* * Set the final shutdown status according to the number of gui * threads found. If the count is zero, we have an unknown process. */ if (cThreads == 0) ShutdownInfo.StatusShutdown = SHUTDOWN_UNKNOWN_PROCESS; else ShutdownInfo.StatusShutdown = SHUTDOWN_KNOWN_PROCESS; if (ShutdownInfo.StatusShutdown == SHUTDOWN_UNKNOWN_PROCESS || !(dwClientFlags & WMCS_CONTEXTLOGOFF)) { /* * This process is not in the context being logged off. Do * not terminate it and let console send an event to the process. */ Status = SHUTDOWN_UNKNOWN_PROCESS; if (ShutdownInfo.drdRestore.hdeskNew) { goto RestoreDesktop; } goto CleanupAndExit; } /* * Calling ExitProcess() in the app's context will not always work * because the app may have .dll termination deadlocks: so the thread * will hang with the rest of the process. To ensure apps go away, * we terminate the process with NtTerminateProcess(). * * Pass this special value, DBG_TERMINATE_PROCESS, which tells * NtTerminateProcess() to return failure if it can't terminate the * process because the app is being debugged. */ if (ISTS()) { NTSTATUS ExitStatus; HANDLE DebugPort; ExitStatus = DBG_TERMINATE_PROCESS; if (NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessDebugPort, &DebugPort, sizeof(HANDLE), NULL)) && (DebugPort != NULL)) { // Csr is being debugged - go ahead and kill the process ExitStatus = 0; } TerminateStatus = NtTerminateProcess(pcsrp->ProcessHandle, ExitStatus); } else { TerminateStatus = NtTerminateProcess(pcsrp->ProcessHandle, DBG_TERMINATE_PROCESS); } pcsrp->Flags |= CSR_PROCESS_TERMINATED; /* * Let csr know we know about this process - meaning it was our * responsibility to shut it down. */ Status = SHUTDOWN_KNOWN_PROCESS; RestoreDesktop: /* * Release the desktop that was used. */ { USERTHREAD_USEDESKTOPINFO utudi; utudi.hThread = NULL; RtlCopyMemory(&(utudi.drdRestore), &(ShutdownInfo.drdRestore), sizeof(DESKRESTOREDATA)); NtUserSetInformationThread(NtCurrentThread(), UserThreadUseDesktop, &utudi, sizeof(utudi)); } /* * Now that we're done with the process handle, derefence the csr * process structure. */ if (Status != SHUTDOWN_UNKNOWN_PROCESS) { /* * If TerminateProcess returned STATUS_ACCESS_DENIED, then the process * is being debugged and it wasn't terminated.Otherwise we need to wait * anyway since TerminateProcess might return failure when the process * is going away (ie STATUS_PROCESS_IS_TERMINATING).If termination * indeed fail, something is wrong anyway so waiting a bit won't * hurt much. * If we wait give the process whatever exit timeout value configured * in the registry, but no less than the 5 second Hung App timeout. */ if (TerminateStatus != STATUS_ACCESS_DENIED) { LARGE_INTEGER li; li.QuadPart = (LONGLONG)-10000 * gdwProcessTerminateTimeout; TerminateStatus = NtWaitForSingleObject(pcsrp->ProcessHandle, FALSE, &li); if (TerminateStatus != STATUS_WAIT_0) { RIPMSG2(RIP_WARNING, "UserClientShutdown: wait for process %x failed with status %x", pcsrp->ClientId.UniqueProcess, TerminateStatus); } } CsrDereferenceProcess(pcsrp); } CleanupAndExit: return Status; } /***************************************************************************\ * WMCSCallback * * This function is passed to SendMessageCallback when sending the * WM_CLIENTSHUTDOWN message. It propagates the return value back * if ThreadShutdownNotify is still waiting for it; otherwise, * it just frees the memory. * * 03-04-97 GerardoB Created. \***************************************************************************/ VOID CALLBACK WMCSCallback( HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult) { PWMCSDATA pwmcsd = (PWMCSDATA)dwData; UNREFERENCED_PARAMETER(hwnd); UNREFERENCED_PARAMETER(uMsg); if (pwmcsd->dwFlags & WMCSD_IGNORE) { LocalFree(pwmcsd); return; } pwmcsd->dwFlags |= WMCSD_REPLY; pwmcsd->dwRet = (DWORD)lResult; } /***************************************************************************\ * GetInputWindow * * We assume a thread is waiting for input if it's not hung, the (main) * window is disabled, and it owns an enabled popup. * * 03-06-97 GerardoB Created. \***************************************************************************/ HWND GetInputWindow( PCSR_THREAD pcsrt, HWND hwnd) { DWORD dwTimeout; HWND hwndPopup; /* * Ask the kernel if the thread is hung. */ dwTimeout = gCmsHungAppTimeout; NtUserQueryInformationThread(pcsrt->ThreadHandle, UserThreadHungStatus, &dwTimeout, sizeof(dwTimeout), NULL); /* * If not hung and disabled, see if it owns an enabled popup. */ if (!dwTimeout && !IsWindowEnabled(hwnd)) { hwndPopup = GetWindow(hwnd, GW_ENABLEDPOPUP); if (hwndPopup != NULL && hwndPopup != hwnd) { return hwndPopup; } } return NULL; } /***************************************************************************\ * GetApplicationText * * Gets the text that identifies the given window or thread * * 08-01-97 GerardoB Created. \***************************************************************************/ VOID GetApplicationText( HWND hwnd, HANDLE hThread, WCHAR *pwcText, UINT uLen) { /* * GetWindowText doesn't call the hwnd's proc; otherwise, we could * get blocked here for good. */ GetWindowText(hwnd, pwcText, uLen); if (*pwcText == 0) { /* * We couldn't get the window's title; let's try the thread's name. */ NtUserQueryInformationThread(hThread, UserThreadTaskName, pwcText, uLen * sizeof(WCHAR), NULL); } } /***************************************************************************\ * ReportHang * * This function launches an intermediate app (dumprep.exe) which packages up * the hang information & ships it up to MS. We create an event and wait on * it so that we know when the minidump information has been grabbed from the * app. * * 08-31-00 DerekM Created \***************************************************************************/ VOID ReportHang( CLIENT_ID *pcidToKill) { PROCESS_SESSION_INFORMATION psi; SECURITY_ATTRIBUTES sa; SECURITY_DESCRIPTOR sd; PCSR_THREAD pcsrt = CSR_SERVER_QUERYCLIENTTHREAD(); NTSTATUS Status; HANDLE rghWait[2] = { NULL, NULL }; HANDLE hProc = NULL; WCHAR wszEvent[MAX_PATH], *pwszSuffix; DWORD dw, dwTimeout, dwStartWait; BOOL fIs64Bit = FALSE; #ifdef _WIN64 ULONG_PTR Wow64Info = 0; HANDLE hProcKill = NULL; #endif #if defined(_DEBUG) || defined(DEBUG) dwTimeout = 600000; // 10 minutes #else dwTimeout = 120000; // 2 minutes #endif // we're going to launch dwwin in the context of the interactive user that // is logged on to the killing process's session. So we need to figure out // what session it's in. // If any of these fail, we have to bail cuz otherwise we'd have to create // an instance of dwwin.exe in local system context, and since dwwin.exe // can launch helpcenter, this is bad. hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, HandleToLong(pcsrt->ClientId.UniqueProcess)); if (hProc == NULL) { RIPMSG2(RIP_WARNING, "HangReporting: Couldn't open killing process (pid: %d) (err: %08x)\n", HandleToLong(pcsrt->ClientId.UniqueProcess), GetLastError()); goto done; } Status = NtQueryInformationProcess(hProc, ProcessSessionInformation, &psi, sizeof(psi), NULL); if (NT_SUCCESS(Status) == FALSE) { RIPMSG2(RIP_WARNING, "HangReporting: Couldn't get the session ID (pid: %d) (err: %08x)\n", HandleToLong(pcsrt->ClientId.UniqueProcess), RtlNtStatusToDosError(Status)); goto done; } #ifdef _WIN64 // need to determine if we're a Wow64 process so we can build the appropriate // signatures... hProcKill = OpenProcess(PROCESS_ALL_ACCESS, FALSE, HandleToLong(pcidToKill->UniqueProcess)); if (hProcKill == NULL) { RIPMSG2(RIP_WARNING, "HangReporting: Couldn't open dying process (pid: %d) (err: %08x)\n", HandleToLong(pcidToKill->UniqueProcess), GetLastError()); goto done; } Status = NtQueryInformationProcess(hProcKill, ProcessWow64Information, &Wow64Info, sizeof(Wow64Info), NULL); if (NT_SUCCESS(Status) == FALSE) { RIPMSG2(RIP_WARNING, "HangReporting: Couldn't get Wow64 info (pid: %d) (err: %08x)\n", HandleToLong(pcidToKill->UniqueProcess), RtlNtStatusToDosError(Status)); goto done; } fIs64Bit = (Wow64Info == 0); #endif // Because of a bug where CreateProcessAsUser doesn't want to work from // the csrss process, we have to have a remote process sitting around // waiting on a pipe. It will call CreateProcessAsUser (as well as // determine the correct token for the session). // // Note that it only accepts requests from processes running as local // system. // Since a remote process does the creation of dumprep.exe, we need to // used a named event instead of relying on dumprep to inherit the event // handle. dw = swprintf(wszEvent, L"Global\\%d%x%x%x%x%x", psi.SessionId, GetTickCount(), HandleToLong(pcsrt->ClientId.UniqueProcess), HandleToLong(pcsrt->ClientId.UniqueThread), HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess), HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread)); pwszSuffix = wszEvent + dw; // make sure to create this event with a NULL DACL so a generic usermode // process has access to it. Status = RtlCreateSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); if (NT_SUCCESS(Status) == FALSE) { RIPMSG1(RIP_WARNING, "HangReporting: could not create SD (err: %08x)\n", RtlNtStatusToDosError(Status)); goto done; } // This is implemented in reclient.h & creates a SD with creator & // LocalSystem having full rights & world & anonymous having sync // rights. Status = AllocDefSD(&sd, STANDARD_RIGHTS_ALL | GENERIC_ALL | EVENT_ALL_ACCESS, EVENT_MODIFY_STATE | SYNCHRONIZE | GENERIC_READ); if (NT_SUCCESS(Status) == FALSE) { RIPMSG1(RIP_WARNING, "HangReporting: could not create SD (err: %08x)\n", RtlNtStatusToDosError(Status)); goto done; } ZeroMemory(&sa, sizeof(sa)); sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = &sd; // Need an event so we know when the app we're killing is no longer // necessary. If the event already exists, try to create a new one. // But only do this a maximum of 7 times. for (dw = 0; dw < 7; dw++) { rghWait[0] = CreateEventW(&sa, TRUE, FALSE, wszEvent); if (rghWait[0] == NULL) { RIPMSG1(RIP_WARNING, "HangReporting: Error creating wait event (err: %08x)\n", GetLastError()); goto done; } if (GetLastError() != ERROR_ALREADY_EXISTS) { break; } // Sleep for a millisecond to make the result of GetTickCount() even // more unpredictable... Sleep(1); _ltow(GetTickCount(), pwszSuffix, 16); } if (dw >= 7) { RIPMSG0(RIP_WARNING, "HangReporting: Could not find unique wait event name\n"); goto done; } FreeDefSD(&sd); if (StartHangReport(psi.SessionId, wszEvent, HandleToLong(pcidToKill->UniqueProcess), HandleToLong(pcidToKill->UniqueThread), fIs64Bit, &rghWait[1]) == FALSE) { RIPMSG1(RIP_WARNING, "HangReporting: StartHangReport failed (err: %08x)\n", GetLastError()); goto done; } // use MsgWaitForMultipleObjects in case this thread is doing UI processing // Not really likely, but you never know. Anyway, only wait 2 minutes for // dumprep to generate the minidump. If it still hasn't done it by then, // it isn't likely to ever finish. dwStartWait = GetTickCount(); for (;;) { dw = MsgWaitForMultipleObjects(2, rghWait, FALSE, dwTimeout, QS_ALLINPUT); if (dw == WAIT_OBJECT_0 + 2) { DWORD dwNow; DWORD dwSub; MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } dwNow = GetTickCount(); if (dwNow < dwStartWait) dwSub = ((DWORD)-1 - dwStartWait) + dwNow; else dwSub = dwNow - dwStartWait; if (dwSub > dwTimeout) dwTimeout = 0; else dwTimeout -= dwSub; continue; } break; } done: #ifdef _WIN64 if (hProcKill != NULL) { CloseHandle(hProcKill); } #endif if (hProc != NULL) { CloseHandle(hProc); } if (rghWait[0] != NULL) { CloseHandle(rghWait[0]); } if (rghWait[1] != NULL) { CloseHandle(rghWait[1]); } } /***************************************************************************\ * ThreadShutdownNotify * * This function notifies a given thread that it's time (or about time) * to go away. This is called from _EndTask to post the WM_CLOSE message * or from UserClientShutdown to send the WM_QUERYENDSESSION and * WM_ENDSESSION messages. If the thread fails to respond, then the * "End Application" dialog is brought up. This function is also called * from Console to display that dialog too. * * 03-07-97 GerardoB Created to replace SendShutdownMessages, * MySendEndSessionMessages and DoEndTaskDialog * 08-15-00 JasonSch Added code to limit number of CSRSS worker threads * stuck in _EndTask to 8. \***************************************************************************/ DWORD ThreadShutdownNotify( DWORD dwClientFlags, ULONG_PTR dwThread, LPARAM lParam) { HWND hwnd, hwndOwner, hwndDlg; PWMCSDATA pwmcsd = NULL; ENDDLGPARAMS edp; DWORD dwRet, dwRealTimeout, dwTimeout, dwStartTiming, dwCmd; MSG msg; PCSR_THREAD pcsrt; HANDLE hThread; BOOL fEndTaskNow = FALSE; static DWORD dwTSNThreads = 0; #define ESMH_CANCELEVENT 0 #define ESMH_THREAD 1 #define ESMH_HANDLECOUNT 2 HANDLE ahandles[ESMH_HANDLECOUNT]; if (dwTSNThreads > TSN_MAX_THREADS) { /* * If we've already reached our limit in terms of CSRSS threads stuck * in this function, "fail" this call. */ return TSN_USERSAYSCANCEL; } /* * If this is console, just set up the wait loop and * bring the dialog up right away. Otherwise, find * the notification window, notify it, and go wait. */ if (dwClientFlags & WMCS_CONSOLE) { hThread = (HANDLE)dwThread; dwRealTimeout = 0; goto SetupWaitLoop; } else { pcsrt = (PCSR_THREAD)dwThread; hThread = pcsrt->ThreadHandle; hwnd = (HWND)lParam; } /* * If no window was provided, find a top-level window owned by the thread. */ if (hwnd == NULL) { EnumThreadWindows(HandleToUlong(pcsrt->ClientId.UniqueThread), &FindWindowFromThread, (LPARAM)&hwnd); if (hwnd == NULL) { return TSN_NOWINDOW; } } /* * Find the root owner (we'll guess this is the "main" window). */ while ((hwndOwner = GetWindow(hwnd, GW_OWNER)) != NULL) { hwnd = hwndOwner; } #ifdef FE_IME /* * If this is a console window, then just returns TSN_APPSAYSOK. * In this routine: * Normally windows NT environment, hwnd never point to console window. * However, In ConIme process, its owner window point to console window. */ if (!(dwClientFlags & WMCS_ENDTASK)) { if ((HANDLE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE) == ghModuleWin) { return TSN_APPSAYSOK; } } #endif // FE_IME /* * If this is an EndTask request but the window is disabled, * then we want to bring the dialog up right way (the app * is probably waiting for input). * * Otherwise, we bring the window to the foreground, send/post * the request and wait. */ /* * Make sure we respond to the user ASAP when they attempt to shutdown * an application that we know is hung. */ if ((dwClientFlags & WMCS_ENDTASK)) { dwTimeout = gCmsHungAppTimeout; NtUserQueryInformationThread(pcsrt->ThreadHandle, UserThreadHungStatus, &dwTimeout, sizeof(dwTimeout), NULL); if (!IsWindowEnabled(hwnd) || dwTimeout) { dwRealTimeout = 0; fEndTaskNow = TRUE; } } if (!fEndTaskNow) { SetForegroundWindow(hwnd); dwRealTimeout = gCmsHungAppTimeout; if (dwClientFlags & WMCS_ENDTASK) { PostMessage(hwnd, WM_CLOSE, 0, 0); } else { /* * If the shutdown was canceled, we don't need to wait * (we're just sending the WM_ENDSESSION(FALSE)). */ if (!(dwClientFlags & (WMCS_QUERYEND | WMCS_EXIT))) { SendNotifyMessage(hwnd, WM_CLIENTSHUTDOWN, dwClientFlags, 0); return TSN_APPSAYSOK; } /* * Allocate callback data. If out of memory, kill it. */ pwmcsd = (PWMCSDATA)LocalAlloc(LPTR, sizeof(WMCSDATA)); if (pwmcsd == NULL) { return TSN_USERSAYSKILL; } SendMessageCallback(hwnd, WM_CLIENTSHUTDOWN, dwClientFlags, 0, WMCSCallback, (ULONG_PTR)pwmcsd); } } SetupWaitLoop: /* * This thread is now officially going to be "stuck" in TSN. Increment our * count of threads so disposed. */ ++dwTSNThreads; /* * This tells us if the hwndDlg is valid. This is set/cleared by EndTaskDlgProc. */ ZeroMemory(&edp, sizeof(edp)); edp.dwFlags = EDPF_NODLG; /* * Loop until the hwnd replies, the request is canceled * or the thread goes away. If it times out, bring up the * dialog and wait until the user tells us what to do. */ *(ahandles + ESMH_CANCELEVENT) = gheventCancel; *(ahandles + ESMH_THREAD) = hThread; dwStartTiming = GetTickCount(); dwCmd = 0; while (dwCmd == 0) { /* * Calculate how long we have to wait. */ dwTimeout = dwRealTimeout; if ((dwTimeout != 0) && (dwTimeout != INFINITE)) { dwTimeout -= (GetTickCount() - dwStartTiming); if ((int)dwTimeout < 0) { dwTimeout = 0; } } dwRet = MsgWaitForMultipleObjects(ESMH_HANDLECOUNT, ahandles, FALSE, dwTimeout, QS_ALLINPUT); switch (dwRet) { case WAIT_OBJECT_0 + ESMH_CANCELEVENT: /* * The request has been canceled. */ dwCmd = TSN_USERSAYSCANCEL; break; case WAIT_OBJECT_0 + ESMH_THREAD: /* * The thread is gone. */ dwCmd = TSN_APPSAYSOK; break; case WAIT_OBJECT_0 + ESMH_HANDLECOUNT: /* * We got some input; process it. */ while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if ((edp.dwFlags & EDPF_NODLG) || !IsDialogMessage(hwndDlg, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } /* * If we got a reply to the message, act on it */ if (pwmcsd != NULL && (pwmcsd->dwFlags & WMCSD_REPLY)) { switch (pwmcsd->dwRet) { default: /* * If the message was not processed (the thread * exited) or someone processed it and returned * a bogus value, just shut them down. * * Fall through. */ case WMCSR_ALLOWSHUTDOWN: /* * We're going to nuke this app, so get rid of * any pending harderror boxes he might have. */ BoostHardError((ULONG_PTR)pcsrt->ClientId.UniqueProcess, BHE_FORCE); /* * Fall through. */ case WMCSR_DONE: dwCmd = TSN_APPSAYSOK; break; case WMCSR_CANCEL: dwCmd = TSN_APPSAYSNOTOK; break; } } /* * Else if the dialog is still up, keep waiting for the user * to tell us what to do */ else if (!(edp.dwFlags & EDPF_NODLG)) { break; } /* * Else if the user dismissed the dialog, act on his response */ else if (edp.dwFlags & EDPF_RESPONSE) { switch(edp.dwRet) { case IDC_ENDNOW: /* * The user wants us to kill it */ dwCmd = TSN_USERSAYSKILL; if ((dwClientFlags & WMCS_ENDTASK) != 0 && (edp.dwFlags & EDPF_HUNG) != 0) { THREAD_BASIC_INFORMATION tbi; CLIENT_ID *pcidToKill = NULL; if ((dwClientFlags & WMCS_CONSOLE) != 0) { if (NtQueryInformationThread(hThread, ThreadBasicInformation, &tbi, sizeof(tbi), NULL) == STATUS_SUCCESS) { pcidToKill = &(tbi.ClientId); } } else { pcidToKill = &(pcsrt->ClientId); } if (pcidToKill != NULL) { if (!(pcsrt->Flags & CSR_THREAD_HANGREPORTED)) { // insure that we only call this ONCE pcsrt->Flags |= CSR_THREAD_HANGREPORTED; ReportHang(pcidToKill); } } } break; /* case IDCANCEL: */ default: dwCmd = TSN_USERSAYSCANCEL; break; } } break; case WAIT_TIMEOUT: if (dwClientFlags & WMCS_NORETRY) { /* * We come here only for Terminal Server case. We return * TSN_APPSAYSOK as Terminal Server 4 did in this case. */ UserAssert(ISTS()); dwCmd = TSN_APPSAYSOK; break; } /* * Once we time out, we bring up the dialog and let * its timer take over. */ dwRealTimeout = INFINITE; /* * Check if the windows app is waiting for input; * if not, we assume it is hung for EndTask. Otherwise, * we enter a wait mode that brings the dialog up just * to provide some (waiting) feedback. Console just gets * the dialog right away. */ if (!(dwClientFlags & WMCS_CONSOLE)) { if (BoostHardError((ULONG_PTR)pcsrt->ClientId.UniqueProcess, BHE_TEST) || (GetInputWindow(pcsrt, hwnd) != NULL)) { edp.dwFlags |= EDPF_INPUT; } else { /* * If the window's gone and the thread is still responsive, then * this must be an app that just hides its window on WM_CLOSE * (e.g., MSN Instant Messenger). Let's nuke the app w/o * bringing up the EndTask dialog. */ if (!IsWindow(hwnd)) { DWORD dwThreadHung; /* * Ask the kernel if the thread is hung. */ dwThreadHung = gCmsHungAppTimeout; NtUserQueryInformationThread(pcsrt->ThreadHandle, UserThreadHungStatus, &dwThreadHung, sizeof(dwThreadHung), NULL); if (!dwThreadHung) { dwCmd = TSN_APPSAYSOK; break; } } /* * EWX_FORCEIFHUNG support. * Also, if this is an ExitWindows call and the process is * not in the context being logged off, we won't kill it. * So don't bother asking the user what to do. */ if ((dwClientFlags & WMCS_NODLGIFHUNG) || (!(dwClientFlags & WMCS_ENDTASK) && !(dwClientFlags & WMCS_CONTEXTLOGOFF))) { dwCmd = TSN_USERSAYSKILL; break; } /* * Hung or Wait? */ if (dwClientFlags & WMCS_ENDTASK) { edp.dwFlags |= EDPF_HUNG; } else { edp.dwFlags |= EDPF_WAIT; } } } /* * If the registry says no dialog, then tell the caller * the user wants to kill the app. */ if (gfAutoEndTask) { dwCmd = TSN_USERSAYSKILL; break; } /* * Setup the parameters needed by EndTaskDlgProc. */ edp.dwRet = 0; edp.dwClientFlags = dwClientFlags; if (dwClientFlags & WMCS_CONSOLE) { edp.pcsrt = NULL; edp.lParam = lParam; } else { edp.pcsrt = pcsrt; edp.lParam = (LPARAM)hwnd; } hwndDlg = CreateDialogParam(ghModuleWin, MAKEINTRESOURCE(IDD_ENDTASK), NULL, EndTaskDlgProc, (LPARAM)(&edp)); /* * If we cannot ask the user, then kill the app. */ if (hwndDlg == NULL) { edp.dwFlags |= EDPF_NODLG; dwCmd = TSN_USERSAYSKILL; break; } break; default: /* * Unexpected return; something is wrong. Kill the app. */ UserAssert(dwRet != dwRet); dwCmd = TSN_USERSAYSKILL; break; } } /* * If the dialog is up, nuke it. */ if (!(edp.dwFlags & EDPF_NODLG)) { DestroyWindow(hwndDlg); } /* * Make sure pwmcsd is freed or marked to be freed by WMCSCallback * when the reply comes. */ if (pwmcsd != NULL) { if (pwmcsd->dwFlags & WMCSD_REPLY) { LocalFree(pwmcsd); } else { pwmcsd->dwFlags |= WMCSD_IGNORE; } } #if DBG /* * If cancelling, let's name the app that didn't let us log off. */ if ((dwClientFlags & WMCS_EXIT) && (dwCmd == TSN_APPSAYSNOTOK)) { WCHAR achTitle[CCHBODYMAX]; WCHAR *pwcText; UserAssert(!(dwClientFlags & WMCS_CONSOLE)); pwcText = achTitle; *(achTitle + CCHBODYMAX - 1) = (WCHAR)0; GetApplicationText(hwnd, hThread, pwcText, CCHBODYMAX - 1); RIPMSG3(RIP_WARNING, "Log off canceled by pid:%#lx tid:%#lx - '%ws'.\n", HandleToUlong(pcsrt->ClientId.UniqueProcess), HandleToUlong(pcsrt->ClientId.UniqueThread), pwcText); } #endif // DBG /* * If we're killing this dude, clean any hard errors. * Also if wow takes care of it, then our caller doesn't need to */ if ((dwCmd == TSN_USERSAYSKILL) && !(dwClientFlags & WMCS_CONSOLE)) { BoostHardError((ULONG_PTR)pcsrt->ClientId.UniqueProcess, BHE_FORCE); if (!(pcsrt->Flags & CSR_THREAD_DESTROYED) && WowExitTask(pcsrt)) { dwCmd = TSN_APPSAYSOK; } } --dwTSNThreads; return dwCmd; } /***************************************************************************\ * SetEndTaskDlgStatus * * Displays the appropiate message and shows the dialog * * 03-11-97 GerardoB Created \***************************************************************************/ VOID SetEndTaskDlgStatus( ENDDLGPARAMS *pedp, HWND hwndDlg, UINT uStrId, BOOL fInit) { BOOL f, fIsWaiting, fWasWaiting; WCHAR *pwcText; fWasWaiting = (pedp->uStrId == STR_ENDTASK_WAIT); fIsWaiting = (pedp->dwFlags & EDPF_WAIT) != 0; /* * Store the current message id, load it and show it. */ pedp->uStrId = uStrId; pwcText = ServerLoadString(ghModuleWin, uStrId, NULL, &f); if (pwcText != NULL) { SetDlgItemText(hwndDlg, IDC_STATUSMSG, pwcText); LocalFree(pwcText); } /* * If we haven't decided that the app is hung, set a * timer to keep an eye on it. */ if (!(pedp->dwFlags & EDPF_HUNG) && !(pedp->dwClientFlags & WMCS_CONSOLE)) { SetTimer(hwndDlg, IDT_CHECKAPPSTATE, gCmsHungAppTimeout, NULL); } /* * If initializing or switching to/from the wait mode, * set the proper status for IDC_STATUSCANCEL, IDCANCEL, * IDC_ENDNOW and the start/stop the progress bar. * * Invalidate paint if/as needed. */ if (fInit || (fIsWaiting ^ fWasWaiting)) { RECT rc; HWND hwndStatusCancel = GetDlgItem(hwndDlg, IDC_STATUSCANCEL); HWND hwndCancelButton = GetDlgItem(hwndDlg, IDCANCEL); HWND hwndEndButton = GetDlgItem(hwndDlg, IDC_ENDNOW); DWORD dwSwpFlags; /* * If on wait mode, we hide the cancel button and its * explanatory text. The End button will be moved to * the cancel button position. */ dwSwpFlags = ((fIsWaiting ? SWP_HIDEWINDOW : SWP_SHOWWINDOW) | SWP_NOREDRAW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_NOACTIVATE); /* * If we're hiding the cancel button, give focus/def id to * the End button. * * Note that DM_SETDEIF won't do the right thing unless * both Cancel/End buttons are visible. */ if (fIsWaiting) { SendMessage(hwndDlg, DM_SETDEFID, IDC_ENDNOW, 0); SetFocus(hwndEndButton); } SetWindowPos(hwndStatusCancel, NULL, 0, 0, 0, 0, dwSwpFlags); SetWindowPos(hwndCancelButton, NULL, 0, 0, 0, 0, dwSwpFlags); /* * If the cancel button is visible, give it focus/def id. */ if (!fIsWaiting) { SendMessage(hwndDlg, DM_SETDEFID, IDCANCEL, 0); SetFocus(hwndCancelButton); } /* * Initialize progress bar (first time around). */ if (fIsWaiting && (pedp->hbrProgress == NULL)) { int iMagic; /* * Initialize progress bar stuff. * The size and location calculations below were made up * to make it look good(?). * We need that location on dialog coordiantes since the * progress bar is painted on the dialog's WM_PAINT. */ GetClientRect(hwndStatusCancel, &pedp->rcBar); iMagic = (pedp->rcBar.bottom - pedp->rcBar.top) / 4; InflateRect(&pedp->rcBar, 0, -iMagic + GetSystemMetrics(SM_CYEDGE)); pedp->rcBar.right -= (5 * iMagic); OffsetRect(&pedp->rcBar, 0, -iMagic); MapWindowPoints(hwndStatusCancel, hwndDlg, (LPPOINT)&pedp->rcBar, 2); /* * Calculate drawing rectangle and dimensions. We kind of make it * look like comctrl's progress bar. */ pedp->rcProgress = pedp->rcBar; InflateRect(&pedp->rcProgress, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE)); pedp->iProgressStop = pedp->rcProgress.right; pedp->iProgressWidth = ((2 * (pedp->rcProgress.bottom - pedp->rcProgress.top)) / 3); pedp->rcProgress.right = pedp->rcProgress.left + pedp->iProgressWidth - 1; pedp->hbrProgress = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); /* * Remember the End button position. */ GetWindowRect(hwndEndButton, &pedp->rcEndButton); MapWindowPoints(NULL, hwndDlg, (LPPOINT)&pedp->rcEndButton, 2); } /* * Start/Stop progress bar and set End button position */ if (fIsWaiting) { RECT rcEndButton; UINT uTimeout = (gdwHungToKillCount * gCmsHungAppTimeout) / ((pedp->iProgressStop - pedp->rcProgress.left) / pedp->iProgressWidth); SetTimer(hwndDlg, IDT_PROGRESS, uTimeout, NULL); /* * The Cancel and the End buttons might have different widths when * localized. So make sure we position it inside the dialog and * to the right end of the dialog. */ GetWindowRect(hwndCancelButton, &rc); GetWindowRect(hwndEndButton, &rcEndButton); rc.left = rc.right - (rcEndButton.right - rcEndButton.left); MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rc, 2); } else if (fWasWaiting) { KillTimer(hwndDlg, IDT_PROGRESS); rc = pedp->rcEndButton; } /* * Move the End button if needed */ if (fIsWaiting || fWasWaiting) { SetWindowPos(hwndEndButton, NULL, rc.left, rc.top, 0, 0, SWP_NOREDRAW | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING); } /* * Make sure we repaint if needed */ if (!fInit) { InvalidateRect(hwndDlg, NULL, TRUE); } } /* * If initializing or in hung mode, make sure the user can * see the dialog; only bring it to the foreground on * initialization (no rude focus stealing) */ if (fInit || (pedp->dwFlags & EDPF_HUNG)) { SetWindowPos(hwndDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOSENDCHANGING); if (fInit) { SetForegroundWindow(hwndDlg); } } } /***************************************************************************\ * EndTaskDlgProc * * This is the dialog procedure for the dialog that comes up when an app is * not responding. * * 03-06-97 GerardoB Rewrote it once again. New template though. * 07-23-92 ScottLu Rewrote it, but used same dialog template. * 04-28-92 JonPa Created. \***************************************************************************/ INT_PTR APIENTRY EndTaskDlgProc( HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { ENDDLGPARAMS* pedp; WCHAR achTitle[CCHBODYMAX]; WCHAR *pwcText, *pwcTemp; UINT uLen; UINT uStrId; PAINTSTRUCT ps; HDC hdc, hdcMem; BOOL fIsInput, fWasInput; int iOldLayout; switch (msg) { case WM_INITDIALOG: /* * Save parameters */ pedp = (ENDDLGPARAMS*)lParam; SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (ULONG_PTR)pedp); /* * This tells the caller that the dialog is up */ pedp->dwFlags &= ~EDPF_NODLG; /* * Build the dialog title making sure that * we end up with a NULL terminated string. */ *(achTitle + CCHBODYMAX - 1) = (WCHAR)0; uLen = GetWindowText(hwndDlg, achTitle, CCHBODYMAX - 1); pwcText = achTitle + uLen; uLen = CCHBODYMAX - 1 - uLen; /* * Console provides the title; we figure it out for windows apps. */ if (pedp->dwClientFlags & WMCS_CONSOLE) { pwcTemp = (WCHAR *)pedp->lParam; while (uLen-- && (*pwcText++ = *pwcTemp++)); } else { GetApplicationText((HWND)pedp->lParam, pedp->pcsrt->ThreadHandle, pwcText, uLen); } SetWindowText(hwndDlg, achTitle); /* * Get the app's icon: first look for atomIconProperty. * If not available, try the class icon. * Else, use the default icon. */ pedp->hIcon = (HICON)GetProp((HWND)pedp->lParam, ICON_PROP_NAME); if (pedp->hIcon == NULL) { pedp->hIcon = (HICON)GetClassLongPtr((HWND)pedp->lParam, GCLP_HICON); if (pedp->hIcon == NULL) { if (pedp->dwClientFlags & WMCS_CONSOLE) { pedp->hIcon = LoadIcon(ghModuleWin, MAKEINTRESOURCE(IDI_CONSOLE)); } else { pedp->hIcon = LoadIcon(NULL, IDI_APPLICATION); } } } /* * Figure out what message the caller wants initially */ if (pedp->dwClientFlags & WMCS_CONSOLE) { uStrId = STR_ENDTASK_CONSOLE; } else if (pedp->dwFlags & EDPF_INPUT) { uStrId = STR_ENDTASK_INPUT; } else if (pedp->dwFlags & EDPF_WAIT) { uStrId = STR_ENDTASK_WAIT; } else { uStrId = STR_ENDTASK_HUNG; } /* * Display the message, set the focus and show the dialog */ SetEndTaskDlgStatus(pedp, hwndDlg, uStrId, TRUE); return FALSE; case WM_PAINT: pedp = (ENDDLGPARAMS*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); if ((pedp == NULL) || (pedp->hIcon == NULL)) { break; } /* * Draw the icon */ hdc = BeginPaint(hwndDlg, &ps); iOldLayout = GetLayout(hdc); if (iOldLayout != GDI_ERROR) { SetLayout(hdc, iOldLayout|LAYOUT_BITMAPORIENTATIONPRESERVED); } DrawIcon(hdc, ETD_XICON, ETD_YICON, pedp->hIcon); if (iOldLayout != GDI_ERROR) { SetLayout(hdc, iOldLayout); } /* * If waiting, draw the progress bar; * else draw the warning sign. */ if (pedp->dwFlags & EDPF_WAIT) { RECT rc; /* * Draw a client-edge-looking border. */ rc = pedp->rcBar; DrawEdge(hdc, &rc, BDR_SUNKENOUTER, BF_RECT | BF_ADJUST); InflateRect(&rc, -1, -1); /* * Draw the blocks up to the current position. */ rc.right = rc.left + pedp->iProgressWidth - 1; while (rc.left < pedp->rcProgress.left) { if (rc.right > pedp->iProgressStop) { rc.right = pedp->iProgressStop; if (rc.left >= rc.right) { break; } } FillRect(hdc, &rc, pedp->hbrProgress); rc.left += pedp->iProgressWidth; rc.right += pedp->iProgressWidth; } } else { /* * Load the bitmap the first time around and * figure out where it goes. */ if (pedp->hbmpWarning == NULL) { BITMAP bmp; pedp->hbmpWarning = LoadBitmap(ghModuleWin, MAKEINTRESOURCE(IDB_WARNING)); if (GetObject(pedp->hbmpWarning, sizeof(bmp), &bmp)) { pedp->rcWarning.left = ETD_XICON; pedp->rcWarning.top = ETD_XICON + 32 - bmp.bmHeight; pedp->rcWarning.right = bmp.bmWidth; pedp->rcWarning.bottom = bmp.bmHeight; } } /* * Blit it. */ hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem, pedp->hbmpWarning); GdiTransparentBlt(hdc, pedp->rcWarning.left, pedp->rcWarning.top, pedp->rcWarning.right, pedp->rcWarning.bottom, hdcMem, 0, 0, pedp->rcWarning.right, pedp->rcWarning.bottom, RGB(255, 0, 255)); DeleteDC(hdcMem); } EndPaint(hwndDlg, &ps); return TRUE; case WM_TIMER: pedp = (ENDDLGPARAMS*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); if (pedp == NULL) { return TRUE; } switch (wParam) { case IDT_CHECKAPPSTATE: pedp->dwCheckTimerCount++; /* * Check if the app has switched from/to a waiting-for-input * mode. If so, update the dialog and wait a little longer */ fIsInput = (BoostHardError((ULONG_PTR)pedp->pcsrt->ClientId.UniqueProcess, BHE_TEST) || (GetInputWindow(pedp->pcsrt, (HWND)pedp->lParam) != NULL)); fWasInput = (pedp->dwFlags & EDPF_INPUT); if (fIsInput ^ fWasInput) { UINT uProgress; pedp->dwFlags &= ~(EDPF_INPUT | EDPF_WAIT); pedp->dwFlags |= (fIsInput ? EDPF_INPUT : EDPF_WAIT); SetEndTaskDlgStatus(pedp, hwndDlg, (fIsInput ? STR_ENDTASK_INPUT : STR_ENDTASK_WAIT), FALSE); pedp->dwCheckTimerCount /= 2; uProgress = pedp->rcProgress.left - pedp->rcBar.left - GetSystemMetrics(SM_CXEDGE); uProgress /= 2; pedp->rcProgress.left -= uProgress; pedp->rcProgress.right -= uProgress; } /* * Is it time to declare it hung? */ if (pedp->dwCheckTimerCount >= gdwHungToKillCount) { KillTimer(hwndDlg, IDT_CHECKAPPSTATE); pedp->dwFlags &= ~(EDPF_INPUT | EDPF_WAIT); pedp->dwFlags |= EDPF_HUNG; SetEndTaskDlgStatus(pedp, hwndDlg, STR_ENDTASK_HUNG, FALSE); } break; case IDT_PROGRESS: /* * Draw the next block in the progress bar. */ if (pedp->rcProgress.right >= pedp->iProgressStop) { pedp->rcProgress.right = pedp->iProgressStop; if (pedp->rcProgress.left >= pedp->rcProgress.right) { break; } } hdc = GetDC(hwndDlg); FillRect(hdc, &pedp->rcProgress, pedp->hbrProgress); ReleaseDC(hwndDlg, hdc); pedp->rcProgress.left += pedp->iProgressWidth; pedp->rcProgress.right += pedp->iProgressWidth; break; } return TRUE; case WM_NCACTIVATE: /* * Make sure we're uncovered when active and not covering the app * when inactive */ pedp = (ENDDLGPARAMS*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); if (pedp != NULL) { HWND hwnd; if (wParam) { hwnd = HWND_TOPMOST; } else if (pedp->dwClientFlags & WMCS_CONSOLE) { hwnd = HWND_TOP; } else { hwnd = (HWND)pedp->lParam; } SetWindowPos(hwndDlg, hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } break; case WM_COMMAND: /* * The user has made a choice, we're done. */ pedp = (ENDDLGPARAMS*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); if (pedp != NULL) { pedp->dwRet = (DWORD)wParam; } DestroyWindow(hwndDlg); break; case WM_DESTROY: /* * We're dead. Make sure the caller knows we're history */ pedp = (ENDDLGPARAMS*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); if (pedp != NULL) { pedp->dwFlags |= (EDPF_NODLG | EDPF_RESPONSE); if (pedp->hbmpWarning != NULL) { DeleteObject(pedp->hbmpWarning); } if (pedp->hbrProgress != NULL) { DeleteObject(pedp->hbrProgress); } } break; } return FALSE; } /***************************************************************************\ * _EndTask * * This routine is called from the task manager to end an application - for * gui apps, either a win32 app or a win16 app. Note: Multiple console * processes can live in a single console window. We'll pass these requests * for destruction to console. * * 07-25-92 ScottLu Created. \***************************************************************************/ BOOL _EndTask( HWND hwnd, BOOL fMeanKill) { BOOL fRet = TRUE; PCSR_THREAD pcsrt = CSR_SERVER_QUERYCLIENTTHREAD(); PCSR_THREAD pcsrtKill; DWORD dwThreadId; DWORD dwProcessId; LPWSTR lpszMsg; BOOL fAllocated; DWORD dwCmd; USERTHREAD_USEDESKTOPINFO utudi; NTSTATUS Status; /* * Set the current work thread to a desktop so we can * go safely into win32k.sys. */ utudi.hThread = pcsrt->ThreadHandle; utudi.drdRestore.pdeskRestore = NULL; Status = NtUserSetInformationThread(NtCurrentThread(), UserThreadUseDesktop, &utudi, sizeof(utudi)); if (!NT_SUCCESS(Status)) { /* * We were unable to get the thread's desktop. Game over. */ return TRUE; } /* * Get the process and thread that owns hwnd. */ dwThreadId = GetWindowThreadProcessId(hwnd, &dwProcessId); if (dwThreadId == 0) { goto RestoreDesktop; } /* * Don't allow destruction of winlogon. */ if (dwProcessId == gIdLogon) { goto RestoreDesktop; } /* * If this is a console window, then just send the close message to * it, and let console clean up the processes in it. */ if ((HANDLE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE) == ghModuleWin) { PostMessage(hwnd, WM_CLOSE, 0, 0); goto RestoreDesktop; } /* * Find the CSR_THREAD for the window. */ Status = CsrLockThreadByClientId(LongToHandle(dwThreadId), &pcsrtKill); if (!NT_SUCCESS(Status)) { /* * This is probably the ghost thread, which CSRSS doesn't know about * (as it's created via RtlCreateUserThread, which doesn't LPC into * CSRSS like regular CreateThread does). When the ghost window gets * the WM_CLOSE it'll handle removing the real window and thread. If * this *isn't* a ghost window, then no real harm done, so we post * no matter what. */ PostMessage(hwnd, WM_CLOSE, 0, 0); goto RestoreDesktop; } CsrReferenceThread(pcsrtKill); CsrUnlockThread(pcsrtKill); /* * If this is a WOW app, then shutdown just this wow application. */ if (!fMeanKill) { /* * Find out what to do now - did the user cancel or the app cancel, * etc? Only allow cancelling if we are not forcing the app to * exit. */ dwCmd = ThreadShutdownNotify(WMCS_ENDTASK, (ULONG_PTR)pcsrtKill, (LPARAM)hwnd); switch (dwCmd) { case TSN_APPSAYSNOTOK: /* * App says not ok - this'll let taskman bring up the "are you sure?" * dialog to the user. */ CsrDereferenceThread(pcsrtKill); fRet = FALSE; goto RestoreDesktop; case TSN_USERSAYSCANCEL: /* * User hit cancel on the timeout dialog - so the user really meant * it. Let taskman know everything is ok by returning TRUE. */ CsrDereferenceThread(pcsrtKill); goto RestoreDesktop; } } /* * Kill the application now. If the thread has not been destroyed, * nuke the task. If WowExitTask returns that the thread is not * a WOW task, terminate the process. */ if (!(pcsrtKill->Flags & CSR_THREAD_DESTROYED) && !WowExitTask(pcsrtKill)) { BOOL bDoBlock; /* * Calling ExitProcess() in the app's context will not always work * because the app may have .dll termination deadlocks: so the thread * will hang with the rest of the process. To ensure apps go away, * we terminate the process with NtTerminateProcess(). * * Pass this special value, DBG_TERMINATE_PROCESS, which tells * NtTerminateProcess() to return failure if it can't terminate the * process because the app is being debugged. */ if (ISTS()) { NTSTATUS ExitStatus; HANDLE DebugPort; ExitStatus = DBG_TERMINATE_PROCESS; if (NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessDebugPort, &DebugPort, sizeof(HANDLE), NULL)) && (DebugPort != NULL)) { // Csr is being debugged - go ahead and kill the process ExitStatus = 0; } Status = NtTerminateProcess(pcsrtKill->Process->ProcessHandle, ExitStatus); if (!NT_SUCCESS(Status) && Status != STATUS_PROCESS_IS_TERMINATING) { bDoBlock = TRUE; } else { bDoBlock = FALSE; } } else { Status = NtTerminateProcess(pcsrtKill->Process->ProcessHandle, DBG_TERMINATE_PROCESS); if (!NT_SUCCESS(Status) && Status != STATUS_PROCESS_IS_TERMINATING) { bDoBlock = TRUE; } else { bDoBlock = FALSE; } } if (bDoBlock) { /* * If the app is being debugged, don't close it - because that can * cause a hang to the NtTerminateProcess() call. */ lpszMsg = ServerLoadString(ghModuleWin, STR_APPDEBUGGED, NULL, &fAllocated); if (lpszMsg) { MessageBoxEx(NULL, lpszMsg, NULL, MB_OK | MB_SETFOREGROUND, 0); LocalFree(lpszMsg); } } else { pcsrtKill->Process->Flags |= CSR_PROCESS_TERMINATED; } } CsrDereferenceThread(pcsrtKill); RestoreDesktop: utudi.hThread = NULL; Status = NtUserSetInformationThread(NtCurrentThread(), UserThreadUseDesktop, &utudi, sizeof(utudi)); UserAssert(NT_SUCCESS(Status)); return fRet; } /***************************************************************************\ * WowExitTask * * Calls wow back to make sure a specific task has exited. Returns * TRUE if the thread is a WOW task, FALSE if not. * * 08-02-92 ScottLu Created. \***************************************************************************/ BOOL WowExitTask( PCSR_THREAD pcsrt) { HANDLE ahandle[2]; USERTHREAD_WOW_INFORMATION WowInfo; NTSTATUS Status; ahandle[1] = gheventCancel; /* * Query task id and exit function. */ Status = NtUserQueryInformationThread(pcsrt->ThreadHandle, UserThreadWOWInformation, &WowInfo, sizeof(WowInfo), NULL); if (!NT_SUCCESS(Status)) { return FALSE; } /* * If no task id was returned, it is not a WOW task */ if (WowInfo.hTaskWow == 0) { return FALSE; } /* * Try to make it exit itself. This will work most of the time. * If this doesn't work, terminate this process. */ ahandle[0] = InternalCreateCallbackThread(pcsrt->Process->ProcessHandle, (ULONG_PTR)WowInfo.lpfnWowExitTask, (ULONG_PTR)WowInfo.hTaskWow); if (ahandle[0] == NULL) { NtTerminateProcess(pcsrt->Process->ProcessHandle, 0); pcsrt->Process->Flags |= CSR_PROCESS_TERMINATED; goto Exit; } WaitForMultipleObjects(2, ahandle, FALSE, INFINITE); NtClose(ahandle[0]); Exit: return TRUE; } /***************************************************************************\ * InternalWaitCancel * * Console calls this to wait for objects or shutdown to be cancelled * * 29-Oct-1992 mikeke Created \***************************************************************************/ DWORD InternalWaitCancel( HANDLE handle, DWORD dwMilliseconds) { HANDLE ahandle[2]; ahandle[0] = handle; ahandle[1] = gheventCancel; return WaitForMultipleObjects(2, ahandle, FALSE, dwMilliseconds); } /***************************************************************************\ * InternalCreateCallbackThread * * This routine creates a remote thread in the context of a given process. * It is used to call the console control routine, as well as ExitProcess when * forcing an exit. Returns a thread handle. * * 07-28-92 ScottLu Created. \***************************************************************************/ HANDLE InternalCreateCallbackThread( HANDLE hProcess, ULONG_PTR lpfn, ULONG_PTR dwData) { LONG BasePriority; HANDLE hThread, hToken; PTOKEN_DEFAULT_DACL lpDaclDefault; TOKEN_DEFAULT_DACL daclDefault; ULONG cbDacl; SECURITY_ATTRIBUTES attrThread; SECURITY_DESCRIPTOR sd; DWORD idThread; NTSTATUS Status; hThread = NULL; Status = NtOpenProcessToken(hProcess, TOKEN_QUERY, &hToken); if (!NT_SUCCESS(Status)) { KdPrint(("NtOpenProcessToken failed, status = %x\n", Status)); return NULL; } cbDacl = 0; NtQueryInformationToken(hToken, TokenDefaultDacl, &daclDefault, sizeof(daclDefault), &cbDacl); lpDaclDefault = (PTOKEN_DEFAULT_DACL)LocalAlloc(LMEM_FIXED, cbDacl); if (lpDaclDefault == NULL) { KdPrint(("LocalAlloc failed for lpDaclDefault")); goto closeexit; } Status = NtQueryInformationToken(hToken, TokenDefaultDacl, lpDaclDefault, cbDacl, &cbDacl); if (!NT_SUCCESS(Status)) { KdPrint(("NtQueryInformationToken failed, status = %x\n", Status)); goto freeexit; } if (!NT_SUCCESS(RtlCreateSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION1))) { UserAssert(FALSE); goto freeexit; } RtlSetDaclSecurityDescriptor(&sd, TRUE, lpDaclDefault->DefaultDacl, TRUE); attrThread.nLength = sizeof(attrThread); attrThread.lpSecurityDescriptor = &sd; attrThread.bInheritHandle = FALSE; GetLastError(); hThread = CreateRemoteThread(hProcess, &attrThread, 0L, (LPTHREAD_START_ROUTINE)lpfn, (LPVOID)dwData, 0, &idThread); if (hThread != NULL) { BasePriority = THREAD_PRIORITY_HIGHEST; NtSetInformationThread(hThread, ThreadBasePriority, &BasePriority, sizeof(LONG)); } freeexit: LocalFree((HANDLE)lpDaclDefault); closeexit: NtClose(hToken); return hThread; } ULONG SrvExitWindowsEx( IN OUT PCSR_API_MSG m, IN OUT PCSR_REPLY_STATUS ReplyStatus) { BEGIN_LPC_RECV(EXITWINDOWSEX); Status = _ExitWindowsEx(pcsrt, a->uFlags); a->fSuccess = NT_SUCCESS(Status); END_LPC_RECV(); } ULONG SrvEndTask( IN OUT PCSR_API_MSG m, IN OUT PCSR_REPLY_STATUS ReplyStatus) { PENDTASKMSG petm = (PENDTASKMSG)&m->u.ApiMessageData; PCSR_THREAD pcsrt; PTEB Teb = NtCurrentTeb(); Teb->LastErrorValue = 0; pcsrt = CSR_SERVER_QUERYCLIENTTHREAD(); /* * Don't block the client so it can respond to messages while we * process this request -- we might bring up the End Application * dialog or the hwnd being shutdown might request some user action. */ if (pcsrt->Process->ClientPort != NULL) { m->ReturnValue = STATUS_SUCCESS; petm->dwLastError = 0; petm->fSuccess = TRUE; NtReplyPort(pcsrt->Process->ClientPort, (PPORT_MESSAGE)m); *ReplyStatus = CsrServerReplied; } petm->fSuccess = _EndTask(petm->hwnd, petm->fForce); petm->dwLastError = Teb->LastErrorValue; return STATUS_SUCCESS; } /***************************************************************************\ * IsPrivileged * * Check to see if the client has the specified privileges * * History: * 01-02-91 JimA Created. \***************************************************************************/ BOOL IsPrivileged( PPRIVILEGE_SET ppSet) { HANDLE hToken; NTSTATUS Status; BOOLEAN bResult = FALSE; /* * Impersonate the client. */ if (!CsrImpersonateClient(NULL)) { return FALSE; } /* * Open the client's token. */ Status = NtOpenThreadToken(NtCurrentThread(), TOKEN_QUERY, TRUE, &hToken); if (NT_SUCCESS(Status)) { UNICODE_STRING strSubSystem; RtlInitUnicodeString(&strSubSystem, L"USER32"); /* * Perform the check. */ Status = NtPrivilegeCheck(hToken, ppSet, &bResult); NtPrivilegeObjectAuditAlarm(&strSubSystem, NULL, hToken, 0, ppSet, bResult); NtClose(hToken); if (!bResult) { SetLastError(ERROR_ACCESS_DENIED); } } CsrRevertToSelf(); if (!NT_SUCCESS(Status)) { SetLastError(RtlNtStatusToDosError(Status)); } /* * Return result of privilege check. */ return (BOOL)(bResult && NT_SUCCESS(Status)); } /***************************************************************************\ * SrvRegisterServicesProcess * * Register the services process. * * History: * 05-05-95 BradG Created. \***************************************************************************/ ULONG SrvRegisterServicesProcess( IN OUT PCSR_API_MSG m, IN OUT PCSR_REPLY_STATUS ReplyStatus) { PRIVILEGE_SET psTcb = { 1, PRIVILEGE_SET_ALL_NECESSARY, { SE_TCB_PRIVILEGE, 0 } }; BEGIN_LPC_RECV(REGISTERSERVICESPROCESS); /* * Allow only one services process and then only if it has TCB * privilege. */ EnterCrit(); if ((gdwServicesProcessId != 0) || !IsPrivileged(&psTcb)) { SetLastError(ERROR_ACCESS_DENIED); a->fSuccess = FALSE; } else { gdwServicesProcessId = a->dwProcessId; a->fSuccess = TRUE; } LeaveCrit(); END_LPC_RECV(); } #ifdef FE_IME /***************************************************************************\ * IsImeWindow * * Returns TRUE if it's an IME window. * * History: * 06-05-96 KazuM Created. \***************************************************************************/ BOOL IsImeWindow( HWND hwnd) { int num; WCHAR ClassName[16]; num = GetClassName(hwnd, ClassName, sizeof(ClassName)/sizeof(WCHAR)-1); if (num == 0) { return FALSE; } ClassName[num] = L'\0'; if (wcsncmp(ClassName, L"IME", 3) == 0) { return TRUE; } return (GetClassLong(hwnd, GCL_STYLE) & CS_IME) != 0; } #endif // FE_IME /***************************************************************************\ * CancelExitWindows * * Cancel any logoff/shutdown that is in progress. This is called from _ExitWindowsEx * to cancel an existing exitwindows call if a new call arrives with a different sid. * This call is also used for Personal Terminal Services single session scenatio so * that a force logoff can be initiated once the existing ExitWindows call is * cancelled. * * History: \***************************************************************************/ BOOL CancelExitWindows( VOID) { LARGE_INTEGER li; /* * Another logoff/shutdown is in progress and we need * to cancel it so we can do an override. * * If someone else is trying to cancel shutdown, exit. */ EnterCrit(); li.QuadPart = 0; if (NtWaitForSingleObject(gheventCancel, FALSE, &li) == WAIT_OBJECT_0) { LeaveCrit(); return FALSE; } /* * If no one will set gheventCancelled, don't wait. */ if (gdwThreadEndSession == 0) { LeaveCrit(); return TRUE; } NtClearEvent(gheventCancelled); NtSetEvent(gheventCancel, NULL); LeaveCrit(); /* * Wait for the other guy to be cancelled */ NtWaitForSingleObject(gheventCancelled, FALSE, NULL); EnterCrit(); /* * This signals that we are no longer trying to cancel a * shutdown */ NtClearEvent(gheventCancel); /* * If someone managed to start a shutdown again, exit. * Can this happen? Let's assert to check it out. */ if (gdwThreadEndSession != 0) { UserAssert(gdwThreadEndSession == 0); LeaveCrit(); return FALSE; } LeaveCrit(); return TRUE; } /***************************************************************************\ * TestShutdownPrivilege * * Test to see if the clent has shutdown privilege. * * History: * 02-02-20 qingboz Created. \***************************************************************************/ BOOL TestShutdownPrivilege( HANDLE UserToken) { NTSTATUS Status; LUID LuidPrivilege = RtlConvertLongToLuid(SE_SHUTDOWN_PRIVILEGE); LUID TokenPrivilege; ULONG BytesRequired; ULONG i; BOOL bHasPrivilege = FALSE; BOOL bNetWork = FALSE; PSID NetworkSid = NULL; PTOKEN_PRIVILEGES Privileges = NULL; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; Status = RtlAllocateAndInitializeSid(&NtAuthority, 1, SECURITY_NETWORK_RID, 0, 0, 0, 0, 0, 0, 0, &NetworkSid ); if (!NT_SUCCESS(Status)) { NetworkSid = NULL; goto Cleanup; } if (CheckTokenMembership(UserToken, NetworkSid, &bNetWork)) { if (bNetWork) { LuidPrivilege = RtlConvertLongToLuid(SE_REMOTE_SHUTDOWN_PRIVILEGE); } } else { goto Cleanup; } Status = NtQueryInformationToken( UserToken, TokenPrivileges, NULL, 0, &BytesRequired ); if (Status != STATUS_BUFFER_TOO_SMALL) { goto Cleanup; } Privileges = (PTOKEN_PRIVILEGES)LocalAlloc(LPTR, BytesRequired); if (!Privileges) { goto Cleanup; } Status = NtQueryInformationToken( UserToken, TokenPrivileges, Privileges, BytesRequired, &BytesRequired ); if (!NT_SUCCESS(Status)) { goto Cleanup; } for (i=0; iPrivilegeCount; i++) { TokenPrivilege = *((LUID UNALIGNED *) &Privileges->Privileges[i].Luid); if (RtlEqualLuid(&TokenPrivilege, &LuidPrivilege)) { bHasPrivilege = TRUE; break; } } Cleanup: if (NetworkSid) { RtlFreeSid(NetworkSid); } if (Privileges) { LocalFree(Privileges); } return bHasPrivilege; } /***************************************************************************\ * GetUserSid * * Allocs space for the user sid, fills it in and returns a pointer. * * Returns pointer to sid or NULL on failure. * * History: * 02-02-20 qingboz Created. \***************************************************************************/ PSID GetUserSid( HANDLE UserToken) { PTOKEN_USER pUser; PSID pSid; DWORD BytesRequired = 200; NTSTATUS status; if (UserToken == NULL) { return NULL; } // // Allocate space for the user info // pUser = (PTOKEN_USER)LocalAlloc(LMEM_FIXED, BytesRequired); if (pUser == NULL) { return NULL; } // // Read in the UserInfo // status = NtQueryInformationToken( UserToken, TokenUser, pUser, BytesRequired, &BytesRequired ); if (status == STATUS_BUFFER_TOO_SMALL) { // // Allocate a bigger buffer and try again. // PTOKEN_USER pTemp = pUser; pUser = LocalReAlloc(pUser, BytesRequired, LMEM_MOVEABLE); if (pUser == NULL) { LocalFree( pTemp ); return NULL; } status = NtQueryInformationToken( UserToken, TokenUser, pUser, BytesRequired, &BytesRequired ); } if (!NT_SUCCESS(status)) { LocalFree(pUser); return NULL; } BytesRequired = RtlLengthSid(pUser->User.Sid); pSid = LocalAlloc(LMEM_FIXED, BytesRequired); if (pSid == NULL) { LocalFree(pUser); return NULL; } status = RtlCopySid(BytesRequired, pSid, pUser->User.Sid); LocalFree(pUser); if (!NT_SUCCESS(status)) { LocalFree(pSid); pSid = NULL; } return pSid; } /***************************************************************************\ * SrvRecordShutdownReason * * Process RecordShutdownReason request from clients. This will log an event * in the event log, and optionally take a system snapshot. * * History: * 01-12-12 qingboz Created. * 02-02-20 qingboz Added privilege check and moved some stuff from * client to server (such as user name and SID). \***************************************************************************/ ULONG SrvRecordShutdownReason( IN OUT PCSR_API_MSG m, IN OUT PCSR_REPLY_STATUS ReplyStatus) { PRECORDSHUTDOWNREASONMSG prm = (PRECORDSHUTDOWNREASONMSG)&m->u.ApiMessageData; LPWSTR lpStrings[8]; WORD wEventType; WORD wStringCnt = 0; WCHAR szReasonCode[32]; BOOL bRet = FALSE; BOOL bReportEvent = FALSE; BOOL bIsCrsssOrWinlogon = FALSE; BOOL bIsCancelEvent = FALSE; struct { DWORD Reason; PWCHAR SnapShotBuf; } SnapShot; UINT SnapShotSize = 0; HANDLE hEventLog = NULL; LPWSTR lpszUserName = NULL; LPWSTR lpszUserDomain = NULL; LPWSTR lpszComputerName = NULL; LPWSTR lpszReasonTitle = NULL; LPWSTR lpszComment = NULL; DWORD dwComputerNameLen = MAX_COMPUTERNAME_LENGTH + 1; DWORD dwUserNameLen = MAX_PATH + 1; DWORD dwUserDomainLen = MAX_PATH + 1; DWORD dwReasonTitleLen = MAX_REASON_NAME_LEN; PSID psid; SID_NAME_USE eUse; HANDLE hToken; UNREFERENCED_PARAMETER(ReplyStatus); /* * Need to impersonate in order to check privilege and get user name. */ if (!CsrImpersonateClient(NULL)) { return FALSE; } if (!NT_SUCCESS(NtOpenThreadToken(NtCurrentThread(), TOKEN_QUERY, (BOOLEAN)TRUE, &hToken))) { CsrRevertToSelf(); return FALSE; } /* * Find out whether this is csrss/winlogon calling us. */ if (m->h.ClientId.UniqueProcess == NtCurrentTeb()->ClientId.UniqueProcess || HandleToUlong(m->h.ClientId.UniqueProcess) == gIdLogon) { bIsCrsssOrWinlogon = TRUE; } /* * We just need the token, so we can revert now. */ CsrRevertToSelf(); // Check for privilege. if (!TestShutdownPrivilege(hToken)) { NtClose(hToken); return FALSE; } // Get SID for reporting event. psid = GetUserSid(hToken); NtClose(hToken); // done with the token. if (!psid) { return FALSE; } SnapShot.SnapShotBuf = NULL; // so cleanup won't free an uninitialized buffer when memory alloc fails later. /* * For cancel event we don't need capature buffer, so we only validate * the buffer for non-cancelling events. */ if (prm->dwEventType == SR_EVENT_EXITWINDOWS && prm->fShutdownCancelled || prm->dwEventType == SR_EVENT_INITIATE_CLEAN_ABORT) { bIsCancelEvent = TRUE; } else { if (!m->CaptureBuffer) { goto Cleanup; } if (!CsrValidateMessageBuffer(m, &prm->psr, sizeof(SHUTDOWN_REASON), sizeof(BYTE))) { goto Cleanup; } if (prm->dwProcessNameLen == 0 || prm->dwProcessNameLen - 1 > MAX_PATH || !CsrValidateMessageBuffer(m, &prm->pwchProcessName, prm->dwProcessNameLen, sizeof(WCHAR))) { goto Cleanup; } if (prm->dwShutdownTypeLen == 0 || prm->dwShutdownTypeLen > SHUTDOWN_TYPE_LEN || !CsrValidateMessageBuffer(m, &prm->pwchShutdownType, prm->dwShutdownTypeLen, sizeof(WCHAR))) { goto Cleanup; } if (prm->dwCommentLen && (prm->dwCommentLen > MAX_REASON_COMMENT_LEN || !CsrValidateMessageBuffer(m, &prm->pwchComment, prm->dwCommentLen, sizeof(WCHAR)))) { goto Cleanup; } SnapShot.Reason = prm->psr->dwReasonCode; prm->pwchProcessName[prm->dwProcessNameLen - 1] = 0; prm->pwchShutdownType[prm->dwShutdownTypeLen - 1] = 0; if (prm->dwCommentLen) { prm->pwchComment[prm->dwCommentLen - 1] = 0; } } /* * This function could be called multiple times during a single shutdown, * we need to make sure that we don't log two shutdown events. We also * need to make sure max one dirty event per reboot. */ if (prm->dwEventType == SR_EVENT_DIRTY) { if (InterlockedCompareExchange((volatile LONG*)&g_DirtyShutdownMax, 0L, 1L)) { bReportEvent = TRUE; } } else { if (prm->dwEventType == SR_EVENT_EXITWINDOWS && prm->fShutdownCancelled || prm->dwEventType == SR_EVENT_INITIATE_CLEAN_ABORT) { /* * If csrss or winlogon issue the abort we will log the event no matter what. */ if (bIsCrsssOrWinlogon) { bReportEvent = TRUE; InterlockedCompareExchange((volatile LONG*)&g_ShutdownState, 0L, 1L); } else if (InterlockedCompareExchange((volatile LONG*)&g_ShutdownState, 0L, 1L)) { bReportEvent = TRUE; } } else { if (!InterlockedCompareExchange((volatile LONG*)&g_ShutdownState, 1L, 0L)) { bReportEvent = TRUE; } } } if (!bReportEvent) { return TRUE; } // Allocate buffers after validations. lpszUserName = (LPWSTR)LocalAlloc(LPTR, dwUserNameLen * sizeof(WCHAR)); lpszUserDomain = (LPWSTR)LocalAlloc(LPTR, dwUserDomainLen * sizeof(WCHAR)); lpszComputerName = (LPWSTR)LocalAlloc(LPTR, dwComputerNameLen * sizeof(WCHAR)); lpszReasonTitle = (LPWSTR)LocalAlloc(LPTR, dwReasonTitleLen * sizeof(WCHAR)); if (!lpszUserName || !lpszUserDomain || !lpszComputerName || !lpszReasonTitle) { goto Cleanup; } // Get the comment. if (!bIsCancelEvent && prm->dwCommentLen > 0) { lpszComment = LocalAlloc(LPTR, prm->dwCommentLen * sizeof(WCHAR)); if (!lpszComment) { goto Cleanup; } wcsncpy(lpszComment, prm->pwchComment, prm->dwCommentLen); lpszComment[prm->dwCommentLen-1] = 0; } // Get User name. if (!LookupAccountSidW(NULL, psid, lpszUserName, &dwUserNameLen, lpszUserDomain, &dwUserDomainLen, &eUse)) { // // log an event w/o user info, because shutdown might be initiated // when lsass was terminated unexpected. // lpszUserName[0] = lpszUserDomain[0] = 0; } else { lpszUserName[MAX_PATH] = 0; lpszUserDomain[MAX_PATH] = 0; // We need to pack into a buffer of MAX_PATH + 1 in the form L"domain\\username" if (wcslen(lpszUserDomain) + wcslen(lpszUserName) > MAX_PATH - 1) { goto Cleanup; } if (wcslen(lpszUserDomain) > 0) { wcscat(lpszUserDomain, L"\\"); } wcscat(lpszUserDomain, lpszUserName); } // Get Computer Name. if (!GetComputerNameW(lpszComputerName, &dwComputerNameLen)) { // // log an event w/o user info, because shutdown might be initiated // when some critical process/service got terminated unexpected. // lpszComputerName[0]=0; } else { lpszComputerName[MAX_COMPUTERNAME_LENGTH] = 0; } // Get reason title. if (!GetReasonTitleFromReasonCode(prm->psr->dwReasonCode, lpszReasonTitle, dwReasonTitleLen)) { goto Cleanup; } lpszReasonTitle[MAX_REASON_NAME_LEN-1] = 0; // Get the reason code string. _snwprintf(szReasonCode, ARRAY_SIZE(szReasonCode), L"0x%x", prm->psr->dwReasonCode); szReasonCode[ARRAY_SIZE(szReasonCode)-1] = 0; switch (prm->dwEventType) { LPWSTR lpCommentStart; LPWSTR lpCommentEnd; INT i; case SR_EVENT_EXITWINDOWS: if (prm->fShutdownCancelled) { wEventType = EVENTLOG_WARNING_TYPE; lpStrings[wStringCnt++] = lpszComputerName; lpStrings[wStringCnt++] = lpszUserDomain; } else { wEventType = EVENTLOG_INFORMATION_TYPE; lpStrings[wStringCnt++] = prm->pwchProcessName; lpStrings[wStringCnt++] = lpszComputerName; lpStrings[wStringCnt++] = lpszReasonTitle; lpStrings[wStringCnt++] = szReasonCode; lpStrings[wStringCnt++] = prm->pwchShutdownType; lpStrings[wStringCnt++] = lpszComment; lpStrings[wStringCnt++] = lpszUserDomain; } break; case SR_EVENT_INITIATE_CLEAN: wEventType = EVENTLOG_INFORMATION_TYPE; lpStrings[wStringCnt++] = prm->pwchProcessName; lpStrings[wStringCnt++] = lpszComputerName; lpStrings[wStringCnt++] = lpszReasonTitle; lpStrings[wStringCnt++] = szReasonCode; lpStrings[wStringCnt++] = prm->pwchShutdownType; lpStrings[wStringCnt++] = lpszComment; lpStrings[wStringCnt++] = lpszUserDomain; break; case SR_EVENT_INITIATE_CLEAN_ABORT: wEventType = EVENTLOG_WARNING_TYPE; lpStrings[wStringCnt++] = lpszComputerName; lpStrings[wStringCnt++] = lpszUserDomain; break; case SR_EVENT_DIRTY: lpCommentStart = lpszComment; lpCommentEnd = lpCommentStart + (lpCommentStart ? wcslen(lpCommentStart) : 0); wEventType = EVENTLOG_WARNING_TYPE; lpStrings[wStringCnt++] = lpszReasonTitle; lpStrings[wStringCnt++] = szReasonCode; /* * In case of dirty event, the comment is in the following format: * L"nnn\nccccccccnnn\ncccccccnnn\ncccccc" * Basically it is three strings with each one headed by its length * and a newline. */ for (i = 0; i < 3; i++) { INT cnt; if (lpCommentStart >= lpCommentEnd) { break; } cnt = _wtoi(lpCommentStart); *lpCommentStart++ = L'\0'; while (lpCommentStart < lpCommentEnd && *lpCommentStart != L'\n') { lpCommentStart++; } if (*lpCommentStart) { lpStrings[wStringCnt++] = ++lpCommentStart; } else { lpStrings[wStringCnt++] = NULL; } lpCommentStart += cnt; } for (; i < 3; i++) { lpStrings[wStringCnt++] = NULL; } lpStrings[wStringCnt++] = lpszUserDomain; break; default: RIPERR1(ERROR_INVALID_PARAMETER, RIP_WARNING, "Unknown prm->dwEventType 0x%x", prm->dwEventType); goto Cleanup; } // First see if we need to take a snapshot. if (prm->dwEventType == SR_EVENT_INITIATE_CLEAN) { CONST WCHAR szSnapShotVal[] = L"Snapshot"; CONST ULONG ulMaxSnapShotSize = 2048; SNAPSHOTFUNC pSnapShotProc; DWORD DoSnapShotValue = SNAPSHOT_POLICY_UNPLANNED; HKEY hKey = NULL; // First try to read the policy. if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_RELIABILITY_POLICY, 0, KEY_QUERY_VALUE, &hKey)) { DWORD dwSize = sizeof(DWORD); DWORD dwType; if (ERROR_SUCCESS == RegQueryValueEx(hKey, szSnapShotVal, NULL, &dwType, (UCHAR*)&DoSnapShotValue, &dwSize)) { if (dwType != REG_DWORD) { DoSnapShotValue = SNAPSHOT_POLICY_UNPLANNED; } else if (DoSnapShotValue == 0) { DoSnapShotValue = SNAPSHOT_POLICY_NEVER; } else { DoSnapShotValue = SNAPSHOT_POLICY_UNPLANNED; } } RegCloseKey(hKey); } // else SNAPSHOT_POLICY_UNPLANNED will be used. /* * SNAPSHOT_POLICY_ALWAYS: we will take a snapshot when we get here. * SNAPSHOT_POLICY_NEVER: we wont event proceed into the if (no snapshot) * SNAPSHOT_POLICY_UNPLANNED: Snapshot only if the reason is unplanned. */ if (DoSnapShotValue == SNAPSHOT_POLICY_ALWAYS || (DoSnapShotValue == SNAPSHOT_POLICY_UNPLANNED && !(prm->psr->dwReasonCode & SHTDN_REASON_FLAG_PLANNED))) { SnapShotSize = ulMaxSnapShotSize/sizeof(WCHAR); SnapShot.SnapShotBuf = LocalAlloc(LPTR, ulMaxSnapShotSize); if (SnapShot.SnapShotBuf == NULL) { goto Cleanup; } SnapShot.SnapShotBuf[0] = 0; /* * Snapshot.dll is loaded once and will be unloaded when system * shutdowns down. If we fail in any way make sure we can try it * again next time this function gets called. */ if (InterlockedCompareExchange(&g_SnapShot, 0L, 1L)) { g_SnapShotDllHandle = LoadLibrary(L"snapshot.dll"); if (!g_SnapShotDllHandle) { InterlockedExchange(&g_SnapShot, 1L); } } if (g_SnapShotDllHandle) { pSnapShotProc = (SNAPSHOTFUNC)GetProcAddress(g_SnapShotDllHandle, "LogSystemSnapshot"); if (pSnapShotProc) { (*pSnapShotProc)(wStringCnt, lpStrings, &SnapShotSize, SnapShot.SnapShotBuf); } else { SnapShot.SnapShotBuf[0] = L'\0'; } } else { SnapShot.SnapShotBuf[0] = L'\0'; } SnapShot.SnapShotBuf[ulMaxSnapShotSize/sizeof(WCHAR)-1] = L'\0'; SnapShotSize = wcslen(SnapShot.SnapShotBuf); if (SnapShotSize > 0 ) { if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_RELIABILITY, 0, KEY_ALL_ACCESS, &hKey)) { RegSetValueEx(hKey, REGSTR_VAL_SHUTDOWN_STATE_SNAPSHOT, 0, REG_SZ, (LPBYTE)SnapShot.SnapShotBuf, SnapShotSize * sizeof(WCHAR)); RegCloseKey(hKey); } } } } /* * If the client is server, we need to revert so RegisterEventSourceW() can * succeed if the user is not in admin group. */ if (m->h.ClientId.UniqueProcess == NtCurrentTeb()->ClientId.UniqueProcess) { CsrRevertToSelf(); } hEventLog = RegisterEventSourceW(NULL, L"USER32"); if (m->h.ClientId.UniqueProcess == NtCurrentTeb()->ClientId.UniqueProcess) { CsrImpersonateClient(NULL); } if (!hEventLog) { goto Cleanup; } /* * We are required to log the snapshot info (if a snopshot is taken) * into the data part, so we need to realloc the snapshot buf in order * to insert the reason code. */ if (!SnapShot.SnapShotBuf || wcslen(SnapShot.SnapShotBuf) == 0) { // no snapshot, so just report reason. bRet = ReportEventW(hEventLog, wEventType, 0, prm->dwEventID, psid, wStringCnt, sizeof(DWORD), lpStrings, &SnapShot); } else { /* need to repack */ DWORD dwNewBuf = (lstrlenW(SnapShot.SnapShotBuf)+1) * sizeof(WCHAR) + sizeof(DWORD); PWCHAR pBuf = LocalAlloc(LPTR, dwNewBuf); if (pBuf) { *((DWORD*)pBuf) = SnapShot.Reason; swprintf(&pBuf[2], L"%s", SnapShot.SnapShotBuf); LocalFree(SnapShot.SnapShotBuf); SnapShot.SnapShotBuf = pBuf; bRet = ReportEventW(hEventLog, wEventType, 0, prm->dwEventID, psid, wStringCnt, dwNewBuf, lpStrings, pBuf); } } DeregisterEventSource(hEventLog); Cleanup: LocalFree(SnapShot.SnapShotBuf); LocalFree(psid); LocalFree(lpszUserDomain); LocalFree(lpszUserName); LocalFree(lpszComputerName); LocalFree(lpszReasonTitle); LocalFree(lpszComment); return bRet; }