High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionPrivilege escalation in the Application Update component
ComponentToolkit
Bug ClassLogic Error
Tracker2037979
Fix commit2f4e1be33d5f (firefox) +511/-164
CISA KEVNot listed
CreditedNoel
Disclosed2026-10-13

Changed Functions

FunctionChangeNotes
if
toolkit/mozapps/update/UpdateService.sys.mjs
modified
switch
toolkit/mozapps/update/common/elevation_type.cpp
modified
if
toolkit/mozapps/update/common/elevation_type.cpp
modified
switch
toolkit/mozapps/update/common/updater_invocation.cpp
modified

Files Changed

  • toolkit/components/maintenanceservice/workmonitor.cpp
  • toolkit/mozapps/update/UpdateService.sys.mjs
  • toolkit/mozapps/update/common/elevation_type.cpp
  • toolkit/mozapps/update/common/elevation_type.h
  • toolkit/mozapps/update/common/moz.build
  • toolkit/mozapps/update/common/updatecommon.cpp
  • toolkit/mozapps/update/common/updatecommon.h
  • toolkit/mozapps/update/common/updater_invocation.cpp
  • toolkit/mozapps/update/common/updater_invocation.h
  • toolkit/mozapps/update/tests/gtest/ElevationTypeTests.cpp
  • toolkit/mozapps/update/tests/gtest/README.md
  • toolkit/mozapps/update/tests/gtest/moz.build
  • toolkit/mozapps/update/updater/updater.cpp
