Bug 709158 - updater.exe was not verified to be a particular product's/channel's/version of the updater. r=rstrong.
authorBrian R. Bondy <netzen@gmail.com>
Wed, 04 Jan 2012 23:19:16 -0500
changeset 83773 7fbfb0e7256533952fe9544fc79d518cfa2c86e9
parent 83772 ee4def38eede8560ea1a183bfc7ac143e92781d9
child 83774 df4ba8bfc9caf08eb89c4dcd09561e394a8d2cfe
push id21792
push userbbondy@mozilla.com
push dateThu, 05 Jan 2012 04:20:59 +0000
treeherdermozilla-central@10894668e37f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrstrong
bugs709158
milestone12.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 709158 - updater.exe was not verified to be a particular product's/channel's/version of the updater. r=rstrong.
toolkit/components/maintenanceservice/maintenanceservice.cpp
toolkit/components/maintenanceservice/servicebase.cpp
toolkit/components/maintenanceservice/servicebase.h
toolkit/components/maintenanceservice/workmonitor.cpp
toolkit/mozapps/update/nsUpdateService.js
--- a/toolkit/components/maintenanceservice/maintenanceservice.cpp
+++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp
@@ -34,22 +34,22 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include <windows.h>
 #include <shlwapi.h>
 #include <stdio.h>
 #include <wchar.h>
+#include <shlobj.h>
 
 #include "serviceinstall.h"
 #include "maintenanceservice.h"
 #include "servicebase.h"
 #include "workmonitor.h"
-#include "shlobj.h"
 
 SERVICE_STATUS gSvcStatus = { 0 }; 
 SERVICE_STATUS_HANDLE gSvcStatusHandle = NULL; 
 HANDLE ghSvcStopEvent = NULL;
 BOOL gServiceStopping = FALSE;
 
 // logs are pretty small ~10 lines, so 5 seems reasonable.
 #define LOGS_TO_KEEP 5
--- a/toolkit/components/maintenanceservice/servicebase.cpp
+++ b/toolkit/components/maintenanceservice/servicebase.cpp
@@ -31,11 +31,89 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "servicebase.h"
+#include "nsWindowsHelpers.h"
 
 // Shared code between applications and updater.exe
 #include "nsWindowsRestart.cpp"
+
+/**
+ * Verifies if 2 files are byte for byte equivalent.
+ *
+ * @param  file1Path   The first file to verify.
+ * @param  file2Path   The second file to verify.
+ * @param  sameContent Out parameter, TRUE if the files are equal
+ * @return TRUE If there was no error checking the files.
+ */
+BOOL
+VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL &sameContent)
+{
+  sameContent = FALSE;
+  nsAutoHandle file1(CreateFileW(file1Path, GENERIC_READ, FILE_SHARE_READ, 
+                                 NULL, OPEN_EXISTING, 0, NULL));
+  if (!file1) {
+    return FALSE;
+  }
+  nsAutoHandle file2(CreateFileW(file2Path, GENERIC_READ, FILE_SHARE_READ, 
+                                 NULL, OPEN_EXISTING, 0, NULL));
+  if (!file2) {
+    return FALSE;
+  }
+
+  DWORD fileSize1 = GetFileSize(file1, NULL);
+  DWORD fileSize2 = GetFileSize(file2, NULL);
+  if (INVALID_FILE_SIZE == fileSize1 || INVALID_FILE_SIZE == fileSize2) {
+    return FALSE;
+  }
+
+  if (fileSize1 != fileSize2) {
+    // sameContent is already set to FALSE
+    return TRUE;
+  }
+
+  char buf1[COMPARE_BLOCKSIZE];
+  char buf2[COMPARE_BLOCKSIZE];
+  DWORD numBlocks = fileSize1 / COMPARE_BLOCKSIZE;
+  DWORD leftOver = fileSize1 % COMPARE_BLOCKSIZE;
+  DWORD readAmount;
+  for (DWORD i = 0; i < numBlocks; i++) {
+    if (!ReadFile(file1, buf1, COMPARE_BLOCKSIZE, &readAmount, NULL) || 
+        readAmount != COMPARE_BLOCKSIZE) {
+      return FALSE;
+    }
+
+    if (!ReadFile(file2, buf2, COMPARE_BLOCKSIZE, &readAmount, NULL) || 
+        readAmount != COMPARE_BLOCKSIZE) {
+      return FALSE;
+    }
+
+    if (memcmp(buf1, buf2, COMPARE_BLOCKSIZE)) {
+      // sameContent is already set to FALSE
+      return TRUE;
+    }
+  }
+
+  if (leftOver) {
+    if (!ReadFile(file1, buf1, leftOver, &readAmount, NULL) || 
+        readAmount != leftOver) {
+      return FALSE;
+    }
+
+    if (!ReadFile(file2, buf2, leftOver, &readAmount, NULL) || 
+        readAmount != leftOver) {
+      return FALSE;
+    }
+
+    if (memcmp(buf1, buf2, leftOver)) {
+      // sameContent is already set to FALSE
+      return TRUE;
+    }
+  }
+
+  sameContent = TRUE;
+  return TRUE;
+}
--- a/toolkit/components/maintenanceservice/servicebase.h
+++ b/toolkit/components/maintenanceservice/servicebase.h
@@ -34,8 +34,14 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include <windows.h>
 #include "updatelogging.h"
 
 BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
