Skip to content

Commit

Permalink
Fix NSIS uninstall to work with UAC
Browse files Browse the repository at this point in the history
Use ShellExecuteEx() to elevate privilege if CreateProcess() fails.

Signed-off-by: Kevin Wasserman <kevin.wasserman@painless-security.com>

ticket: 7265 (new)
queue: kfw
target_version: 1.10.4
tags: pullup
  • Loading branch information
Kevin Wasserman authored and kaduk committed Aug 24, 2012
1 parent 6201bbc commit d66fcb1
Showing 1 changed file with 192 additions and 115 deletions.
307 changes: 192 additions & 115 deletions src/windows/installer/wix/custom/custom.cpp
Expand Up @@ -83,11 +83,13 @@ SOFTWARE.
// Only works for Win2k and above
#define _WIN32_WINNT 0x500
#include "custom.h"
#include <shellapi.h>

// linker stuff
#pragma comment(lib, "msi")
#pragma comment(lib, "advapi32")

#pragma comment(lib, "shell32")
#pragma comment(lib, "user32")

void ShowMsiError( MSIHANDLE hInstall, DWORD errcode, DWORD param ){
MSIHANDLE hRecord;
Expand All @@ -102,6 +104,22 @@ void ShowMsiError( MSIHANDLE hInstall, DWORD errcode, DWORD param ){
MsiCloseHandle( hRecord );
}

static void ShowMsiErrorEx(MSIHANDLE hInstall, DWORD errcode, LPTSTR str,
DWORD param )
{
MSIHANDLE hRecord;

hRecord = MsiCreateRecord(3);
MsiRecordClearData(hRecord);
MsiRecordSetInteger(hRecord, 1, errcode);
MsiRecordSetString(hRecord, 2, str);
MsiRecordSetInteger(hRecord, 3, param);

MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecord);

MsiCloseHandle(hRecord);
}

#define LSA_KERBEROS_KEY "SYSTEM\\CurrentControlSet\\Control\\Lsa\\Kerberos"
#define LSA_KERBEROS_PARM_KEY "SYSTEM\\CurrentControlSet\\Control\\Lsa\\Kerberos\\Parameters"
#define KFW_CLIENT_KEY "SOFTWARE\\MIT\\Kerberos\\Client\\"
Expand Down Expand Up @@ -520,130 +538,189 @@ UINT KillRunningProcessesSlave( MSIHANDLE hInstall, BOOL bKill )
return rv;
}

/* Uninstall NSIS */
MSIDLLEXPORT UninstallNsisInstallation( MSIHANDLE hInstall )
static bool IsNSISInstalled()
{
DWORD rv = ERROR_SUCCESS;
// lookup the NSISUNINSTALL property value
LPTSTR cNsisUninstall = _T("UPGRADENSIS");
HANDLE hIo = NULL;
DWORD dwSize = 0;
LPTSTR strPathUninst = NULL;
HANDLE hJob = NULL;
STARTUPINFO sInfo;
PROCESS_INFORMATION pInfo;

pInfo.hProcess = NULL;
pInfo.hThread = NULL;

rv = MsiGetProperty( hInstall, cNsisUninstall, _T(""), &dwSize );
if(rv != ERROR_MORE_DATA) goto _cleanup;

strPathUninst = new TCHAR[ ++dwSize ];

rv = MsiGetProperty( hInstall, cNsisUninstall, strPathUninst, &dwSize );
if(rv != ERROR_SUCCESS) goto _cleanup;

// Create a process for the uninstaller
sInfo.cb = sizeof(sInfo);
sInfo.lpReserved = NULL;
sInfo.lpDesktop = _T("");
sInfo.lpTitle = _T("NSIS Uninstaller for Kerberos for Windows");
sInfo.dwX = 0;
sInfo.dwY = 0;
sInfo.dwXSize = 0;
sInfo.dwYSize = 0;
sInfo.dwXCountChars = 0;
sInfo.dwYCountChars = 0;
sInfo.dwFillAttribute = 0;
sInfo.dwFlags = 0;
sInfo.wShowWindow = 0;
sInfo.cbReserved2 = 0;
sInfo.lpReserved2 = 0;
sInfo.hStdInput = 0;
sInfo.hStdOutput = 0;
sInfo.hStdError = 0;

if(!CreateProcess(
strPathUninst,
_T("Uninstall /S"),
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&sInfo,
&pInfo)) {
DWORD lastError = GetLastError();
MSIHANDLE hRecord;

hRecord = MsiCreateRecord(4);
MsiRecordClearData(hRecord);
MsiRecordSetInteger(hRecord, 1, ERR_NSS_FAILED_CP);
MsiRecordSetString(hRecord, 2, strPathUninst);
MsiRecordSetInteger(hRecord, 3, lastError);

MsiProcessMessage( hInstall, INSTALLMESSAGE_ERROR, hRecord );

MsiCloseHandle( hRecord );

pInfo.hProcess = NULL;
pInfo.hThread = NULL;
rv = 40;
goto _cleanup;
};

// Create a job object to contain the NSIS uninstall process tree

JOBOBJECT_ASSOCIATE_COMPLETION_PORT acp;
HKEY nsisKfwKey = NULL;
// Note: check Wow6432 node if 64 bit build
HRESULT res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion"
"\\Uninstall\\Kerberos for Windows",
0,
KEY_READ | KEY_WOW64_32KEY,
&nsisKfwKey);
if (res != ERROR_SUCCESS)
return FALSE;

