krb5 commit: Fix NSIS uninstall to work with UAC

Benjamin Kaduk kaduk at MIT.EDU
Fri Aug 24 15:55:28 EDT 2012


https://github.com/krb5/krb5/commit/d66fcb1784fc6b5a6b01748dda7f99e0afa3fc69
commit d66fcb1784fc6b5a6b01748dda7f99e0afa3fc69
Author: Kevin Wasserman <kevin.wasserman at painless-security.com>
Date:   Tue Jun 5 13:03:21 2012 -0400

    Fix NSIS uninstall to work with UAC
    
    Use ShellExecuteEx() to elevate privilege if CreateProcess() fails.
    
    Signed-off-by: Kevin Wasserman <kevin.wasserman at painless-security.com>
    
    ticket: 7265 (new)
    queue: kfw
    target_version: 1.10.4
    tags: pullup

 src/windows/installer/wix/custom/custom.cpp |  307 +++++++++++++++++----------
 1 files changed, 192 insertions(+), 115 deletions(-)

diff --git a/src/windows/installer/wix/custom/custom.cpp b/src/windows/installer/wix/custom/custom.cpp
index 5f0f42f..3ef726d 100644
--- a/src/windows/installer/wix/custom/custom.cpp
+++ b/src/windows/installer/wix/custom/custom.cpp
@@ -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;
@@ -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\\"
@@ -520,130 +538,189 @@ _cleanup:
     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


More information about the cvs-krb5 mailing list