Test Windows patch for pre m-c pushing
☠☠ backed out by 4ad053cb848f ☠ ☠
authorBrian R. Bondy <netzen@gmail.com>
Thu, 02 Oct 2014 21:50:58 -0400
changeset 491317 d25cccda055f52e6534aa044b0a3f518f6160c0c
parent 491316 ebf3623173cf27141a63305b95583474c7e3fd84
child 491319 f38d74b29015725c5b20de96cf75d75fa53aca97
push id47343
push userbmo:dothayer@mozilla.com
push dateWed, 01 Mar 2017 22:58:58 +0000
milestone35.0a1
Test Windows patch for pre m-c pushing
toolkit/components/maintenanceservice/workmonitor.cpp
toolkit/components/telemetry/Histograms.json
toolkit/mozapps/update/common/errors.h
toolkit/mozapps/update/common/uachelper.cpp
toolkit/mozapps/update/common/uachelper.h
toolkit/mozapps/update/common/updatehelper.cpp
toolkit/mozapps/update/common/updatehelper.h
toolkit/mozapps/update/nsUpdateService.js
toolkit/mozapps/update/tests/unit_aus_update/head_update.js
toolkit/mozapps/update/updater/moz.build
toolkit/mozapps/update/updater/updater.cpp
--- a/toolkit/components/maintenanceservice/workmonitor.cpp
+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
@@ -25,18 +25,16 @@
 #include "errors.h"
 
 // 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;
 char16_t* MakeCommandLine(int argc, char16_t **argv);
 BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
-BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer,  LPCWSTR siblingFilePath, 
-                            LPCWSTR newFileName);
 
 /*
  * 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.
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3392,19 +3392,25 @@
   "UPDATER_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY": {
     "expires_in_version": "40",
     "kind": "exponential",
     "n_buckets": 10,
     "high": "60",
     "description": "Updater: The interval in days between the previous and the current background update check when the check was timer initiated"
   },
   "UPDATER_STATUS_CODES": {
+    "expires_in_version": "35",
+    "kind": "enumerated",
+    "n_values": 50,
+    "description": "Updater: the status of the latest update performed"
+  },
+  "UPDATER_ALL_STATUS_CODES": {
     "expires_in_version": "never",
     "kind": "enumerated",
-    "n_values": 50,
+    "n_values": 200,
     "description": "Updater: the status of the latest update performed"
   },
   "UPDATER_UPDATES_ENABLED": {
     "expires_in_version": "default",
     "kind": "boolean",
     "description": "Updater: Whether or not updates are enabled"
   },
   "UPDATER_UPDATES_METRO_ENABLED": {
--- a/toolkit/mozapps/update/common/errors.h
+++ b/toolkit/mozapps/update/common/errors.h
@@ -64,16 +64,28 @@
 #define UNEXPECTED_FILE_OPERATION_ERROR 42
 #define FILESYSTEM_MOUNT_READWRITE_ERROR 43
 #define FOTA_GENERAL_ERROR 44
 #define FOTA_UNKNOWN_ERROR 45
 #define WRITE_ERROR_SHARING_VIOLATION_SIGNALED 46
 #define WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID 47
 #define WRITE_ERROR_SHARING_VIOLATION_NOPID 48
 
+#define FOTA_FILE_OPERATION_ERROR 49
+#define FOTA_RECOVERY_ERROR 50
+
+#define SECURE_LOCATION_UPDATE_ERROR 51
+#define SECURE_LOCATION_UPDATE_ERROR_2 52
+#define SECURE_LOCATION_UPDATE_ERROR_3 53
+#define SECURE_LOCATION_UPDATE_ERROR_4 54
+#define SECURE_LOCATION_UPDATE_ERROR_5 55
+#define SECURE_LOCATION_UPDATE_ERROR_6 56
+#define SECURE_LOCATION_UPDATE_ERROR_7 57
+#define SECURE_LOCATION_UPDATE_ERROR_8 58
+
 // The following error codes are only used by updater.exe
 // when a fallback key exists and XPCShell tests are being run.
 #define FALLBACKKEY_UNKNOWN_ERROR 100
 #define FALLBACKKEY_REGPATH_ERROR 101
 #define FALLBACKKEY_NOKEY_ERROR 102
 #define FALLBACKKEY_SERVICE_NO_STOP_ERROR 103
 #define FALLBACKKEY_LAUNCH_ERROR 104
 
--- a/toolkit/mozapps/update/common/uachelper.cpp
+++ b/toolkit/mozapps/update/common/uachelper.cpp
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <windows.h>
 #include <wtsapi32.h>
+#include <aclapi.h>
 #include "uachelper.h"
 #include "updatelogging.h"
 
 // See the MSDN documentation with title: Privilege Constants
 // At the time of this writing, this documentation is located at: 
 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716%28v=vs.85%29.aspx
 LPCTSTR UACHelper::PrivsToDisable[] = { 
   SE_ASSIGNPRIMARYTOKEN_NAME,
@@ -156,21 +157,25 @@ UACHelper::DisableUnneededPrivileges(HAN
       return FALSE;
     }
     token = obtainedToken;
   }
 
   BOOL result = TRUE;
   for (size_t i = 0; i < count; i++) {
     if (SetPrivilege(token, unneededPrivs[i], FALSE)) {
+#ifdef UPDATER_LOG_PRIVS
       LOG(("Disabled unneeded token privilege: %s.",
            unneededPrivs[i]));
+#endif
     } else {
+#ifdef UPDATER_LOG_PRIVS
       LOG(("Could not disable token privilege value: %s. (%d)",
            unneededPrivs[i], GetLastError()));
+#endif
       result = FALSE;
     }
   }
 
   if (obtainedToken) {
     CloseHandle(obtainedToken);
   }
   return result;
@@ -215,8 +220,98 @@ UACHelper::CanUserElevate()
   bool canElevate = GetTokenInformation(token, TokenElevationType,
                                         &elevationType,
                                         sizeof(elevationType), &len) &&
                     (elevationType == TokenElevationTypeLimited);
   CloseHandle(token);
 
   return canElevate;
 }
+
+/**
+ * Denies write access for everyone on the specified path.
+ *
+ * @param path The file path to modify the DACL on
+ * @param originalACL out parameter, set only if successful.
+ *                    caller must free.
+ * @return true on success
+ */
+bool
+UACHelper::DenyWriteACLOnPath(LPCWSTR path, PACL *originalACL,
+                              PSECURITY_DESCRIPTOR *sd)
+{
+  // Get the old security information on the path.
+  // Note that the actual buffer to be freed is contained in *sd.
+  // originalACL points within *sd's buffer.
+  *originalACL = nullptr;
+  *sd = nullptr;
+  DWORD result =
+    GetNamedSecurityInfoW(path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
+                          nullptr, nullptr, originalACL, nullptr, sd);
+  if (result != ERROR_SUCCESS) {
+    *sd = nullptr;
+    *originalACL = nullptr;
+    return false;
+  }
+
+  // Adjust the security for everyone to deny write
+  EXPLICIT_ACCESSW ea;
+  ZeroMemory(&ea, sizeof(EXPLICIT_ACCESSW));
+  ea.grfAccessPermissions = FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES |
+                            FILE_WRITE_DATA | FILE_WRITE_EA;
+  ea.grfAccessMode = DENY_ACCESS;
+  ea.grfInheritance = NO_INHERITANCE;
+  ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
+  ea.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+  ea.Trustee.ptstrName = L"EVERYONE";
+  PACL dacl = nullptr;
+  result = SetEntriesInAclW(1, &ea, *originalACL, &dacl);
+  if (result != ERROR_SUCCESS) {
+    LocalFree(*sd);
+    *originalACL = nullptr;
+    *sd = nullptr;
+    return false;
+  }
+
+  // Update the path to have a the new DACL
+  result = SetNamedSecurityInfoW(const_cast<LPWSTR>(path), SE_FILE_OBJECT,
+                                 DACL_SECURITY_INFORMATION, nullptr, nullptr,
+                                 dacl, nullptr);
+  LocalFree(dacl);
+  return result == ERROR_SUCCESS;
+}
+
+/**
+ * Determines if the specified directory only has updater.exe inside of it,
+ * and nothing else.
+ *
+ * @param inputPath the directory path to search
+ * @return true if updater.exe is the only file in the directory
+ */
+bool
+UACHelper::IsDirectorySafe(LPCWSTR inputPath)
+{
+  WIN32_FIND_DATAW findData;
+  HANDLE findHandle = nullptr;
+
+  WCHAR searchPath[MAX_PATH + 1] = { L'\0' };
+  wsprintfW(searchPath, L"%s\\*.*", inputPath);
+
+  findHandle = FindFirstFileW(searchPath, &findData);
+  if(findHandle == INVALID_HANDLE_VALUE) {
+    return false;
+  }
+
+  // Enumerate the files and if we find anything other than the current
+  // directory, the parent directory, or updater.exe. Then fail.
+  do {
+    if(wcscmp(findData.cFileName, L".") != 0 &&
+       wcscmp(findData.cFileName, L"..") != 0 &&
+       wcscmp(findData.cFileName, L"updater.exe") != 0) {
+         FindClose(findHandle);
+      return false;
+    }
+  } while(FindNextFileW(findHandle, &findData));
+  FindClose(findHandle);
+
+  return true;
+}
+
--- a/toolkit/mozapps/update/common/uachelper.h
+++ b/toolkit/mozapps/update/common/uachelper.h
@@ -7,17 +7,20 @@
 
 class UACHelper
 {
 public:
   static HANDLE OpenUserToken(DWORD sessionID);
   static HANDLE OpenLinkedToken(HANDLE token);
   static BOOL DisablePrivileges(HANDLE token);
   static bool CanUserElevate();
+  static bool IsDirectorySafe(LPCWSTR inputPath);
+  static bool DenyWriteACLOnPath(LPCWSTR path, PACL *originalACL,
+                                 PSECURITY_DESCRIPTOR *sd);
 
 private:
   static BOOL SetPrivilege(HANDLE token, LPCTSTR privs, BOOL enable);
-  static BOOL DisableUnneededPrivileges(HANDLE token, 
+  static BOOL DisableUnneededPrivileges(HANDLE token,
                                         LPCTSTR *unneededPrivs, size_t count);
-  static LPCTSTR PrivsToDisable[];  
+  static LPCTSTR PrivsToDisable[];
 };
 
 #endif
--- a/toolkit/mozapps/update/common/updatehelper.cpp
+++ b/toolkit/mozapps/update/common/updatehelper.cpp
@@ -28,17 +28,16 @@
 
 // Needed for PathAppendW
 #include <shlwapi.h>
 
 using mozilla::MakeUnique;
 using mozilla::UniquePtr;
 
 WCHAR* MakeCommandLine(int argc, WCHAR **argv);
-BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
 
 /**
  * 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
--- a/toolkit/mozapps/update/common/updatehelper.h
+++ b/toolkit/mozapps/update/common/updatehelper.h
@@ -12,16 +12,20 @@ DWORD LaunchServiceSoftwareUpdateCommand
 BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
 BOOL WriteStatusPending(LPCWSTR updateDirPath);
 DWORD WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds);
 DWORD WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds);
 BOOL DoesFallbackKeyExist();
 BOOL IsLocalFile(LPCWSTR file, BOOL &isLocal);
 DWORD StartServiceCommand(int argc, LPCWSTR* argv);
 BOOL IsUnpromptedElevation(BOOL &isUnpromptedElevation);
+BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer,
+                            LPCWSTR siblingFilePath,
+                            LPCWSTR newFileName);
+BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
 
 #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
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -157,16 +157,19 @@ const UNEXPECTED_FILE_OPERATION_ERROR   
 const FILESYSTEM_MOUNT_READWRITE_ERROR              = 43;
 const FOTA_GENERAL_ERROR                            = 44;
 const FOTA_UNKNOWN_ERROR                            = 45;
 const WRITE_ERROR_SHARING_VIOLATION_SIGNALED        = 46;
 const WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID = 47;
 const WRITE_ERROR_SHARING_VIOLATION_NOPID           = 48;
 const FOTA_FILE_OPERATION_ERROR                     = 49;
 const FOTA_RECOVERY_ERROR                           = 50;
+const SECURE_LOCATION_UPDATE_ERROR                  = 51;
+const SECURE_LOCATION_UPDATE_ERROR_FIRST            = 51;
+const SECURE_LOCATION_UPDATE_ERROR_LAST             = 58;
 
 const CERT_ATTR_CHECK_FAILED_NO_UPDATE  = 100;
 const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
 const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
 const NETWORK_ERROR_OFFLINE             = 111;
 const FILE_ERROR_TOO_BIG                = 112;
 
 // Error codes should be < 1000. Errors above 1000 represent http status codes
@@ -1482,17 +1485,18 @@ function handleUpdateFailure(update, err
       update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR) {
     Cc["@mozilla.org/updates/update-prompt;1"].
       createInstance(Ci.nsIUpdatePrompt).
       showUpdateError(update);
     writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
     return true;
   }
 
-  if (update.errorCode == ELEVATION_CANCELED) {
+  if (update.errorCode == ELEVATION_CANCELED ||
+      update.errorCode == SECURE_LOCATION_UPDATE_ERROR) {
     writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
     return true;
   }
 
   if (update.errorCode == SERVICE_UPDATER_COULD_NOT_BE_STARTED ||
       update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS ||
       update.errorCode == SERVICE_UPDATER_SIGN_ERROR ||
       update.errorCode == SERVICE_UPDATER_COMPARE_ERROR ||
@@ -2144,16 +2148,35 @@ UpdateService.prototype = {
           "updates... returning early");
       // If the update is present in the update directly somehow,
       // it would prevent us from notifying the user of futher updates.
       cleanupActiveUpdate();
       return;
     }
 
     var status = readStatusFile(getUpdatesDir());
+
+    // TODO: Remove, temporary workaround to gather more telemetry data.
+    // If the error is between SECURE_LOCATION_UPDATE_ERROR_FIRST and
+    // SECURE_LOCATION_UPDATE_ERROR_LAST, then the udpate actually
+    // succeeded.  We only reported it as an error to gather more
+    // telemetry data.
+    if (status != STATE_SUCCEEDED) {
+        let parts = status.split(":");
+        let result = 0; // 0 means success
+        if (parts.length > 1) {
+            result = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
+        }
+        if (result >= SECURE_LOCATION_UPDATE_ERROR_FIRST &&
+            result <= SECURE_LOCATION_UPDATE_ERROR_LAST) {
+            this._sendStatusCodeTelemetryPing(result);
+            status = STATE_SUCCEEDED;
+        }
+    }
+
     // STATE_NONE status means that the update.status file is present but a
     // background download error occurred.
     if (status == STATE_NONE) {
       LOG("UpdateService:_postUpdateProcessing - no status, no update");
       cleanupActiveUpdate();
       return;
     }
 
@@ -2396,17 +2419,17 @@ UpdateService.prototype = {
           (parts.length > 1  && parts[0] != STATE_FAILED)) {
         // Should also report STATE_DOWNLOAD_FAILED
         return;
       }
       let result = 0; // 0 means success
       if (parts.length > 1) {
         result = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
       }
-      Services.telemetry.getHistogramById("UPDATER_STATUS_CODES").add(result);
+      Services.telemetry.getHistogramById("UPDATER_ALL_STATUS_CODES").add(result);
     } catch(e) {
       // Don't allow any exception to be propagated.
       Cu.reportError(e);
     }
   },
 
   /**
    * Submit the interval in days since the last notification for this background
--- a/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
@@ -1529,25 +1529,27 @@ function runUpdate(aExpectedExitValue, a
   }
   logTestInfo("running the updater: " + updateBin.path + " " + args.join(" "));
 
   let env = AUS_Cc["@mozilla.org/process/environment;1"].
             getService(AUS_Ci.nsIEnvironment);
   if (gDisableReplaceFallback) {
     env.set("MOZ_NO_REPLACE_FALLBACK", "1");
   }
+  env.set("MOZ_EMULATE_ELEVATION_PATH", "1");
 
   let process = AUS_Cc["@mozilla.org/process/util;1"].
                 createInstance(AUS_Ci.nsIProcess);
   process.init(updateBin);
   process.run(true, args, args.length);
 
   if (gDisableReplaceFallback) {
     env.set("MOZ_NO_REPLACE_FALLBACK", "");
   }
+  env.set("MOZ_EMULATE_ELEVATION_PATH", "");
 
   let status = readStatusFile();
   if (process.exitValue != aExpectedExitValue || status != aExpectedStatus) {
     if (process.exitValue != aExpectedExitValue) {
       logTestInfo("updater exited with unexpected value! Got: " +
                   process.exitValue + ", Expected: " +  aExpectedExitValue);
     }
     if (status != aExpectedStatus) {
--- a/toolkit/mozapps/update/updater/moz.build
+++ b/toolkit/mozapps/update/updater/moz.build
@@ -36,16 +36,18 @@ if CONFIG['OS_ARCH'] == 'WINNT':
     ]
     OS_LIBS += [
         'comctl32',
         'ws2_32',
         'shell32',
         'shlwapi',
         'crypt32',
         'advapi32',
+        'ole32',
+        'rpcrt4',
     ]
 else:
     USE_LIBS += [
         'updatecommon',
     ]
 
 USE_LIBS += [
     'mar',
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -106,16 +106,17 @@ extern "C" MOZ_EXPORT int ioprio_set(int
 # define MAYBE_USE_HARD_LINKS 1
 static bool sUseHardLinks = true;
 #else
 # define MAYBE_USE_HARD_LINKS 0
 #endif
 
 #ifdef XP_WIN
 #include "updatehelper.h"
+#include <aclapi.h>
 
 // Closes the handle if valid and if the updater is elevated returns with the
 // 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); \
@@ -2264,17 +2265,19 @@ int NS_main(int argc, NS_tchar **argv)
   if (slash && !slash[1]) {
     *slash = NS_T('\0');
   }
 
 #ifdef XP_WIN
   bool useService = false;
   bool testOnlyFallbackKeyExists = false;
   bool noServiceFallback = getenv("MOZ_NO_SERVICE_FALLBACK") != nullptr;
+  bool emulateElevation = getenv("MOZ_EMULATE_ELEVATION_PATH") != nullptr;
   putenv(const_cast<char*>("MOZ_NO_SERVICE_FALLBACK="));
+  putenv(const_cast<char*>("MOZ_EMULATE_ELEVATION_PATH="));
 
   // We never want the service to be used unless we build with
   // the maintenance service.
 #ifdef MOZ_MAINTENANCE_SERVICE
   useService = IsUpdateStatusPendingService();
   // Our tests run with a different apply directory for each test.
   // We use this registry key on our test slaves to store the
   // allowed name/issuers.
@@ -2360,22 +2363,16 @@ int NS_main(int argc, NS_tchar **argv)
     LogInit(gPatchDirPath, NS_T("update.log"));
   }
 
   if (!WriteStatusFile("applying")) {
     LOG(("failed setting status to 'applying'"));
     return 1;
   }
 
-  if (sStagedUpdate) {
-    LOG(("Performing a staged update"));
-  } else if (sReplaceRequest) {
-    LOG(("Performing a replace request"));
-  }
-
   LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath));
   LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath));
   LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath));
 
 #ifdef MOZ_WIDGET_GONK
   const char *prioEnv = getenv("MOZ_UPDATER_PRIO");
   if (prioEnv) {
     int32_t prioVal;
@@ -2496,23 +2493,25 @@ int NS_main(int argc, NS_tchar **argv)
         // Note that this could fail, but if it does, there isn't too much we
         // can do in order to recover anyways.
         WriteStatusFile("pending");
       }
       LOG(("Update already in progress! Exiting"));
       return 1;
     }
 
-    updateLockFileHandle = CreateFileW(updateLockFilePath,
-                                       GENERIC_READ | GENERIC_WRITE,
-                                       0,
-                                       nullptr,
-                                       OPEN_ALWAYS,
-                                       FILE_FLAG_DELETE_ON_CLOSE,
-                                       nullptr);
+    if (!emulateElevation) {
+      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;
@@ -2647,17 +2646,18 @@ int NS_main(int argc, NS_tchar **argv)
         } else {
           lastFallbackError = FALLBACKKEY_LAUNCH_ERROR;
         }
       }
 
       // If the service can't be used when staging and update, make sure that
       // the UAC prompt is not shown! In this case, just set the status to
       // pending and the update will be applied during the next startup.
-      if (!useService && sStagedUpdate) {
+      // When emulateElevation is true fall through to the elevation code path.
+      if (!useService && sStagedUpdate && !emulateElevation) {
         if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
           CloseHandle(updateLockFileHandle);
         }
         WriteStatusPending(gPatchDirPath);
         return 0;
       }
 
       // If we started the service command, and it finished, check the
@@ -2673,60 +2673,191 @@ int NS_main(int argc, NS_tchar **argv)
             updateStatusSucceeded) {
           if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath, false, nullptr)) {
             fprintf(stderr, "The post update process which runs as the user"
                     " for service update could not be launched.");
           }
         }
       }
 
+      DWORD returnCode = 0;
+
       // 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) {
-        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;
-        sinfo.lpVerb       = L"runas";
-        sinfo.nShow        = SW_SHOWNORMAL;
-
-        bool result = ShellExecuteEx(&sinfo);
-        free(cmdLine);
-
-        if (result) {
-          WaitForSingleObject(sinfo.hProcess, INFINITE);
-          CloseHandle(sinfo.hProcess);
+
+        // Get a unique directory name to secure
+        RPC_WSTR guidString = RPC_WSTR(L"");
+        GUID guid;
+        HRESULT hr = CoCreateGuid(&guid);
+        BOOL result = TRUE;
+        bool safeToUpdate = true;
+        int safeToUpdateResult = 0;
+        WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' };
+        WCHAR secureDirPath[MAX_PATH + 1] = { L'\0' };
+        if (SUCCEEDED(hr)) {
+          UuidToString(&guid, &guidString);
+          result = PathGetSiblingFilePath(secureDirPath, argv[0],
+                                          reinterpret_cast<LPCWSTR>(guidString));
+          RpcStringFree(&guidString);
+
         } else {
-          WriteStatusFile(ELEVATION_CANCELED);
+          // This should never happen, but just in case
+          result = PathGetSiblingFilePath(secureDirPath, argv[0], L"tmp_update");
+        }
+
+        if (!result) {
+          fprintf(stderr, "Could not obtain secure update directory path");
+          safeToUpdate = false;
+          safeToUpdateResult = SECURE_LOCATION_UPDATE_ERROR_2;
+        }
+
+        // If it's still safe to update, create the directory
+        if (safeToUpdate) {
+          result = CreateDirectoryW(secureDirPath, nullptr);
+          if (!result) {
+            fprintf(stderr, "Could not create secure update directory");
+            safeToUpdate = false;
+            safeToUpdateResult = SECURE_LOCATION_UPDATE_ERROR_3;
+          }
+        }
+
+        // If it's still safe to update, get the new updater path
+        if (safeToUpdate) {
+          wcsncpy(secureUpdaterPath, secureDirPath, MAX_PATH);
+          result = PathAppendSafe(secureUpdaterPath, L"updater.exe");
+          if (!result) {
+            fprintf(stderr, "Could not obtain secure updater file name");
+            safeToUpdate = false;
+            safeToUpdateResult = SECURE_LOCATION_UPDATE_ERROR_4;
+          }
+        }
+
+        // If it's still safe to update, copy the file in
+        if (safeToUpdate) {
+          result = CopyFileW(argv[0], secureUpdaterPath, TRUE);
+          if (!result) {
+            fprintf(stderr, "Could not copy updater to secure location");
+            safeToUpdate = false;
+            safeToUpdateResult = SECURE_LOCATION_UPDATE_ERROR_5;
+          }
+        }
+
+        // If it's still safe to update, restrict access to the directory item
+        // itself so that the directory cannot be deleted and re-created,
+        // nor have its properties modified.  Note that this does not disallow
+        // adding items inside the directory.
+        HANDLE handle = INVALID_HANDLE_VALUE;
+        if (safeToUpdate) {
+          handle = CreateFileW(secureDirPath, GENERIC_READ, FILE_SHARE_READ,
+                               nullptr, OPEN_EXISTING,
+                               FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+          safeToUpdate = handle != INVALID_HANDLE_VALUE;
+          if (!safeToUpdate) {
+            safeToUpdateResult = SECURE_LOCATION_UPDATE_ERROR_6;
+          }
+        }
+
+        // If it's still safe to update, deny write access completely to the
+        // directory.
+        PACL originalACL = nullptr;
+        PSECURITY_DESCRIPTOR sd = nullptr;
+        if (safeToUpdate) {
+          safeToUpdate = UACHelper::DenyWriteACLOnPath(secureDirPath,
+                                                       &originalACL, &sd);
+          if (!safeToUpdate) {
+            safeToUpdateResult = SECURE_LOCATION_UPDATE_ERROR_7;
+          }
         }
+
+        // If it's still safe to update, verify that there is only updater.exe
+        // in the directory and nothing else.
+        if (safeToUpdate) {
+          if (!UACHelper::IsDirectorySafe(secureDirPath)) {
+            safeToUpdate = false;
+            safeToUpdateResult = SECURE_LOCATION_UPDATE_ERROR_8;
+          }
+        }
+
+        // TODO: Re-enable this after we gather telemetry data
+        // This will try to update in the most secure way, but if that's
+        // not possilbe, then it falls back.  Ideally we'd fail the update
+        // here but it's failing for some users currently.
+        /*
+        if (!safeToUpdate) {
+          fprintf(stderr, "Will not proceed to copy secure updater because it "
+                          "is not safe to do so.");
+          WriteStatusFile(SECURE_LOCATION_UPDATE_ERROR);
+        } else*/ {
+          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       = secureUpdaterPath;
+          sinfo.lpParameters = cmdLine;
+          sinfo.lpVerb       = emulateElevation ? L"open" : L"runas";
+          sinfo.nShow        = SW_SHOWNORMAL;
+
+          bool result = ShellExecuteEx(&sinfo);
+          free(cmdLine);
+
+          if (result) {
+            WaitForSingleObject(sinfo.hProcess, INFINITE);
+            // Bubble the elevated updater return code to this updater
+            GetExitCodeProcess(sinfo.hProcess, &returnCode);
+            CloseHandle(sinfo.hProcess);
+            if (returnCode == 0 && !safeToUpdate && safeToUpdateResult != 0) {
+              WriteStatusFile(safeToUpdateResult);
+            }
+          } else {
+            WriteStatusFile(ELEVATION_CANCELED);
+          }
+        }
+
+        // All done, revert back the permissions.
+        if (originalACL) {
+          SetNamedSecurityInfoW(const_cast<LPWSTR>(secureDirPath), SE_FILE_OBJECT,
+                                DACL_SECURITY_INFORMATION, nullptr, nullptr,
+                                originalACL, nullptr);
+        }
+        if (sd) {
+          LocalFree(sd);
+        }
+
+        // Done with the directory, no need to lock it.
+        if (INVALID_HANDLE_VALUE != handle) {
+          CloseHandle(handle);
+        }
+
+        // We no longer need the secure updater and directory
+        DeleteFileW(secureUpdaterPath);
+        RemoveDirectoryW(secureDirPath);
       }
 
       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.