RegCloseKey(nsisKfwKey);
return TRUE;
}

acp.CompletionKey = 0;
static HANDLE NSISUninstallShellExecute(LPTSTR pathUninstall)
{
SHELLEXECUTEINFO sei;
ZeroMemory ( &sei, sizeof(sei) );

sei.cbSize = sizeof(sei);
sei.hwnd = GetForegroundWindow();
sei.fMask = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI |
SEE_MASK_NOCLOSEPROCESS;
sei.lpVerb = _T("runas"); // run as administrator
sei.lpFile = pathUninstall;
sei.lpParameters = _T("");
sei.nShow = SW_SHOWNORMAL;

if (!ShellExecuteEx(&sei)) {
// FAILED! TODO: report details?
}
return sei.hProcess;
}

hJob = CreateJobObject(NULL, _T("NSISUninstallObject"));
if(!hJob) {
rv = 41;
goto _cleanup;
}
static HANDLE NSISUninstallCreateProcess(LPTSTR pathUninstall)
{
STARTUPINFO sInfo;
PROCESS_INFORMATION pInfo;
pInfo.hProcess = NULL;
pInfo.hThread = NULL;

// Create a process for the uninstaller
sInfo.cb = sizeof(sInfo);
sInfo.lpReserved = NULL;
sInfo.lpDesktop = _T("");
sInfo.lpTitle = _T("NSIS Uninstaller for Kerberos for Windows");
sInfo.dwX = 0;
sInfo.dwY = 0;
sInfo.dwXSize = 0;
sInfo.dwYSize = 0;
sInfo.dwXCountChars = 0;
sInfo.dwYCountChars = 0;
sInfo.dwFillAttribute = 0;
sInfo.dwFlags = 0;
sInfo.wShowWindow = 0;
sInfo.cbReserved2 = 0;
sInfo.lpReserved2 = 0;
sInfo.hStdInput = 0;
sInfo.hStdOutput = 0;
sInfo.hStdError = 0;

if (!CreateProcess(pathUninstall,
_T("Uninstall /S"),
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&sInfo,
&pInfo)) {
// failure; could grab info, but we should be able to recover by
// using NSISUninstallShellExecute...
} else {
// success
// start up the thread
ResumeThread(pInfo.hThread);
// done with thread handle
CloseHandle(pInfo.hThread);
}
return pInfo.hProcess;
}

hIo = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
if(!hIo) {
rv = 42;
goto _cleanup;
}

