You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1138 lines
36 KiB
1138 lines
36 KiB
/******************************************************************************
|
|
|
|
Copyright (c) 2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
erwait.cpp
|
|
|
|
Revision History:
|
|
derekm 02/28/2001 created
|
|
|
|
******************************************************************************/
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// tracing
|
|
|
|
#ifdef THIS_FILE
|
|
#undef THIS_FILE
|
|
#endif
|
|
static char __szTraceSourceFile[] = __FILE__;
|
|
#define THIS_FILE __szTraceSourceFile
|
|
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include <stdio.h>
|
|
#include <pfrcfg.h>
|
|
|
|
|
|
// this is a private NT function that doesn't appear to be defined anywhere
|
|
// public, so I'm including it here
|
|
extern "C"
|
|
{
|
|
HANDLE GetCurrentUserTokenW( WCHAR Winsta[], DWORD DesiredAccess);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// utility functions
|
|
|
|
// ***************************************************************************
|
|
BOOL CreateQueueDir(void)
|
|
{
|
|
SECURITY_DESCRIPTOR sd;
|
|
SECURITY_ATTRIBUTES sa;
|
|
WCHAR wszDir[MAX_PATH], *pwszDir = NULL;
|
|
DWORD dw, cchNeed, cch;
|
|
BOOL fRet = FALSE;
|
|
|
|
USE_TRACING("CreateQueueDir");
|
|
|
|
ZeroMemory(&sa, sizeof(sa));
|
|
ZeroMemory(&sd, sizeof(sd));
|
|
|
|
#define DIR_ACCESS_ALL GENERIC_ALL | DELETE | READ_CONTROL | SYNCHRONIZE | SPECIFIC_RIGHTS_ALL
|
|
if (AllocSD(&sd, DIR_ACCESS_ALL, DIR_ACCESS_ALL, 0) == FALSE)
|
|
{
|
|
DBG_MSG("AllocSD dies");
|
|
goto done;
|
|
}
|
|
|
|
sa.nLength = sizeof(sa);
|
|
sa.bInheritHandle = FALSE;
|
|
sa.lpSecurityDescriptor = &sd;
|
|
|
|
cch = GetSystemWindowsDirectoryW(wszDir, sizeofSTRW(wszDir));
|
|
if (cch == 0)
|
|
goto done;
|
|
|
|
cchNeed = cch + sizeofSTRW(c_wszQSubdir) + 2;
|
|
if (cchNeed > sizeofSTRW(wszDir))
|
|
{
|
|
__try { pwszDir = (WCHAR *)_alloca(cchNeed * sizeof(WCHAR)); }
|
|
__except(EXCEPTION_STACK_OVERFLOW == GetExceptionCode() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
|
|
{
|
|
pwszDir = NULL;
|
|
}
|
|
if (pwszDir == NULL)
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
goto done;
|
|
}
|
|
|
|
if (cch > sizeofSTRW(wszDir))
|
|
cch = GetSystemWindowsDirectoryW(pwszDir, cchNeed);
|
|
else
|
|
StringCchCopyW(pwszDir, cchNeed, wszDir);
|
|
}
|
|
else
|
|
{
|
|
pwszDir = wszDir;
|
|
cchNeed = sizeofSTRW(wszDir);
|
|
}
|
|
|
|
// if the length of the system directory is 3, then %windir% is in the form
|
|
// of X:\, so clip off the backslash so we don't have to special case
|
|
// it below...
|
|
if (cch == 3)
|
|
*(pwszDir + 2) = L'\0';
|
|
|
|
StringCchCatW(pwszDir, cchNeed, L"\\PCHealth");
|
|
|
|
if (CreateDirectoryW(pwszDir, NULL) == FALSE &&
|
|
GetLastError() != ERROR_ALREADY_EXISTS)
|
|
goto done;
|
|
|
|
StringCchCatW(pwszDir, cchNeed, L"\\ErrorRep");
|
|
|
|
if (CreateDirectoryW(pwszDir, NULL) == FALSE &&
|
|
GetLastError() != ERROR_ALREADY_EXISTS)
|
|
goto done;
|
|
|
|
StringCchCatW(pwszDir, cchNeed, L"\\UserDumps");
|
|
|
|
if (CreateDirectoryW(pwszDir, &sa) == FALSE &&
|
|
GetLastError() != ERROR_ALREADY_EXISTS)
|
|
goto done;
|
|
|
|
// if the directory is created, then we need to write the security
|
|
// descriptor to it cuz we want to make sure that no inappropriate
|
|
// people can access it.
|
|
if (GetLastError() != ERROR_ALREADY_EXISTS)
|
|
{
|
|
SID_IDENTIFIER_AUTHORITY siaNT = SECURITY_NT_AUTHORITY;
|
|
PACL pacl = NULL;
|
|
PSID psidLS = NULL;
|
|
BOOL fDef, fACL;
|
|
|
|
if (GetSecurityDescriptorDacl(&sd, &fACL, &pacl, &fDef) == FALSE)
|
|
goto done;
|
|
|
|
if (AllocateAndInitializeSid(&siaNT, 1, SECURITY_LOCAL_SYSTEM_RID, 0,
|
|
0, 0, 0, 0, 0, 0, &psidLS) == FALSE)
|
|
goto done;
|
|
|
|
dw = SetNamedSecurityInfoW(pwszDir, SE_FILE_OBJECT,
|
|
DACL_SECURITY_INFORMATION |
|
|
OWNER_SECURITY_INFORMATION |
|
|
PROTECTED_DACL_SECURITY_INFORMATION,
|
|
psidLS, NULL, pacl, NULL);
|
|
FreeSid(psidLS);
|
|
if (dw != ERROR_SUCCESS)
|
|
{
|
|
SetLastError(dw);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
fRet = TRUE;
|
|
|
|
done:
|
|
return fRet;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
HMODULE LoadERDll(void)
|
|
{
|
|
HMODULE hmod = NULL;
|
|
WCHAR wszMod[MAX_PATH], *pwszMod;
|
|
DWORD cch, cchNeed;
|
|
|
|
cch = GetSystemDirectoryW(wszMod, sizeofSTRW(wszMod));
|
|
if (cch == 0)
|
|
goto done;
|
|
|
|
// the '14' is for the
|
|
cchNeed = cch + 14;
|
|
|
|
if (cchNeed > sizeofSTRW(wszMod))
|
|
{
|
|
__try { pwszMod = (WCHAR *)_alloca(cchNeed * sizeof(WCHAR)); }
|
|
__except(EXCEPTION_STACK_OVERFLOW == GetExceptionCode() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
|
|
{
|
|
pwszMod = NULL;
|
|
}
|
|
if (pwszMod == NULL)
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
goto done;
|
|
}
|
|
|
|
if (cch > sizeofSTRW(wszMod))
|
|
cch = GetSystemDirectoryW(pwszMod, cchNeed);
|
|
else
|
|
StringCchCopyW(pwszMod, cchNeed, wszMod);
|
|
}
|
|
else
|
|
{
|
|
pwszMod = wszMod;
|
|
cchNeed = sizeofSTRW(wszMod);
|
|
}
|
|
|
|
// if the length of the system directory is 3, then %windir% is in the form
|
|
// of X:\, so clip off the backslash so we don't have to special case
|
|
// it below...
|
|
if (cch == 3)
|
|
*(pwszMod + 2) = L'\0';
|
|
|
|
StringCchCatW(pwszMod, cchNeed, L"\\faultrep.dll");
|
|
|
|
hmod = LoadLibraryExW(wszMod, NULL, 0);
|
|
|
|
done:
|
|
return hmod;
|
|
}
|
|
|
|
// **************************************************************************
|
|
BOOL FindAdminSession(DWORD *pdwSession, HANDLE *phToken)
|
|
{
|
|
USE_TRACING("FindAdminSession");
|
|
|
|
WINSTATIONUSERTOKEN wsut;
|
|
LOGONIDW *rgSesn = NULL;
|
|
DWORD i, cSesn, cb, dw;
|
|
BOOL fRet = FALSE;
|
|
HRESULT hr = NOERROR;
|
|
|
|
ZeroMemory(&wsut, sizeof(wsut));
|
|
|
|
VALIDATEPARM(hr, (pdwSession == NULL || phToken == NULL));
|
|
if (FAILED(hr))
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
goto done;
|
|
}
|
|
|
|
*pdwSession = (DWORD)-1;
|
|
*phToken = NULL;
|
|
|
|
fRet = WinStationEnumerateW(SERVERNAME_CURRENT, &rgSesn, &cSesn);
|
|
if (fRet == FALSE)
|
|
goto done;
|
|
|
|
wsut.ProcessId = LongToHandle(GetCurrentProcessId());
|
|
wsut.ThreadId = LongToHandle(GetCurrentThreadId());
|
|
|
|
for(i = 0; i < cSesn; i++)
|
|
{
|
|
if (rgSesn[i].State != State_Active)
|
|
continue;
|
|
|
|
fRet = WinStationQueryInformationW(SERVERNAME_CURRENT,
|
|
rgSesn[i].SessionId,
|
|
WinStationUserToken, &wsut,
|
|
sizeof(wsut), &cb);
|
|
if (fRet == FALSE)
|
|
continue;
|
|
|
|
if (wsut.UserToken != NULL)
|
|
{
|
|
if (IsUserAnAdmin(wsut.UserToken))
|
|
break;
|
|
|
|
CloseHandle(wsut.UserToken);
|
|
wsut.UserToken = NULL;
|
|
}
|
|
}
|
|
|
|
if (i < cSesn)
|
|
{
|
|
fRet = TRUE;
|
|
*pdwSession = rgSesn[i].SessionId;
|
|
*phToken = wsut.UserToken;
|
|
}
|
|
else
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
|
|
done:
|
|
dw = GetLastError();
|
|
if (rgSesn != NULL)
|
|
WinStationFreeMemory(rgSesn);
|
|
SetLastError(dw);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
BOOL GetInteractiveUsersToken(HANDLE *phTokenUser)
|
|
{
|
|
HWINSTA hwinsta = NULL;
|
|
DWORD cbNeed;
|
|
BOOL fRet = FALSE;
|
|
PSID psid = NULL;
|
|
HRESULT hr = NOERROR;
|
|
|
|
USE_TRACING("GetInteractiveUsersToken");
|
|
|
|
VALIDATEPARM(hr, (phTokenUser == NULL));
|
|
if (FAILED(hr))
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
goto done;
|
|
}
|
|
|
|
*phTokenUser = NULL;
|
|
|
|
hwinsta = OpenWindowStationW(L"WinSta0", FALSE, MAXIMUM_ALLOWED);
|
|
if (hwinsta == NULL)
|
|
goto done;
|
|
|
|
// if this function returns 0 for cbNeed, there is no one logged in. Also,
|
|
// it should never return TRUE with these parameters. If it does,
|
|
// something's wrong...
|
|
fRet = GetUserObjectInformationW(hwinsta, UOI_USER_SID, NULL, 0, &cbNeed);
|
|
if (fRet || cbNeed == 0)
|
|
{
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
*phTokenUser = GetCurrentUserTokenW(L"WinSta0", TOKEN_ALL_ACCESS);
|
|
fRet = (*phTokenUser != NULL);
|
|
|
|
done:
|
|
if (hwinsta != NULL)
|
|
CloseWindowStation(hwinsta);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
BOOL ValidateUserAccessToProcess(HANDLE hPipe, HANDLE hProcRemote)
|
|
{
|
|
PSECURITY_DESCRIPTOR psd = NULL;
|
|
GENERIC_MAPPING gm;
|
|
HRESULT hr = NOERROR;
|
|
PRIVILEGE_SET *pPS;
|
|
ACCESS_MASK amReq;
|
|
HANDLE hTokenImp = NULL;
|
|
DWORD dwErr = 0, cbPS, dwGranted;
|
|
PACL pDACL = NULL;
|
|
PACL pSACL = NULL;
|
|
PSID psidOwner = NULL;
|
|
PSID psidGroup = NULL;
|
|
BYTE rgBuf[sizeof(PRIVILEGE_SET) + 3 * sizeof(LUID_AND_ATTRIBUTES)];
|
|
BOOL fRet = FALSE, fStatus = FALSE;
|
|
|
|
USE_TRACING("ValidateUserAccessToProcess");
|
|
|
|
VALIDATEPARM(hr, (hPipe == NULL || hProcRemote == NULL));
|
|
if (FAILED(hr))
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
goto done;
|
|
}
|
|
|
|
ZeroMemory(&gm, sizeof(gm));
|
|
gm.GenericAll = GENERIC_ALL;
|
|
gm.GenericExecute = GENERIC_EXECUTE;
|
|
gm.GenericRead = GENERIC_READ;
|
|
gm.GenericWrite = GENERIC_WRITE;
|
|
pPS = (PRIVILEGE_SET *)rgBuf;
|
|
cbPS = sizeof(rgBuf);
|
|
|
|
// get the SD for the remote process
|
|
dwErr = GetSecurityInfo(hProcRemote, SE_KERNEL_OBJECT,
|
|
DACL_SECURITY_INFORMATION |
|
|
GROUP_SECURITY_INFORMATION |
|
|
OWNER_SECURITY_INFORMATION |
|
|
SACL_SECURITY_INFORMATION, &psidOwner, &psidGroup,
|
|
&pDACL, &pSACL, &psd);
|
|
if (dwErr != ERROR_SUCCESS)
|
|
{
|
|
SetLastError(dwErr);
|
|
goto done;
|
|
}
|
|
|
|
// get the client's token
|
|
fRet = ImpersonateNamedPipeClient(hPipe);
|
|
if (fRet == FALSE)
|
|
goto done;
|
|
|
|
fRet = OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hTokenImp);
|
|
|
|
// don't need to be the other user anymore, so go back to being LocalSystem
|
|
RevertToSelf();
|
|
|
|
if (fRet == FALSE)
|
|
goto done;
|
|
|
|
amReq = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;
|
|
fRet = AccessCheck(psd, hTokenImp, amReq, &gm, pPS, &cbPS, &dwGranted,
|
|
&fStatus);
|
|
if (fRet == FALSE)
|
|
goto done;
|
|
|
|
if (fStatus == FALSE || (dwGranted & amReq) != amReq)
|
|
{
|
|
fRet = FALSE;
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
goto done;
|
|
}
|
|
|
|
fRet = TRUE;
|
|
|
|
done:
|
|
if (hTokenImp != NULL)
|
|
CloseHandle(hTokenImp);
|
|
if (psd != NULL)
|
|
LocalFree(psd);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// remote execution functions
|
|
|
|
// ***************************************************************************
|
|
BOOL ProcessFaultRequest(HANDLE hPipe, PBYTE pBuf, DWORD *pcbBuf)
|
|
{
|
|
SPCHExecServFaultRequest *pesreq = (SPCHExecServFaultRequest *)pBuf;
|
|
SPCHExecServFaultReply esrep, *pesrep;
|
|
OSVERSIONINFOEXW osvi;
|
|
SFaultRepManifest frm;
|
|
EFaultRepRetVal frrv = frrvErrNoDW;
|
|
HMODULE hmodFR = NULL;
|
|
LPWSTR wszTempDir = NULL, wszDumpFileName = NULL;
|
|
HANDLE hprocRemote = NULL;
|
|
HANDLE hTokenUser = NULL;
|
|
LPVOID pvEnv = NULL;
|
|
WCHAR *pwszDump = NULL;
|
|
WCHAR wszTemp[MAX_PATH];
|
|
DWORD cb, dw, dwSessionId;
|
|
WCHAR wch = L'\0', *pwch, *pwszFile;
|
|
BOOL fRet = FALSE, fUseManifest = FALSE;
|
|
BOOL fQueue = FALSE, fTS = FALSE;
|
|
HRESULT hr = NOERROR;
|
|
BOOL fLeaveTempDir = FALSE;
|
|
|
|
USE_TRACING("ProcessFaultRequest");
|
|
|
|
ZeroMemory(&esrep, sizeof(esrep));
|
|
ZeroMemory(&frm, sizeof(frm));
|
|
|
|
pwszDump = &wch;
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
esrep.cb = sizeof(esrep);
|
|
esrep.ess = essErr;
|
|
|
|
// validate parameters
|
|
VALIDATEPARM(hr, (pesreq->cbTotal > *pcbBuf ||
|
|
pesreq->cbTotal < sizeof(SPCHExecServFaultRequest) ||
|
|
pesreq->cbESR != sizeof(SPCHExecServFaultRequest) ||
|
|
pesreq->thidFault == 0 ||
|
|
pesreq->wszExe == 0 ||
|
|
pesreq->wszExe >= *pcbBuf));
|
|
if (FAILED(hr))
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
goto done;
|
|
}
|
|
|
|
// check and make sure that there is a NULL terminator between the
|
|
// start of the string & the end of the buffer
|
|
pwszFile = (LPWSTR)(pBuf + *pcbBuf);
|
|
frm.wszExe = (LPWSTR)(pBuf + (DWORD)pesreq->wszExe);
|
|
for (pwch = frm.wszExe; pwch < pwszFile && *pwch != L'\0'; pwch++);
|
|
VALIDATEPARM(hr, (pwch >= pwszFile));
|
|
if (FAILED(hr))
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
goto done;
|
|
}
|
|
|
|
// need the filename for comparison later...
|
|
for (pwszFile = pwch;
|
|
pwszFile > frm.wszExe && *pwszFile != L'\\';
|
|
pwszFile--);
|
|
if (*pwszFile == L'\\')
|
|
pwszFile++;
|
|
|
|
frm.pidReqProcess = pesreq->pidReqProcess;
|
|
frm.pvFaultAddr = pesreq->pvFaultAddr;
|
|
frm.thidFault = pesreq->thidFault;
|
|
frm.fIs64bit = pesreq->fIs64bit;
|
|
frm.pEP = pesreq->pEP;
|
|
|
|
ZeroMemory(&osvi, sizeof(osvi));
|
|
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
|
|
|
GetVersionExW((LPOSVERSIONINFOW)&osvi);
|
|
if ((osvi.wSuiteMask & (VER_SUITE_TERMINAL | VER_SUITE_SINGLEUSERTS)) != 0)
|
|
fTS = TRUE;
|
|
|
|
// if it's one of these processes, then we need to ALWAYS go queued.
|
|
if (_wcsicmp(pwszFile, L"lsass.exe") == 0 ||
|
|
_wcsicmp(pwszFile, L"winlogon.exe") == 0 ||
|
|
_wcsicmp(pwszFile, L"csrss.exe") == 0 ||
|
|
_wcsicmp(pwszFile, L"smss.exe") == 0)
|
|
{
|
|
fQueue = TRUE;
|
|
fUseManifest = FALSE;
|
|
}
|
|
|
|
if (fQueue == FALSE)
|
|
{
|
|
// need the session id for the process
|
|
fRet = ProcessIdToSessionId(pesreq->pidReqProcess, &dwSessionId);
|
|
if (fTS && fRet)
|
|
{
|
|
WINSTATIONINFORMATIONW wsi;
|
|
WINSTATIONUSERTOKEN wsut;
|
|
|
|
ZeroMemory(&wsi, sizeof(wsi));
|
|
fRet = WinStationQueryInformationW(SERVERNAME_CURRENT,
|
|
dwSessionId,
|
|
WinStationInformation,
|
|
&wsi, sizeof(wsi), &cb);
|
|
if (fRet == FALSE)
|
|
goto doneTSTokenFetch;
|
|
|
|
// so the session isn't active. Determine what our session is cuz
|
|
// if the faulting process and the ERSvc are in the same session,
|
|
// then our session is obviously not active either. So it's not
|
|
// useful to obtain the user token for it.
|
|
if (wsi.ConnectState != State_Active)
|
|
{
|
|
fRet = FALSE;
|
|
goto doneTokenFetch;
|
|
}
|
|
|
|
// get the token of the user we want to pop up the display to.
|
|
// If this fn returns FALSE, assume there is no one logged
|
|
// into our session & just go to delayed fault reporting
|
|
ZeroMemory(&wsut, sizeof(wsut));
|
|
wsut.ProcessId = LongToHandle(GetCurrentProcessId());
|
|
wsut.ThreadId = LongToHandle(GetCurrentThreadId());
|
|
fRet = WinStationQueryInformationW(SERVERNAME_CURRENT,
|
|
dwSessionId,
|
|
WinStationUserToken, &wsut,
|
|
sizeof(wsut), &cb);
|
|
if (fRet == FALSE)
|
|
goto doneTSTokenFetch;
|
|
|
|
hTokenUser = wsut.UserToken;
|
|
}
|
|
else
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
|
|
doneTSTokenFetch:
|
|
if (fRet == FALSE)
|
|
fRet = GetInteractiveUsersToken(&hTokenUser);
|
|
|
|
// if the above call succeeded, check if the user is an admin or not...
|
|
// If he is, we can just display DW directly. Otherwise, we go into
|
|
// delayed reporting mode.
|
|
if (fRet && hTokenUser != NULL)
|
|
fUseManifest = IsUserAnAdmin(hTokenUser);
|
|
}
|
|
|
|
|
|
doneTokenFetch:
|
|
// if we are going to go into manifest mode, then check to see if we really
|
|
// need to go into Q mode
|
|
if (fQueue == FALSE)
|
|
{
|
|
EEnDis eedReport;
|
|
HKEY hkey = NULL;
|
|
|
|
// first check the policy key
|
|
dw = RegOpenKeyExW(HKEY_LOCAL_MACHINE, c_wszRPCfgPolicy, 0, KEY_READ,
|
|
&hkey);
|
|
if (dw == ERROR_SUCCESS)
|
|
{
|
|
// ok, if that succeeded then check and see if the DoReport value
|
|
// is here...
|
|
cb = sizeof(eedReport);
|
|
dw = RegQueryValueExW(hkey, c_wszRVDoReport, 0, NULL,
|
|
(LPBYTE)&eedReport, &cb);
|
|
if (dw == ERROR_SUCCESS /*&& eedReport != eedDisabled*/)
|
|
{
|
|
cb = sizeof(fQueue);
|
|
dw = RegQueryValueExW(hkey, c_wszRVForceQueue, 0, NULL,
|
|
(LPBYTE)&fQueue, &cb);
|
|
|
|
// if it's not a valid value, then pretend we got an error
|
|
if (dw == ERROR_SUCCESS && fQueue != TRUE && fQueue != FALSE)
|
|
dw = ERROR_INVALID_PARAMETER;
|
|
}
|
|
else
|
|
{
|
|
RegCloseKey(hkey);
|
|
hkey = NULL;
|
|
}
|
|
}
|
|
|
|
// if we didn't find a policy key or we were not able to read the
|
|
// 'DoReport' value from it, then try the CPL key
|
|
if (dw != ERROR_SUCCESS && hkey == NULL)
|
|
{
|
|
dw = RegOpenKeyExW(HKEY_LOCAL_MACHINE, c_wszRPCfg, 0, KEY_READ,
|
|
&hkey);
|
|
if (dw == ERROR_SUCCESS)
|
|
{
|
|
cb = sizeof(eedReport);
|
|
dw = RegQueryValueExW(hkey, c_wszRVDoReport, 0, NULL,
|
|
(LPBYTE)&eedReport, &cb);
|
|
if (dw == ERROR_SUCCESS /*&& eedReport != eedDisabled*/)
|
|
{
|
|
cb = sizeof(fQueue);
|
|
dw = RegQueryValueExW(hkey, c_wszRVForceQueue, 0, NULL,
|
|
(LPBYTE)&fQueue, &cb);
|
|
|
|
// if it's not a valid value, then pretend we got an error
|
|
if (dw == ERROR_SUCCESS && fQueue != TRUE &&
|
|
fQueue != FALSE)
|
|
dw = ERROR_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ok, if we still haven't got an ERROR_SUCCESS value back, then
|
|
// determine what the default should be.
|
|
if (dw != ERROR_SUCCESS)
|
|
fQueue = (osvi.wProductType == VER_NT_SERVER);
|
|
|
|
if (hkey != NULL)
|
|
RegCloseKey(hkey);
|
|
|
|
if (fQueue)
|
|
fUseManifest = FALSE;
|
|
|
|
}
|
|
|
|
if (SetupIsInProgress())
|
|
{
|
|
// force this to Q mode
|
|
fQueue = TRUE;
|
|
fUseManifest = FALSE;
|
|
}
|
|
|
|
// ok, so if we're not in forced queue mode & we're not in manifest mode
|
|
// then go hunt and see if any other session on the machine has an admin
|
|
// logged into it, assuming we're on a TS machine
|
|
if (fQueue == FALSE && fUseManifest == FALSE && fTS)
|
|
{
|
|
if (hTokenUser != NULL)
|
|
{
|
|
CloseHandle(hTokenUser);
|
|
hTokenUser = NULL;
|
|
}
|
|
|
|
fUseManifest = FindAdminSession(&dwSessionId, &hTokenUser);
|
|
}
|
|
|
|
hmodFR = LoadERDll();
|
|
if (hmodFR == NULL)
|
|
goto done;
|
|
|
|
// need a handle to the process to verify that the user has access to it.
|
|
hprocRemote = OpenProcess(PROCESS_DUP_HANDLE |
|
|
PROCESS_QUERY_INFORMATION |
|
|
PROCESS_VM_READ |
|
|
READ_CONTROL |
|
|
ACCESS_SYSTEM_SECURITY,
|
|
FALSE, pesreq->pidReqProcess);
|
|
if (hprocRemote == NULL)
|
|
goto done;
|
|
|
|
fRet = ValidateUserAccessToProcess(hPipe, hprocRemote);
|
|
if (fRet == FALSE)
|
|
goto done;
|
|
|
|
if (fUseManifest)
|
|
{
|
|
PROCESS_INFORMATION pi;
|
|
pfn_REPORTFAULTDWM pfn;
|
|
DWORD cch;
|
|
|
|
ZeroMemory(&pi, sizeof(pi));
|
|
|
|
pfn = (pfn_REPORTFAULTDWM)GetProcAddress(hmodFR, "ReportFaultDWM");
|
|
if (pfn == NULL)
|
|
goto done;
|
|
|
|
// need to figure out the temp path for the logged on user...
|
|
wszTemp[0] = L'\0';
|
|
fRet = ExpandEnvironmentStringsForUserW(hTokenUser, L"%TMP%",
|
|
wszTemp, sizeofSTRW(wszTemp));
|
|
if (fRet == FALSE || wszTemp[0] == L'\0')
|
|
{
|
|
fRet = ExpandEnvironmentStringsForUserW(hTokenUser, L"%TEMP%",
|
|
wszTemp, sizeofSTRW(wszTemp));
|
|
if (fRet == FALSE || wszTemp[0] == L'\0')
|
|
{
|
|
fRet = ExpandEnvironmentStringsForUserW(hTokenUser, L"%USERPROFILE%",
|
|
wszTemp, sizeofSTRW(wszTemp));
|
|
if (fRet == FALSE || wszTemp[0] == L'\0')
|
|
GetTempPathW(sizeofSTRW(wszTemp), wszTemp);
|
|
}
|
|
}
|
|
|
|
// determine what the name of the dump file will be
|
|
cch = wcslen(pwszFile) + sizeofSTRW(c_wszDumpSuffix) + 1;
|
|
|
|
__try { wszDumpFileName = (WCHAR *)_alloca(cch * sizeof(WCHAR)); }
|
|
__except(EXCEPTION_STACK_OVERFLOW == GetExceptionCode() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
|
|
{
|
|
wszDumpFileName = NULL;
|
|
}
|
|
if (wszDumpFileName == NULL)
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
goto done;
|
|
}
|
|
|
|
StringCchCopyW(wszDumpFileName, cch, pwszFile);
|
|
StringCchCatW(wszDumpFileName, cch, c_wszDumpSuffix);
|
|
|
|
// get a dir where we'll put all our temp files
|
|
if (CreateTempDirAndFile(wszTemp, NULL, &wszTempDir) == 0)
|
|
goto done;
|
|
|
|
// need an environment block for the target user to properly launch DW
|
|
if (CreateEnvironmentBlock(&pvEnv, hTokenUser, FALSE) == FALSE)
|
|
pvEnv = NULL;
|
|
|
|
// do the real work.
|
|
frrv = (*pfn)(&frm, wszTempDir, hTokenUser, pvEnv, &pi,
|
|
wszDumpFileName);
|
|
|
|
// we only need to duplicate the hProcess back to the requesting process
|
|
// cuz it doesn't use any of the other values in the PROCESSINFORMATION
|
|
// structure.
|
|
if (pi.hThread != NULL)
|
|
CloseHandle(pi.hThread);
|
|
|
|
// if we don't get this error code back, then we didn't launch the
|
|
// process, or it died for some reason. Now we will try to queue it
|
|
if (frrv != frrvOk)
|
|
{
|
|
fUseManifest = FALSE;
|
|
goto try_queue;
|
|
}
|
|
|
|
if (hprocRemote != NULL)
|
|
{
|
|
fRet = DuplicateHandle(GetCurrentProcess(), pi.hProcess,
|
|
hprocRemote, &esrep.hProcess, 0, FALSE,
|
|
DUPLICATE_SAME_ACCESS);
|
|
if (fRet == FALSE)
|
|
esrep.hProcess = NULL;
|
|
}
|
|
|
|
if (pi.hProcess != NULL)
|
|
CloseHandle(pi.hProcess);
|
|
|
|
esrep.ess = essOk;
|
|
// The temp directory will be handed off in the reply
|
|
// so do not clean it up.
|
|
fLeaveTempDir = TRUE;
|
|
}
|
|
|
|
|
|
// since the user is NOT an admin, we have to queue it for later viewing
|
|
// the next time an admin logs on
|
|
else
|
|
{
|
|
pfn_REPORTFAULTTOQ pfn;
|
|
try_queue:
|
|
pfn = (pfn_REPORTFAULTTOQ)GetProcAddress(hmodFR, "ReportFaultToQueue");
|
|
if (pfn == NULL)
|
|
goto done;
|
|
|
|
if (CreateQueueDir() == FALSE)
|
|
goto done;
|
|
|
|
frrv = (*pfn)(&frm);
|
|
|
|
// want to be local system again...
|
|
dw = GetLastError();
|
|
RevertToSelf();
|
|
SetLastError(dw);
|
|
|
|
// if (frrv != frrvOk)
|
|
// goto done;
|
|
|
|
esrep.ess = essOkQueued;
|
|
}
|
|
|
|
SetLastError(0);
|
|
fRet = TRUE;
|
|
|
|
done:
|
|
esrep.dwErr = GetLastError();
|
|
|
|
// build the reply packet with the handle valid in the context
|
|
// of the requesting process
|
|
pesrep = (SPCHExecServFaultReply *)pBuf;
|
|
RtlCopyMemory(pesrep, &esrep, sizeof(esrep));
|
|
*pcbBuf = sizeof(esrep) + sizeof(esrep) % sizeof(WCHAR);
|
|
|
|
if (fUseManifest)
|
|
{
|
|
pBuf += *pcbBuf;
|
|
|
|
if (wszTempDir != NULL)
|
|
{
|
|
cb = (wcslen(wszTempDir) + 1) * sizeof(WCHAR);
|
|
RtlCopyMemory(pBuf, wszTempDir, cb);
|
|
}
|
|
else
|
|
{
|
|
cb = sizeof(WCHAR);
|
|
*pBuf = L'\0';
|
|
}
|
|
*pcbBuf += cb;
|
|
pesrep->wszDir = (UINT64)pBuf - (UINT64)pesrep;
|
|
|
|
pBuf += cb;
|
|
if (wszDumpFileName != NULL)
|
|
{
|
|
cb = (wcslen(wszDumpFileName) + 1) * sizeof(WCHAR);
|
|
RtlCopyMemory(pBuf, wszDumpFileName, cb);
|
|
}
|
|
else
|
|
{
|
|
cb = sizeof(WCHAR);
|
|
*pBuf = L'\0';
|
|
}
|
|
|
|
*pcbBuf += cb;
|
|
pesrep->wszDumpName = (UINT64)pBuf - (UINT64)pesrep;
|
|
}
|
|
|
|
if (wszTempDir != NULL)
|
|
{
|
|
if (!fLeaveTempDir)
|
|
DeleteTempDirAndFile(wszTempDir, FALSE);
|
|
MyFree(wszTempDir);
|
|
}
|
|
if (pvEnv != NULL)
|
|
DestroyEnvironmentBlock(pvEnv);
|
|
if (hprocRemote != NULL)
|
|
CloseHandle(hprocRemote);
|
|
if (hTokenUser != NULL)
|
|
CloseHandle(hTokenUser);
|
|
if (hmodFR != NULL)
|
|
FreeLibrary(hmodFR);
|
|
|
|
ErrorTrace(1, "return %d", esrep.ess);
|
|
return fRet;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
// Note that the pipe that this thread services is secured such that only
|
|
// local system has access to it. Thus, we do not need to impersonate the
|
|
// pipe client in it.
|
|
BOOL ProcessHangRequest(HANDLE hPipe, PBYTE pBuf, DWORD *pcbBuf)
|
|
{
|
|
SPCHExecServHangRequest *pesreq = (SPCHExecServHangRequest *)pBuf;
|
|
SPCHExecServHangReply esrep;
|
|
PROCESS_INFORMATION pi;
|
|
WINSTATIONUSERTOKEN wsut;
|
|
OSVERSIONINFOEXW osvi;
|
|
STARTUPINFOW si;
|
|
HANDLE hprocRemote = NULL, hTokenUser = NULL;
|
|
LPVOID pvEnv = NULL;
|
|
LPWSTR wszEventName;
|
|
DWORD cbWrote, dwErr, cch, cchNeed;
|
|
WCHAR wszSysDir[MAX_PATH], *pwszSysDir = NULL;
|
|
WCHAR *pwszCmdLine = NULL, *pwszEnd = NULL;
|
|
WCHAR *pwch;
|
|
BOOL fRet = FALSE;
|
|
HRESULT hr;
|
|
|
|
USE_TRACING("ProcessHangRequest");
|
|
|
|
ZeroMemory(&pi, sizeof(pi));
|
|
ZeroMemory(&wsut, sizeof(wsut));
|
|
|
|
ZeroMemory(&esrep, sizeof(esrep));
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
esrep.cb = sizeof(esrep);
|
|
esrep.ess = essErr;
|
|
|
|
// validate parameters
|
|
VALIDATEPARM(hr, (*pcbBuf < sizeof(SPCHExecServHangRequest) ||
|
|
pesreq->cbESR != sizeof(SPCHExecServHangRequest) ||
|
|
pesreq->wszEventName == 0 ||
|
|
pesreq->wszEventName >= *pcbBuf ||
|
|
pesreq->dwpidHung == 0 ||
|
|
pesreq->dwtidHung == 0));
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
goto done;
|
|
}
|
|
|
|
// check and make sure that there is a NULL terminator between the
|
|
// start of the string & the end of the buffer
|
|
pwszEnd = (LPWSTR)(pBuf + *pcbBuf);
|
|
wszEventName = (LPWSTR)(pBuf + (DWORD)pesreq->wszEventName);
|
|
for (pwch = wszEventName; pwch < pwszEnd && *pwch != L'\0'; pwch++);
|
|
if (pwch >= pwszEnd)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
DBG_MSG("Bad event name");
|
|
goto done;
|
|
}
|
|
|
|
// Get the handle to remote process
|
|
hprocRemote = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION,
|
|
FALSE, pesreq->pidReqProcess);
|
|
if (hprocRemote == NULL)
|
|
{
|
|
DBG_MSG("Can't get handle to process");
|
|
goto done;
|
|
}
|
|
|
|
ZeroMemory(&osvi, sizeof(osvi));
|
|
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
|
|
|
GetVersionExW((LPOSVERSIONINFOW)&osvi);
|
|
if ((osvi.wSuiteMask & (VER_SUITE_TERMINAL | VER_SUITE_SINGLEUSERTS)) != 0)
|
|
{
|
|
WINSTATIONINFORMATIONW wsi;
|
|
DWORD cb;
|
|
|
|
ZeroMemory(&wsi, sizeof(wsi));
|
|
fRet = WinStationQueryInformationW(SERVERNAME_CURRENT,
|
|
pesreq->ulSessionId,
|
|
WinStationInformation,
|
|
&wsi, sizeof(wsi), &cb);
|
|
if (fRet == FALSE)
|
|
{
|
|
DBG_MSG("WinStationQI failed");
|
|
goto doneTSTokenFetch;
|
|
}
|
|
|
|
// if the session where the hang was terminated isn't active (which
|
|
// would be an odd state to be in- perhaps it is closing down?)
|
|
// then just bail cuz we don't want to put up UI in it
|
|
if (wsi.ConnectState != State_Active)
|
|
{
|
|
DBG_MSG("No Active Session found!");
|
|
SetLastError(0);
|
|
goto done;
|
|
}
|
|
|
|
// fetch the token associated with the sessions user
|
|
wsut.ProcessId = LongToHandle(GetCurrentProcessId());
|
|
wsut.ThreadId = LongToHandle(GetCurrentThreadId());
|
|
fRet = WinStationQueryInformationW(SERVERNAME_CURRENT,
|
|
pesreq->ulSessionId,
|
|
WinStationUserToken, &wsut,
|
|
sizeof(wsut), &cbWrote);
|
|
if (fRet)
|
|
{
|
|
if (wsut.UserToken != NULL)
|
|
hTokenUser = wsut.UserToken;
|
|
else
|
|
{
|
|
DBG_MSG("no token found");
|
|
fRet = FALSE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DBG_MSG("WTS not found");
|
|
fRet = FALSE;
|
|
}
|
|
doneTSTokenFetch:
|
|
if (fRet == FALSE)
|
|
{
|
|
DWORD dwERSvcSession = (DWORD)-1;
|
|
|
|
// make sure the hung app is in our session before using this API and
|
|
// if it's not, just bail.
|
|
fRet = ProcessIdToSessionId(GetCurrentProcessId(), &dwERSvcSession);
|
|
if (fRet == FALSE)
|
|
{
|
|
DBG_MSG("Failed in ProcessIdToSessionId");
|
|
goto done;
|
|
}
|
|
if (dwERSvcSession != pesreq->ulSessionId)
|
|
{
|
|
DBG_MSG("Session IDs do not match");
|
|
goto done;
|
|
}
|
|
|
|
fRet = GetInteractiveUsersToken(&hTokenUser);
|
|
if (fRet == FALSE)
|
|
{
|
|
DBG_MSG("Failure in GetInteractiveUsersToken");
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
// create the default environment for the user token- note that we
|
|
// have to set the CREATE_UNICODE_ENVIRONMENT flag...
|
|
fRet = CreateEnvironmentBlock(&pvEnv, hTokenUser, FALSE);
|
|
if (fRet == FALSE)
|
|
{
|
|
DBG_MSG("CreateEnvironmentBlock failed");
|
|
pvEnv = NULL;
|
|
}
|
|
|
|
// note that we do not allow inheritance of handles cuz they would be
|
|
// inherited from this process and not the real parent, making it sorta
|
|
// pointless.
|
|
ZeroMemory(&si, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
#ifdef _WIN64
|
|
if (pesreq->fIs64bit == FALSE)
|
|
cch = GetSystemWow64DirectoryW(wszSysDir, sizeofSTRW(wszSysDir));
|
|
else
|
|
#endif
|
|
cch = GetSystemDirectoryW(wszSysDir, sizeofSTRW(wszSysDir));
|
|
if (cch == 0)
|
|
{
|
|
DBG_MSG("GetSystemDirectoryW failed");
|
|
goto done;
|
|
}
|
|
|
|
cchNeed = cch + sizeofSTRW(c_wszQSubdir) + 2;
|
|
if (cchNeed > sizeofSTRW(wszSysDir))
|
|
{
|
|
__try { pwszSysDir = (WCHAR *)_alloca(cchNeed * sizeof(WCHAR)); }
|
|
__except(EXCEPTION_STACK_OVERFLOW == GetExceptionCode() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
|
|
{
|
|
pwszSysDir = NULL;
|
|
}
|
|
if (pwszSysDir == NULL)
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
DBG_MSG("out of memory");
|
|
goto done;
|
|
}
|
|
|
|
if (cch > sizeofSTRW(wszSysDir))
|
|
{
|
|
#ifdef _WIN64
|
|
if (pesreq->fIs64bit == FALSE)
|
|
cch = GetSystemWow64DirectoryW(pwszSysDir, cchNeed);
|
|
else
|
|
#endif
|
|
cch = GetSystemDirectoryW(pwszSysDir, cchNeed);
|
|
}
|
|
else
|
|
{
|
|
StringCchCopyW(pwszSysDir, cchNeed, wszSysDir);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pwszSysDir = wszSysDir;
|
|
}
|
|
|
|
// if the length of the system directory is 3, then %windir% is in the form
|
|
// of X:\, so clip off the backslash so we don't have to special case
|
|
// it below...
|
|
if (cch == 3)
|
|
*(pwszSysDir + 2) = L'\0';
|
|
|
|
// compute the size of the buffer to hold the command line. 14 is for
|
|
// the max # of characters in a DWORD
|
|
cchNeed = 12 + cch + wcslen((LPWSTR)wszEventName) +
|
|
sizeofSTRW(c_wszDWMCmdLine64);
|
|
|
|
__try { pwszCmdLine = (WCHAR *)_alloca(cchNeed * sizeof(WCHAR)); }
|
|
__except(EXCEPTION_STACK_OVERFLOW == GetExceptionCode() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
|
|
{
|
|
pwszCmdLine = NULL;
|
|
}
|
|
if (pwszCmdLine == NULL)
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
goto done;
|
|
}
|
|
|
|
#ifdef _WIN64
|
|
StringCchPrintfW(pwszCmdLine, cchNeed, c_wszDWMCmdLine64, pwszSysDir, pesreq->dwpidHung,
|
|
((pesreq->fIs64bit) ? L'6' : L' '), pesreq->dwtidHung,
|
|
wszEventName);
|
|
|
|
#else
|
|
StringCchPrintfW(pwszCmdLine, cchNeed, c_wszDWMCmdLine32, pwszSysDir, pesreq->dwpidHung,
|
|
pesreq->dwtidHung, wszEventName);
|
|
#endif
|
|
|
|
ErrorTrace(0, "Launching hang report \'%S\'", pwszCmdLine);
|
|
|
|
TESTBOOL(hr, CreateProcessAsUserW(hTokenUser, NULL, pwszCmdLine, NULL, NULL,
|
|
FALSE, CREATE_DEFAULT_ERROR_MODE |
|
|
CREATE_UNICODE_ENVIRONMENT |
|
|
NORMAL_PRIORITY_CLASS, pvEnv, pwszSysDir,
|
|
&si, &pi));
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
DBG_MSG("CreateProcessAsUser failed");
|
|
fRet = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
// duplicate the process & thread handles back into the remote process
|
|
fRet = DuplicateHandle(GetCurrentProcess(), pi.hProcess, hprocRemote,
|
|
&esrep.hProcess, 0, FALSE, DUPLICATE_SAME_ACCESS);
|
|
if (fRet == FALSE)
|
|
esrep.hProcess = NULL;
|
|
|
|
// get rid of any errors we might have encountered
|
|
SetLastError(0);
|
|
fRet = TRUE;
|
|
|
|
esrep.ess = essOk;
|
|
|
|
done:
|
|
esrep.dwErr = GetLastError();
|
|
|
|
// build the reply packet with the handle valid in the context
|
|
// of the requesting process
|
|
RtlCopyMemory(pBuf, &esrep, sizeof(esrep));
|
|
*pcbBuf = sizeof(esrep);
|
|
|
|
// close our versions of the handles. The requestors references
|
|
// are now the main ones
|
|
if (hTokenUser != NULL)
|
|
CloseHandle(hTokenUser);
|
|
if (pvEnv != NULL)
|
|
DestroyEnvironmentBlock(pvEnv);
|
|
if (pi.hProcess != NULL)
|
|
CloseHandle(pi.hProcess);
|
|
if (pi.hThread != NULL)
|
|
CloseHandle(pi.hThread);
|
|
if (hprocRemote != NULL)
|
|
CloseHandle(hprocRemote);
|
|
|
|
return fRet;
|
|
}
|