-        return 0;
+        return returnCode;
       } 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);
         }
         return 0;
       } else {
@@ -2738,16 +2869,23 @@ int NS_main(int argc, NS_tchar **argv)
         CloseHandle(updateLockFileHandle);
         WriteStatusFile(lastFallbackError);
         return 0;
       }
     }
   }
 #endif
 
+  if (sStagedUpdate) {
+    LOG(("Performing a staged update"));
+  }
+  else if (sReplaceRequest) {
+    LOG(("Performing a replace request"));
+  }
+
 #if defined(MOZ_WIDGET_GONK)
   // In gonk, the master b2g process sets its umask to 0027 because
   // there's no reason for it to ever create world-readable files.
   // The updater binary, however, needs to do this, and it inherits
   // the master process's cautious umask.  So we drop down a bit here.
   umask(0022);
 
   // Remount the /system partition as read-write for gonk. The destructor will
@@ -3075,17 +3213,18 @@ int NS_main(int argc, NS_tchar **argv)
       if (!sUsingService) {
         if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath, false, nullptr)) {
           LOG(("NS_main: The post update process could not be launched."));
         }
 
         StartServiceUpdate(gInstallDirPath);
       }
     }
-    EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
+    EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle,
+        gSucceeded ? 0 : 1);
 #endif /* XP_WIN */
 #ifdef XP_MACOSX
     if (gSucceeded) {
       LaunchMacPostProcess(gInstallDirPath);
     }
 #endif /* XP_MACOSX */
 
     if (getenv("MOZ_PROCESS_UPDATES") == nullptr) {