acp.CompletionPort = hIo;
/* Uninstall NSIS */
MSIDLLEXPORT UninstallNsisInstallation( MSIHANDLE hInstall )
{
DWORD rv = ERROR_SUCCESS;
DWORD lastError;
// lookup the NSISUNINSTALL property value
LPTSTR cNsisUninstall = _T("UPGRADENSIS");
LPTSTR strPathUninst = NULL;
DWORD dwSize = 0;
HANDLE hProcess = NULL;
HANDLE hIo = NULL;
HANDLE hJob = NULL;

rv = MsiGetProperty( hInstall, cNsisUninstall, _T(""), &dwSize );
if(rv != ERROR_MORE_DATA) goto _cleanup;

strPathUninst = new TCHAR[ ++dwSize ];

rv = MsiGetProperty(hInstall, cNsisUninstall, strPathUninst, &dwSize);
if(rv != ERROR_SUCCESS) goto _cleanup;

hProcess = NSISUninstallCreateProcess(strPathUninst);
if (hProcess == NULL) // expected when run on UAC-limited account
hProcess = NSISUninstallShellExecute(strPathUninst);

if (hProcess == NULL) {
// still no uninstall process? ick...
lastError = GetLastError();
rv = 40;
goto _cleanup;
}
// note that it is not suffiecient to wait for the initial process to
// finish; there is a whole process tree that we need to wait for. sigh.
JOBOBJECT_ASSOCIATE_COMPLETION_PORT acp;
acp.CompletionKey = 0;
hJob = CreateJobObject(NULL, _T("NSISUninstallObject"));
if(!hJob) {
rv = 41;
goto _cleanup;
}

SetInformationJobObject( hJob, JobObjectAssociateCompletionPortInformation, &acp, sizeof(acp));
hIo = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
if(!hIo) {
rv = 42;
goto _cleanup;
}

AssignProcessToJobObject( hJob, pInfo.hProcess );
acp.CompletionPort = hIo;

SetInformationJobObject(hJob,
JobObjectAssociateCompletionPortInformation,
&acp,
sizeof(acp));

AssignProcessToJobObject(hJob, hProcess);

DWORD msgId;
ULONG_PTR unusedCompletionKey;
LPOVERLAPPED unusedOverlapped;
for (;;) {
if (!GetQueuedCompletionStatus(hIo,
&msgId,
&unusedCompletionKey,
&unusedOverlapped,
INFINITE)) {
Sleep(1000);
} else if (msgId == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO) {
break;
}
}

ResumeThread( pInfo.hThread );
_cleanup:
if (hProcess) CloseHandle(hProcess);
if (hIo) CloseHandle(hIo);
if (hJob) CloseHandle(hJob);

if (IsNSISInstalled()) {
// uninstall failed: maybe user cancelled uninstall, or something else
// went wrong...
if (rv == ERROR_SUCCESS)
rv = 43;
} else {
// Maybe something went wrong, but it doesn't matter as long as nsis
// is gone now...
rv = ERROR_SUCCESS;
}

DWORD a,b,c;
for(;;) {
if(!GetQueuedCompletionStatus(hIo, &a, (PULONG_PTR) &b, (LPOVERLAPPED *) &c, INFINITE)) {
Sleep(1000);
continue;
}
if(a == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO) {
break;
}
}
if (rv == 40) {
// CreateProcess() / ShellExecute() errors get extra data
ShowMsiErrorEx(hInstall, ERR_NSS_FAILED_CP, strPathUninst, lastError);
} else if (rv != ERROR_SUCCESS) {
ShowMsiError(hInstall, ERR_NSS_FAILED, rv);
}

rv = ERROR_SUCCESS;

_cleanup:
if(hIo) CloseHandle(hIo);
if(pInfo.hProcess) CloseHandle( pInfo.hProcess );
if(pInfo.hThread) CloseHandle( pInfo.hThread );
if(hJob) CloseHandle(hJob);
if(strPathUninst) delete strPathUninst;

if(rv != ERROR_SUCCESS && rv != 40) {
ShowMsiError( hInstall, ERR_NSS_FAILED, rv );
}
return rv;
if (strPathUninst) delete strPathUninst;
return rv;
}

/* Check and add or remove networkprovider key value
Expand Down

0 comments on commit d66fcb1

Please sign in to comment.