Bug 1510494 - write elevated updater log and status files to a new directory in the Maintenance Service directory. r=agashlin,mhowell
☠☠ backed out by 6b9cf21a84bc ☠ ☠
authorRobert Strong <robert.bugzilla@gmail.com>
Fri, 04 Oct 2019 03:11:40 +0000
changeset 496280 27df5399e032451d3bdebcd86155ec69da504c99
parent 496279 d2706c5d06eafdc10bbd25fba3f332b6954e3bd3
child 496281 b50de96607f422c1b7a41ed3d5f43635747addfb
push id97151
push userrstrong@mozilla.com
push dateFri, 04 Oct 2019 03:14:02 +0000
treeherderautoland@27df5399e032 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersagashlin, mhowell
bugs1510494
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1510494 - write elevated updater log and status files to a new directory in the Maintenance Service directory. r=agashlin,mhowell Differential Revision: https://phabricator.services.mozilla.com/D46627
toolkit/components/maintenanceservice/maintenanceservice.cpp
toolkit/components/maintenanceservice/workmonitor.cpp
toolkit/mozapps/update/common/updatecommon.cpp
toolkit/mozapps/update/common/updatecommon.h
toolkit/mozapps/update/common/updatehelper.cpp
toolkit/mozapps/update/common/updatehelper.h
toolkit/mozapps/update/common/updatererrors.h
toolkit/mozapps/update/common/updateutils_win.cpp
toolkit/mozapps/update/common/updateutils_win.h
toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js
toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js
toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js
toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js
toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js
toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js
toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js
toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js
toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js
toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js
toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js
toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js
toolkit/mozapps/update/updater/updater.cpp
--- a/toolkit/components/maintenanceservice/maintenanceservice.cpp
+++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp
@@ -37,19 +37,20 @@ int wmain(int argc, WCHAR** argv) {
   // If command line parameter is "forceinstall", install the service
   // even if it is older than what is already installed.
   // If command-line parameter is "upgrade", upgrade the service
   // but do not install it if it is not already installed.
   // If command line parameter is "uninstall", uninstall the service.
   // Otherwise, the service is probably being started by the SCM.
   bool forceInstall = !lstrcmpi(argv[1], L"forceinstall");
   if (!lstrcmpi(argv[1], L"install") || forceInstall) {
-    WCHAR updatePath[MAX_PATH + 1];
-    if (GetLogDirectoryPath(updatePath)) {
-      LogInit(updatePath, L"maintenanceservice-install.log");
+    WCHAR logFilePath[MAX_PATH + 1];
+    if (GetLogDirectoryPath(logFilePath) &&
+        PathAppendSafe(logFilePath, L"maintenanceservice-install.log")) {
+      LogInit(logFilePath);
     }
 
     SvcInstallAction action = InstallSvc;
     if (forceInstall) {
       action = ForceInstallSvc;
       LOG(("Installing service with force specified..."));
     } else {
       LOG(("Installing service..."));
@@ -63,37 +64,39 @@ int wmain(int argc, WCHAR** argv) {
     }
 
     LOG(("The service was installed successfully"));
     LogFinish();
     return 0;
   }
 
   if (!lstrcmpi(argv[1], L"upgrade")) {
-    WCHAR updatePath[MAX_PATH + 1];
-    if (GetLogDirectoryPath(updatePath)) {
-      LogInit(updatePath, L"maintenanceservice-install.log");
+    WCHAR logFilePath[MAX_PATH + 1];
+    if (GetLogDirectoryPath(logFilePath) &&
+        PathAppendSafe(logFilePath, L"maintenanceservice-install.log")) {
+      LogInit(logFilePath);
     }
 
     LOG(("Upgrading service if installed..."));
     if (!SvcInstall(UpgradeSvc)) {
       LOG_WARN(("Could not upgrade service.  (%d)", GetLastError()));
       LogFinish();
       return 1;
     }
 
     LOG(("The service was upgraded successfully"));
     LogFinish();
     return 0;
   }
 
   if (!lstrcmpi(argv[1], L"uninstall")) {
-    WCHAR updatePath[MAX_PATH + 1];
-    if (GetLogDirectoryPath(updatePath)) {
-      LogInit(updatePath, L"maintenanceservice-uninstall.log");
+    WCHAR logFilePath[MAX_PATH + 1];
+    if (GetLogDirectoryPath(logFilePath) &&
+        PathAppendSafe(logFilePath, L"maintenanceservice-uninstall.log")) {
+      LogInit(logFilePath);
     }
     LOG(("Uninstalling service..."));
     if (!SvcUninstall()) {
       LOG_WARN(("Could not uninstall service.  (%d)", GetLastError()));
       LogFinish();
       return 1;
     }
     LOG(("The service was uninstalled successfully"));
@@ -221,20 +224,22 @@ void StartTerminationThread() {
   }
 }
 
 /**
  * Main entry point when running as a service.
  */
 void WINAPI SvcMain(DWORD argc, LPWSTR* argv) {
   // Setup logging, and backup the old logs
-  WCHAR updatePath[MAX_PATH + 1];
-  if (GetLogDirectoryPath(updatePath)) {
-    BackupOldLogs(updatePath, LOGS_TO_KEEP);
-    LogInit(updatePath, L"maintenanceservice.log");
+  WCHAR logFilePath[MAX_PATH + 1];
+  if (GetLogDirectoryPath(logFilePath)) {
+    BackupOldLogs(logFilePath, LOGS_TO_KEEP);
+    if (PathAppendSafe(logFilePath, L"maintenanceservice.log")) {
+      LogInit(logFilePath);
+    }
   }
 
   // Disable every privilege we don't need. Processes started using
   // CreateProcess will use the same token as this process.
   UACHelper::DisablePrivileges(nullptr);
 
   // Register the handler function for the service
   gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler);
--- a/toolkit/components/maintenanceservice/workmonitor.cpp
+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
@@ -27,46 +27,44 @@ using mozilla::UniquePtr;
 #include "servicebase.h"
 #include "registrycertificates.h"
 #include "uachelper.h"
 #include "updatehelper.h"
 #include "pathhash.h"
 #include "updatererrors.h"
 #include "commonupdatedir.h"
 
-#define PATCH_DIR_PATH L"\\updates\\0"
-
 // Wait 15 minutes for an update operation to run at most.
 // Updates usually take less than a minute so this seems like a
 // significantly large and safe amount of time to wait.
 static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000;
 BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
                             LPCWSTR newFileName);
 BOOL DoesFallbackKeyExist();
 
 /*
- * Read the update.status file and sets isApplying to true if
- * the status is set to applying.
+ * Reads the secure 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  patchDirPath
+ *         The update patch directory path
  * @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) {
+static BOOL IsStatusApplying(LPCWSTR patchDirPath, BOOL& isApplying) {
   isApplying = FALSE;
-  WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'};
-  wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
-  if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
-    LOG_WARN(("Could not append path for update.status file"));
+  WCHAR statusFilePath[MAX_PATH + 1] = {L'\0'};
+  if (!GetSecureOutputFilePath(patchDirPath, L".status", statusFilePath)) {
+    LOG_WARN(("Could not get path for the secure update status file"));
     return FALSE;
   }
 
   nsAutoHandle statusFile(
-      CreateFileW(updateStatusFilePath, GENERIC_READ,
+      CreateFileW(statusFilePath, GENERIC_READ,
                   FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                   nullptr, OPEN_EXISTING, 0, nullptr));
 
   if (INVALID_HANDLE_VALUE == statusFile) {
     LOG_WARN(("Could not open update.status file"));
     return FALSE;
   }
 
@@ -580,28 +578,22 @@ BOOL ExecuteServiceCommand(int argc, LPW
   if (argc < 3) {
     LOG_WARN(
         ("Not enough command line arguments to execute a service command"));
     return FALSE;
   }
 
   // The tests work by making sure the log has changed, so we put a
   // unique ID in the log.
-  GUID guid;
-  HRESULT hr = CoCreateGuid(&guid);
-  if (SUCCEEDED(hr)) {
-    RPC_WSTR guidString = RPC_WSTR(L"");
-    if (UuidToString(&guid, &guidString) == RPC_S_OK) {
-      LOG(("Executing service command %ls, ID: %ls", argv[2],
-           reinterpret_cast<LPCWSTR>(guidString)));
-      RpcStringFree(&guidString);
-    } else {
-      // The ID is only used by tests, so failure to allocate it isn't fatal.
-      LOG(("Executing service command %ls", argv[2]));
-    }
+  WCHAR uuidString[MAX_PATH + 1] = {L'\0'};
+  if (GetUUIDString(uuidString)) {
+    LOG(("Executing service command %ls, ID: %ls", argv[2], uuidString));
+  } else {
+    // The ID is only used by tests, so failure to allocate it isn't fatal.
+    LOG(("Executing service command %ls", argv[2]));
   }
 
   BOOL result = FALSE;
   if (!lstrcmpi(argv[2], L"software-update")) {
     // This check is also performed in updater.cpp and is performed here
     // as well since the maintenance service can be called directly.
     if (argc < 4 || !IsValidFullPath(argv[4])) {
       // Since the status file is written to the patch directory and the patch
@@ -624,16 +616,26 @@ BOOL ExecuteServiceCommand(int argc, LPW
     if (_wcsnicmp(argv[4] + fullPathLen - relPathLen, PATCH_DIR_PATH,
                   relPathLen) != 0) {
       LOG_WARN(
           ("The patch directory path subdirectory is not valid for this "
            "application."));
       return FALSE;
     }
 
+    // Remove the secure output files so it is easier to determine when new
+    // files are created in the unelevated updater.
+    RemoveSecureOutputFiles(argv[4]);
+
+    // Create a new secure ID for this update.
+    if (!WriteSecureIDFile(argv[4])) {
+      LOG_WARN(("Unable to write to secure ID file."));
+      return FALSE;
+    }
+
     // This check is also performed in updater.cpp and is performed here
     // as well since the maintenance service can be called directly.
     if (argc < 5 || !IsValidFullPath(argv[5])) {
       LOG_WARN(
           ("The install directory path is not valid for this application."));
       if (!WriteStatusFailure(argv[4],
                               SERVICE_INVALID_INSTALL_DIR_PATH_ERROR)) {
         LOG_WARN(("Could not write update.status for previous failure."));
--- a/toolkit/mozapps/update/common/updatecommon.cpp
+++ b/toolkit/mozapps/update/common/updatecommon.cpp
@@ -44,37 +44,27 @@ typedef struct _REPARSE_DATA_BUFFER {
       UCHAR DataBuffer[1];
     } GenericReparseBuffer;
   } DUMMYUNIONNAME;
 } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
 #endif
 
 UpdateLog::UpdateLog() : logFP(nullptr) {}
 
-void UpdateLog::Init(NS_tchar* sourcePath, const NS_tchar* fileName) {
+void UpdateLog::Init(NS_tchar* logFilePath) {
   if (logFP) {
     return;
   }
 
-  int dstFilePathLen =
-      NS_tsnprintf(mDstFilePath, sizeof(mDstFilePath) / sizeof(mDstFilePath[0]),
-                   NS_T("%s/%s"), sourcePath, fileName);
-  // If the destination path was over the length limit,
-  // disable logging by skipping opening the file and setting logFP.
-  if ((dstFilePathLen > 0) &&
-      (dstFilePathLen <
-       static_cast<int>(sizeof(mDstFilePath) / sizeof(mDstFilePath[0])))) {
-#ifdef XP_WIN
-    if (GetUUIDTempFilePath(sourcePath, L"log", mTmpFilePath)) {
-      logFP = NS_tfopen(mTmpFilePath, NS_T("w"));
-      // Delete this file now so it is possible to tell from the unelevated
-      // updater process if the elevated updater process has written the log.
-      DeleteFileW(mDstFilePath);
-    }
-#elif XP_MACOSX
+  // When the path is over the length limit disable logging by not opening the
+  // file and not setting logFP.
+  int dstFilePathLen = NS_tstrlen(logFilePath);
+  if (dstFilePathLen > 0 && dstFilePathLen < MAXPATHLEN - 1) {
+    NS_tstrncpy(mDstFilePath, logFilePath, MAXPATHLEN);
+#if defined(XP_WIN) || defined(XP_MACOSX)
     logFP = NS_tfopen(mDstFilePath, NS_T("w"));
 #else
     // On platforms that have an updates directory in the installation directory
     // (e.g. platforms other than Windows and Mac) the update log is written to
     // a temporary file and then to the update log file. This is needed since
     // the installation directory is moved during a replace request. This can be
     // removed when the platform's updates directory is located outside of the
     // installation directory.
@@ -121,26 +111,16 @@ void UpdateLog::Finish() {
     }
   }
   fclose(updateLogFP);
   updateLogFP = nullptr;
 #endif
 
   fclose(logFP);
   logFP = nullptr;
-
-#ifdef XP_WIN
-  // When the log file already exists then the elevated updater has already
-  // written the log file and the temp file for the log should be discarded.
-  if (!NS_taccess(mDstFilePath, F_OK)) {
-    DeleteFileW(mTmpFilePath);
-  } else {
-    MoveFileW(mTmpFilePath, mDstFilePath);
-  }
-#endif
 }
 
 void UpdateLog::Flush() {
   if (!logFP) {
     return;
   }
 
   fflush(logFP);
--- a/toolkit/mozapps/update/common/updatecommon.h
+++ b/toolkit/mozapps/update/common/updatecommon.h
@@ -11,33 +11,31 @@
 
 class UpdateLog {
  public:
   static UpdateLog& GetPrimaryLog() {
     static UpdateLog primaryLog;
     return primaryLog;
   }
 
-  void Init(NS_tchar* sourcePath, const NS_tchar* fileName);
+  void Init(NS_tchar* logFilePath);
   void Finish();
   void Flush();
   void Printf(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
   void WarnPrintf(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
 
   ~UpdateLog() { Finish(); }
 
  protected:
   UpdateLog();
   FILE* logFP;
-  NS_tchar mTmpFilePath[MAXPATHLEN];
   NS_tchar mDstFilePath[MAXPATHLEN];
 };
 
 bool IsValidFullPath(NS_tchar* fullPath);
 
 #define LOG_WARN(args) UpdateLog::GetPrimaryLog().WarnPrintf args
 #define LOG(args) UpdateLog::GetPrimaryLog().Printf args
-#define LogInit(PATHNAME_, FILENAME_) \
-  UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_)
+#define LogInit(FILEPATH_) UpdateLog::GetPrimaryLog().Init(FILEPATH_)
 #define LogFinish() UpdateLog::GetPrimaryLog().Finish()
 #define LogFlush() UpdateLog::GetPrimaryLog().Flush()
 
 #endif
--- a/toolkit/mozapps/update/common/updatehelper.cpp
+++ b/toolkit/mozapps/update/common/updatehelper.cpp
@@ -4,16 +4,17 @@
 
 #include <windows.h>
 
 // Needed for CreateToolhelp32Snapshot
 #include <tlhelp32.h>
 #ifndef ONLY_SERVICE_LAUNCHING
 
 #  include <stdio.h>
+#  include <direct.h>
 #  include "mozilla/UniquePtr.h"
 #  include "pathhash.h"
 #  include "shlobj.h"
 #  include "registrycertificates.h"
 #  include "uachelper.h"
 #  include "updatehelper.h"
 #  include "updateutils_win.h"
 
@@ -31,30 +32,228 @@ BOOL PathGetSiblingFilePath(LPWSTR desti
  *
  * @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) {
+  if (wcslen(siblingFilePath) > MAX_PATH) {
     return FALSE;
   }
 
-  wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH);
+  wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH + 1);
   if (!PathRemoveFileSpecW(destinationBuffer)) {
     return FALSE;
   }
 
-  if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) {
+  return PathAppendSafe(destinationBuffer, newFileName);
+}
+
+/**
+ * Obtains the path of the secure directory used to write the status and log
+ * files for updates applied with an elevated updater or an updater that is
+ * launched using the maintenance service.
+ *
+ * Example
+ * Destination buffer value:
+ *   C:\Program Files (x86)\Mozilla Maintenance Service\UpdateLogs
+ *
+ * @param  outBuf
+ *         A buffer of size MAX_PATH + 1 to store the result.
+ * @return TRUE if successful
+ */
+BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) {
+  PWSTR progFilesX86;
+  if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, KF_FLAG_CREATE,
+                                  nullptr, &progFilesX86))) {
+    return FALSE;
+  }
+  if (wcslen(progFilesX86) > MAX_PATH) {
+    CoTaskMemFree(progFilesX86);
+    return FALSE;
+  }
+  wcsncpy(outBuf, progFilesX86, MAX_PATH + 1);
+  CoTaskMemFree(progFilesX86);
+
+  if (!PathAppendSafe(outBuf, L"Mozilla Maintenance Service")) {
+    return FALSE;
+  }
+
+  // Create the Maintenance Service directory in case it doesn't exist.
+  if (!CreateDirectoryW(outBuf, nullptr) &&
+      GetLastError() != ERROR_ALREADY_EXISTS) {
+    return FALSE;
+  }
+
+  if (!PathAppendSafe(outBuf, L"UpdateLogs")) {
+    return FALSE;
+  }
+
+  // Create the secure update output directory in case it doesn't exist.
+  if (!CreateDirectoryW(outBuf, nullptr) &&
+      GetLastError() != ERROR_ALREADY_EXISTS) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**
+ * Obtains the name of the update output file using the update patch directory
+ * path and file extension (must include the '.' separator) passed to this
+ * function.
+ *
+ * Example
+ * Patch directory path parameter:
+ *   C:\ProgramData\Mozilla\updates\0123456789ABCDEF\updates\0
+ * File extension parameter:
+ *   .status
+ * Destination buffer value:
+ *   0123456789ABCDEF.status
+ *
+ * @param  patchDirPath
+ *         The path to the update patch directory.
+ * @param  fileExt
+ *         The file extension for the file including the '.' separator.
+ * @param  outBuf
+ *         A buffer of size MAX_PATH + 1 to store the result.
+ * @return TRUE if successful
+ */
+BOOL GetSecureOutputFileName(LPCWSTR patchDirPath, LPCWSTR fileExt,
+                             LPWSTR outBuf) {
+  size_t fullPathLen = wcslen(patchDirPath);
+  if (fullPathLen > MAX_PATH) {
+    return FALSE;
+  }
+
+  size_t relPathLen = wcslen(PATCH_DIR_PATH);
+  if (relPathLen > fullPathLen) {
+    return FALSE;
+  }
+
+  // The patch directory path must end with updates\0 for updates applied with
+  // an elevated updater or an updater that is launched using the maintenance
+  // service.
+  if (_wcsnicmp(patchDirPath + fullPathLen - relPathLen, PATCH_DIR_PATH,
+                relPathLen) != 0) {
+    return FALSE;
+  }
+
+  wcsncpy(outBuf, patchDirPath, MAX_PATH + 1);
+  if (!PathRemoveFileSpecW(outBuf)) {
+    return FALSE;
+  }
+
+  if (!PathRemoveFileSpecW(outBuf)) {
     return FALSE;
   }
 
-  return PathAppendSafe(destinationBuffer, newFileName);
+  PathStripPathW(outBuf);
+
+  size_t outBufLen = wcslen(outBuf);
+  size_t fileExtLen = wcslen(fileExt);
+  if (outBufLen + fileExtLen > MAX_PATH) {
+    return FALSE;
+  }
+
+  wcsncat(outBuf, fileExt, fileExtLen);
+
+  return TRUE;
+}
+
+/**
+ * Obtains the full path of the secure update output file using the update patch
+ * directory path and file extension (must include the '.' separator) passed to
+ * this function.
+ *
+ * Example
+ * Patch directory path parameter:
+ *   C:\ProgramData\Mozilla\updates\0123456789ABCDEF\updates\0
+ * File extension parameter:
+ *   .status
+ * Destination buffer value:
+ *   C:\Program Files (x86)\Mozilla Maintenance
+ *     Service\UpdateLogs\0123456789ABCDEF.status
+ *
+ * @param  patchDirPath
+ *         The path to the update patch directory.
+ * @param  fileExt
+ *         The file extension for the file including the '.' separator.
+ * @param  outBuf
+ *         A buffer of size MAX_PATH + 1 to store the result.
+ * @return TRUE if successful
+ */
+BOOL GetSecureOutputFilePath(LPCWSTR patchDirPath, LPCWSTR fileExt,
+                             LPWSTR outBuf) {
+  if (!GetSecureOutputDirectoryPath(outBuf)) {
+    return FALSE;
+  }
+
+  WCHAR statusFileName[MAX_PATH + 1] = {L'\0'};
+  if (!GetSecureOutputFileName(patchDirPath, fileExt, statusFileName)) {
+    return FALSE;
+  }
+
+  return PathAppendSafe(outBuf, statusFileName);
+}
+
+/**
+ * Writes a UUID to the ID file in the secure output directory. This is used by
+ * the unelevated updater to determine whether an existing update status file in
+ * the secure output directory has been updated.
+ *
+ * @param  patchDirPath
+ *         The path to the update patch directory.
+ * @return TRUE if successful
+ */
+BOOL WriteSecureIDFile(LPCWSTR patchDirPath) {
+  WCHAR uuidString[MAX_PATH + 1] = {L'\0'};
+  if (!GetUUIDString(uuidString)) {
+    return FALSE;
+  }
+
+  WCHAR idFilePath[MAX_PATH + 1] = {L'\0'};
+  if (!GetSecureOutputFilePath(patchDirPath, L".id", idFilePath)) {
+    return FALSE;
+  }
+
+  FILE* idFile = _wfopen(idFilePath, L"wb+");
+  if (idFile == nullptr) {
+    return FALSE;
+  }
+
+  if (fprintf(idFile, "%ls\n", uuidString) == -1) {
+    fclose(idFile);
+    return FALSE;
+  }
+
+  fclose(idFile);
+
+  return TRUE;
+}
+
+/**
+ * Removes the update status and log files from the secure output directory.
+ *
+ * @param  patchDirPath
+ *         The path to the update patch directory.
+ */
+void RemoveSecureOutputFiles(LPCWSTR patchDirPath) {
+  WCHAR filePath[MAX_PATH + 1] = {L'\0'};
+  if (GetSecureOutputFilePath(patchDirPath, L".id", filePath)) {
+    (void)_wremove(filePath);
+  }
+  if (GetSecureOutputFilePath(patchDirPath, L".status", filePath)) {
+    (void)_wremove(filePath);
+  }
+  if (GetSecureOutputFilePath(patchDirPath, L".log", filePath)) {
+    (void)_wremove(filePath);
+  }
 }
 
 /**
  * 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.
@@ -107,16 +306,20 @@ BOOL StartServiceUpdate(LPCWSTR installD
 
   // 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;
   }
 
+  if (wcslen(installDir) > MAX_PATH) {
+    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)) {
@@ -236,59 +439,49 @@ LaunchServiceSoftwareUpdateCommand(int a
   // Execute the service command by starting the service with
   // the passed in arguments.
   DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv);
   delete[] updaterServiceArgv;
   return ret;
 }
 
 /**
- * Sets update.status to a specific failure code
+ * Writes a specific failure code for the update status to a file in the secure
+ * output directory. The status file's name without the '.' separator and
+ * extension is the same as the update directory name.
  *
- * @param  updateDirPath   The path of the update directory
- * @param  errorCode       Error code to set
- *
+ * @param  patchDirPath
+ *         The path of the update patch directory.
+ * @param  errorCode
+ *         Error code to set
  * @return TRUE if successful
  */
-BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode) {
-  // The temp file is not removed on failure since there is client code that
-  // will remove it.
-  WCHAR tmpUpdateStatusFilePath[MAX_PATH + 1] = {L'\0'};
-  if (!GetUUIDTempFilePath(updateDirPath, L"svc", tmpUpdateStatusFilePath)) {
+BOOL WriteStatusFailure(LPCWSTR patchDirPath, int errorCode) {
+  WCHAR statusFilePath[MAX_PATH + 1] = {L'\0'};
+  if (!GetSecureOutputFilePath(patchDirPath, L".status", statusFilePath)) {
     return FALSE;
   }
 
-  HANDLE tmpStatusFile = CreateFileW(tmpUpdateStatusFilePath, GENERIC_WRITE, 0,
-                                     nullptr, CREATE_ALWAYS, 0, nullptr);
-  if (tmpStatusFile == INVALID_HANDLE_VALUE) {
+  HANDLE hStatusFile = CreateFileW(statusFilePath, GENERIC_WRITE, 0, nullptr,
+                                   CREATE_ALWAYS, 0, nullptr);
+  if (hStatusFile == INVALID_HANDLE_VALUE) {
     return FALSE;
   }
 
   char failure[32];
   sprintf(failure, "failed: %d", errorCode);
   DWORD toWrite = strlen(failure);
   DWORD wrote;
-  BOOL ok = WriteFile(tmpStatusFile, failure, toWrite, &wrote, nullptr);
-  CloseHandle(tmpStatusFile);
+  BOOL ok = WriteFile(hStatusFile, failure, toWrite, &wrote, nullptr);
+  CloseHandle(hStatusFile);
 
   if (!ok || wrote != toWrite) {
     return FALSE;
   }
 
-  WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'};
-  wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
-  if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
-    return FALSE;
-  }
-
-  if (MoveFileExW(tmpUpdateStatusFilePath, updateStatusFilePath,
-                  MOVEFILE_REPLACE_EXISTING) == 0) {
-    return FALSE;
-  }
-
   return TRUE;
 }
 
 #endif
 
 /**
  * Waits for a service to enter a stopped state.
  * This function does not stop the service, it just blocks until the service
--- a/toolkit/mozapps/update/common/updatehelper.h
+++ b/toolkit/mozapps/update/common/updatehelper.h
@@ -7,16 +7,23 @@ DWORD LaunchServiceSoftwareUpdateCommand
 BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
 DWORD WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds);
 DWORD WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds);
 DWORD IsProcessRunning(LPCWSTR filename);
 BOOL DoesFallbackKeyExist();
 BOOL IsLocalFile(LPCWSTR file, BOOL& isLocal);
 DWORD StartServiceCommand(int argc, LPCWSTR* argv);
 BOOL IsUnpromptedElevation(BOOL& isUnpromptedElevation);
+BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf);
+BOOL GetSecureOutputFilePath(LPCWSTR patchDirPath, LPCWSTR fileExt,
+                             LPWSTR outBuf);
+BOOL WriteSecureIDFile(LPCWSTR patchDirPath);
+void RemoveSecureOutputFiles(LPCWSTR patchDirPath);
+
+#define PATCH_DIR_PATH L"\\updates\\0"
 
 #define SVC_NAME L"MozillaMaintenance"
 
 #define BASE_SERVICE_REG_KEY L"SOFTWARE\\Mozilla\\MaintenanceService"
 
 // The test only fallback key, as its name implies, is only present on machines
 // that will use automated tests.  Since automated tests always run from a
 // different directory for each test, the presence of this key bypasses the
--- a/toolkit/mozapps/update/common/updatererrors.h
+++ b/toolkit/mozapps/update/common/updatererrors.h
@@ -93,16 +93,17 @@
 #define REMOVE_FILE_SPEC_ERROR 71
 #define INVALID_APPLYTO_DIR_STAGED_ERROR 72
 #define LOCK_ERROR_PATCH_FILE 73
 #define INVALID_APPLYTO_DIR_ERROR 74
 #define INVALID_INSTALL_DIR_PATH_ERROR 75
 #define INVALID_WORKING_DIR_PATH_ERROR 76
 #define INVALID_CALLBACK_PATH_ERROR 77
 #define INVALID_CALLBACK_DIR_ERROR 78
+#define UPDATE_STATUS_UNCHANGED 79
 
 // Error codes 80 through 99 are reserved for nsUpdateService.js
 
 // The following error codes are only used by updater.exe
 // when a fallback key exists for tests.
 #define FALLBACKKEY_UNKNOWN_ERROR 100
 #define FALLBACKKEY_REGPATH_ERROR 101
 #define FALLBACKKEY_NOKEY_ERROR 102
--- a/toolkit/mozapps/update/common/updateutils_win.cpp
+++ b/toolkit/mozapps/update/common/updateutils_win.cpp
@@ -84,44 +84,79 @@ BOOL PathAppendSafe(LPWSTR base, LPCWSTR
   if (wcslen(base) + wcslen(extra) >= MAX_PATH) {
     return FALSE;
   }
 
   return PathAppendW(base, extra);
 }
 
 /**
+ * Obtains a uuid as a wide string.
+ *
+ * @param  outBuf
+ *         A buffer of size MAX_PATH + 1 to store the result.
+ * @return TRUE if successful
+ */
+BOOL GetUUIDString(LPWSTR outBuf) {
+  UUID uuid;
+  RPC_WSTR uuidString = nullptr;
+
+  // Note: the return value of UuidCreate should always be RPC_S_OK on systems
+  // after Win2K / Win2003 due to the network hardware address no longer being
+  // used to create the UUID.
+  if (UuidCreate(&uuid) != RPC_S_OK) {
+    return FALSE;
+  }
+  if (UuidToStringW(&uuid, &uuidString) != RPC_S_OK) {
+    return FALSE;
+  }
+  if (!uuidString) {
+    return FALSE;
+  }
+
+  if (wcslen(reinterpret_cast<LPCWSTR>(uuidString)) > MAX_PATH) {
+    return FALSE;
+  }
+  wcsncpy(outBuf, reinterpret_cast<LPCWSTR>(uuidString), MAX_PATH + 1);
+  RpcStringFreeW(&uuidString);
+
+  return TRUE;
+}
+
+/**
  * Build a temporary file path whose name component is a UUID.
  *
  * @param  basePath  The base directory path for the temp file
  * @param  prefix    Optional prefix for the beginning of the file name
  * @param  tmpPath   Output full path, with the base directory and the file
  * name. Must already have been allocated with size >= MAX_PATH.
  * @return TRUE if tmpPath was successfully filled in, FALSE on errors
  */
 BOOL GetUUIDTempFilePath(LPCWSTR basePath, LPCWSTR prefix, LPWSTR tmpPath) {
   WCHAR filename[MAX_PATH + 1] = {L"\0"};
   if (prefix) {
-    wcsncpy(filename, prefix, MAX_PATH);
+    if (wcslen(prefix) > MAX_PATH) {
+      return FALSE;
+    }
+    wcsncpy(filename, prefix, MAX_PATH + 1);
   }
 
-  UUID tmpFileNameUuid;
-  RPC_WSTR tmpFileNameString = nullptr;
-  if (UuidCreate(&tmpFileNameUuid) != RPC_S_OK) {
-    return FALSE;
-  }
-  if (UuidToStringW(&tmpFileNameUuid, &tmpFileNameString) != RPC_S_OK) {
-    return FALSE;
-  }
-  if (!tmpFileNameString) {
+  WCHAR tmpFileNameString[MAX_PATH + 1] = {L"\0"};
+  if (!GetUUIDString(tmpFileNameString)) {
     return FALSE;
   }
 
-  wcsncat(filename, (LPCWSTR)tmpFileNameString, MAX_PATH);
-  RpcStringFreeW(&tmpFileNameString);
+  size_t tmpFileNameStringLen = wcslen(tmpFileNameString);
+  if (wcslen(filename) + tmpFileNameStringLen > MAX_PATH) {
+    return FALSE;
+  }
+  wcsncat(filename, tmpFileNameString, tmpFileNameStringLen);
 
-  wcsncpy(tmpPath, basePath, MAX_PATH);
+  if (wcslen(basePath) > MAX_PATH) {
+    return FALSE;
+  }
+  wcsncpy(tmpPath, basePath, MAX_PATH + 1);
   if (!PathAppendSafe(tmpPath, filename)) {
     return FALSE;
   }
 
   return TRUE;
 }
--- a/toolkit/mozapps/update/common/updateutils_win.h
+++ b/toolkit/mozapps/update/common/updateutils_win.h
@@ -31,12 +31,17 @@ struct dirent {
   dirent();
   WCHAR d_name[MAX_PATH + 1];
 };
 
 DIR* opendir(const WCHAR* path);
 int closedir(DIR* dir);
 dirent* readdir(DIR* dir);
 
+// This is the length of the UUID string including null termination returned by
+// GetUUIDString.
+#define UUID_LEN 37
+
 BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
+BOOL GetUUIDString(LPWSTR outBuf);
 BOOL GetUUIDTempFilePath(LPCWSTR basePath, LPCWSTR prefix, LPWSTR tmpPath);
 
 #endif  // WINDIRENT_H__
--- a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
+++ b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
@@ -915,16 +915,28 @@ function setupTestCommon(aAppUpdateAutoE
 
   if (AppConstants.platform == "win") {
     Services.prefs.setBoolPref(
       PREF_APP_UPDATE_SERVICE_ENABLED,
       !!gIsServiceTest
     );
   }
 
+  if (gIsServiceTest) {
+    let exts = ["id", "log", "status"];
+    for (let i = 0; i < exts.length; ++i) {
+      let file = getSecureOutputFile(exts[i]);
+      if (file.exists()) {
+        try {
+          file.remove(false);
+        } catch (e) {}
+      }
+    }
+  }
+
   adjustGeneralPaths();
   createWorldWritableAppUpdateDir();
 
   // Logged once here instead of in the mock directory provider to lessen test
   // log spam.
   debugDump("Updates Directory (UpdRootD) Path: " + getMockUpdRootD().path);
 
   // This prevents a warning about not being able to find the greprefs.js file
@@ -979,16 +991,28 @@ function cleanupTestCommon() {
 
   gTestserver = null;
 
   if (AppConstants.platform == "macosx" || AppConstants.platform == "linux") {
     // This will delete the launch script if it exists.
     getLaunchScript();
   }
 
+  if (gIsServiceTest) {
+    let exts = ["id", "log", "status"];
+    for (let i = 0; i < exts.length; ++i) {
+      let file = getSecureOutputFile(exts[i]);
+      if (file.exists()) {
+        try {
+          file.remove(false);
+        } catch (e) {}
+      }
+    }
+  }
+
   if (AppConstants.platform == "win" && MOZ_APP_BASENAME) {
     let appDir = getApplyDirFile();
     let vendor = MOZ_APP_VENDOR ? MOZ_APP_VENDOR : "Mozilla";
     const REG_PATH =
       "SOFTWARE\\" + vendor + "\\" + MOZ_APP_BASENAME + "\\TaskBarIDs";
     let key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
       Ci.nsIWindowsRegKey
     );
@@ -1500,16 +1524,46 @@ function getMaintSvcDir() {
   if (!maintSvcDir) {
     do_throw("Unable to find the maintenance service install directory");
   }
 
   return maintSvcDir;
 }
 
 /**
+ * Reads the current update operation/state in the status file in the secure
+ * update log directory.
+ *
+ * @return The status value.
+ */
+function readSecureStatusFile() {
+  let file = getSecureOutputFile("status");
+  if (!file.exists()) {
+    debugDump("update status file does not exist, path: " + file.path);
+    return STATE_NONE;
+  }
+  return readFile(file).split("\n")[0];
+}
+
+/**
+ * Get the nsIFile in the secure update log directory. The file name is always
+ * the value of gTestID with either a file extension of 'log' or 'status'.
+ *
+ * @param  aFileExt
+ *         The file extension.
+ * @return The nsIFile of the secure update file.
+ */
+function getSecureOutputFile(aFileExt) {
+  let file = getMaintSvcDir();
+  file.append("UpdateLogs");
+  file.append(gTestID + "." + aFileExt);
+  return file;
+}
+
+/**
  * Get the nsIFile for a Windows special folder determined by the CSIDL
  * passed.
  *
  * @param   aCSIDL
  *          The CSIDL for the Windows special folder.
  * @return  The nsIFile for the Windows special folder.
  * @throws  If called from a platform other than Windows.
  */
@@ -1962,17 +2016,17 @@ function runUpdate(
 
   if (gPIDPersistProcess) {
     gEnv.set("MOZ_TEST_SHORTER_WAIT_PID", "");
   }
 
   let status = readStatusFile();
   if (
     (!gIsServiceTest && process.exitValue != aExpectedExitValue) ||
-    status != aExpectedStatus
+    (status != aExpectedStatus && !gIsServiceTest && !isInvalidArgTest)
   ) {
     if (process.exitValue != aExpectedExitValue) {
       logTestInfo(
         "updater exited with unexpected value! Got: " +
           process.exitValue +
           ", Expected: " +
           aExpectedExitValue
       );
@@ -1983,16 +2037,23 @@ function runUpdate(
           status +
           ", Expected: " +
           aExpectedStatus
       );
     }
     logUpdateLog(FILE_LAST_UPDATE_LOG);
   }
 
+  if (gIsServiceTest && isInvalidArgTest) {
+    let secureStatus = readSecureStatusFile();
+    if (secureStatus != STATE_NONE) {
+      status = secureStatus;
+    }
+  }
+
   if (!gIsServiceTest) {
     Assert.equal(
       process.exitValue,
       aExpectedExitValue,
       "the process exit value" + MSG_SHOULD_EQUAL
     );
   }
   Assert.equal(status, aExpectedStatus, "the update status" + MSG_SHOULD_EQUAL);
--- a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js
@@ -26,23 +26,24 @@ async function run_test() {
     path = path.repeat(1000); // 10000 characters
   }
   runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_INSTALL_DIR_PATH_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_INSTALL_DIR_PATH_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js
@@ -23,23 +23,24 @@ async function run_test() {
     path = "/" + path + "/../" + path;
   }
   runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_INSTALL_DIR_PATH_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_INSTALL_DIR_PATH_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js
@@ -18,23 +18,24 @@ async function run_test() {
   await setupUpdaterTest(FILE_COMPLETE_MAR, false);
   let path = getApplyDirFile("..", false).path;
   runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_APPLYTO_DIR_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_APPLYTO_DIR_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js
@@ -18,23 +18,24 @@ async function run_test() {
   await setupUpdaterTest(FILE_COMPLETE_MAR, false);
   let path = getApplyDirFile("..", false).path;
   runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true, null, null, path, null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_APPLYTO_DIR_STAGED_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js
@@ -18,23 +18,24 @@ async function run_test() {
   await setupUpdaterTest(FILE_COMPLETE_MAR, false);
   let path = "\\\\.\\" + getApplyDirFile(null, false).path;
   runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_WORKING_DIR_PATH_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_WORKING_DIR_PATH_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js
@@ -17,23 +17,24 @@ async function run_test() {
   setTestFilesAndDirsForFailure();
   await setupUpdaterTest(FILE_COMPLETE_MAR, false);
   runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, "test", null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_WORKING_DIR_PATH_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_WORKING_DIR_PATH_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js
@@ -26,23 +26,24 @@ async function run_test() {
     path = path.repeat(1000); // 10000 characters
   }
   runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_INSTALL_DIR_PATH_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_INSTALL_DIR_PATH_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js
@@ -23,23 +23,24 @@ async function run_test() {
     path = "/" + path + "/../" + path;
   }
   runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_INSTALL_DIR_PATH_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_INSTALL_DIR_PATH_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js
@@ -18,23 +18,24 @@ async function run_test() {
   await setupUpdaterTest(FILE_COMPLETE_MAR, false);
   let path = getApplyDirFile("..", false).path;
   runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_APPLYTO_DIR_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_APPLYTO_DIR_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js
@@ -18,23 +18,24 @@ async function run_test() {
   await setupUpdaterTest(FILE_COMPLETE_MAR, false);
   let path = getApplyDirFile("..", false).path;
   runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true, null, null, path, null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_APPLYTO_DIR_STAGED_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js
@@ -18,23 +18,24 @@ async function run_test() {
   await setupUpdaterTest(FILE_COMPLETE_MAR, false);
   let path = "\\\\.\\" + getApplyDirFile(null, false).path;
   runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_WORKING_DIR_PATH_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_WORKING_DIR_PATH_ERROR,
       1
     );
--- a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js
@@ -17,23 +17,24 @@ async function run_test() {
   setTestFilesAndDirsForFailure();
   await setupUpdaterTest(FILE_COMPLETE_MAR, false);
   runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, "test", null);
   standardInit();
   checkPostUpdateRunningFile(false);
   checkFilesAfterUpdateFailure(getApplyDirFile);
   await waitForUpdateXMLFiles();
   if (gIsServiceTest) {
-    checkUpdateManager(
-      STATE_NONE,
-      false,
-      STATE_FAILED,
-      SERVICE_INVALID_WORKING_DIR_PATH_ERROR,
-      1
-    );
+    // The invalid argument service tests launch the maintenance service
+    // directly so the unelevated updater doesn't handle the invalid argument.
+    // By doing this it is possible to test that the maintenance service
+    // properly handles the invalid argument but since the updater isn't used to
+    // launch the maintenance service the update.status file isn't copied from
+    // the secure log directory to the patch directory and the update manager
+    // won't read the failure from the update.status file.
+    checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
   } else {
     checkUpdateManager(
       STATE_NONE,
       false,
       STATE_FAILED,
       INVALID_WORKING_DIR_PATH_ERROR,
       1
     );
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -124,17 +124,16 @@ BOOL PathGetSiblingFilePath(LPWSTR desti
 // return code specified. This prevents multiple launches of the callback
 // application by preventing the elevated process from launching the callback.
 #  define EXIT_WHEN_ELEVATED(path, handle, retCode) \
     {                                               \
       if (handle != INVALID_HANDLE_VALUE) {         \
         CloseHandle(handle);                        \
       }                                             \
       if (NS_tremove(path) && errno != ENOENT) {    \
-        LogFinish();                                \
         return retCode;                             \
       }                                             \
     }
 #endif
 
 //-----------------------------------------------------------------------------
 
 // This BZ2_crc32Table variable lives in libbz2. We just took the
@@ -278,16 +277,22 @@ static bool gSucceeded = false;
 static bool sStagedUpdate = false;
 static bool sReplaceRequest = false;
 static bool sUsingService = false;
 
 #ifdef XP_WIN
 static NS_tchar gCallbackRelPath[MAXPATHLEN];
 static NS_tchar gCallbackBackupPath[MAXPATHLEN];
 static NS_tchar gDeleteDirPath[MAXPATHLEN];
+
+// Whether to copy the update.log and update.status file to the update patch
+// directory from a secure directory.
+static bool gCopyOutputFiles = false;
+// Whether to write the update.log and update.status file to a secure directory.
+static bool gUseSecureOutputPath = false;
 #endif
 
 static const NS_tchar kWhitespace[] = NS_T(" \t");
 static const NS_tchar kNL[] = NS_T("\r\n");
 static const NS_tchar kQuote[] = NS_T("\"");
 
 static inline size_t mmin(size_t a, size_t b) { return (a > b) ? b : a; }
 
@@ -338,16 +343,81 @@ static NS_tchar* mstrtok(const NS_tchar*
 
 #if defined(HAS_ENV_CHECK)
 static bool EnvHasValue(const char* name) {
   const char* val = getenv(name);
   return (val && *val);
 }
 #endif
 
+#ifdef XP_WIN
+/**
+ * Obtains the update ID from the secure id file located in secure output
+ * directory.
+ *
+ * @param  outBuf
+ *         A buffer of size UUID_LEN (e.g. 37) to store the result. The uuid is
+ *         36 characters in length and 1 more for null termination.
+ * @return true if successful
+ */
+bool GetSecureID(char* outBuf) {
+  NS_tchar idFilePath[MAX_PATH + 1] = {L'\0'};
+  if (!GetSecureOutputFilePath(gPatchDirPath, L".id", idFilePath)) {
+    return false;
+  }
+
+  AutoFile idFile(NS_tfopen(idFilePath, NS_T("rb")));
+  if (idFile == nullptr) {
+    return false;
+  }
+
+  size_t read = fread(outBuf, UUID_LEN - 1, 1, idFile);
+  if (read != 1) {
+    return false;
+  }
+
+  outBuf[UUID_LEN] = '\0';
+  return true;
+}
+#endif
+
+/**
+ * Calls LogFinish for the update log. On Windows, the unelevated updater copies
+ * the update status file and the update log file that were written by the
+ * elevated updater from the secure directory to the update patch directory.
+ *
+ * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
+ *       because this function copies the update status file for the elevated
+ *       updater and writing the status file after calling output_finish will
+ *       overwrite it.
+ */
+static void output_finish() {
+  LogFinish();
+#ifdef XP_WIN
+  if (gCopyOutputFiles) {
+    NS_tchar srcStatusPath[MAXPATHLEN + 1] = {NS_T('\0')};
+    if (GetSecureOutputFilePath(gPatchDirPath, L".status", srcStatusPath)) {
+      NS_tchar dstStatusPath[MAXPATHLEN + 1] = {NS_T('\0')};
+      NS_tsnprintf(dstStatusPath,
+                   sizeof(dstStatusPath) / sizeof(dstStatusPath[0]),
+                   NS_T("%s\\update.status"), gPatchDirPath);
+      CopyFileW(srcStatusPath, dstStatusPath, false);
+    }
+
+    NS_tchar srcLogPath[MAXPATHLEN + 1] = {NS_T('\0')};
+    if (GetSecureOutputFilePath(gPatchDirPath, L".log", srcLogPath)) {
+      NS_tchar dstLogPath[MAXPATHLEN + 1] = {NS_T('\0')};
+      NS_tsnprintf(dstLogPath, sizeof(dstLogPath) / sizeof(dstLogPath[0]),
+                   NS_T("%s\\update.log"), gPatchDirPath);
+      CopyFileW(srcLogPath, dstLogPath, false);
+    }
+  }
+#endif
+}
+
 /**
  * Coverts a relative update path to a full path.
  *
  * @param  relpath
  *         The relative path to convert to a full path.
  * @return valid filesystem full path or nullptr if memory allocation fails.
  */
 static NS_tchar* get_full_path(const NS_tchar* relpath) {
@@ -1953,19 +2023,25 @@ bool LaunchWinPostProcess(const WCHAR* i
 #  endif
 
   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;
+  if (gCopyOutputFiles) {
+    if (!GetSecureOutputFilePath(gPatchDirPath, L".log", slogFile)) {
+      return false;
+    }
+  } else {
+    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));
@@ -2037,62 +2113,88 @@ static void LaunchCallbackApp(const NS_t
     }
   }
 #else
 #  warning "Need implementaton of LaunchCallbackApp"
 #endif
 }
 
 static bool WriteToFile(const NS_tchar* aFilename, const char* aStatus) {
-  NS_tchar filename[MAXPATHLEN] = {NS_T('\0')};
+  NS_tchar statusFilePath[MAXPATHLEN + 1] = {NS_T('\0')};
 #if defined(XP_WIN)
-  // The temp file is not removed on failure since there is client code that
-  // will remove it.
-  if (!GetUUIDTempFilePath(gPatchDirPath, L"sta", filename)) {
-    return false;
+  if (gUseSecureOutputPath) {
+    if (!GetSecureOutputFilePath(gPatchDirPath, L".status", statusFilePath)) {
+      return false;
+    }
+  } else {
+    NS_tsnprintf(statusFilePath,
+                 sizeof(statusFilePath) / sizeof(statusFilePath[0]),
+                 NS_T("%s\\%s"), gPatchDirPath, aFilename);
   }
 #else
-  NS_tsnprintf(filename, sizeof(filename) / sizeof(filename[0]), NS_T("%s/%s"),
-               gPatchDirPath, aFilename);
-#endif
-
+  NS_tsnprintf(statusFilePath,
+               sizeof(statusFilePath) / sizeof(statusFilePath[0]),
+               NS_T("%s/%s"), gPatchDirPath, aFilename);
   // Make sure that the directory for the update status file exists
-  if (ensure_parent_dir(filename)) {
+  if (ensure_parent_dir(statusFilePath)) {
     return false;
   }
-
-  // This is scoped to make the AutoFile close the file so it is possible to
-  // move the temp file to the update.status file on Windows.
-  {
-    AutoFile file(NS_tfopen(filename, NS_T("wb+")));
-    if (file == nullptr) {
-      return false;
-    }
-
-    if (fwrite(aStatus, strlen(aStatus), 1, file) != 1) {
-      return false;
-    }
+#endif
+
+  AutoFile statusFile(NS_tfopen(statusFilePath, NS_T("wb+")));
+  if (statusFile == nullptr) {
+    return false;
+  }
+
+  if (fwrite(aStatus, strlen(aStatus), 1, statusFile) != 1) {
+    return false;
   }
 
 #if defined(XP_WIN)
-  NS_tchar dstfilename[MAXPATHLEN] = {NS_T('\0')};
-  NS_tsnprintf(dstfilename, sizeof(dstfilename) / sizeof(dstfilename[0]),
-               NS_T("%s\\%s"), gPatchDirPath, aFilename);
-  if (MoveFileExW(filename, dstfilename, MOVEFILE_REPLACE_EXISTING) == 0) {
-    return false;
+  if (gUseSecureOutputPath) {
+    // This is done after the update status file has been written so if the
+    // write to the update status file fails an existing update status file
+    // won't be used.
+    if (!WriteSecureIDFile(gPatchDirPath)) {
+      return false;
+    }
   }
 #endif
 
   return true;
 }
 
+/**
+ * Writes a string to the update.status file.
+ *
+ * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
+ *       because the output_finish function copies the update status file for
+ *       the elevated updater and writing the status file after calling
+ *       output_finish will overwrite it.
+ *
+ * @param  aStatus
+ *         The string to write to the update.status file.
+ * @return true on success.
+ */
 static bool WriteStatusFile(const char* aStatus) {
   return WriteToFile(NS_T("update.status"), aStatus);
 }
 
+/**
+ * Writes a string to the update.status file based on the status param.
+ *
+ * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
+ *       because the output_finish function copies the update status file for
+ *       the elevated updater and writing the status file after calling
+ *       output_finish will overwrite it.
+ *
+ * @param  status
+ *         A status code used to determine what string to write to the
+ *         update.status file (see code).
+ */
 static void WriteStatusFile(int status) {
   const char* text;
 
   char buf[32];
   if (status == OK) {
     if (sStagedUpdate) {
       text = "applied\n";
     } else {
@@ -2134,30 +2236,31 @@ static bool IsUpdateStatusPendingService
 
   return (strncmp(buf, kPendingService, sizeof(kPendingService) - 1) == 0) ||
          (strncmp(buf, kAppliedService, sizeof(kAppliedService) - 1) == 0);
 }
 #endif
 
 #ifdef XP_WIN
 /*
- * Read the update.status file and sets isSuccess to true if
- * the status is set to succeeded.
+ * Reads the secure update status file and sets isSucceeded to true if the
+ * status is set to succeeded.
  *
  * @param  isSucceeded Out parameter for specifying if the status
  *         is set to succeeded or not.
  * @return true if the information was retrieved and it is succeeded.
  */
-static bool IsUpdateStatusSucceeded(bool& isSucceeded) {
+static bool IsSecureUpdateStatusSucceeded(bool& isSucceeded) {
   isSucceeded = false;
-  NS_tchar filename[MAXPATHLEN];
-  NS_tsnprintf(filename, sizeof(filename) / sizeof(filename[0]),
-               NS_T("%s/update.status"), gPatchDirPath);
-
-  AutoFile file(NS_tfopen(filename, NS_T("rb")));
+  NS_tchar statusFilePath[MAX_PATH + 1] = {L'\0'};
+  if (!GetSecureOutputFilePath(gPatchDirPath, L".status", statusFilePath)) {
+    return FALSE;
+  }
+
+  AutoFile file(NS_tfopen(statusFilePath, NS_T("rb")));
   if (file == nullptr) {
     return false;
   }
 
   char buf[32] = {0};
   fread(buf, sizeof(buf), 1, file);
 
   const char kSucceeded[] = "succeeded";
@@ -2682,16 +2785,25 @@ int NS_main(int argc, NS_tchar** argv) {
     }
 #endif
     return 1;
   }
   // The directory containing the update information.
   NS_tstrncpy(gPatchDirPath, argv[1], MAXPATHLEN);
   gPatchDirPath[MAXPATHLEN - 1] = NS_T('\0');
 
+#ifdef XP_WIN
+  NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')};
+  NS_tsnprintf(elevatedLockFilePath,
+               sizeof(elevatedLockFilePath) / sizeof(elevatedLockFilePath[0]),
+               NS_T("%s\\update_elevated.lock"), gPatchDirPath);
+  gUseSecureOutputPath =
+      sUsingService || (NS_tremove(elevatedLockFilePath) && errno != ENOENT);
+#endif
+
   // This check is also performed in workmonitor.cpp since the maintenance
   // service can be called directly.
   if (!IsValidFullPath(argv[2])) {
     WriteStatusFile(INVALID_INSTALL_DIR_PATH_ERROR);
     fprintf(stderr,
             "The install directory path is not valid for this "
             "application (" LOG_S ")\n",
             argv[2]);
@@ -2846,26 +2958,43 @@ int NS_main(int argc, NS_tchar** argv) {
     }
     t1.Join();
 
     LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex, false);
     return gSucceeded ? 0 : 1;
   }
 #endif
 
-  LogInit(gPatchDirPath, NS_T("update.log"));
+  NS_tchar logFilePath[MAXPATHLEN + 1] = {L'\0'};
+#ifdef XP_WIN
+  if (gUseSecureOutputPath) {
+    // Remove the secure output files so it is easier to determine when new
+    // files are created in the unelevated updater.
+    RemoveSecureOutputFiles(gPatchDirPath);
+
+    (void)GetSecureOutputFilePath(gPatchDirPath, L".log", logFilePath);
+  } else {
+    NS_tsnprintf(logFilePath, sizeof(logFilePath) / sizeof(logFilePath[0]),
+                 NS_T("%s\\update.log"), gPatchDirPath);
+  }
+#else
+  NS_tsnprintf(logFilePath, sizeof(logFilePath) / sizeof(logFilePath[0]),
+               NS_T("%s/update.log"), gPatchDirPath);
+#endif
+  LogInit(logFilePath);
 
   if (!WriteStatusFile("applying")) {
     LOG(("failed setting status to 'applying'"));
 #ifdef XP_MACOSX
     if (isElevated) {
       freeArguments(argc, argv);
       CleanupElevatedMacUpdate(true);
     }
 #endif
+    output_finish();
     return 1;
   }
 
   if (sStagedUpdate) {
     LOG(("Performing a staged update"));
   } else if (sReplaceRequest) {
     LOG(("Performing a replace request"));
   }
@@ -2878,37 +3007,37 @@ int NS_main(int argc, NS_tchar** argv) {
   // These checks are also performed in workmonitor.cpp since the maintenance
   // service can be called directly.
   if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0) {
     if (!sStagedUpdate && !sReplaceRequest) {
       WriteStatusFile(INVALID_APPLYTO_DIR_ERROR);
       LOG(
           ("Installation directory and working directory must be the same "
            "for non-staged updates. Exiting."));
-      LogFinish();
+      output_finish();
       return 1;
     }
 
     NS_tchar workingDirParent[MAX_PATH];
     NS_tsnprintf(workingDirParent,
                  sizeof(workingDirParent) / sizeof(workingDirParent[0]),
                  NS_T("%s"), gWorkingDirPath);
     if (!PathRemoveFileSpecW(workingDirParent)) {
       WriteStatusFile(REMOVE_FILE_SPEC_ERROR);
       LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError()));
-      LogFinish();
+      output_finish();
       return 1;
     }
 
     if (_wcsnicmp(workingDirParent, gInstallDirPath, MAX_PATH) != 0) {
       WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR);
       LOG(
           ("The apply-to directory must be the same as or "
            "a child of the installation directory! Exiting."));
-      LogFinish();
+      output_finish();
       return 1;
     }
   }
 #endif
 
 #ifdef XP_WIN
   if (pid > 0) {
     HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD)pid);
@@ -2947,21 +3076,20 @@ int NS_main(int argc, NS_tchar** argv) {
 
   // lastFallbackError keeps track of the last error for the service not being
   // used, in case of an error when fallback is not enabled we write the
   // error to the update.status file.
   // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
   // we will instead fallback to not using the service and display a UAC prompt.
   int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR;
 
-  // Launch a second instance of the updater with the runas verb on Windows
-  // when write access is denied to the installation directory and the update
-  // isn't being staged.
+  // Check whether a second instance of the updater should be launched by the
+  // maintenance service or with the 'runas' verb when write access is denied to
+  // the installation directory and the update isn't being staged.
   HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE;
-  NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')};
   if (!sUsingService &&
       (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) {
     NS_tchar updateLockFilePath[MAXPATHLEN];
     if (sStagedUpdate) {
       // When staging an update, the lock file is:
       // <install_dir>\updated.update_in_progress.lock
       NS_tsnprintf(updateLockFilePath,
                    sizeof(updateLockFilePath) / sizeof(updateLockFilePath[0]),
@@ -2993,27 +3121,24 @@ int NS_main(int argc, NS_tchar** argv) {
       if (sReplaceRequest) {
         // Note that this could fail, but if it does, there isn't too much we
         // can do in order to recover anyways.
         WriteStatusFile("pending");
       } else if (sStagedUpdate) {
         WriteStatusFile(DELETE_ERROR_STAGING_LOCK_FILE);
       }
       LOG(("Update already in progress! Exiting"));
+      output_finish();
       return 1;
     }
 
     updateLockFileHandle =
         CreateFileW(updateLockFilePath, GENERIC_READ | GENERIC_WRITE, 0,
                     nullptr, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr);
 
-    NS_tsnprintf(elevatedLockFilePath,
-                 sizeof(elevatedLockFilePath) / sizeof(elevatedLockFilePath[0]),
-                 NS_T("%s/update_elevated.lock"), gPatchDirPath);
-
     // Even if a file has no sharing access, you can still get its attributes
     bool startedFromUnelevatedUpdater =
         GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
 
     // If we're running from the service, then we were started with the same
     // token as the service so the permissions are already dropped.  If we're
     // running from an elevated updater that was started from an unelevated
     // updater, then we drop the permissions here. We do not drop the
@@ -3024,31 +3149,34 @@ int NS_main(int argc, NS_tchar** argv) {
       // CreateProcess will use the same token as this process.
       UACHelper::DisablePrivileges(nullptr);
     }
 
     if (updateLockFileHandle == INVALID_HANDLE_VALUE ||
         (useService && testOnlyFallbackKeyExists && noServiceFallback)) {
       HANDLE elevatedFileHandle;
       if (NS_tremove(elevatedLockFilePath) && errno != ENOENT) {
-        fprintf(stderr, "Unable to create elevated lock file! Exiting\n");
+        LOG(("Unable to create elevated lock file! Exiting"));
+        output_finish();
         return 1;
       }
 
       elevatedFileHandle =
           CreateFileW(elevatedLockFilePath, GENERIC_READ | GENERIC_WRITE, 0,
                       nullptr, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr);
       if (elevatedFileHandle == INVALID_HANDLE_VALUE) {
         LOG(("Unable to create elevated lock file! Exiting"));
+        output_finish();
         return 1;
       }
 
       auto cmdLine = mozilla::MakeCommandLine(argc - 1, argv + 1);
       if (!cmdLine) {
         CloseHandle(elevatedFileHandle);
+        output_finish();
         return 1;
       }
 
       // Make sure the path to the updater to use for the update is on local.
       // We do this check to make sure that file locking is available for
       // race condition security checks.
       if (useService) {
         BOOL isLocal = FALSE;
@@ -3097,16 +3225,21 @@ int NS_main(int argc, NS_tchar** argv) {
       // launching the service command.  This is no longer needed now
       // since the service command is launched from updater.exe.  If anything
       // fails in between, we can fall back to using the normal update process
       // on our own.
 
       // If we still want to use the service try to launch the service
       // comamnd for the update.
       if (useService) {
+        // Get the secure ID before trying to update so it is possible to
+        // determine if the updater or the maintenance service has created a
+        // new one.
+        char uuidStringBefore[UUID_LEN] = {'\0'};
+        bool checkID = GetSecureID(uuidStringBefore);
         // Write a catchall service failure status in case it fails without
         // changing the status.
         WriteStatusFile(SERVICE_UPDATE_STATUS_UNCHANGED);
 
         // If the update couldn't be started, then set useService to false so
         // we do the update the old way.
         DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR*)argv);
         useService = (ret == ERROR_SUCCESS);
@@ -3134,16 +3267,28 @@ int NS_main(int argc, NS_tchar** argv) {
           }
 
           lastState = WaitForServiceStop(SVC_NAME, 1);
           if (lastState != SERVICE_STOPPED) {
             // If the service doesn't stop after 10 minutes there is
             // something seriously wrong.
             lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR;
             useService = false;
+          } else {
+            // Copy the secure output files if the secure ID has changed.
+            gCopyOutputFiles = true;
+            char uuidStringAfter[UUID_LEN] = {'\0'};
+            if (checkID && GetSecureID(uuidStringAfter) &&
+                strncmp(uuidStringBefore, uuidStringAfter,
+                        sizeof(uuidStringBefore)) == 0) {
+              LOG(
+                  ("The secure ID hasn't changed after launching the updater "
+                   "using the service"));
+              gCopyOutputFiles = false;
+            }
           }
         } else {
           lastFallbackError = FALLBACKKEY_LAUNCH_ERROR;
         }
       }
 
       // If the service can't be used when staging an update, make sure that
       // the UAC prompt is not shown! In this case, just set the status to
@@ -3151,29 +3296,29 @@ int NS_main(int argc, NS_tchar** argv) {
       if (!useService && sStagedUpdate) {
         if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
           CloseHandle(updateLockFileHandle);
         }
         WriteStatusFile(UNEXPECTED_STAGING_ERROR);
         LOG(
             ("Non-critical update staging error! Falling back to non-staged "
              "updates and exiting"));
+        output_finish();
         return 0;
       }
 
-      // If we started the service command, and it finished, check the
-      // update.status file to make sure it succeeded, and if it did
-      // we need to manually start the PostUpdate process from the
-      // current user's session of this unelevated updater.exe the
-      // current process is running as.
-      // Note that we don't need to do this if we're just staging the update,
-      // as the PostUpdate step runs when performing the replacing in that case.
+      // If we started the service command, and it finished, check the secure
+      // update status file to make sure that it succeeded, and if it did we
+      // need to launch the PostUpdate process in the unelevated updater which
+      // is running in the current user's session. Note that we don't need to do
+      // this when staging an update since the PostUpdate step runs during the
+      // replace request.
       if (useService && !sStagedUpdate) {
         bool updateStatusSucceeded = false;
-        if (IsUpdateStatusSucceeded(updateStatusSucceeded) &&
+        if (IsSecureUpdateStatusSucceeded(updateStatusSucceeded) &&
             updateStatusSucceeded) {
           if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
             fprintf(stderr,
                     "The post update process which runs as the user"
                     " for service update could not be launched.");
           }
         }
       }
@@ -3181,69 +3326,112 @@ int NS_main(int argc, NS_tchar** argv) {
       // If we didn't want to use the service at all, or if an update was
       // already happening, or launching the service command failed, then
       // launch the elevated updater.exe as we do without the service.
       // We don't launch the elevated updater in the case that we did have
       // write access all along because in that case the only reason we're
       // using the service is because we are testing.
       if (!useService && !noServiceFallback &&
           updateLockFileHandle == INVALID_HANDLE_VALUE) {
+        // Get the secure ID before trying to update so it is possible to
+        // determine if the updater has created a new one.
+        char uuidStringBefore[UUID_LEN] = {'\0'};
+        bool checkID = GetSecureID(uuidStringBefore);
+        // Write a catchall failure status in case it fails without changing the
+        // status.
+        WriteStatusFile(UPDATE_STATUS_UNCHANGED);
+
         SHELLEXECUTEINFO sinfo;
         memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
         sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
         sinfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_FLAG_DDEWAIT |
                       SEE_MASK_NOCLOSEPROCESS;
         sinfo.hwnd = nullptr;
         sinfo.lpFile = argv[0];
         sinfo.lpParameters = cmdLine.get();
         sinfo.lpVerb = L"runas";
         sinfo.nShow = SW_SHOWNORMAL;
 
         bool result = ShellExecuteEx(&sinfo);
 
         if (result) {
           WaitForSingleObject(sinfo.hProcess, INFINITE);
           CloseHandle(sinfo.hProcess);
+
+          // Copy the secure output files if the secure ID has changed.
+          gCopyOutputFiles = true;
+          char uuidStringAfter[UUID_LEN] = {'\0'};
+          if (checkID && GetSecureID(uuidStringAfter) &&
+              strncmp(uuidStringBefore, uuidStringAfter,
+                      sizeof(uuidStringBefore)) == 0) {
+            LOG(
+                ("The secure ID hasn't changed after launching the updater "
+                 "using runas"));
+            gCopyOutputFiles = false;
+          }
         } else {
+          // Don't copy the secure output files if the elevation request was
+          // canceled since the status file written below is in the patch
+          // directory. At this point it should already be set to false and this
+          // is set here to make it clear that it should be false at this point
+          // and to prevent future changes from regressing this code.
+          gCopyOutputFiles = false;
           WriteStatusFile(ELEVATION_CANCELED);
         }
       }
 
+      // Note: The PostUpdate process is launched by the elevated updater which
+      // is running in the current user's session when the update is successful
+      // and doesn't need to be performed by the unelevated updater as is done
+      // when the maintenance service launches the updater.
+
       if (argc > callbackIndex) {
         LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
                           sUsingService);
       }
 
       CloseHandle(elevatedFileHandle);
 
       if (!useService && !noServiceFallback &&
           INVALID_HANDLE_VALUE == updateLockFileHandle) {
         // We didn't use the service and we did run the elevated updater.exe.
         // The elevated updater.exe is responsible for writing out the
         // update.status file.
+        output_finish();
         return 0;
       } else if (useService) {
         // The service command was launched. The service is responsible for
         // writing out the update.status file.
         if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
           CloseHandle(updateLockFileHandle);
         }
+        output_finish();
         return 0;
       } else {
         // Otherwise the service command was not launched at all.
         // We are only reaching this code path because we had write access
         // all along to the directory and a fallback key existed, and we
         // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists).
         // We only currently use this env var from XPCShell tests.
         CloseHandle(updateLockFileHandle);
         WriteStatusFile(lastFallbackError);
+        output_finish();
         return 0;
       }
+      // This is the end of the code block for launching another instance of the
+      // updater using either the maintenance service or with the 'runas' verb
+      // when the updater doesn't have write access to the installation
+      // directory.
     }
-  }
+    // This is the end of the code block when the updater was not launched by
+    // the service that checks whether the updater has write access to the
+    // installation directory.
+  }
+  // If we made it this far this is the updater instance that will perform the
+  // actual update and gCopyOutputFiles will be false (e.g. the default value).
 #endif
 
   if (sStagedUpdate) {
 #ifdef TEST_UPDATER
     // This allows testing that the correct UI after an update staging failure
     // that falls back to applying the update on startup. It is simulated due
     // to the difficulty of creating the conditions for this type of staging
     // failure.
@@ -3251,16 +3439,17 @@ int NS_main(int argc, NS_tchar** argv) {
 #  ifdef XP_WIN
       if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
         CloseHandle(updateLockFileHandle);
       }
 #  endif
       // WRITE_ERROR is one of the cases where the staging failure falls back to
       // applying the update on startup.
       WriteStatusFile(WRITE_ERROR);
+      output_finish();
       return 0;
     }
 #endif
     // When staging updates, blow away the old installation directory and create
     // it from scratch.
     ensure_remove_recursive(gWorkingDirPath);
   }
   if (!sReplaceRequest) {
@@ -3268,28 +3457,29 @@ int NS_main(int argc, NS_tchar** argv) {
     int rv = NS_tmkdir(gWorkingDirPath, 0755);
     if (rv != OK && errno != EEXIST) {
 #ifdef XP_MACOSX
       if (isElevated) {
         freeArguments(argc, argv);
         CleanupElevatedMacUpdate(true);
       }
 #endif
+      output_finish();
       return 1;
     }
   }
 
 #ifdef XP_WIN
   NS_tchar applyDirLongPath[MAXPATHLEN];
   if (!GetLongPathNameW(
           gWorkingDirPath, applyDirLongPath,
           sizeof(applyDirLongPath) / sizeof(applyDirLongPath[0]))) {
+    WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH);
     LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath));
-    LogFinish();
-    WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH);
+    output_finish();
     EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
     if (argc > callbackIndex) {
       LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
                         sUsingService);
     }
     return 1;
   }
 
@@ -3331,19 +3521,19 @@ int NS_main(int argc, NS_tchar** argv) {
                   argv[callbackIndex] +
                       std::max(callbackPrefixLength, commonPrefixLength),
                   bufferLeft);
       targetPath = buffer;
     }
     if (!GetLongPathNameW(
             targetPath, callbackLongPath,
             sizeof(callbackLongPath) / sizeof(callbackLongPath[0]))) {
+      WriteStatusFile(WRITE_ERROR_CALLBACK_PATH);
       LOG(("NS_main: unable to find callback file: " LOG_S, targetPath));
-      LogFinish();
-      WriteStatusFile(WRITE_ERROR_CALLBACK_PATH);
+      output_finish();
       EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
       if (argc > callbackIndex) {
         LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
                           sUsingService);
       }
       return 1;
     }
 
@@ -3377,39 +3567,38 @@ int NS_main(int argc, NS_tchar** argv) {
           sizeof(gCallbackBackupPath) / sizeof(gCallbackBackupPath[0]);
       const int callbackBackupPathLen =
           NS_tsnprintf(gCallbackBackupPath, callbackBackupPathBufSize,
                        NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]);
 
       if (callbackBackupPathLen < 0 ||
           callbackBackupPathLen >=
               static_cast<int>(callbackBackupPathBufSize)) {
+        WriteStatusFile(USAGE_ERROR);
         LOG(("NS_main: callback backup path truncated"));
-        LogFinish();
-        WriteStatusFile(USAGE_ERROR);
+        output_finish();
 
         // Don't attempt to launch the callback when the callback path is
         // longer than expected.
         EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
         return 1;
       }
 
       // Make a copy of the callback executable so it can be read when patching.
       if (!CopyFileW(argv[callbackIndex], gCallbackBackupPath, false)) {
         DWORD copyFileError = GetLastError();
-        LOG(("NS_main: failed to copy callback file " LOG_S
-             " into place at " LOG_S,
-             argv[callbackIndex], gCallbackBackupPath));
-        LogFinish();
         if (copyFileError == ERROR_ACCESS_DENIED) {
           WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
         } else {
           WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
         }
-
+        LOG(("NS_main: failed to copy callback file " LOG_S
+             " into place at " LOG_S,
+             argv[callbackIndex], gCallbackBackupPath));
+        output_finish();
         EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
         LaunchCallbackApp(argv[callbackIndex], argc - callbackIndex,
                           argv + callbackIndex, sUsingService);
         return 1;
       }
 
       // Since the process may be signaled as exited by WaitForSingleObject
       // before the release of the executable image try to lock the main
@@ -3442,29 +3631,29 @@ int NS_main(int argc, NS_tchar** argv) {
       // CreateFileW will fail if the callback executable is already in use.
       if (callbackFile == INVALID_HANDLE_VALUE) {
         // Only fail the update if the last error was not a sharing violation.
         if (lastWriteError != ERROR_SHARING_VIOLATION) {
           LOG(
               ("NS_main: callback app file in use, failed to exclusively open "
                "executable file: " LOG_S,
                argv[callbackIndex]));
-          LogFinish();
           if (lastWriteError == ERROR_ACCESS_DENIED) {
             WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
           } else {
             WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
           }
 
           if (NS_tremove(gCallbackBackupPath) && errno != ENOENT) {
             LOG(
                 ("NS_main: unable to remove backup of callback app file, "
                  "path: " LOG_S,
                  gCallbackBackupPath));
           }
+          output_finish();
           EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
           LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
                             sUsingService);
           return 1;
         }
         LOG(
             ("NS_main: callback app file in use, continuing without "
              "exclusive access for executable file: " LOG_S,
@@ -3557,17 +3746,17 @@ int NS_main(int argc, NS_tchar** argv) {
     // If the group ownership of the Firefox .app bundle was set to the "admin"
     // group during a previous elevated update, we need to ensure that all files
     // in the bundle have group ownership of "admin" as well as write permission
     // for the group to not break updates in the future.
     SetGroupOwnershipAndPermissions(gInstallDirPath);
   }
 #endif /* XP_MACOSX */
 
-  LogFinish();
+  output_finish();
 
   int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex
 #ifdef XP_WIN
                                                 ,
                                                 elevatedLockFilePath,
                                                 updateLockFileHandle
 #elif XP_MACOSX
                                                   ,