/** * Obtains the updater path alongside a subdir of the service binary. * The purpose of this function is to return a path that is likely high * integrity and therefore more safe to execute code from. * * @param serviceUpdaterPath Out parameter for the path where the updater * should be copied to. * @return TRUE if a file path was obtained. */ BOOL GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1]) { if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) { LOG_WARN(("Could not obtain module filename when attempting to " "use a secure updater path. (%d)", GetLastError())); return FALSE; } if (!PathRemoveFileSpecW(serviceUpdaterPath)) { LOG_WARN(("Couldn't remove file spec when attempting to use a secure " "updater path. (%d)", GetLastError())); return FALSE; } if (!PathAppendSafe(serviceUpdaterPath, L"update")) { LOG_WARN(("Couldn't append file spec when attempting to use a secure " "updater path. (%d)", GetLastError())); return FALSE; } CreateDirectoryW(serviceUpdaterPath, nullptr); if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) { LOG_WARN(("Couldn't append file spec when attempting to use a secure " "updater path. (%d)", GetLastError())); return FALSE; } return TRUE; }
/** * Determines if the MozillaMaintenance service path needs to be updated * and fixes it if it is wrong. * * @param service A handle to the service to fix. * @param currentServicePath The current (possibly wrong) path that is used. * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed. * @return TRUE if the service path is now correct. */ BOOL FixServicePath(SC_HANDLE service, LPCWSTR currentServicePath, BOOL &servicePathWasWrong) { // When we originally upgraded the MozillaMaintenance service we // would uninstall the service on each upgrade. This had an // intermittent error which could cause the service to use the file // maintenanceservice_tmp.exe as the install path. Only a small number // of Nightly users would be affected by this, but we check for this // state here and fix the user if they are affected. // // We also fix the path in the case of the path not being quoted. size_t currentServicePathLen = wcslen(currentServicePath); bool doesServiceHaveCorrectPath = currentServicePathLen > 2 && !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") && currentServicePath[0] == L'\"' && currentServicePath[currentServicePathLen - 1] == L'\"'; if (doesServiceHaveCorrectPath) { LOG(("The MozillaMaintenance service path is correct.")); servicePathWasWrong = FALSE; return TRUE; } // This is a recoverable situation so not logging as a warning LOG(("The MozillaMaintenance path is NOT correct. It was: %ls", currentServicePath)); servicePathWasWrong = TRUE; WCHAR fixedPath[MAX_PATH + 1] = { L'\0' }; wcsncpy(fixedPath, currentServicePath, MAX_PATH); PathUnquoteSpacesW(fixedPath); if (!PathRemoveFileSpecW(fixedPath)) { LOG_WARN(("Couldn't remove file spec. (%d)", GetLastError())); return FALSE; } if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) { LOG_WARN(("Couldn't append file spec. (%d)", GetLastError())); return FALSE; } PathQuoteSpacesW(fixedPath); if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { LOG_WARN(("Could not fix service path. (%d)", GetLastError())); return FALSE; } LOG(("Fixed service path to: %ls.", fixedPath)); return TRUE; }
/** * Obtains the base path where logs should be stored * * @param path The out buffer for the backup log path of size MAX_PATH + 1 * @return TRUE if successful. */ BOOL GetLogDirectoryPath(WCHAR *path) { HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, SHGFP_TYPE_CURRENT, path); if (FAILED(hr)) { return FALSE; } if (!PathAppendSafe(path, L"Mozilla")) { return FALSE; } // The directory should already be created from the installer, but // just to be safe in case someone deletes. CreateDirectoryW(path, NULL); if (!PathAppendSafe(path, L"logs")) { return FALSE; } CreateDirectoryW(path, NULL); return TRUE; }
/** * Calculated a backup path based on the log number. * * @param path The out buffer to store the log path of size MAX_PATH + 1 * @param basePath The base directory where the calculated path should go * @param logNumber The log number, 0 == updater.log * @return TRUE if successful. */ BOOL GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber) { WCHAR logName[64]; wcscpy(path, basePath); if (logNumber <= 0) { swprintf(logName, sizeof(logName) / sizeof(logName[0]), L"maintenanceservice.log"); } else { swprintf(logName, sizeof(logName) / sizeof(logName[0]), L"maintenanceservice-%d.log", logNumber); } return PathAppendSafe(path, logName); }
/** * Starts the upgrade process for update of the service if it is * already installed. * * @param installDir the installation directory where * maintenanceservice_installer.exe is located. * @return TRUE if successful */ BOOL StartServiceUpdate(LPCWSTR installDir) { // Get a handle to the local computer SCM database SC_HANDLE manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (!manager) { return FALSE; } // Open the service SC_HANDLE svc = OpenServiceW(manager, SVC_NAME, SERVICE_ALL_ACCESS); if (!svc) { CloseServiceHandle(manager); return FALSE; } CloseServiceHandle(svc); CloseServiceHandle(manager); // If we reach here, then the service is installed, so // proceed with upgrading it. STARTUPINFOW si = {0}; si.cb = sizeof(STARTUPINFOW); // No particular desktop because no UI si.lpDesktop = L""; PROCESS_INFORMATION pi = {0}; WCHAR maintserviceInstallerPath[MAX_PATH + 1] = { L'\0' }; wcsncpy(maintserviceInstallerPath, installDir, MAX_PATH); PathAppendSafe(maintserviceInstallerPath, L"maintenanceservice_installer.exe"); WCHAR cmdLine[64] = { '\0' }; wcsncpy(cmdLine, L"dummyparam.exe /Upgrade", sizeof(cmdLine) / sizeof(cmdLine[0]) - 1); BOOL svcUpdateProcessStarted = CreateProcessW(maintserviceInstallerPath, cmdLine, NULL, NULL, FALSE, 0, NULL, installDir, &si, &pi); if (svcUpdateProcessStarted) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } return svcUpdateProcessStarted; }
/** * Determines if the MozillaMaintenance service path needs to be updated * and fixes it if it is wrong. * * @param service A handle to the service to fix. * @param currentServicePath The current (possibly wrong) path that is used. * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed. * @return TRUE if the service path is now correct. */ BOOL FixServicePath(SC_HANDLE service, LPCWSTR currentServicePath, BOOL &servicePathWasWrong) { // When we originally upgraded the MozillaMaintenance service we // would uninstall the service on each upgrade. This had an // intermittent error which could cause the service to use the file // maintenanceservice_tmp.exe as the install path. Only a small number // of Nightly users would be affected by this, but we check for this // state here and fix the user if they are affected. bool doesServiceHaveCorrectPath = !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe"); if (doesServiceHaveCorrectPath) { LOG(("The MozillaMaintenance service path is correct.\n")); servicePathWasWrong = FALSE; return TRUE; } LOG(("The MozillaMaintenance path is NOT correct.\n")); servicePathWasWrong = TRUE; WCHAR fixedPath[MAX_PATH + 1] = { L'\0' }; wcsncpy(fixedPath, currentServicePath, MAX_PATH); PathUnquoteSpacesW(fixedPath); if (!PathRemoveFileSpecW(fixedPath)) { LOG(("Couldn't remove file spec. (%d)\n", GetLastError())); return FALSE; } if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) { LOG(("Couldn't append file spec. (%d)\n", GetLastError())); return FALSE; } PathQuoteSpacesW(fixedPath); if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, fixedPath, NULL, NULL, NULL, NULL, NULL, NULL)) { LOG(("Could not fix service path. (%d)\n", GetLastError())); return FALSE; } LOG(("Fixed service path to: %ls.\n", fixedPath)); return TRUE; }
/** * Obtains the path of a file in the same directory as the specified file. * * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result. * @param siblingFIlePath The path of another file in the same directory * @param newFileName The filename of another file in the same directory * @return TRUE if successful */ BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, LPCWSTR newFileName) { if (wcslen(siblingFilePath) >= MAX_PATH) { return FALSE; } wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH); if (!PathRemoveFileSpecW(destinationBuffer)) { return FALSE; } if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) { return FALSE; } return PathAppendSafe(destinationBuffer, newFileName); }
/** * Sets update.status to pending so that the next startup will not use * the service and instead will attempt an update the with a UAC prompt. * * @param updateDirPath The path of the update directory * @return TRUE if successful */ BOOL WriteStatusPending(LPCWSTR updateDirPath) { WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' }; wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { return FALSE; } const char pending[] = "pending"; HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr); if (statusFile == INVALID_HANDLE_VALUE) { return FALSE; } DWORD wrote; BOOL ok = WriteFile(statusFile, pending, sizeof(pending) - 1, &wrote, nullptr); CloseHandle(statusFile); return ok && (wrote == sizeof(pending) - 1); }
/* * Read the update.status file and sets isApplying to true if * the status is set to applying * * @param updateDirPath The directory where update.status is stored * @param isApplying Out parameter for specifying if the status * is set to applying or not. * @return TRUE if the information was filled. */ static BOOL IsStatusApplying(LPCWSTR updateDirPath, BOOL &isApplying) { isApplying = FALSE; WCHAR updateStatusFilePath[MAX_PATH + 1]; wcscpy(updateStatusFilePath, updateDirPath); if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { LOG(("Warning: Could not append path for update.status file\n")); return FALSE; } nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL)); if (INVALID_HANDLE_VALUE == statusFile) { LOG(("Warning: Could not open update.status file\n")); return FALSE; } char buf[32] = { 0 }; DWORD read; if (!ReadFile(statusFile, buf, sizeof(buf), &read, NULL)) { LOG(("Warning: Could not read from update.status file\n")); return FALSE; } LOG(("updater.exe returned status: %s\n", buf)); const char kApplying[] = "applying"; isApplying = strncmp(buf, kApplying, sizeof(kApplying) - 1) == 0; return TRUE; }
/** * Sets update.status to a specific failure code * * @param updateDirPath The path of the update directory * @return TRUE if successful */ BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode) { WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' }; wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { return FALSE; } HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr); if (statusFile == INVALID_HANDLE_VALUE) { return FALSE; } char failure[32]; sprintf(failure, "failed: %d", errorCode); DWORD toWrite = strlen(failure); DWORD wrote; BOOL ok = WriteFile(statusFile, failure, toWrite, &wrote, nullptr); CloseHandle(statusFile); return ok && wrote == toWrite; }
/** * Processes a software update command * * @param argc The number of arguments in argv * @param argv The arguments normally passed to updater.exe * argv[0] must be the path to updater.exe * @return TRUE if the update was successful. */ BOOL ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv) { BOOL result = TRUE; if (argc < 3) { LOG(("Not enough command line parameters specified. " "Updating update.status.\n")); // We can only update update.status if argv[1] exists. argv[1] is // the directory where the update.status file exists. if (argc > 1 || !WriteStatusFailure(argv[1], SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) { LOG(("Could not write update.status service update failure." "Last error: %d\n", GetLastError())); } return FALSE; } // Verify that the updater.exe that we are executing is the same // as the one in the installation directory which we are updating. // The installation dir that we are installing to is argv[2]. WCHAR installDirUpdater[MAX_PATH + 1]; wcsncpy(installDirUpdater, argv[2], MAX_PATH); if (!PathAppendSafe(installDirUpdater, L"updater.exe")) { LOG(("Install directory updater could not be determined.\n")); result = FALSE; } BOOL updaterIsCorrect; if (result && !VerifySameFiles(argv[0], installDirUpdater, updaterIsCorrect)) { LOG(("Error checking if the updaters are the same.\n" "Path 1: %ls\nPath 2: %ls\n", argv[0], installDirUpdater)); result = FALSE; } if (result && !updaterIsCorrect) { LOG(("The updaters do not match, udpater will not run.\n")); result = FALSE; } if (result) { LOG(("updater.exe was compared successfully to the installation directory" " updater.exe.\n")); } else { if (!WriteStatusFailure(argv[1], SERVICE_UPDATER_COMPARE_ERROR)) { LOG(("Could not write update.status updater compare failure.\n")); } return FALSE; } // Check to make sure the udpater.exe module has the unique updater identity. // This is a security measure to make sure that the signed executable that // we will run is actually an updater. HMODULE updaterModule = LoadLibrary(argv[0]); if (!updaterModule) { LOG(("updater.exe module could not be loaded. (%d)\n", GetLastError())); result = FALSE; } else { char updaterIdentity[64]; if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY, updaterIdentity, sizeof(updaterIdentity))) { LOG(("The updater.exe application does not contain the Mozilla" " updater identity.\n")); result = FALSE; } if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) { LOG(("The updater.exe identity string is not valid.\n")); result = FALSE; } FreeLibrary(updaterModule); } if (result) { LOG(("The updater.exe application contains the Mozilla" " updater identity.\n")); } else { if (!WriteStatusFailure(argv[1], SERVICE_UPDATER_IDENTITY_ERROR)) { LOG(("Could not write update.status no updater identity.\n")); } return TRUE; } // Check for updater.exe sign problems BOOL updaterSignProblem = FALSE; #ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK updaterSignProblem = !DoesBinaryMatchAllowedCertificates(argv[2], argv[0]); #endif // Only proceed with the update if we have no signing problems if (!updaterSignProblem) { BOOL updateProcessWasStarted = FALSE; if (StartUpdateProcess(argc, argv, updateProcessWasStarted)) { LOG(("updater.exe was launched and run successfully!\n")); LogFlush(); // We might not execute code after StartServiceUpdate because // the service installer will stop the service if it is running. StartServiceUpdate(argc, argv); } else { result = FALSE; LOG(("Error running update process. Updating update.status" " Last error: %d\n", GetLastError())); LogFlush(); // If the update process was started, then updater.exe is responsible for // setting the failure code. If it could not be started then we do the // work. We set an error instead of directly setting status pending // so that the app.update.service.errors pref can be updated when // the callback app restarts. if (!updateProcessWasStarted) { if (!WriteStatusFailure(argv[1], SERVICE_UPDATER_COULD_NOT_BE_STARTED)) { LOG(("Could not write update.status service update failure." "Last error: %d\n", GetLastError())); } } } } else { result = FALSE; LOG(("Could not start process due to certificate check error on " "updater.exe. Updating update.status. Last error: %d\n", GetLastError())); // When there is a certificate check error on the updater.exe application, // we want to write out the error. if (!WriteStatusFailure(argv[1], SERVICE_UPDATER_SIGN_ERROR)) { LOG(("Could not write pending state to update.status. (%d)\n", GetLastError())); } } LocalFree(argv); return result; }
/** * Processes a software update command * * @param argc The number of arguments in argv * @param argv The arguments normally passed to updater.exe * argv[0] must be the path to updater.exe * @return TRUE if the update was successful. */ BOOL ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv) { BOOL result = TRUE; if (argc < 3) { LOG_WARN(("Not enough command line parameters specified. " "Updating update.status.")); // We can only update update.status if argv[1] exists. argv[1] is // the directory where the update.status file exists. if (argc < 2 || !WriteStatusFailure(argv[1], SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) { LOG_WARN(("Could not write update.status service update failure. (%d)", GetLastError())); } return FALSE; } WCHAR installDir[MAX_PATH + 1] = {L'\0'}; if (!GetInstallationDir(argc, argv, installDir)) { LOG_WARN(("Could not get the installation directory")); if (!WriteStatusFailure(argv[1], SERVICE_INSTALLDIR_ERROR)) { LOG_WARN(("Could not write update.status for GetInstallationDir failure.")); } return FALSE; } // Make sure the path to the updater to use for the update is local. // We do this check to make sure that file locking is available for // race condition security checks. BOOL isLocal = FALSE; if (!IsLocalFile(argv[0], isLocal) || !isLocal) { LOG_WARN(("Filesystem in path %ls is not supported (%d)", argv[0], GetLastError())); if (!WriteStatusFailure(argv[1], SERVICE_UPDATER_NOT_FIXED_DRIVE)) { LOG_WARN(("Could not write update.status service update failure. (%d)", GetLastError())); } return FALSE; } nsAutoHandle noWriteLock(CreateFileW(argv[0], GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr)); if (INVALID_HANDLE_VALUE == noWriteLock) { LOG_WARN(("Could not set no write sharing access on file. (%d)", GetLastError())); if (!WriteStatusFailure(argv[1], SERVICE_COULD_NOT_LOCK_UPDATER)) { LOG_WARN(("Could not write update.status service update failure. (%d)", GetLastError())); } return FALSE; } // Verify that the updater.exe that we are executing is the same // as the one in the installation directory which we are updating. // The installation dir that we are installing to is installDir. WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' }; wcsncpy(installDirUpdater, installDir, MAX_PATH); if (!PathAppendSafe(installDirUpdater, L"updater.exe")) { LOG_WARN(("Install directory updater could not be determined.")); result = FALSE; } BOOL updaterIsCorrect; if (result && !VerifySameFiles(argv[0], installDirUpdater, updaterIsCorrect)) { LOG_WARN(("Error checking if the updaters are the same.\n" "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater)); result = FALSE; } if (result && !updaterIsCorrect) { LOG_WARN(("The updaters do not match, updater will not run.")); result = FALSE; } if (result) { LOG(("updater.exe was compared successfully to the installation directory" " updater.exe.")); } else { if (!WriteStatusFailure(argv[1], SERVICE_UPDATER_COMPARE_ERROR)) { LOG_WARN(("Could not write update.status updater compare failure.")); } return FALSE; } // Check to make sure the updater.exe module has the unique updater identity. // This is a security measure to make sure that the signed executable that // we will run is actually an updater. HMODULE updaterModule = LoadLibraryEx(argv[0], nullptr, LOAD_LIBRARY_AS_DATAFILE); if (!updaterModule) { LOG_WARN(("updater.exe module could not be loaded. (%d)", GetLastError())); result = FALSE; } else { char updaterIdentity[64]; if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY, updaterIdentity, sizeof(updaterIdentity))) { LOG_WARN(("The updater.exe application does not contain the Mozilla" " updater identity.")); result = FALSE; } if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) { LOG_WARN(("The updater.exe identity string is not valid.")); result = FALSE; } FreeLibrary(updaterModule); } if (result) { LOG(("The updater.exe application contains the Mozilla" " updater identity.")); } else { if (!WriteStatusFailure(argv[1], SERVICE_UPDATER_IDENTITY_ERROR)) { LOG_WARN(("Could not write update.status no updater identity.")); } return TRUE; } // Check for updater.exe sign problems BOOL updaterSignProblem = FALSE; #ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK updaterSignProblem = !DoesBinaryMatchAllowedCertificates(installDir, argv[0]); #endif // Only proceed with the update if we have no signing problems if (!updaterSignProblem) { BOOL updateProcessWasStarted = FALSE; if (StartUpdateProcess(argc, argv, installDir, updateProcessWasStarted)) { LOG(("updater.exe was launched and run successfully!")); LogFlush(); // Don't attempt to update the service when the update is being staged. if (!IsUpdateBeingStaged(argc, argv)) { // We might not execute code after StartServiceUpdate because // the service installer will stop the service if it is running. StartServiceUpdate(installDir); } } else { result = FALSE; LOG_WARN(("Error running update process. Updating update.status (%d)", GetLastError())); LogFlush(); // If the update process was started, then updater.exe is responsible for // setting the failure code. If it could not be started then we do the // work. We set an error instead of directly setting status pending // so that the app.update.service.errors pref can be updated when // the callback app restarts. if (!updateProcessWasStarted) { if (!WriteStatusFailure(argv[1], SERVICE_UPDATER_COULD_NOT_BE_STARTED)) { LOG_WARN(("Could not write update.status service update failure. (%d)", GetLastError())); } } } } else { result = FALSE; LOG_WARN(("Could not start process due to certificate check error on " "updater.exe. Updating update.status. (%d)", GetLastError())); // When there is a certificate check error on the updater.exe application, // we want to write out the error. if (!WriteStatusFailure(argv[1], SERVICE_UPDATER_SIGN_ERROR)) { LOG_WARN(("Could not write pending state to update.status. (%d)", GetLastError())); } } return result; }
/** * Launch the post update application as the specified user (helper.exe). * It takes in the path of the callback application to calculate the path * of helper.exe. For service updates this is called from both the system * account and the current user account. * * @param installationDir The path to the callback application binary. * @param updateInfoDir The directory where update info is stored. * @param forceSync If true even if the ini file specifies async, the * process will wait for termination of PostUpdate. * @param userToken The user token to run as, if nullptr the current * user will be used. * @return TRUE if there was no error starting the process. */ BOOL LaunchWinPostProcess(const WCHAR *installationDir, const WCHAR *updateInfoDir, bool forceSync, HANDLE userToken) { WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' }; wcsncpy(workingDirectory, installationDir, MAX_PATH); // Launch helper.exe to perform post processing (e.g. registry and log file // modifications) for the update. WCHAR inifile[MAX_PATH + 1] = { L'\0' }; wcsncpy(inifile, installationDir, MAX_PATH); if (!PathAppendSafe(inifile, L"updater.ini")) { return FALSE; } WCHAR exefile[MAX_PATH + 1]; WCHAR exearg[MAX_PATH + 1]; WCHAR exeasync[10]; bool async = true; if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr, exefile, MAX_PATH + 1, inifile)) { return FALSE; } if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg, MAX_PATH + 1, inifile)) { return FALSE; } if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE", exeasync, sizeof(exeasync)/sizeof(exeasync[0]), inifile)) { return FALSE; } WCHAR exefullpath[MAX_PATH + 1] = { L'\0' }; wcsncpy(exefullpath, installationDir, MAX_PATH); if (!PathAppendSafe(exefullpath, exefile)) { return false; } WCHAR dlogFile[MAX_PATH + 1]; if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) { return FALSE; } WCHAR slogFile[MAX_PATH + 1] = { L'\0' }; wcsncpy(slogFile, updateInfoDir, MAX_PATH); if (!PathAppendSafe(slogFile, L"update.log")) { return FALSE; } WCHAR dummyArg[14] = { L'\0' }; wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1); size_t len = wcslen(exearg) + wcslen(dummyArg); WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR)); if (!cmdline) { return FALSE; } wcsncpy(cmdline, dummyArg, len); wcscat(cmdline, exearg); if (forceSync || !_wcsnicmp(exeasync, L"false", 6) || !_wcsnicmp(exeasync, L"0", 2)) { async = false; } // We want to launch the post update helper app to update the Windows // registry even if there is a failure with removing the uninstall.update // file or copying the update.log file. CopyFileW(slogFile, dlogFile, false); STARTUPINFOW si = {sizeof(si), 0}; si.lpDesktop = L""; PROCESS_INFORMATION pi = {0}; bool ok; if (userToken) { ok = CreateProcessAsUserW(userToken, exefullpath, cmdline, nullptr, // no special security attributes nullptr, // no special thread attributes false, // don't inherit filehandles 0, // No special process creation flags nullptr, // inherit my environment workingDirectory, &si, &pi); } else { ok = CreateProcessW(exefullpath, cmdline, nullptr, // no special security attributes nullptr, // no special thread attributes false, // don't inherit filehandles 0, // No special process creation flags nullptr, // inherit my environment workingDirectory, &si, &pi); } free(cmdline); if (ok) { if (!async) WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } return ok; }
/** * Starts the upgrade process for update of the service if it is * already installed. * * @param installDir the installation directory where * maintenanceservice_installer.exe is located. * @return TRUE if successful */ BOOL StartServiceUpdate(LPCWSTR installDir) { // Get a handle to the local computer SCM database SC_HANDLE manager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); if (!manager) { return FALSE; } // Open the service SC_HANDLE svc = OpenServiceW(manager, SVC_NAME, SERVICE_ALL_ACCESS); if (!svc) { CloseServiceHandle(manager); return FALSE; } // If we reach here, then the service is installed, so // proceed with upgrading it. CloseServiceHandle(manager); // The service exists and we opened it, get the config bytes needed DWORD bytesNeeded; if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { CloseServiceHandle(svc); return FALSE; } // Get the service config information, in particular we want the binary // path of the service. mozilla::ScopedDeleteArray<char> serviceConfigBuffer(new char[bytesNeeded]); if (!QueryServiceConfigW(svc, reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), bytesNeeded, &bytesNeeded)) { CloseServiceHandle(svc); return FALSE; } CloseServiceHandle(svc); QUERY_SERVICE_CONFIGW &serviceConfig = *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); PathUnquoteSpacesW(serviceConfig.lpBinaryPathName); // Obtain the temp path of the maintenance service binary WCHAR tmpService[MAX_PATH + 1] = { L'\0' }; if (!PathGetSiblingFilePath(tmpService, serviceConfig.lpBinaryPathName, L"maintenanceservice_tmp.exe")) { return FALSE; } // Get the new maintenance service path from the install dir WCHAR newMaintServicePath[MAX_PATH + 1] = { L'\0' }; wcsncpy(newMaintServicePath, installDir, MAX_PATH); PathAppendSafe(newMaintServicePath, L"maintenanceservice.exe"); // Copy the temp file in alongside the maintenace service. // This is a requirement for maintenance service upgrades. if (!CopyFileW(newMaintServicePath, tmpService, FALSE)) { return FALSE; } // Start the upgrade comparison process STARTUPINFOW si = {0}; si.cb = sizeof(STARTUPINFOW); // No particular desktop because no UI si.lpDesktop = L""; PROCESS_INFORMATION pi = {0}; WCHAR cmdLine[64] = { '\0' }; wcsncpy(cmdLine, L"dummyparam.exe upgrade", sizeof(cmdLine) / sizeof(cmdLine[0]) - 1); BOOL svcUpdateProcessStarted = CreateProcessW(tmpService, cmdLine, nullptr, nullptr, FALSE, 0, nullptr, installDir, &si, &pi); if (svcUpdateProcessStarted) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } return svcUpdateProcessStarted; }
/** * Updates the service description with what is stored in updater.ini * at the same path as the currently executing module binary. * * @param serviceHandle A handle to an opened service with * SERVICE_CHANGE_CONFIG access right * @param TRUE on succcess. */ BOOL UpdateServiceDescription(SC_HANDLE serviceHandle) { WCHAR updaterINIPath[MAX_PATH + 1]; if (!GetModuleFileNameW(NULL, updaterINIPath, sizeof(updaterINIPath) / sizeof(updaterINIPath[0]))) { LOG(("Could not obtain module filename when attempting to " "modify service description. (%d)\n", GetLastError())); return FALSE; } if (!PathRemoveFileSpecW(updaterINIPath)) { LOG(("Could not remove file spec when attempting to " "modify service description. (%d)\n", GetLastError())); return FALSE; } if (!PathAppendSafe(updaterINIPath, L"updater.ini")) { LOG(("Could not append updater.ini filename when attempting to " "modify service description. (%d)\n", GetLastError())); return FALSE; } if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES) { LOG(("updater.ini file does not exist, will not modify " "service description. (%d)\n", GetLastError())); return FALSE; } MaintenanceServiceStringTable serviceStrings; int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings); if (rv != OK || !strlen(serviceStrings.serviceDescription)) { LOG(("updater.ini file does not contain a maintenance " "service description.\n")); return FALSE; } WCHAR serviceDescription[MAX_TEXT_LEN]; if (!MultiByteToWideChar(CP_UTF8, 0, serviceStrings.serviceDescription, -1, serviceDescription, sizeof(serviceDescription) / sizeof(serviceDescription[0]))) { LOG(("Could not convert description to wide string format (%d)\n", GetLastError())); return FALSE; } SERVICE_DESCRIPTIONW descriptionConfig; descriptionConfig.lpDescription = serviceDescription; if (!ChangeServiceConfig2W(serviceHandle, SERVICE_CONFIG_DESCRIPTION, &descriptionConfig)) { LOG(("Could not change service config (%d)\n", GetLastError())); return FALSE; } LOG(("The service description was updated successfully.\n")); return TRUE; }