diff --git a/toolkit/components/maintenanceservice/workmonitor.cpp b/toolkit/components/maintenanceservice/workmonitor.cpp
index 316513fe121..82047d18347 100644
--- a/toolkit/components/maintenanceservice/workmonitor.cpp
+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
@@ -165,6 +165,11 @@ Maybe<UpdaterArgs> parseUpdaterArgs(int argc, wchar_t** argv) {
     installDirPath = optionalArg(argc, argv, 3);
     applyToDirPath = optionalArg(argc, argv, 4);
     whichInvocation = optionalArg(argc, argv, 5);
+    if (whichInvocation.isSome() &&
+        wcscmp(whichInvocation.value(), L"second") != 0) {
+      LOG_WARN(("only the second invocation should be through MMS"));
+      return Nothing();
+    }
     waitPid = optionalArg(argc, argv, 6);
     callbackWorkingDir = optionalArg(argc, argv, 7);
     callbackBinPath = optionalArg(argc, argv, 8);
diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs
index 1166c7b232e..164381ebc10 100644
--- a/toolkit/mozapps/update/UpdateService.sys.mjs
+++ b/toolkit/mozapps/update/UpdateService.sys.mjs
@@ -1229,11 +1229,11 @@ function shouldUseService() {
   // This function will return true if the mantenance service should be used if
   // all of the following conditions are met:
   // 1) This build was done with the maintenance service enabled
-  // 2) The maintenance service is installed
+  // 2) The maintenance service is installed (this is skipped in automation)
   // 3) The pref for using the service is enabled
   if (
     !AppConstants.MOZ_MAINTENANCE_SERVICE ||
-    !isServiceInstalled() ||
+    (!Cu.isInAutomation && !isServiceInstalled()) ||
     !Services.prefs.getBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, true)
   ) {
     LOG("shouldUseService - returning false");
@@ -4709,7 +4709,7 @@ export class UpdateManager {
       let activeUpdates = this._loadXMLFileIntoArray(FILE_ACTIVE_UPDATE_XML);
       if (activeUpdates.length) {
         const status = readStatusFile(getReadyUpdateDir());
-
+        console.error(`CPD: got update status: ${status}`);
         // If there are two updates, the first one is the ready update.
         // If there is only 1 update, we don't know which is which. We use the
         // state to figure it out.
@@ -4769,7 +4769,9 @@ export class UpdateManager {
           this._downloadingUpdate.state
       );
     }
-    LOG("UpdateManager:#reload - Reloaded readyUpdate as " + this._readyUpdate);
+    LOG(
+      `UpdateManager:#reload - Reloaded readyUpdate as ${JSON.stringify(this._readyUpdate)}`
+    );
     if (this._readyUpdate) {
       LOG(
         "UpdateManager:#reload - Reloaded readyUpdate state as " +
diff --git a/toolkit/mozapps/update/common/elevation_type.cpp b/toolkit/mozapps/update/common/elevation_type.cpp
new file mode 100644
index 00000000000..e478df8c091
--- /dev/null
+++ b/toolkit/mozapps/update/common/elevation_type.cpp
@@ -0,0 +1,131 @@
+/* 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 "elevation_type.h"
+#include "updatecommon.h"
+#ifdef XP_WIN
+#  include "mozilla/WinHeaderOnlyUtils.h"
+#  include "mozilla/WinTokenUtils.h"
+#endif
+
+const char* BoolResultToString(BoolResult boolResult) {
+  switch (boolResult) {
+    case BoolResult::True:
+      return "True";
+    case BoolResult::False:
+      return "False";
+    case BoolResult::Error:
+      return "Error";
+  }
+  MOZ_CRASH("impossible value for BoolResult");
+}
+
+// Indicates that we are running with elevated privileges.
+// This is only ever true on macOS and Windows. We don't currently have a
+// way of elevating on other platforms.
+// Note that this should not be used to determine whether this is the first or
+// second invocation of the updater, even though the first invocation will
+// _usually_ be unelevated and the second invocation should always be
+// elevated. `gInvocation` can be used for that purpose.
+BoolResult isProcessElevated(const NS_tchar* cmd) {
+#ifdef XP_WIN
+  auto isAdmin = mozilla::UserHasAdminPrivileges();
+  if (isAdmin.isErr()) {
+    fprintf(stderr,
+            "Failed to query if the current process has admin privileges.\n");
+    return BoolResult::Error;
+  }
+  auto isLocalSystem = mozilla::UserIsLocalSystem();
+  if (isLocalSystem.isErr()) {
+    fprintf(
+        stderr,
+        "Failed to query if the current process has LocalSystem privileges.\n");
+    return BoolResult::Error;
+  }
+  // While is it technically redundant to check LocalSystem in addition to
+  // Admin given the former contains privileges of the latter, we have opt
+  // to verify both. A few reasons for this decision include the off chance
+  // that the Windows security model changes in the future and weird system
+  // setups where someone has modified the group lists in surprising ways.
+  //
+  // We use this to detect if we were launched from the Maintenance Service
+  // under LocalSystem or UAC under the user's account, and therefore can
+  // proceed with an install to `Program Files` or `Program Files(x86)`.
+  return isAdmin.unwrap() || isLocalSystem.unwrap() ? BoolResult::True
+                                                    : BoolResult::False;
+#elif defined(XP_MACOSX)
+  if (!cmd) {
+    return BoolResult::False;
+  }
+  return strstr(cmd, "/Library/PrivilegedHelperTools/org.mozilla.updater") !=
+                 nullptr
+             ? BoolResult::True
+             : BoolResult::False;
+#else
+  return BoolResult::False;
+#endif
+}
+
+const char* elevationTypeToString(ElevationType elevationType) {
+  switch (elevationType) {
+    case ElevationType::Unknown:
+      return "ElevationType::Unknown";
+    case ElevationType::None:
+      return "ElevationType::None";
+    case ElevationType::ElevatedByMMS:
+      return "ElevationType::ElevatedByMMS";
+    case ElevationType::ElevatedWithoutMMS:
+      return "ElevationType::ElevatedWithoutMMS";
+    case ElevationType::Error:
+      return "ElevationType::Error";
+  }
+  MOZ_CRASH("impossible value for ElevationType");
+}
+
+ElevationType getElevationType(int argc, NS_tchar** argv) {
+  // First, are we elevated at all?
+  const NS_tchar* cmd = nullptr;
+#ifdef XP_MACOSX
+  if (argc > 0) {
+    cmd = argv[0];
+  }
+#endif
+  BoolResult isElevatedResult = isProcessElevated(cmd);
+  if (isElevatedResult == BoolResult::Error) {
+    fprintf(stderr, "Check for elevation failed\n");
+    return ElevationType::Error;
+  } else if (isElevatedResult == BoolResult::False) {
+    return ElevationType::None;
+  }
+
+  // If we are elevated, is it through MMS or some other way?
+#ifdef MOZ_MAINTENANCE_SERVICE
+  if (EnvHasValue("MOZ_USING_SERVICE")) {
+    putenv(const_cast<char*>("MOZ_USING_SERVICE="));
+    return ElevationType::ElevatedByMMS;
+  }
+#endif
+  return ElevationType::ElevatedWithoutMMS;
+}
+
+bool isElevationTypeElevated(ElevationType elevationType) {
+  return elevationType == ElevationType::ElevatedByMMS ||
+         elevationType == ElevationType::ElevatedWithoutMMS;
+}
+
+bool isValidInvocationForElevationType(UpdaterInvocation updaterInvocation,
+                                       ElevationType elevationType) {
+  switch (updaterInvocation) {
+    case UpdaterInvocation::Error:
+      return false;
+    case UpdaterInvocation::Unknown:
+      return false;
+    case UpdaterInvocation::First:
+      return elevationType == ElevationType::None ||
+             elevationType == ElevationType::ElevatedWithoutMMS;
+    case UpdaterInvocation::Second:
+      return elevationType != ElevationType::Error &&
+             elevationType != ElevationType::Unknown;
+  }
+}
diff --git a/toolkit/mozapps/update/common/elevation_type.h b/toolkit/mozapps/update/common/elevation_type.h
new file mode 100644
index 00000000000..cf3c3ed0888
--- /dev/null
+++ b/toolkit/mozapps/update/common/elevation_type.h
@@ -0,0 +1,26 @@
+/* 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/. */
+
+#ifndef ELEVATION_TYPE_H
+#define ELEVATION_TYPE_H
+
+#include "updater_invocation.h"
+
+enum class BoolResult { True, False, Error };
+
+enum class ElevationType {
+  Unknown,
+  None,
+  ElevatedByMMS,
+  ElevatedWithoutMMS,
+  Error,
+};
+const char* elevationTypeToString(ElevationType elevationType);
+BoolResult isProcessElevated(const NS_tchar* cmd = nullptr);
+ElevationType getElevationType(int argc = 0, NS_tchar** argv = nullptr);
+bool isElevationTypeElevated(ElevationType elevationType);
+bool isValidInvocationForElevationType(UpdaterInvocation updaterInvocation,
+                                       ElevationType elevationType);
+
+#endif
diff --git a/toolkit/mozapps/update/common/moz.build b/toolkit/mozapps/update/common/moz.build
index f5aa1f1f3c4..8a1e0c688a7 100644
--- a/toolkit/mozapps/update/common/moz.build
+++ b/toolkit/mozapps/update/common/moz.build
@@ -4,9 +4,11 @@
 
 EXPORTS += [
     "commonupdatedir.h",
+    "elevation_type.h",
     "readstrings.h",
     "updatecommon.h",
     "updatedefines.h",
+    "updater_invocation.h",
     "updatererrors.h",
 ]
 
@@ -70,8 +72,10 @@ if CONFIG["OS_ARCH"] == "WINNT":
 SOURCES += [
     "/other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp",
     "commonupdatedir.cpp",
+    "elevation_type.cpp",
     "readstrings.cpp",
     "updatecommon.cpp",
+    "updater_invocation.cpp",
 ]
 
 LOCAL_INCLUDES += [
diff --git a/toolkit/mozapps/update/common/updatecommon.cpp b/toolkit/mozapps/update/common/updatecommon.cpp
index 7202cb3c3d5..928e4057799 100644
--- a/toolkit/mozapps/update/common/updatecommon.cpp
+++ b/toolkit/mozapps/update/common/updatecommon.cpp
@@ -558,3 +558,8 @@ void LogToOS(const NS_tchar* message) {
   os_log(updaterLogger, "%{public}s", message);
 #endif
 }
+
+bool EnvHasValue(const char* name) {
+  const char* val = getenv(name);
+  return (val && *val);
+}
diff --git a/toolkit/mozapps/update/common/updatecommon.h b/toolkit/mozapps/update/common/updatecommon.h
index 7ac501fee13..b80e4f6c829 100644
--- a/toolkit/mozapps/update/common/updatecommon.h
+++ b/toolkit/mozapps/update/common/updatecommon.h
@@ -37,6 +37,7 @@ bool IsValidFullPath(NS_tchar* fullPath);
 bool IsProgramFilesPath(NS_tchar* fullPath);
 FILE* CreateAndOpenFile(NS_tchar* logFilePath, bool binary);
 void LogToOS(const NS_tchar* message);
+bool EnvHasValue(const char* name);
 
 #define LOG_WARN(args) UpdateLog::GetPrimaryLog().WarnPrintf args
 #define LOG(args) UpdateLog::GetPrimaryLog().Printf args
diff --git a/toolkit/mozapps/update/common/updater_invocation.cpp b/toolkit/mozapps/update/common/updater_invocation.cpp
new file mode 100644
index 00000000000..5179c6f7d04
--- /dev/null
+++ b/toolkit/mozapps/update/common/updater_invocation.cpp
@@ -0,0 +1,41 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#include "updater_invocation.h"
+
+const NS_tchar* firstUpdateInvocationArg = NS_T("first");
+const NS_tchar* secondUpdateInvocationArg = NS_T("second");
+
+/**
+ * Gets which updater invocation this is based on the value passed to this
+ * function by the caller.
+ */
+UpdaterInvocation getUpdaterInvocationFromArg(const NS_tchar* argument) {
+  if (NS_tstrcmp(argument, firstUpdateInvocationArg) == 0) {
+    return UpdaterInvocation::First;
+  }
+  if (NS_tstrcmp(argument, secondUpdateInvocationArg) == 0) {
+    // The second invocation may or may not be through MMS. Both are valid.
+    return UpdaterInvocation::Second;
+  }
+  // Didn't recognize the invocation arg.
+  return UpdaterInvocation::Error;
+}
+
+/**
+ * Returns a human-readable representation of an `UpdaterInvocation`.
+ */
+const char* updaterInvocationToString(UpdaterInvocation value) {
+  switch (value) {
+    case UpdaterInvocation::First:
+      return "UpdaterInvocation::First";
+    case UpdaterInvocation::Second:
+      return "UpdaterInvocation::Second";
+    case UpdaterInvocation::Error:
+      return "UpdaterInvocation::Error";
+    case UpdaterInvocation::Unknown:
+      return "UpdaterInvocation::Unknown";
+  }
+  MOZ_CRASH("impossible value for UpdaterInvocation");
+}
diff --git a/toolkit/mozapps/update/common/updater_invocation.h b/toolkit/mozapps/update/common/updater_invocation.h
new file mode 100644
index 00000000000..119857d4f58
--- /dev/null
+++ b/toolkit/mozapps/update/common/updater_invocation.h
@@ -0,0 +1,37 @@
+/* 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/. */
+
+#ifndef UPDATER_INVOCATION_H
+#define UPDATER_INVOCATION_H
+
+#include "updatedefines.h"
+
+extern const NS_tchar* firstUpdateInvocationArg;
+extern const NS_tchar* secondUpdateInvocationArg;
+
+/**
+ * This enum and its related functions are intended for interpreting the passed
+ * parameter and using it to determine whether this is the first or second
+ * invocation of the updater.
+ */
+enum class UpdaterInvocation {
+  // The initial invocation of the updater. This may apply the update, or it may
+  // start the second invocation of the updater to update depending on whether
+  // elevation is required.
+  // This invocation always does all modifications of the update directory and
+  // calls the callback application, even if another updater is launched.
+  First,
+  // The second invocation of the updater. This basically applies the update to
+  // the installation directory, calls PostUpdate (on Windows) and exits.
+  Second,
+  // It cannot be determined that we are doing either of the above invocations.
+  // This generally represents an uninitialized value or an error.
+  Unknown,
+  // There is some kind of error.
+  Error,
+};
+const char* updaterInvocationToString(UpdaterInvocation value);
+UpdaterInvocation getUpdaterInvocationFromArg(const NS_tchar* argument);
+
+#endif
diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
index 724166110d2..eb67ea4c08c 100644
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -47,7 +47,7 @@
 #include "archivereader.h"
 #include "readstrings.h"
 #include "updatererrors.h"
-
+#include "elevation_type.h"
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
@@ -167,26 +167,6 @@ BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
 
 //-----------------------------------------------------------------------------
 
-/**
- * This enum and its related functions are intended for interpreting the passed
- * parameter and using it to determine whether this is the first or second
- * invocation of the updater.
- */
-enum class UpdaterInvocation {
-  // The initial invocation of the updater. This may apply the update, or it may
-  // start the second invocation of the updater to update depending on whether
-  // elevation is required.
-  // This invocation always does all modifications of the update directory and
-  // calls the callback application, even if another updater is launched.
-  First,
-  // The second invocation of the updater. This basically applies the update to
-  // the installation directory, calls PostUpdate (on Windows) and exits.
-  Second,
-  // It cannot be determined that we are doing either of the above invocations.
-  // This generally represents an uninitialized value or an error.
-  Unknown,
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/toolkit/mozapps/update/tests/gtest/ElevationTypeTests.cpp b/toolkit/mozapps/update/tests/gtest/ElevationTypeTests.cpp
new file mode 100644
index 00000000000..362162af190
--- /dev/null
+++ b/toolkit/mozapps/update/tests/gtest/ElevationTypeTests.cpp
@@ -0,0 +1,159 @@
+/* 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 "gtest/gtest.h"
+#include "elevation_type.h"
+
+static bool mockUserHasAdminPrivileges = false;
+static bool mockUserIsLocalSystem = false;
+static bool mockError = false;
+
+#ifdef XP_WIN
+#  include "mozilla/WinHeaderOnlyUtils.h"
+
+// Mocks for testing
+namespace mozilla {
+
+LauncherResult<bool> UserHasAdminPrivileges() {
+  if (mockError) {
+    return LAUNCHER_ERROR_GENERIC();
+  }
+  return mockUserHasAdminPrivileges;
+}
+
+LauncherResult<bool> UserIsLocalSystem() {
+  if (mockError) {
+    return LAUNCHER_ERROR_GENERIC();
+  }
+  return mockUserIsLocalSystem;
+}
+
+}  // namespace mozilla
+#endif
+
+class ElevationTypeTest : public ::testing::Test {
+  void SetUp() override {
+    // Reset mocks before every test
+    mockUserHasAdminPrivileges = false;
+    mockUserIsLocalSystem = false;
+    mockError = false;
+  }
+
+  void TearDown() override {}
+};
+
+TEST_F(ElevationTypeTest, IsProcessElevated_NotElevatedTest) {
+  BoolResult res = isProcessElevated();
+  EXPECT_EQ(res, BoolResult::False);
+}
+
+#ifdef XP_WIN
+TEST_F(ElevationTypeTest, IsProcessElevated_LocalSystemElevatedTest) {
+  mockUserIsLocalSystem = true;
+  BoolResult res = isProcessElevated();
+  EXPECT_EQ(res, BoolResult::True);
+}
+
+TEST_F(ElevationTypeTest, IsProcessElevated_AdminElevatedTest) {
+  mockUserHasAdminPrivileges = true;
+  BoolResult res = isProcessElevated();
+  EXPECT_EQ(res, BoolResult::True);
+}
+TEST_F(ElevationTypeTest, IsProcessElevated_ErrorTest) {
+  mockError = true;
+  BoolResult res = isProcessElevated();
+  EXPECT_EQ(res, BoolResult::Error);
+}
+TEST_F(ElevationTypeTest, GetElevationType_NotElevated) {
+  ElevationType elevationType = getElevationType();
+  EXPECT_EQ(elevationType, ElevationType::None);
+}
+
+TEST_F(ElevationTypeTest, GetElevationType_ElevatedNotMMS) {
+  mockUserIsLocalSystem = true;
+  putenv("MOZ_USING_SERVICE=");
+  ElevationType elevationType = getElevationType();
+  EXPECT_EQ(elevationType, ElevationType::ElevatedWithoutMMS);
+}
+TEST_F(ElevationTypeTest, GetElevationType_ElevatedMMS) {
+  mockUserIsLocalSystem = true;
+  putenv("MOZ_USING_SERVICE=1");
+  ElevationType elevationType = getElevationType();
+  putenv("MOZ_USING_SERVICE=");
+  EXPECT_EQ(elevationType, ElevationType::ElevatedByMMS);
+}
+
+TEST_F(ElevationTypeTest, GetElevationType_Error) {
+  mockError = true;
+  ElevationType elevationType = getElevationType();
+  EXPECT_EQ(elevationType, ElevationType::Error);
+}
+
+TEST_F(ElevationTypeTest, IsValidInvocationForElevationType_First_NotElevated) {
+  bool isValid = isValidInvocationForElevationType(UpdaterInvocation::First,
+                                                   ElevationType::None);
+  EXPECT_TRUE(isValid);
+}
+
+TEST_F(ElevationTypeTest,
+       IsValidInvocationForElevationType_First_ElevatedNotMMS) {
+  bool isValid = isValidInvocationForElevationType(
+      UpdaterInvocation::First, ElevationType::ElevatedWithoutMMS);
+  EXPECT_TRUE(isValid);
+}
+TEST_F(ElevationTypeTest, IsValidInvocationForElevationType_First_ElevatedMMS) {
+  bool isValid = isValidInvocationForElevationType(
+      UpdaterInvocation::First, ElevationType::ElevatedByMMS);
+  EXPECT_FALSE(isValid);
+}
+TEST_F(ElevationTypeTest, IsValidInvocationForElevationType_First_Error) {
+  bool isValid = isValidInvocationForElevationType(UpdaterInvocation::First,
+                                                   ElevationType::Error);
+  EXPECT_FALSE(isValid);
+}
+TEST_F(ElevationTypeTest, IsValidInvocationForElevationType_First_Unknown) {
+  bool isValid = isValidInvocationForElevationType(UpdaterInvocation::First,
+                                                   ElevationType::Unknown);
+  EXPECT_FALSE(isValid);
+}
+TEST_F(ElevationTypeTest,
+       IsValidInvocationForElevationType_Second_NotElevated) {
+  bool isValid = isValidInvocationForElevationType(UpdaterInvocation::Second,
+                                                   ElevationType::None);
+  EXPECT_TRUE(isValid);
+}
+TEST_F(ElevationTypeTest,
+       IsValidInvocationForElevationType_Second_ElevatedNotMMS) {
+  bool isValid = isValidInvocationForElevationType(
+      UpdaterInvocation::Second, ElevationType::ElevatedWithoutMMS);
+  EXPECT_TRUE(isValid);
+}
+TEST_F(ElevationTypeTest,
+       IsValidInvocationForElevationType_Second_ElevatedMMS) {
+  bool isValid = isValidInvocationForElevationType(
+      UpdaterInvocation::Second, ElevationType::ElevatedByMMS);
+  EXPECT_TRUE(isValid);
+}
+TEST_F(ElevationTypeTest, IsValidInvocationForElevationType_Second_Error) {
+  bool isValid = isValidInvocationForElevationType(UpdaterInvocation::Second,
+                                                   ElevationType::Error);
+  EXPECT_FALSE(isValid);
+}
+TEST_F(ElevationTypeTest, IsValidInvocationForElevationType_Second_Unknown) {
+  bool isValid = isValidInvocationForElevationType(UpdaterInvocation::Second,
+                                                   ElevationType::Unknown);
+  EXPECT_FALSE(isValid);
+}
+#elif defined(XP_MACOSX)
+TEST_F(ElevationTypeTest, IsProcessElevated_NonElevatedCmd) {
+  BoolResult res = isProcessElevated("hello there");
+  EXPECT_EQ(res, BoolResult::False);
+}
+
+TEST_F(ElevationTypeTest, IsProcessElevated_ElevatedCmd) {
+  BoolResult res =
+      isProcessElevated("/Library/PrivilegedHelperTools/org.mozilla.updater");
+  EXPECT_EQ(res, BoolResult::True);
+}
+#endif
diff --git a/toolkit/mozapps/update/tests/gtest/README.md b/toolkit/mozapps/update/tests/gtest/README.md
new file mode 100644
index 00000000000..1efc5af7f33
--- /dev/null
+++ b/toolkit/mozapps/update/tests/gtest/README.md
@@ -0,0 +1,6 @@
+# Running these tests
+
+The tests in this directory are built into the program `update-gtest`, rather than `xul-gtest`.
+
+To run them, first run `./mach build` and then run the `update-gtext` executable from your obj
+directory. For example, on Windows, this would be: `.\obj-x86_64-pc-windows-msvc\dist\bin\update-gtest.exe`
diff --git a/toolkit/mozapps/update/tests/gtest/moz.build b/toolkit/mozapps/update/tests/gtest/moz.build
index bdcef75c62b..7a06d45fbd9 100644
--- a/toolkit/mozapps/update/tests/gtest/moz.build
+++ b/toolkit/mozapps/update/tests/gtest/moz.build
@@ -2,29 +2,32 @@
 # 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/.
 
-if CONFIG["OS_ARCH"] == "WINNT":
-    Program("update-gtest")
+Program("update-gtest")
 
-    DisableStlWrapping()
+DisableStlWrapping()
 
-    LOCAL_INCLUDES += [
-        "/third_party/googletest/googletest",
-        "/third_party/googletest/googletest/include",
-        "/toolkit/mozapps/update/common",
-    ]
+LOCAL_INCLUDES += [
+    "/third_party/googletest/googletest",
+    "/third_party/googletest/googletest/include",
+    "/toolkit/mozapps/update/common",
+]
+
+SOURCES += [
+    "/third_party/googletest/googletest/src/gtest-all.cc",
+    "/third_party/googletest/googletest/src/gtest_main.cc",
+    "ElevationTypeTests.cpp",
+]
+
+USE_LIBS += [
+    "updatecommon",
+]
 
+if CONFIG["OS_ARCH"] == "WINNT":
     SOURCES += [
-        "/third_party/googletest/googletest/src/gtest-all.cc",
-        "/third_party/googletest/googletest/src/gtest_main.cc",
         "EnterprisePoliciesFlagFileTests.cpp",
         "EnterprisePoliciesTests.cpp",
         "TestDirHelpers.cpp",
     ]
-
-    USE_LIBS += [
-        "updatecommon",
-    ]
-
     USE_STATIC_MSVCRT = True
     DEFINES["UNICODE"] = True
     DEFINES["_UNICODE"] = True
Loading diff…