+BOOL VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL &sameContent);
+
+// 32KiB for comparing files at a time seems reasonable.
+// The bigger the better for speed, but this will be used
+// on the stack so I don't want it to be too big.
+#define COMPARE_BLOCKSIZE 32768
--- a/toolkit/components/maintenanceservice/workmonitor.cpp
+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
@@ -66,16 +66,17 @@ BOOL WriteStatusFailure(LPCWSTR updateDi
 BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer,  LPCWSTR siblingFilePath, 
                             LPCWSTR newFileName);
 
 // The error codes start from 16000 since Windows system error 
 // codes only go up to 15999
 const int SERVICE_UPDATER_COULD_NOT_BE_STARTED = 16000;
 const int SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 16001;
 const int SERVICE_UPDATER_SIGN_ERROR = 16002;
+const int SERVICE_UPDATER_COMPARE_ERROR = 16003;
 
 /**
  * Runs an update process as the service using the SYSTEM account.
  *
  * @param  updaterPath    The path to the update process to start.
  * @param  workingDir     The working directory to execute the update process in
  * @param  cmdLine        The command line parameters to pass to updater.exe
  * @param  processStarted Set to TRUE if the process was started.
@@ -337,16 +338,51 @@ ProcessWorkItem(LPCWSTR monitoringBasePa
   cmdlineBuffer[cmdLineBufferRead + 1] = '\0';
   WCHAR *cmdlineBufferWide = reinterpret_cast<WCHAR*>(cmdlineBuffer.get());
   LOG(("An update command was detected and is being processed for command meta "
        "file: %ls\n", notifyInfo.FileName));
 
   int argcTmp = 0;
   LPWSTR* argvTmp = CommandLineToArgvW(cmdlineBufferWide, &argcTmp);
 
+  // Verify that the updater.exe that we are executing is the same
+  // as the one in the installation directory which we are updating.
+  // The installation dir that we are installing to is argvTmp[2].
+  WCHAR installDirUpdater[MAX_PATH + 1];
+  wcsncpy(installDirUpdater, argvTmp[2], MAX_PATH);
+  if (!PathAppendSafe(installDirUpdater, L"updater.exe")) {
+    LOG(("Install directory updater could not be determined.\n"));
+    result = FALSE;
+  }
+
+  BOOL updaterIsCorrect;
+  if (result && !VerifySameFiles(updaterPath, installDirUpdater, 
+                                 updaterIsCorrect)) {
+    LOG(("Error checking if the updaters are the same.\n")); 
+    result = FALSE;
+  }
+
+  if (result && !updaterIsCorrect) {
+    LOG(("The updaters do not match, udpater will not run.\n")); 
+    result = FALSE;
+  }
+
+  if (result) {
+    LOG(("updater.exe was compared successfully to the installation directory"
+         " updater.exe.\n"));
+  } else {
+    SetEvent(serviceRunningEvent);
+    if (argcTmp < 2 || 
+        !WriteStatusFailure(argvTmp[1], 
+                            SERVICE_UPDATER_COMPARE_ERROR)) {
+      LOG(("Could not write update.status updater compare failure.\n"));
+    }
+    return TRUE;
+  }
+
   // Check for updater.exe sign problems
   BOOL updaterSignProblem = FALSE;
 #ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK
   if (argcTmp > 2) {
     updaterSignProblem = !DoesBinaryMatchAllowedCertificates(argvTmp[2], 
                                                              updaterPath);
   }
 #endif
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -141,16 +141,17 @@ const STATE_FAILED          = "failed";
 
 // From updater/errors.h:
 const WRITE_ERROR        = 7;
 const UNEXPECTED_ERROR   = 8;
 const ELEVATION_CANCELED = 9;
 const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 16000;
 const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 16001;
 const SERVICE_UPDATER_SIGN_ERROR           = 16002;
+const SERVICE_UPDATER_COMPARE_ERROR        = 16003;
 
 const CERT_ATTR_CHECK_FAILED_NO_UPDATE  = 100;
 const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
 const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
 
 const DOWNLOAD_CHUNK_SIZE           = 300000; // bytes
 const DOWNLOAD_BACKGROUND_INTERVAL  = 600;    // seconds
 const DOWNLOAD_FOREGROUND_INTERVAL  = 0;
@@ -1430,17 +1431,18 @@ UpdateService.prototype = {
 
         if (update.errorCode == ELEVATION_CANCELED) {
           writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
           return;
         }
 
         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_SIGN_ERROR ||
+            update.errorCode == SERVICE_UPDATER_COMPARE_ERROR) {
           var failCount = getPref("getIntPref", 
                                   PREF_APP_UPDATE_SERVICE_ERRORS, 0);
           var maxFail = getPref("getIntPref", 
                                 PREF_APP_UPDATE_SERVICE_MAX_ERRORS, 
                                 DEFAULT_SERVICE_MAX_ERRORS);
 
           // As a safety, when the service reaches maximum failures, it will
           // disable itself and fallback to using the normal update mechanism