summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/common
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/update/common')
-rw-r--r--toolkit/mozapps/update/common/certificatecheck.cpp241
-rw-r--r--toolkit/mozapps/update/common/certificatecheck.h22
-rw-r--r--toolkit/mozapps/update/common/commonupdatedir.cpp723
-rw-r--r--toolkit/mozapps/update/common/commonupdatedir.h39
-rw-r--r--toolkit/mozapps/update/common/moz.build76
-rw-r--r--toolkit/mozapps/update/common/pathhash.cpp128
-rw-r--r--toolkit/mozapps/update/common/pathhash.h21
-rw-r--r--toolkit/mozapps/update/common/readstrings.cpp396
-rw-r--r--toolkit/mozapps/update/common/readstrings.h91
-rw-r--r--toolkit/mozapps/update/common/registrycertificates.cpp148
-rw-r--r--toolkit/mozapps/update/common/registrycertificates.h14
-rw-r--r--toolkit/mozapps/update/common/uachelper.cpp186
-rw-r--r--toolkit/mozapps/update/common/uachelper.h24
-rw-r--r--toolkit/mozapps/update/common/updatecommon.cpp470
-rw-r--r--toolkit/mozapps/update/common/updatecommon.h43
-rw-r--r--toolkit/mozapps/update/common/updatedefines.h164
-rw-r--r--toolkit/mozapps/update/common/updatehelper.cpp763
-rw-r--r--toolkit/mozapps/update/common/updatehelper.h39
-rw-r--r--toolkit/mozapps/update/common/updatererrors.h130
-rw-r--r--toolkit/mozapps/update/common/updateutils_win.cpp166
-rw-r--r--toolkit/mozapps/update/common/updateutils_win.h47
21 files changed, 3931 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/common/certificatecheck.cpp b/toolkit/mozapps/update/common/certificatecheck.cpp
new file mode 100644
index 0000000000..8c53fa5fd6
--- /dev/null
+++ b/toolkit/mozapps/update/common/certificatecheck.cpp
@@ -0,0 +1,241 @@
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+#include <softpub.h>
+#include <wintrust.h>
+
+#include "certificatecheck.h"
+#include "updatecommon.h"
+
+static const int ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
+
+/**
+ * Checks to see if a file stored at filePath matches the specified info.
+ *
+ * @param filePath The PE file path to check
+ * @param infoToMatch The acceptable information to match
+ * @return ERROR_SUCCESS if successful, ERROR_NOT_FOUND if the info
+ * does not match, or the last error otherwise.
+ */
+DWORD
+CheckCertificateForPEFile(LPCWSTR filePath, CertificateCheckInfo& infoToMatch) {
+ HCERTSTORE certStore = nullptr;
+ HCRYPTMSG cryptMsg = nullptr;
+ PCCERT_CONTEXT certContext = nullptr;
+ PCMSG_SIGNER_INFO signerInfo = nullptr;
+ DWORD lastError = ERROR_SUCCESS;
+
+ // Get the HCERTSTORE and HCRYPTMSG from the signed file.
+ DWORD encoding, contentType, formatType;
+ BOOL result = CryptQueryObject(
+ CERT_QUERY_OBJECT_FILE, filePath,
+ CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, CERT_QUERY_CONTENT_FLAG_ALL,
+ 0, &encoding, &contentType, &formatType, &certStore, &cryptMsg, nullptr);
+ if (!result) {
+ lastError = GetLastError();
+ LOG_WARN(("CryptQueryObject failed. (%lu)", lastError));
+ goto cleanup;
+ }
+
+ // Pass in nullptr to get the needed signer information size.
+ DWORD signerInfoSize;
+ result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0, nullptr,
+ &signerInfoSize);
+ if (!result) {
+ lastError = GetLastError();
+ LOG_WARN(("CryptMsgGetParam failed. (%lu)", lastError));
+ goto cleanup;
+ }
+
+ // Allocate the needed size for the signer information.
+ signerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, signerInfoSize);
+ if (!signerInfo) {
+ lastError = GetLastError();
+ LOG_WARN(("Unable to allocate memory for Signer Info. (%lu)", lastError));
+ goto cleanup;
+ }
+
+ // Get the signer information (PCMSG_SIGNER_INFO).
+ // In particular we want the issuer and serial number.
+ result = CryptMsgGetParam(cryptMsg, CMSG_SIGNER_INFO_PARAM, 0,
+ (PVOID)signerInfo, &signerInfoSize);
+ if (!result) {
+ lastError = GetLastError();
+ LOG_WARN(("CryptMsgGetParam failed. (%lu)", lastError));
+ goto cleanup;
+ }
+
+ // Search for the signer certificate in the certificate store.
+ CERT_INFO certInfo;
+ certInfo.Issuer = signerInfo->Issuer;
+ certInfo.SerialNumber = signerInfo->SerialNumber;
+ certContext =
+ CertFindCertificateInStore(certStore, ENCODING, 0, CERT_FIND_SUBJECT_CERT,
+ (PVOID)&certInfo, nullptr);
+ if (!certContext) {
+ lastError = GetLastError();
+ LOG_WARN(("CertFindCertificateInStore failed. (%lu)", lastError));
+ goto cleanup;
+ }
+
+ if (!DoCertificateAttributesMatch(certContext, infoToMatch)) {
+ lastError = ERROR_NOT_FOUND;
+ LOG_WARN(("Certificate did not match issuer or name. (%lu)", lastError));
+ goto cleanup;
+ }
+
+cleanup:
+ if (signerInfo) {
+ LocalFree(signerInfo);
+ }
+ if (certContext) {
+ CertFreeCertificateContext(certContext);
+ }
+ if (certStore) {
+ CertCloseStore(certStore, 0);
+ }
+ if (cryptMsg) {
+ CryptMsgClose(cryptMsg);
+ }
+ return lastError;
+}
+
+/**
+ * Checks to see if a file stored at filePath matches the specified info.
+ *
+ * @param certContext The certificate context of the file
+ * @param infoToMatch The acceptable information to match
+ * @return FALSE if the info does not match or if any error occurs in the check
+ */
+BOOL DoCertificateAttributesMatch(PCCERT_CONTEXT certContext,
+ CertificateCheckInfo& infoToMatch) {
+ DWORD dwData;
+ LPWSTR szName = nullptr;
+
+ if (infoToMatch.issuer) {
+ // Pass in nullptr to get the needed size of the issuer buffer.
+ dwData = CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE,
+ CERT_NAME_ISSUER_FLAG, nullptr, nullptr, 0);
+
+ if (!dwData) {
+ LOG_WARN(("CertGetNameString failed. (%lu)", GetLastError()));
+ return FALSE;
+ }
+
+ // Allocate memory for Issuer name buffer.
+ szName = (LPWSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR));
+ if (!szName) {
+ LOG_WARN(("Unable to allocate memory for issuer name. (%lu)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ // Get Issuer name.
+ if (!CertGetNameStringW(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE,
+ CERT_NAME_ISSUER_FLAG, nullptr, szName, dwData)) {
+ LOG_WARN(("CertGetNameString failed. (%lu)", GetLastError()));
+ LocalFree(szName);
+ return FALSE;
+ }
+
+ // If the issuer does not match, return a failure.
+ if (!infoToMatch.issuer || wcscmp(szName, infoToMatch.issuer)) {
+ LocalFree(szName);
+ return FALSE;
+ }
+
+ LocalFree(szName);
+ szName = nullptr;
+ }
+
+ if (infoToMatch.name) {
+ // Pass in nullptr to get the needed size of the name buffer.
+ dwData = CertGetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
+ nullptr, nullptr, 0);
+ if (!dwData) {
+ LOG_WARN(("CertGetNameString failed. (%lu)", GetLastError()));
+ return FALSE;
+ }
+
+ // Allocate memory for the name buffer.
+ szName = (LPWSTR)LocalAlloc(LPTR, dwData * sizeof(WCHAR));
+ if (!szName) {
+ LOG_WARN(("Unable to allocate memory for subject name. (%lu)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ // Obtain the name.
+ if (!(CertGetNameStringW(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
+ nullptr, szName, dwData))) {
+ LOG_WARN(("CertGetNameString failed. (%lu)", GetLastError()));
+ LocalFree(szName);
+ return FALSE;
+ }
+
+ // If the issuer does not match, return a failure.
+ if (!infoToMatch.name || wcscmp(szName, infoToMatch.name)) {
+ LocalFree(szName);
+ return FALSE;
+ }
+
+ // We have a match!
+ LocalFree(szName);
+ }
+
+ // If there were any errors we would have aborted by now.
+ return TRUE;
+}
+
+/**
+ * Verifies the trust of the specified file path.
+ *
+ * @param filePath The file path to check.
+ * @return ERROR_SUCCESS if successful, or the last error code otherwise.
+ */
+DWORD
+VerifyCertificateTrustForFile(LPCWSTR filePath) {
+ // Setup the file to check.
+ WINTRUST_FILE_INFO fileToCheck;
+ ZeroMemory(&fileToCheck, sizeof(fileToCheck));
+ fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO);
+ fileToCheck.pcwszFilePath = filePath;
+
+ // Setup what to check, we want to check it is signed and trusted.
+ WINTRUST_DATA trustData;
+ ZeroMemory(&trustData, sizeof(trustData));
+ trustData.cbStruct = sizeof(trustData);
+ trustData.pPolicyCallbackData = nullptr;
+ trustData.pSIPClientData = nullptr;
+ trustData.dwUIChoice = WTD_UI_NONE;
+ trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
+ trustData.dwUnionChoice = WTD_CHOICE_FILE;
+ trustData.dwStateAction = 0;
+ trustData.hWVTStateData = nullptr;
+ trustData.pwszURLReference = nullptr;
+ // no UI
+ trustData.dwUIContext = 0;
+ trustData.pFile = &fileToCheck;
+
+ GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+ // Check if the file is signed by something that is trusted.
+ LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData);
+ if (ERROR_SUCCESS == ret) {
+ // The hash that represents the subject is trusted and there were no
+ // verification errors. No publisher nor time stamp chain errors.
+ LOG(("The file \"%ls\" is signed and the signature was verified.",
+ filePath));
+ return ERROR_SUCCESS;
+ }
+
+ DWORD lastError = GetLastError();
+ LOG_WARN(
+ ("There was an error validating trust of the certificate for file"
+ " \"%ls\". Returned: %ld. (%lu)",
+ filePath, ret, lastError));
+ return ret;
+}
diff --git a/toolkit/mozapps/update/common/certificatecheck.h b/toolkit/mozapps/update/common/certificatecheck.h
new file mode 100644
index 0000000000..af9f8456df
--- /dev/null
+++ b/toolkit/mozapps/update/common/certificatecheck.h
@@ -0,0 +1,22 @@
+/* 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 _CERTIFICATECHECK_H_
+#define _CERTIFICATECHECK_H_
+
+#include <windows.h>
+#include <wincrypt.h>
+
+struct CertificateCheckInfo {
+ LPCWSTR name;
+ LPCWSTR issuer;
+};
+
+BOOL DoCertificateAttributesMatch(PCCERT_CONTEXT pCertContext,
+ CertificateCheckInfo& infoToMatch);
+DWORD VerifyCertificateTrustForFile(LPCWSTR filePath);
+DWORD CheckCertificateForPEFile(LPCWSTR filePath,
+ CertificateCheckInfo& infoToMatch);
+
+#endif
diff --git a/toolkit/mozapps/update/common/commonupdatedir.cpp b/toolkit/mozapps/update/common/commonupdatedir.cpp
new file mode 100644
index 0000000000..0ba9fcef94
--- /dev/null
+++ b/toolkit/mozapps/update/common/commonupdatedir.cpp
@@ -0,0 +1,723 @@
+/* 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/. */
+
+/*
+ * This file does not use many of the features Firefox provides such as
+ * nsAString and nsIFile because code in this file will be included not only
+ * in Firefox, but also in the Mozilla Maintenance Service, the Mozilla
+ * Maintenance Service installer, and TestAUSHelper.
+ */
+
+#include <cinttypes>
+#include <cwchar>
+#include <string>
+#include "city.h"
+#include "commonupdatedir.h"
+#include "updatedefines.h"
+
+#ifdef XP_WIN
+# include <accctrl.h>
+# include <aclapi.h>
+# include <cstdarg>
+# include <errno.h>
+# include <objbase.h>
+# include <shellapi.h>
+# include <shlobj.h>
+# include <strsafe.h>
+# include <winerror.h>
+# include "nsWindowsHelpers.h"
+# include "updateutils_win.h"
+#endif
+
+#ifdef XP_WIN
+// This is the name of the old update directory
+// (i.e. C:\ProgramData\<OLD_ROOT_UPDATE_DIR_NAME>)
+# define OLD_ROOT_UPDATE_DIR_NAME "Mozilla"
+// This is the name of the current update directory
+// (i.e. C:\ProgramData\<ROOT_UPDATE_DIR_NAME>)
+// It is really important that we properly set the permissions on this
+// directory at creation time. Thus, it is really important that this code be
+// the creator of this directory. We had many problems with the old update
+// directory having been previously created by old versions of Firefox. To avoid
+// this problem in the future, we are including a UUID in the root update
+// directory name to attempt to ensure that it will be created by this code and
+// won't already exist with the wrong permissions.
+# define ROOT_UPDATE_DIR_NAME "Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38"
+// This describes the directory between the "Mozilla" directory and the install
+// path hash (i.e. C:\ProgramData\Mozilla\<UPDATE_PATH_MID_DIR_NAME>\<hash>)
+# define UPDATE_PATH_MID_DIR_NAME "updates"
+
+enum class WhichUpdateDir {
+ CurrentUpdateDir,
+ UnmigratedUpdateDir,
+};
+
+/**
+ * This is a very simple string class.
+ *
+ * This class has some substantial limitations for the sake of simplicity. It
+ * has no support whatsoever for modifying a string that already has data. There
+ * is, therefore, no append function and no support for automatically resizing
+ * strings.
+ *
+ * Error handling is also done in a slightly unusual manner. If there is ever
+ * a failure allocating or assigning to a string, it will do the simplest
+ * possible recovery: truncate itself to 0-length.
+ * This coupled with the fact that the length is cached means that an effective
+ * method of error checking is to attempt assignment and then check the length
+ * of the result.
+ */
+class SimpleAutoString {
+ private:
+ size_t mLength;
+ // Unique pointer frees the buffer when the class is deleted or goes out of
+ // scope.
+ mozilla::UniquePtr<wchar_t[]> mString;
+
+ /**
+ * Allocates enough space to store a string of the specified length.
+ */
+ bool AllocLen(size_t len) {
+ mString = mozilla::MakeUnique<wchar_t[]>(len + 1);
+ return mString.get() != nullptr;
+ }
+
+ /**
+ * Allocates a buffer of the size given.
+ */
+ bool AllocSize(size_t size) {
+ mString = mozilla::MakeUnique<wchar_t[]>(size);
+ return mString.get() != nullptr;
+ }
+
+ public:
+ SimpleAutoString() : mLength(0) {}
+
+ /*
+ * Allocates enough space for a string of the given length and formats it as
+ * an empty string.
+ */
+ bool AllocEmpty(size_t len) {
+ bool success = AllocLen(len);
+ Truncate();
+ return success;
+ }
+
+ /**
+ * These functions can potentially return null if no buffer has yet been
+ * allocated. After changing a string retrieved with MutableString, the Check
+ * method should be called to synchronize other members (ex: mLength) with the
+ * new buffer.
+ */
+ wchar_t* MutableString() { return mString.get(); }
+ const wchar_t* String() const { return mString.get(); }
+
+ size_t Length() const { return mLength; }
+
+ /**
+ * This method should be called after manually changing the string's buffer
+ * via MutableString to synchronize other members (ex: mLength) with the
+ * new buffer.
+ * Returns true if the string is now in a valid state.
+ */
+ bool Check() {
+ mLength = wcslen(mString.get());
+ return true;
+ }
+
+ void SwapBufferWith(mozilla::UniquePtr<wchar_t[]>& other) {
+ mString.swap(other);
+ if (mString) {
+ mLength = wcslen(mString.get());
+ } else {
+ mLength = 0;
+ }
+ }
+
+ void Swap(SimpleAutoString& other) {
+ mString.swap(other.mString);
+ size_t newLength = other.mLength;
+ other.mLength = mLength;
+ mLength = newLength;
+ }
+
+ /**
+ * Truncates the string to the length specified. This must not be greater than
+ * or equal to the size of the string's buffer.
+ */
+ void Truncate(size_t len = 0) {
+ if (len > mLength) {
+ return;
+ }
+ mLength = len;
+ if (mString) {
+ mString.get()[len] = L'\0';
+ }
+ }
+
+ /**
+ * Assigns a string and ensures that the resulting string is valid and has its
+ * length set properly.
+ *
+ * Note that although other similar functions in this class take length, this
+ * function takes buffer size instead because it is intended to be follow the
+ * input convention of sprintf.
+ *
+ * Returns the new length, which will be 0 if there was any failure.
+ *
+ * This function does no allocation or reallocation. If the buffer is not
+ * large enough to hold the new string, the call will fail.
+ */
+ size_t AssignSprintf(size_t bufferSize, const wchar_t* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ size_t returnValue = AssignVsprintf(bufferSize, format, ap);
+ va_end(ap);
+ return returnValue;
+ }
+ /**
+ * Same as the above, but takes a va_list like vsprintf does.
+ */
+ size_t AssignVsprintf(size_t bufferSize, const wchar_t* format, va_list ap) {
+ if (!mString) {
+ Truncate();
+ return 0;
+ }
+
+ int charsWritten = vswprintf(mString.get(), bufferSize, format, ap);
+ if (charsWritten < 0 || static_cast<size_t>(charsWritten) >= bufferSize) {
+ // charsWritten does not include the null terminator. If charsWritten is
+ // equal to the buffer size, we do not have a null terminator nor do we
+ // have room for one.
+ Truncate();
+ return 0;
+ }
+ mString.get()[charsWritten] = L'\0';
+
+ mLength = charsWritten;
+ return mLength;
+ }
+
+ /**
+ * Allocates enough space for the string and assigns a value to it with
+ * sprintf. Takes, as an argument, the maximum length that the string is
+ * expected to use (which, after adding 1 for the null byte, is the amount of
+ * space that will be allocated).
+ *
+ * Returns the new length, which will be 0 on any failure.
+ */
+ size_t AllocAndAssignSprintf(size_t maxLength, const wchar_t* format, ...) {
+ if (!AllocLen(maxLength)) {
+ Truncate();
+ return 0;
+ }
+ va_list ap;
+ va_start(ap, format);
+ size_t charsWritten = AssignVsprintf(maxLength + 1, format, ap);
+ va_end(ap);
+ return charsWritten;
+ }
+
+ /*
+ * Allocates enough for the formatted text desired. Returns maximum storable
+ * length of a string in the allocated buffer on success, or 0 on failure.
+ */
+ size_t AllocFromScprintf(const wchar_t* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ size_t returnValue = AllocFromVscprintf(format, ap);
+ va_end(ap);
+ return returnValue;
+ }
+ /**
+ * Same as the above, but takes a va_list like vscprintf does.
+ */
+ size_t AllocFromVscprintf(const wchar_t* format, va_list ap) {
+ int len = _vscwprintf(format, ap);
+ if (len < 0) {
+ Truncate();
+ return 0;
+ }
+ if (!AllocEmpty(len)) {
+ // AllocEmpty will Truncate, no need to call it here.
+ return 0;
+ }
+ return len;
+ }
+
+ /**
+ * Automatically determines how much space is necessary, allocates that much
+ * for the string, and assigns the data using swprintf. Returns the resulting
+ * length of the string, which will be 0 if the function fails.
+ */
+ size_t AutoAllocAndAssignSprintf(const wchar_t* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ size_t len = AllocFromVscprintf(format, ap);
+ va_end(ap);
+ if (len == 0) {
+ // AllocFromVscprintf will Truncate, no need to call it here.
+ return 0;
+ }
+
+ va_start(ap, format);
+ size_t charsWritten = AssignVsprintf(len + 1, format, ap);
+ va_end(ap);
+
+ if (len != charsWritten) {
+ Truncate();
+ return 0;
+ }
+ return charsWritten;
+ }
+
+ /**
+ * The following CopyFrom functions take various types of strings, allocate
+ * enough space to hold them, and then copy them into that space.
+ *
+ * They return an HRESULT that should be interpreted with the SUCCEEDED or
+ * FAILED macro.
+ */
+ HRESULT CopyFrom(const wchar_t* src) {
+ mLength = wcslen(src);
+ if (!AllocLen(mLength)) {
+ Truncate();
+ return E_OUTOFMEMORY;
+ }
+ HRESULT hrv = StringCchCopyW(mString.get(), mLength + 1, src);
+ if (FAILED(hrv)) {
+ Truncate();
+ }
+ return hrv;
+ }
+ HRESULT CopyFrom(const SimpleAutoString& src) {
+ if (!src.mString) {
+ Truncate();
+ return S_OK;
+ }
+ mLength = src.mLength;
+ if (!AllocLen(mLength)) {
+ Truncate();
+ return E_OUTOFMEMORY;
+ }
+ HRESULT hrv = StringCchCopyW(mString.get(), mLength + 1, src.mString.get());
+ if (FAILED(hrv)) {
+ Truncate();
+ }
+ return hrv;
+ }
+ HRESULT CopyFrom(const char* src) {
+ int bufferSize =
+ MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, nullptr, 0);
+ if (bufferSize == 0) {
+ Truncate();
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+ if (!AllocSize(bufferSize)) {
+ Truncate();
+ return E_OUTOFMEMORY;
+ }
+ int charsWritten = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src,
+ -1, mString.get(), bufferSize);
+ if (charsWritten == 0) {
+ Truncate();
+ return HRESULT_FROM_WIN32(GetLastError());
+ } else if (charsWritten != bufferSize) {
+ Truncate();
+ return E_FAIL;
+ }
+ mLength = charsWritten - 1;
+ return S_OK;
+ }
+
+ bool StartsWith(const SimpleAutoString& prefix) const {
+ if (!mString) {
+ return (prefix.mLength == 0);
+ }
+ if (!prefix.mString) {
+ return true;
+ }
+ if (prefix.mLength > mLength) {
+ return false;
+ }
+ return (wcsncmp(mString.get(), prefix.mString.get(), prefix.mLength) == 0);
+ }
+};
+
+// Deleter for use with UniquePtr
+struct CoTaskMemFreeDeleter {
+ void operator()(void* aPtr) { ::CoTaskMemFree(aPtr); }
+};
+
+/**
+ * A lot of data goes into constructing an ACL and security attributes, and the
+ * Windows documentation does not make it very clear what can be safely freed
+ * after these objects are constructed. This struct holds all of the
+ * construction data in one place so that it can be passed around and freed
+ * properly.
+ */
+struct AutoPerms {
+ SID_IDENTIFIER_AUTHORITY sidIdentifierAuthority;
+ UniqueSidPtr usersSID;
+ UniqueSidPtr adminsSID;
+ UniqueSidPtr systemSID;
+ EXPLICIT_ACCESS_W ea[3];
+ mozilla::UniquePtr<ACL, LocalFreeDeleter> acl;
+ mozilla::UniquePtr<uint8_t[]> securityDescriptorBuffer;
+ PSECURITY_DESCRIPTOR securityDescriptor;
+ SECURITY_ATTRIBUTES securityAttributes;
+};
+
+static bool GetCachedHash(const char16_t* installPath, HKEY rootKey,
+ const SimpleAutoString& regPath,
+ mozilla::UniquePtr<NS_tchar[]>& result);
+static HRESULT GetUpdateDirectory(const wchar_t* installPath,
+ WhichUpdateDir whichDir,
+ mozilla::UniquePtr<wchar_t[]>& result);
+static HRESULT GeneratePermissions(AutoPerms& result);
+static HRESULT MakeDir(const SimpleAutoString& path, const AutoPerms& perms);
+#endif // XP_WIN
+
+/**
+ * Returns a hash of the install path, suitable for uniquely identifying the
+ * particular Firefox installation that is running.
+ *
+ * This function includes a compatibility mode that should NOT be used except by
+ * GetUserUpdateDirectory. Previous implementations of this function could
+ * return a value inconsistent with what our installer would generate. When the
+ * update directory was migrated, this function was re-implemented to return
+ * values consistent with those generated by the installer. The compatibility
+ * mode is retained only so that we can properly get the old update directory
+ * when migrating it.
+ *
+ * @param installPath
+ * The null-terminated path to the installation directory (i.e. the
+ * directory that contains the binary). Must not be null. The path must
+ * not include a trailing slash.
+ * @param result
+ * The out parameter that will be set to contain the resulting hash.
+ * The value is wrapped in a UniquePtr to make cleanup easier on the
+ * caller.
+ * @return true if successful and false otherwise.
+ */
+bool GetInstallHash(const char16_t* installPath,
+ mozilla::UniquePtr<NS_tchar[]>& result) {
+ MOZ_ASSERT(installPath != nullptr,
+ "Install path must not be null in GetInstallHash");
+
+ size_t pathSize =
+ std::char_traits<char16_t>::length(installPath) * sizeof(*installPath);
+ uint64_t hash =
+ CityHash64(reinterpret_cast<const char*>(installPath), pathSize);
+
+ size_t hashStrSize = sizeof(hash) * 2 + 1; // 2 hex digits per byte + null
+ result = mozilla::MakeUnique<NS_tchar[]>(hashStrSize);
+ int charsWritten =
+ NS_tsnprintf(result.get(), hashStrSize, NS_T("%") NS_T(PRIX64), hash);
+ return !(charsWritten < 1 ||
+ static_cast<size_t>(charsWritten) > hashStrSize - 1);
+}
+
+#ifdef XP_WIN
+/**
+ * Returns true if the registry key was successfully found and read into result.
+ */
+static bool GetCachedHash(const char16_t* installPath, HKEY rootKey,
+ const SimpleAutoString& regPath,
+ mozilla::UniquePtr<NS_tchar[]>& result) {
+ // Find the size of the string we are reading before we read it so we can
+ // allocate space.
+ unsigned long bufferSize;
+ LSTATUS lrv = RegGetValueW(rootKey, regPath.String(),
+ reinterpret_cast<const wchar_t*>(installPath),
+ RRF_RT_REG_SZ, nullptr, nullptr, &bufferSize);
+ if (lrv != ERROR_SUCCESS) {
+ return false;
+ }
+ result = mozilla::MakeUnique<NS_tchar[]>(bufferSize);
+ // Now read the actual value from the registry.
+ lrv = RegGetValueW(rootKey, regPath.String(),
+ reinterpret_cast<const wchar_t*>(installPath),
+ RRF_RT_REG_SZ, nullptr, result.get(), &bufferSize);
+ return (lrv == ERROR_SUCCESS);
+}
+
+/**
+ * Returns the update directory path. The update directory needs to have
+ * different permissions from the default, so we don't really want anyone using
+ * the path without the directory already being created with the correct
+ * permissions. Therefore, this function also ensures that the base directory
+ * that needs permissions set already exists. If it does not exist, it is
+ * created with the needed permissions.
+ * The desired permissions give Full Control to SYSTEM, Administrators, and
+ * Users.
+ *
+ * @param installPath
+ * Must be the null-terminated path to the installation directory (i.e.
+ * the directory that contains the binary). The path must not include a
+ * trailing slash.
+ * @param result
+ * The out parameter that will be set to contain the resulting path.
+ * The value is wrapped in a UniquePtr to make cleanup easier on the
+ * caller.
+ *
+ * @return An HRESULT that should be tested with SUCCEEDED or FAILED.
+ */
+HRESULT
+GetCommonUpdateDirectory(const wchar_t* installPath,
+ mozilla::UniquePtr<wchar_t[]>& result) {
+ return GetUpdateDirectory(installPath, WhichUpdateDir::CurrentUpdateDir,
+ result);
+}
+
+/**
+ * This function is identical to the function above except that it gets the
+ * "old" (pre-migration) update directory.
+ *
+ * The other difference is that this function does not create the directory.
+ */
+HRESULT
+GetOldUpdateDirectory(const wchar_t* installPath,
+ mozilla::UniquePtr<wchar_t[]>& result) {
+ return GetUpdateDirectory(installPath, WhichUpdateDir::UnmigratedUpdateDir,
+ result);
+}
+
+/**
+ * This is a version of the GetCommonUpdateDirectory that can be called from
+ * Rust.
+ * The result parameter must be a valid pointer to a buffer of length
+ * MAX_PATH + 1
+ */
+extern "C" HRESULT get_common_update_directory(const wchar_t* installPath,
+ wchar_t* result) {
+ mozilla::UniquePtr<wchar_t[]> uniqueResult;
+ HRESULT hr = GetCommonUpdateDirectory(installPath, uniqueResult);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ return StringCchCopyW(result, MAX_PATH + 1, uniqueResult.get());
+}
+
+/**
+ * This is a helper function that does all of the work for
+ * GetCommonUpdateDirectory and GetUserUpdateDirectory.
+ *
+ * For information on the parameters and return value, see
+ * GetCommonUpdateDirectory.
+ */
+static HRESULT GetUpdateDirectory(const wchar_t* installPath,
+ WhichUpdateDir whichDir,
+ mozilla::UniquePtr<wchar_t[]>& result) {
+ MOZ_ASSERT(installPath != nullptr,
+ "Install path must not be null in GetUpdateDirectory");
+
+ AutoPerms perms;
+ HRESULT hrv = GeneratePermissions(perms);
+ if (FAILED(hrv)) {
+ return hrv;
+ }
+
+ PWSTR baseDirParentPath;
+ hrv = SHGetKnownFolderPath(FOLDERID_ProgramData, KF_FLAG_CREATE, nullptr,
+ &baseDirParentPath);
+ // Free baseDirParentPath when it goes out of scope.
+ mozilla::UniquePtr<wchar_t, CoTaskMemFreeDeleter> baseDirParentPathUnique(
+ baseDirParentPath);
+ if (FAILED(hrv)) {
+ return hrv;
+ }
+
+ SimpleAutoString baseDir;
+ if (whichDir == WhichUpdateDir::UnmigratedUpdateDir) {
+ const wchar_t baseDirLiteral[] = NS_T(OLD_ROOT_UPDATE_DIR_NAME);
+ hrv = baseDir.CopyFrom(baseDirLiteral);
+ } else {
+ const wchar_t baseDirLiteral[] = NS_T(ROOT_UPDATE_DIR_NAME);
+ hrv = baseDir.CopyFrom(baseDirLiteral);
+ }
+ if (FAILED(hrv)) {
+ return hrv;
+ }
+
+ // Generate the base path
+ // (C:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38)
+ SimpleAutoString basePath;
+ size_t basePathLen =
+ wcslen(baseDirParentPath) + 1 /* path separator */ + baseDir.Length();
+ basePath.AllocAndAssignSprintf(basePathLen, L"%s\\%s", baseDirParentPath,
+ baseDir.String());
+ if (basePath.Length() != basePathLen) {
+ return E_FAIL;
+ }
+
+ if (whichDir == WhichUpdateDir::CurrentUpdateDir) {
+ hrv = MakeDir(basePath, perms);
+ if (FAILED(hrv)) {
+ return hrv;
+ }
+ }
+
+ // Generate what we are going to call the mid-path
+ // (C:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38\updates)
+ const wchar_t midPathDirName[] = NS_T(UPDATE_PATH_MID_DIR_NAME);
+ size_t midPathLen =
+ basePath.Length() + 1 /* path separator */ + wcslen(midPathDirName);
+ SimpleAutoString midPath;
+ midPath.AllocAndAssignSprintf(midPathLen, L"%s\\%s", basePath.String(),
+ midPathDirName);
+ if (midPath.Length() != midPathLen) {
+ return E_FAIL;
+ }
+
+ mozilla::UniquePtr<NS_tchar[]> hash;
+
+ // The Windows installer caches this hash value in the registry
+ bool gotHash = false;
+ SimpleAutoString regPath;
+ regPath.AutoAllocAndAssignSprintf(L"SOFTWARE\\Mozilla\\%S\\TaskBarIDs",
+ MOZ_APP_BASENAME);
+ if (regPath.Length() != 0) {
+ gotHash = GetCachedHash(reinterpret_cast<const char16_t*>(installPath),
+ HKEY_LOCAL_MACHINE, regPath, hash);
+ if (!gotHash) {
+ gotHash = GetCachedHash(reinterpret_cast<const char16_t*>(installPath),
+ HKEY_CURRENT_USER, regPath, hash);
+ }
+ }
+ // If we couldn't get it out of the registry, we'll just have to regenerate
+ // it.
+ if (!gotHash) {
+ bool success =
+ GetInstallHash(reinterpret_cast<const char16_t*>(installPath), hash);
+ if (!success) {
+ return E_FAIL;
+ }
+ }
+
+ size_t updatePathLen =
+ midPath.Length() + 1 /* path separator */ + wcslen(hash.get());
+ SimpleAutoString updatePath;
+ updatePath.AllocAndAssignSprintf(updatePathLen, L"%s\\%s", midPath.String(),
+ hash.get());
+ if (updatePath.Length() != updatePathLen) {
+ return E_FAIL;
+ }
+
+ updatePath.SwapBufferWith(result);
+ return S_OK;
+}
+
+/**
+ * Generates the permission set that we want to be applied to the update
+ * directory and its contents. Returns the permissions data via the result
+ * outparam.
+ *
+ * These are also the permissions that will be used to check that file
+ * permissions are correct.
+ */
+static HRESULT GeneratePermissions(AutoPerms& result) {
+ result.sidIdentifierAuthority = SECURITY_NT_AUTHORITY;
+ ZeroMemory(&result.ea, sizeof(result.ea));
+
+ // Make Users group SID and add it to the Explicit Access List.
+ PSID usersSID = nullptr;
+ BOOL success = AllocateAndInitializeSid(
+ &result.sidIdentifierAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
+ DOMAIN_ALIAS_RID_USERS, 0, 0, 0, 0, 0, 0, &usersSID);
+ result.usersSID.reset(usersSID);
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+ result.ea[0].grfAccessPermissions = FILE_ALL_ACCESS;
+ result.ea[0].grfAccessMode = SET_ACCESS;
+ result.ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ result.ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ result.ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+ result.ea[0].Trustee.ptstrName = static_cast<LPWSTR>(usersSID);
+
+ // Make Administrators group SID and add it to the Explicit Access List.
+ PSID adminsSID = nullptr;
+ success = AllocateAndInitializeSid(
+ &result.sidIdentifierAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
+ DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminsSID);
+ result.adminsSID.reset(adminsSID);
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+ result.ea[1].grfAccessPermissions = FILE_ALL_ACCESS;
+ result.ea[1].grfAccessMode = SET_ACCESS;
+ result.ea[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ result.ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ result.ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+ result.ea[1].Trustee.ptstrName = static_cast<LPWSTR>(adminsSID);
+
+ // Make SYSTEM user SID and add it to the Explicit Access List.
+ PSID systemSID = nullptr;
+ success = AllocateAndInitializeSid(&result.sidIdentifierAuthority, 1,
+ SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0,
+ 0, 0, &systemSID);
+ result.systemSID.reset(systemSID);
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+ result.ea[2].grfAccessPermissions = FILE_ALL_ACCESS;
+ result.ea[2].grfAccessMode = SET_ACCESS;
+ result.ea[2].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ result.ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ result.ea[2].Trustee.TrusteeType = TRUSTEE_IS_USER;
+ result.ea[2].Trustee.ptstrName = static_cast<LPWSTR>(systemSID);
+
+ PACL acl = nullptr;
+ DWORD drv = SetEntriesInAclW(3, result.ea, nullptr, &acl);
+ // Put the ACL in a unique pointer so that LocalFree is called when it goes
+ // out of scope
+ result.acl.reset(acl);
+ if (drv != ERROR_SUCCESS) {
+ return HRESULT_FROM_WIN32(drv);
+ }
+
+ result.securityDescriptorBuffer =
+ mozilla::MakeUnique<uint8_t[]>(SECURITY_DESCRIPTOR_MIN_LENGTH);
+ if (!result.securityDescriptorBuffer) {
+ return E_OUTOFMEMORY;
+ }
+ result.securityDescriptor = reinterpret_cast<PSECURITY_DESCRIPTOR>(
+ result.securityDescriptorBuffer.get());
+ success = InitializeSecurityDescriptor(result.securityDescriptor,
+ SECURITY_DESCRIPTOR_REVISION);
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ success =
+ SetSecurityDescriptorDacl(result.securityDescriptor, TRUE, acl, FALSE);
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ result.securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
+ result.securityAttributes.lpSecurityDescriptor = result.securityDescriptor;
+ result.securityAttributes.bInheritHandle = FALSE;
+ return S_OK;
+}
+
+/**
+ * Creates a directory with the permissions specified. If the directory already
+ * exists, this function will return success.
+ */
+static HRESULT MakeDir(const SimpleAutoString& path, const AutoPerms& perms) {
+ BOOL success = CreateDirectoryW(
+ path.String(),
+ const_cast<LPSECURITY_ATTRIBUTES>(&perms.securityAttributes));
+ if (success) {
+ return S_OK;
+ }
+ DWORD error = GetLastError();
+ if (error == ERROR_ALREADY_EXISTS) {
+ return S_OK;
+ }
+ return HRESULT_FROM_WIN32(error);
+}
+#endif // XP_WIN
diff --git a/toolkit/mozapps/update/common/commonupdatedir.h b/toolkit/mozapps/update/common/commonupdatedir.h
new file mode 100644
index 0000000000..5d7f88b15e
--- /dev/null
+++ b/toolkit/mozapps/update/common/commonupdatedir.h
@@ -0,0 +1,39 @@
+/* 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 COMMONUPDATEDIR_H
+#define COMMONUPDATEDIR_H
+
+#include "mozilla/UniquePtr.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+typedef WCHAR NS_tchar;
+#else
+typedef char NS_tchar;
+#endif
+
+bool GetInstallHash(const char16_t* installPath,
+ mozilla::UniquePtr<NS_tchar[]>& result);
+
+#ifdef XP_WIN
+// In addition to getting the update directory, this function also creates it.
+// This is to ensure that, when it is created, it is created with the correct
+// permissions. The default permissions on the containing directory can cause
+// problems, so it is very, very important that we make sure that the
+// permissions are set properly. Thus, we won't even give out the path of the
+// update directory without ensuring that it was created with the correct
+// permissions.
+HRESULT GetCommonUpdateDirectory(const wchar_t* installPath,
+ mozilla::UniquePtr<wchar_t[]>& result);
+// Returns the old common update directory. Since this directory was used before
+// we made sure to always set the correct permissions, it is possible that the
+// permissions on this directory are set such that files can only be modified
+// or deleted by the user that created them. This function exists entirely to
+// allow us to migrate files out of the old update directory and into the new
+// one.
+HRESULT GetOldUpdateDirectory(const wchar_t* installPath,
+ mozilla::UniquePtr<wchar_t[]>& result);
+#endif
+
+#endif
diff --git a/toolkit/mozapps/update/common/moz.build b/toolkit/mozapps/update/common/moz.build
new file mode 100644
index 0000000000..2c79661c1f
--- /dev/null
+++ b/toolkit/mozapps/update/common/moz.build
@@ -0,0 +1,76 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS += [
+ "commonupdatedir.h",
+ "readstrings.h",
+ "updatecommon.h",
+ "updatedefines.h",
+ "updatererrors.h",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ EXPORTS += [
+ "pathhash.h",
+ "uachelper.h",
+ "updatehelper.cpp",
+ "updatehelper.h",
+ "updateutils_win.h",
+ ]
+
+ if CONFIG["MOZ_MAINTENANCE_SERVICE"]:
+ EXPORTS += [
+ "certificatecheck.h",
+ "registrycertificates.h",
+ ]
+
+Library("updatecommon")
+
+DEFINES["NS_NO_XPCOM"] = True
+USE_STATIC_LIBS = True
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ # This forces the creation of updatecommon.lib, which the update agent needs
+ # in order to link to updatecommon library functions.
+ NO_EXPAND_LIBS = True
+
+DisableStlWrapping()
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ SOURCES += [
+ "pathhash.cpp",
+ "uachelper.cpp",
+ "updatehelper.cpp",
+ "updateutils_win.cpp",
+ ]
+ OS_LIBS += [
+ "advapi32",
+ "ole32",
+ "rpcrt4",
+ "shell32",
+ ]
+ if CONFIG["MOZ_MAINTENANCE_SERVICE"]:
+ SOURCES += [
+ "certificatecheck.cpp",
+ "registrycertificates.cpp",
+ ]
+ OS_LIBS += [
+ "crypt32",
+ "wintrust",
+ ]
+
+SOURCES += [
+ "/other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp",
+ "commonupdatedir.cpp",
+ "readstrings.cpp",
+ "updatecommon.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/other-licenses/nsis/Contrib/CityHash/cityhash",
+]
+
+DEFINES["MOZ_APP_BASENAME"] = '"%s"' % CONFIG["MOZ_APP_BASENAME"]
diff --git a/toolkit/mozapps/update/common/pathhash.cpp b/toolkit/mozapps/update/common/pathhash.cpp
new file mode 100644
index 0000000000..e70f69a755
--- /dev/null
+++ b/toolkit/mozapps/update/common/pathhash.cpp
@@ -0,0 +1,128 @@
+/* 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 <wincrypt.h>
+#include "pathhash.h"
+
+/**
+ * Converts a binary sequence into a hex string
+ *
+ * @param hash The binary data sequence
+ * @param hashSize The size of the binary data sequence
+ * @param hexString A buffer to store the hex string, must be of
+ * size 2 * @hashSize
+ */
+static void BinaryDataToHexString(const BYTE* hash, DWORD& hashSize,
+ LPWSTR hexString) {
+ WCHAR* p = hexString;
+ for (DWORD i = 0; i < hashSize; ++i) {
+ wsprintfW(p, L"%.2x", hash[i]);
+ p += 2;
+ }
+}
+
+/**
+ * Calculates an MD5 hash for the given input binary data
+ *
+ * @param data Any sequence of bytes
+ * @param dataSize The number of bytes inside @data
+ * @param hash Output buffer to store hash, must be freed by the caller
+ * @param hashSize The number of bytes in the output buffer
+ * @return TRUE on success
+ */
+static BOOL CalculateMD5(const char* data, DWORD dataSize, BYTE** hash,
+ DWORD& hashSize) {
+ HCRYPTPROV hProv = 0;
+ HCRYPTHASH hHash = 0;
+
+ if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT)) {
+ if ((DWORD)NTE_BAD_KEYSET != GetLastError()) {
+ return FALSE;
+ }
+
+ // Maybe it doesn't exist, try to create it.
+ if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET)) {
+ return FALSE;
+ }
+ }
+
+ if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
+ return FALSE;
+ }
+
+ if (!CryptHashData(hHash, reinterpret_cast<const BYTE*>(data), dataSize, 0)) {
+ return FALSE;
+ }
+
+ DWORD dwCount = sizeof(DWORD);
+ if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&hashSize, &dwCount, 0)) {
+ return FALSE;
+ }
+
+ *hash = new BYTE[hashSize];
+ ZeroMemory(*hash, hashSize);
+ if (!CryptGetHashParam(hHash, HP_HASHVAL, *hash, &hashSize, 0)) {
+ return FALSE;
+ }
+
+ if (hHash) {
+ CryptDestroyHash(hHash);
+ }
+
+ if (hProv) {
+ CryptReleaseContext(hProv, 0);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Converts a file path into a unique registry location for cert storage
+ *
+ * @param filePath The input file path to get a registry path from
+ * @param registryPath A buffer to write the registry path to, must
+ * be of size in WCHARs MAX_PATH + 1
+ * @return TRUE if successful
+ */
+BOOL CalculateRegistryPathFromFilePath(const LPCWSTR filePath,
+ LPWSTR registryPath) {
+ size_t filePathLen = wcslen(filePath);
+ if (!filePathLen) {
+ return FALSE;
+ }
+
+ // If the file path ends in a slash, ignore that character
+ if (filePath[filePathLen - 1] == L'\\' || filePath[filePathLen - 1] == L'/') {
+ filePathLen--;
+ }
+
+ // Copy in the full path into our own buffer.
+ // Copying in the extra slash is OK because we calculate the hash
+ // based on the filePathLen which excludes the slash.
+ // +2 to account for the possibly trailing slash and the null terminator.
+ WCHAR* lowercasePath = new WCHAR[filePathLen + 2];
+ memset(lowercasePath, 0, (filePathLen + 2) * sizeof(WCHAR));
+ wcsncpy(lowercasePath, filePath, filePathLen + 1);
+ _wcslwr(lowercasePath);
+
+ BYTE* hash;
+ DWORD hashSize = 0;
+ if (!CalculateMD5(reinterpret_cast<const char*>(lowercasePath),
+ filePathLen * 2, &hash, hashSize)) {
+ delete[] lowercasePath;
+ return FALSE;
+ }
+ delete[] lowercasePath;
+
+ LPCWSTR baseRegPath =
+ L"SOFTWARE\\Mozilla\\"
+ L"MaintenanceService\\";
+ wcsncpy(registryPath, baseRegPath, MAX_PATH);
+ BinaryDataToHexString(hash, hashSize, registryPath + wcslen(baseRegPath));
+ delete[] hash;
+ return TRUE;
+}
diff --git a/toolkit/mozapps/update/common/pathhash.h b/toolkit/mozapps/update/common/pathhash.h
new file mode 100644
index 0000000000..17f08ae95e
--- /dev/null
+++ b/toolkit/mozapps/update/common/pathhash.h
@@ -0,0 +1,21 @@
+/* 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 _PATHHASH_H_
+#define _PATHHASH_H_
+
+#include <windows.h>
+
+/**
+ * Converts a file path into a unique registry location for cert storage
+ *
+ * @param filePath The input file path to get a registry path from
+ * @param registryPath A buffer to write the registry path to, must
+ * be of size in WCHARs MAX_PATH + 1
+ * @return TRUE if successful
+ */
+BOOL CalculateRegistryPathFromFilePath(const LPCWSTR filePath,
+ LPWSTR registryPath);
+
+#endif
diff --git a/toolkit/mozapps/update/common/readstrings.cpp b/toolkit/mozapps/update/common/readstrings.cpp
new file mode 100644
index 0000000000..17c2d002a1
--- /dev/null
+++ b/toolkit/mozapps/update/common/readstrings.cpp
@@ -0,0 +1,396 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 <algorithm>
+#include <iterator>
+#include <string.h>
+#include <stdio.h>
+#include "readstrings.h"
+#include "updatererrors.h"
+
+#ifdef XP_WIN
+# define NS_tfopen _wfopen
+# define OPEN_MODE L"rb"
+# define NS_tstrlen wcslen
+# define NS_tstrcpy wcscpy
+#else
+# define NS_tfopen fopen
+# define OPEN_MODE "r"
+# define NS_tstrlen strlen
+# define NS_tstrcpy strcpy
+#endif
+
+// stack based FILE wrapper to ensure that fclose is called.
+class AutoFILE {
+ public:
+ explicit AutoFILE(FILE* fp) : fp_(fp) {}
+ ~AutoFILE() {
+ if (fp_) {
+ fclose(fp_);
+ }
+ }
+ operator FILE*() { return fp_; }
+
+ private:
+ FILE* fp_;
+};
+
+class AutoCharArray {
+ public:
+ explicit AutoCharArray(size_t len) { ptr_ = new char[len]; }
+ ~AutoCharArray() { delete[] ptr_; }
+ operator char*() { return ptr_; }
+
+ private:
+ char* ptr_;
+};
+
+static const char kNL[] = "\r\n";
+static const char kEquals[] = "=";
+static const char kWhitespace[] = " \t";
+static const char kRBracket[] = "]";
+
+static const char* NS_strspnp(const char* delims, const char* str) {
+ const char* d;
+ do {
+ for (d = delims; *d != '\0'; ++d) {
+ if (*str == *d) {
+ ++str;
+ break;
+ }
+ }
+ } while (*d);
+
+ return str;
+}
+
+static char* NS_strtok(const char* delims, char** str) {
+ if (!*str) {
+ return nullptr;
+ }
+
+ char* ret = (char*)NS_strspnp(delims, *str);
+
+ if (!*ret) {
+ *str = ret;
+ return nullptr;
+ }
+
+ char* i = ret;
+ do {
+ for (const char* d = delims; *d != '\0'; ++d) {
+ if (*i == *d) {
+ *i = '\0';
+ *str = ++i;
+ return ret;
+ }
+ }
+ ++i;
+ } while (*i);
+
+ *str = nullptr;
+ return ret;
+}
+
+/**
+ * Find a key in a keyList containing zero-delimited keys ending with "\0\0".
+ * Returns a zero-based index of the key in the list, or -1 if the key is not
+ * found.
+ */
+static int find_key(const char* keyList, char* key) {
+ if (!keyList) {
+ return -1;
+ }
+
+ int index = 0;
+ const char* p = keyList;
+ while (*p) {
+ if (strcmp(key, p) == 0) {
+ return index;
+ }
+
+ p += strlen(p) + 1;
+ index++;
+ }
+
+ // The key was not found if we came here
+ return -1;
+}
+
+/**
+ * A very basic parser for updater.ini taken mostly from nsINIParser.cpp
+ * that can be used by standalone apps.
+ *
+ * @param path Path to the .ini file to read
+ * @param keyList List of zero-delimited keys ending with two zero characters
+ * @param numStrings Number of strings to read into results buffer - must be
+ * equal to the number of keys
+ * @param results Array of strings. Array's length must be equal to
+ * numStrings. Each string will be populated with the value
+ * corresponding to the key with the same index in keyList.
+ * @param section Optional name of the section to read; defaults to "Strings"
+ */
+int ReadStrings(const NS_tchar* path, const char* keyList,
+ unsigned int numStrings, mozilla::UniquePtr<char[]>* results,
+ const char* section) {
+ AutoFILE fp(NS_tfopen(path, OPEN_MODE));
+
+ if (!fp) {
+ return READ_ERROR;
+ }
+
+ /* get file size */
+ if (fseek(fp, 0, SEEK_END) != 0) {
+ return READ_ERROR;
+ }
+
+ long len = ftell(fp);
+ if (len <= 0) {
+ return READ_ERROR;
+ }
+
+ size_t flen = size_t(len);
+ AutoCharArray fileContents(flen + 1);
+ if (!fileContents) {
+ return READ_STRINGS_MEM_ERROR;
+ }
+
+ /* read the file in one swoop */
+ if (fseek(fp, 0, SEEK_SET) != 0) {
+ return READ_ERROR;
+ }
+
+ size_t rd = fread(fileContents, sizeof(char), flen, fp);
+ if (rd != flen) {
+ return READ_ERROR;
+ }
+
+ fileContents[flen] = '\0';
+
+ char* buffer = fileContents;
+ bool inStringsSection = false;
+
+ unsigned int read = 0;
+
+ while (char* token = NS_strtok(kNL, &buffer)) {
+ if (token[0] == '#' || token[0] == ';') { // it's a comment
+ continue;
+ }
+
+ token = (char*)NS_strspnp(kWhitespace, token);
+ if (!*token) { // empty line
+ continue;
+ }
+
+ if (token[0] == '[') { // section header!
+ ++token;
+ char const* currSection = token;
+
+ char* rb = NS_strtok(kRBracket, &token);
+ if (!rb || NS_strtok(kWhitespace, &token)) {
+ // there's either an unclosed [Section or a [Section]Moretext!
+ // we could frankly decide that this INI file is malformed right
+ // here and stop, but we won't... keep going, looking for
+ // a well-formed [section] to continue working with
+ inStringsSection = false;
+ } else {
+ if (section) {
+ inStringsSection = strcmp(currSection, section) == 0;
+ } else {
+ inStringsSection = strcmp(currSection, "Strings") == 0;
+ }
+ }
+
+ continue;
+ }
+
+ if (!inStringsSection) {
+ // If we haven't found a section header (or we found a malformed
+ // section header), or this isn't the [Strings] section don't bother
+ // parsing this line.
+ continue;
+ }
+
+ char* key = token;
+ char* e = NS_strtok(kEquals, &token);
+ if (!e) {
+ continue;
+ }
+
+ int keyIndex = find_key(keyList, key);
+ if (keyIndex >= 0 && (unsigned int)keyIndex < numStrings) {
+ size_t valueSize = strlen(token) + 1;
+ results[keyIndex] = mozilla::MakeUnique<char[]>(valueSize);
+
+ strcpy(results[keyIndex].get(), token);
+ read++;
+ }
+ }
+
+ return (read == numStrings) ? OK : PARSE_ERROR;
+}
+
+// A wrapper function to read strings for the updater.
+// Added for compatibility with the original code.
+int ReadStrings(const NS_tchar* path, StringTable* results) {
+ const unsigned int kNumStrings = 2;
+ const char* kUpdaterKeys = "Title\0Info\0";
+ mozilla::UniquePtr<char[]> updater_strings[kNumStrings];
+
+ int result = ReadStrings(path, kUpdaterKeys, kNumStrings, updater_strings);
+
+ if (result == OK) {
+ results->title.swap(updater_strings[0]);
+ results->info.swap(updater_strings[1]);
+ }
+
+ return result;
+}
+
+IniReader::IniReader(const NS_tchar* iniPath,
+ const char* section /* = nullptr */) {
+ if (iniPath) {
+ mPath = mozilla::MakeUnique<NS_tchar[]>(NS_tstrlen(iniPath) + 1);
+ NS_tstrcpy(mPath.get(), iniPath);
+ mMaybeStatusCode = mozilla::Nothing();
+ } else {
+ mMaybeStatusCode = mozilla::Some(READ_STRINGS_MEM_ERROR);
+ }
+ if (section) {
+ mSection = mozilla::MakeUnique<char[]>(strlen(section) + 1);
+ strcpy(mSection.get(), section);
+ } else {
+ mSection.reset(nullptr);
+ }
+}
+
+bool IniReader::MaybeAddKey(const char* key, size_t& insertionIndex) {
+ if (!key || strlen(key) == 0 || mMaybeStatusCode.isSome()) {
+ return false;
+ }
+ auto existingKey = std::find_if(mKeys.begin(), mKeys.end(),
+ [=](mozilla::UniquePtr<char[]>& searchKey) {
+ return strcmp(key, searchKey.get()) == 0;
+ });
+ if (existingKey != mKeys.end()) {
+ // Key already in list
+ insertionIndex = std::distance(mKeys.begin(), existingKey);
+ return true;
+ }
+
+ // Key not already in list
+ insertionIndex = mKeys.size();
+ mKeys.emplace_back(mozilla::MakeUnique<char[]>(strlen(key) + 1));
+ strcpy(mKeys.back().get(), key);
+ return true;
+}
+
+void IniReader::AddKey(const char* key, mozilla::UniquePtr<char[]>* outputPtr) {
+ size_t insertionIndex;
+ if (!MaybeAddKey(key, insertionIndex)) {
+ return;
+ }
+
+ if (!outputPtr) {
+ return;
+ }
+
+ mNarrowOutputs.emplace_back();
+ mNarrowOutputs.back().keyIndex = insertionIndex;
+ mNarrowOutputs.back().outputPtr = outputPtr;
+}
+
+#ifdef XP_WIN
+void IniReader::AddKey(const char* key,
+ mozilla::UniquePtr<wchar_t[]>* outputPtr) {
+ size_t insertionIndex;
+ if (!MaybeAddKey(key, insertionIndex)) {
+ return;
+ }
+
+ if (!outputPtr) {
+ return;
+ }
+
+ mWideOutputs.emplace_back();
+ mWideOutputs.back().keyIndex = insertionIndex;
+ mWideOutputs.back().outputPtr = outputPtr;
+}
+
+// Returns true on success, false on failure.
+static bool ConvertToWide(const char* toConvert,
+ mozilla::UniquePtr<wchar_t[]>* result) {
+ int bufferSize = MultiByteToWideChar(CP_UTF8, 0, toConvert, -1, nullptr, 0);
+ *result = mozilla::MakeUnique<wchar_t[]>(bufferSize);
+ int charsWritten =
+ MultiByteToWideChar(CP_UTF8, 0, toConvert, -1, result->get(), bufferSize);
+ return charsWritten > 0;
+}
+#endif
+
+int IniReader::Read() {
+ if (mMaybeStatusCode.isSome()) {
+ return mMaybeStatusCode.value();
+ }
+
+ if (mKeys.empty()) {
+ // If there's nothing to read, just report success and return.
+ mMaybeStatusCode = mozilla::Some(OK);
+ return OK;
+ }
+
+ // First assemble the key list, which will be a character array of
+ // back-to-back null-terminated strings ending with a double null termination.
+ size_t keyListSize = 1; // For the final null
+ for (const auto& key : mKeys) {
+ keyListSize += strlen(key.get());
+ keyListSize += 1; // For the terminating null
+ }
+ mozilla::UniquePtr<char[]> keyList = mozilla::MakeUnique<char[]>(keyListSize);
+ char* keyListPtr = keyList.get();
+ for (const auto& key : mKeys) {
+ strcpy(keyListPtr, key.get());
+ // Point keyListPtr directly after the trailing null that strcpy wrote.
+ keyListPtr += strlen(key.get()) + 1;
+ }
+ *keyListPtr = '\0';
+
+ // Now make the array for the resulting data to be stored in
+ mozilla::UniquePtr<mozilla::UniquePtr<char[]>[]> results =
+ mozilla::MakeUnique<mozilla::UniquePtr<char[]>[]>(mKeys.size());
+
+ // Invoke ReadStrings to read the file and store the data for us
+ int statusCode = ReadStrings(mPath.get(), keyList.get(), mKeys.size(),
+ results.get(), mSection.get());
+ mMaybeStatusCode = mozilla::Some(statusCode);
+
+ if (statusCode != OK) {
+ return statusCode;
+ }
+
+ // Now populate the requested locations with the requested data.
+ for (const auto output : mNarrowOutputs) {
+ char* valueBuffer = results[output.keyIndex].get();
+ if (valueBuffer) {
+ *(output.outputPtr) =
+ mozilla::MakeUnique<char[]>(strlen(valueBuffer) + 1);
+ strcpy(output.outputPtr->get(), valueBuffer);
+ }
+ }
+
+#ifdef XP_WIN
+ for (const auto output : mWideOutputs) {
+ char* valueBuffer = results[output.keyIndex].get();
+ if (valueBuffer) {
+ if (!ConvertToWide(valueBuffer, output.outputPtr)) {
+ statusCode = STRING_CONVERSION_ERROR;
+ }
+ }
+ }
+#endif
+
+ return statusCode;
+}
diff --git a/toolkit/mozapps/update/common/readstrings.h b/toolkit/mozapps/update/common/readstrings.h
new file mode 100644
index 0000000000..9e0ebbefb5
--- /dev/null
+++ b/toolkit/mozapps/update/common/readstrings.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 READSTRINGS_H__
+#define READSTRINGS_H__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+
+#include <vector>
+
+#ifdef XP_WIN
+# include <windows.h>
+typedef WCHAR NS_tchar;
+#else
+typedef char NS_tchar;
+#endif
+
+struct StringTable {
+ mozilla::UniquePtr<char[]> title;
+ mozilla::UniquePtr<char[]> info;
+};
+
+/**
+ * This function reads in localized strings from updater.ini
+ */
+int ReadStrings(const NS_tchar* path, StringTable* results);
+
+/**
+ * This function reads in localized strings corresponding to the keys from a
+ * given .ini
+ */
+int ReadStrings(const NS_tchar* path, const char* keyList,
+ unsigned int numStrings, mozilla::UniquePtr<char[]>* results,
+ const char* section = nullptr);
+
+/**
+ * This class is meant to be a slightly cleaner interface into the ReadStrings
+ * function.
+ */
+class IniReader {
+ public:
+ // IniReader must be initialized with the path of the INI file and a
+ // section to read from. If the section is null or not specified, the
+ // default section name ("Strings") will be used.
+ explicit IniReader(const NS_tchar* iniPath, const char* section = nullptr);
+
+ // Records a key that ought to be read from the INI file. When
+ // IniReader::Read() is invoked it will, if successful, store the value
+ // corresponding to the given key in the UniquePtr given.
+ // If IniReader::Read() has already been invoked, these functions do nothing.
+ // The given key must not be the empty string.
+ void AddKey(const char* key, mozilla::UniquePtr<char[]>* outputPtr);
+#ifdef XP_WIN
+ void AddKey(const char* key, mozilla::UniquePtr<wchar_t[]>* outputPtr);
+#endif
+ bool HasRead() { return mMaybeStatusCode.isSome(); }
+ // Performs the actual reading and assigns values to the requested locations.
+ // Returns the same possible values that `ReadStrings` returns.
+ // If this is called more than once, no action will be taken on subsequent
+ // calls, and the stored status code will be returned instead.
+ int Read();
+
+ private:
+ bool MaybeAddKey(const char* key, size_t& insertionIndex);
+
+ mozilla::UniquePtr<NS_tchar[]> mPath;
+ mozilla::UniquePtr<char[]> mSection;
+ std::vector<mozilla::UniquePtr<char[]>> mKeys;
+
+ template <class T>
+ struct ValueOutput {
+ size_t keyIndex;
+ T* outputPtr;
+ };
+
+ // Stores associations between keys and the buffers where their values will
+ // be stored.
+ std::vector<ValueOutput<mozilla::UniquePtr<char[]>>> mNarrowOutputs;
+#ifdef XP_WIN
+ std::vector<ValueOutput<mozilla::UniquePtr<wchar_t[]>>> mWideOutputs;
+#endif
+ // If we have attempted to read the INI, this will store the resulting
+ // status code.
+ mozilla::Maybe<int> mMaybeStatusCode;
+};
+
+#endif // READSTRINGS_H__
diff --git a/toolkit/mozapps/update/common/registrycertificates.cpp b/toolkit/mozapps/update/common/registrycertificates.cpp
new file mode 100644
index 0000000000..786218130a
--- /dev/null
+++ b/toolkit/mozapps/update/common/registrycertificates.cpp
@@ -0,0 +1,148 @@
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#include "registrycertificates.h"
+#include "pathhash.h"
+#include "updatecommon.h"
+#include "updatehelper.h"
+#define MAX_KEY_LENGTH 255
+
+/**
+ * Verifies if the file path matches any certificate stored in the registry.
+ *
+ * @param filePath The file path of the application to check if allowed.
+ * @param allowFallbackKeySkip when this is TRUE the fallback registry key will
+ * be used to skip the certificate check. This is the default since the
+ * fallback registry key is located under HKEY_LOCAL_MACHINE which can't be
+ * written to by a low integrity process.
+ * Note: the maintenance service binary can be used to perform this check for
+ * testing or troubleshooting.
+ * @return TRUE if the binary matches any of the allowed certificates.
+ */
+BOOL DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate,
+ LPCWSTR filePath,
+ BOOL allowFallbackKeySkip) {
+ WCHAR maintenanceServiceKey[MAX_PATH + 1];
+ if (!CalculateRegistryPathFromFilePath(basePathForUpdate,
+ maintenanceServiceKey)) {
+ return FALSE;
+ }
+
+ // We use KEY_WOW64_64KEY to always force 64-bit view.
+ // The user may have both x86 and x64 applications installed
+ // which each register information. We need a consistent place
+ // to put those certificate attributes in and hence why we always
+ // force the non redirected registry under Wow6432Node.
+ // This flag is ignored on 32bit systems.
+ HKEY baseKey;
+ LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, maintenanceServiceKey, 0,
+ KEY_READ | KEY_WOW64_64KEY, &baseKey);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not open key. (%ld)", retCode));
+ // Our tests run with a different apply directory for each test.
+ // We use this registry key on our test machines to store the
+ // allowed name/issuers.
+ retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEST_ONLY_FALLBACK_KEY_PATH, 0,
+ KEY_READ | KEY_WOW64_64KEY, &baseKey);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not open fallback key. (%ld)", retCode));
+ return FALSE;
+ } else if (allowFallbackKeySkip) {
+ LOG_WARN(
+ ("Fallback key present, skipping VerifyCertificateTrustForFile "
+ "check and the certificate attribute registry matching "
+ "check."));
+ RegCloseKey(baseKey);
+ return TRUE;
+ }
+ }
+
+ // Get the number of subkeys.
+ DWORD subkeyCount = 0;
+ retCode = RegQueryInfoKeyW(baseKey, nullptr, nullptr, nullptr, &subkeyCount,
+ nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not query info key. (%ld)", retCode));
+ RegCloseKey(baseKey);
+ return FALSE;
+ }
+
+ // Enumerate the subkeys, each subkey represents an allowed certificate.
+ for (DWORD i = 0; i < subkeyCount; i++) {
+ WCHAR subkeyBuffer[MAX_KEY_LENGTH];
+ DWORD subkeyBufferCount = MAX_KEY_LENGTH;
+ retCode = RegEnumKeyExW(baseKey, i, subkeyBuffer, &subkeyBufferCount,
+ nullptr, nullptr, nullptr, nullptr);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not enum certs. (%ld)", retCode));
+ RegCloseKey(baseKey);
+ return FALSE;
+ }
+
+ // Open the subkey for the current certificate
+ HKEY subKey;
+ retCode = RegOpenKeyExW(baseKey, subkeyBuffer, 0,
+ KEY_READ | KEY_WOW64_64KEY, &subKey);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not open subkey. (%ld)", retCode));
+ continue; // Try the next subkey
+ }
+
+ const int MAX_CHAR_COUNT = 256;
+ DWORD valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR);
+ WCHAR name[MAX_CHAR_COUNT] = {L'\0'};
+ WCHAR issuer[MAX_CHAR_COUNT] = {L'\0'};
+
+ // Get the name from the registry
+ retCode = RegQueryValueExW(subKey, L"name", 0, nullptr, (LPBYTE)name,
+ &valueBufSize);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not obtain name from registry. (%ld)", retCode));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ // Get the issuer from the registry
+ valueBufSize = MAX_CHAR_COUNT * sizeof(WCHAR);
+ retCode = RegQueryValueExW(subKey, L"issuer", 0, nullptr, (LPBYTE)issuer,
+ &valueBufSize);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Could not obtain issuer from registry. (%ld)", retCode));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ CertificateCheckInfo allowedCertificate = {
+ name,
+ issuer,
+ };
+
+ retCode = CheckCertificateForPEFile(filePath, allowedCertificate);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Error on certificate check. (%ld)", retCode));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ retCode = VerifyCertificateTrustForFile(filePath);
+ if (retCode != ERROR_SUCCESS) {
+ LOG_WARN(("Error on certificate trust check. (%ld)", retCode));
+ RegCloseKey(subKey);
+ continue; // Try the next subkey
+ }
+
+ RegCloseKey(baseKey);
+ // Raise the roof, we found a match!
+ return TRUE;
+ }
+
+ RegCloseKey(baseKey);
+ // No certificates match, :'(
+ return FALSE;
+}
diff --git a/toolkit/mozapps/update/common/registrycertificates.h b/toolkit/mozapps/update/common/registrycertificates.h
new file mode 100644
index 0000000000..9f68d1a8d9
--- /dev/null
+++ b/toolkit/mozapps/update/common/registrycertificates.h
@@ -0,0 +1,14 @@
+/* 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 _REGISTRYCERTIFICATES_H_
+#define _REGISTRYCERTIFICATES_H_
+
+#include "certificatecheck.h"
+
+BOOL DoesBinaryMatchAllowedCertificates(LPCWSTR basePathForUpdate,
+ LPCWSTR filePath,
+ BOOL allowFallbackKeySkip = TRUE);
+
+#endif
diff --git a/toolkit/mozapps/update/common/uachelper.cpp b/toolkit/mozapps/update/common/uachelper.cpp
new file mode 100644
index 0000000000..07e9bd53f9
--- /dev/null
+++ b/toolkit/mozapps/update/common/uachelper.cpp
@@ -0,0 +1,186 @@
+/* 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 "uachelper.h"
+#include "updatecommon.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, SE_AUDIT_NAME, SE_BACKUP_NAME,
+ // CreateProcess will succeed but the app will fail to launch on some WinXP
+ // machines if SE_CHANGE_NOTIFY_NAME is disabled. In particular this
+ // happens for limited user accounts on those machines. The define is kept
+ // here as a reminder that it should never be re-added. This permission is
+ // for directory watching but also from MSDN: "This privilege also causes
+ // the system to skip all traversal access checks." SE_CHANGE_NOTIFY_NAME,
+ SE_CREATE_GLOBAL_NAME, SE_CREATE_PAGEFILE_NAME, SE_CREATE_PERMANENT_NAME,
+ SE_CREATE_SYMBOLIC_LINK_NAME, SE_CREATE_TOKEN_NAME, SE_DEBUG_NAME,
+ SE_ENABLE_DELEGATION_NAME, SE_IMPERSONATE_NAME, SE_INC_BASE_PRIORITY_NAME,
+ SE_INCREASE_QUOTA_NAME, SE_INC_WORKING_SET_NAME, SE_LOAD_DRIVER_NAME,
+ SE_LOCK_MEMORY_NAME, SE_MACHINE_ACCOUNT_NAME, SE_MANAGE_VOLUME_NAME,
+ SE_PROF_SINGLE_PROCESS_NAME, SE_RELABEL_NAME, SE_REMOTE_SHUTDOWN_NAME,
+ SE_RESTORE_NAME, SE_SECURITY_NAME, SE_SHUTDOWN_NAME, SE_SYNC_AGENT_NAME,
+ SE_SYSTEM_ENVIRONMENT_NAME, SE_SYSTEM_PROFILE_NAME, SE_SYSTEMTIME_NAME,
+ SE_TAKE_OWNERSHIP_NAME, SE_TCB_NAME, SE_TIME_ZONE_NAME,
+ SE_TRUSTED_CREDMAN_ACCESS_NAME, SE_UNDOCK_NAME, SE_UNSOLICITED_INPUT_NAME};
+
+/**
+ * Opens a user token for the given session ID
+ *
+ * @param sessionID The session ID for the token to obtain
+ * @return A handle to the token to obtain which will be primary if enough
+ * permissions exist. Caller should close the handle.
+ */
+HANDLE
+UACHelper::OpenUserToken(DWORD sessionID) {
+ HMODULE module = LoadLibraryW(L"wtsapi32.dll");
+ HANDLE token = nullptr;
+ decltype(WTSQueryUserToken)* wtsQueryUserToken =
+ (decltype(WTSQueryUserToken)*)GetProcAddress(module, "WTSQueryUserToken");
+ if (wtsQueryUserToken) {
+ wtsQueryUserToken(sessionID, &token);
+ }
+ FreeLibrary(module);
+ return token;
+}
+
+/**
+ * Opens a linked token for the specified token.
+ *
+ * @param token The token to get the linked token from
+ * @return A linked token or nullptr if one does not exist.
+ * Caller should close the handle.
+ */
+HANDLE
+UACHelper::OpenLinkedToken(HANDLE token) {
+ // Magic below...
+ // UAC creates 2 tokens. One is the restricted token which we have.
+ // the other is the UAC elevated one. Since we are running as a service
+ // as the system account we have access to both.
+ TOKEN_LINKED_TOKEN tlt;
+ HANDLE hNewLinkedToken = nullptr;
+ DWORD len;
+ if (GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken,
+ &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) {
+ token = tlt.LinkedToken;
+ hNewLinkedToken = token;
+ }
+ return hNewLinkedToken;
+}
+
+/**
+ * Enables or disables a privilege for the specified token.
+ *
+ * @param token The token to adjust the privilege on.
+ * @param priv The privilege to adjust.
+ * @param enable Whether to enable or disable it
+ * @return TRUE if the token was adjusted to the specified value.
+ */
+BOOL UACHelper::SetPrivilege(HANDLE token, LPCTSTR priv, BOOL enable) {
+ LUID luidOfPriv;
+ if (!LookupPrivilegeValue(nullptr, priv, &luidOfPriv)) {
+ return FALSE;
+ }
+
+ TOKEN_PRIVILEGES tokenPriv;
+ tokenPriv.PrivilegeCount = 1;
+ tokenPriv.Privileges[0].Luid = luidOfPriv;
+ tokenPriv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
+
+ SetLastError(ERROR_SUCCESS);
+ if (!AdjustTokenPrivileges(token, false, &tokenPriv, sizeof(tokenPriv),
+ nullptr, nullptr)) {
+ return FALSE;
+ }
+
+ return GetLastError() == ERROR_SUCCESS;
+}
+
+/**
+ * For each privilege that is specified, an attempt will be made to
+ * drop the privilege.
+ *
+ * @param token The token to adjust the privilege on.
+ * Pass nullptr for current token.
+ * @param unneededPrivs An array of unneeded privileges.
+ * @param count The size of the array
+ * @return TRUE if there were no errors
+ */
+BOOL UACHelper::DisableUnneededPrivileges(HANDLE token, LPCTSTR* unneededPrivs,
+ size_t count) {
+ HANDLE obtainedToken = nullptr;
+ if (!token) {
+ // Note: This handle is a pseudo-handle and need not be closed
+ HANDLE process = GetCurrentProcess();
+ if (!OpenProcessToken(process, TOKEN_ALL_ACCESS_P, &obtainedToken)) {
+ LOG_WARN(
+ ("Could not obtain token for current process, no "
+ "privileges changed. (%lu)",
+ GetLastError()));
+ return FALSE;
+ }
+ token = obtainedToken;
+ }
+
+ BOOL result = TRUE;
+ for (size_t i = 0; i < count; i++) {
+ if (SetPrivilege(token, unneededPrivs[i], FALSE)) {
+ LOG(("Disabled unneeded token privilege: %s.", unneededPrivs[i]));
+ } else {
+ LOG(("Could not disable token privilege value: %s. (%lu)",
+ unneededPrivs[i], GetLastError()));
+ result = FALSE;
+ }
+ }
+
+ if (obtainedToken) {
+ CloseHandle(obtainedToken);
+ }
+ return result;
+}
+
+/**
+ * Disables privileges for the specified token.
+ * The privileges to disable are in PrivsToDisable.
+ * In the future there could be new privs and we are not sure if we should
+ * explicitly disable these or not.
+ *
+ * @param token The token to drop the privilege on.
+ * Pass nullptr for current token.
+ * @return TRUE if there were no errors
+ */
+BOOL UACHelper::DisablePrivileges(HANDLE token) {
+ static const size_t PrivsToDisableSize =
+ sizeof(UACHelper::PrivsToDisable) / sizeof(UACHelper::PrivsToDisable[0]);
+
+ return DisableUnneededPrivileges(token, UACHelper::PrivsToDisable,
+ PrivsToDisableSize);
+}
+
+/**
+ * Check if the current user can elevate.
+ *
+ * @return true if the user can elevate.
+ * false otherwise.
+ */
+bool UACHelper::CanUserElevate() {
+ HANDLE token;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
+ return false;
+ }
+
+ TOKEN_ELEVATION_TYPE elevationType;
+ DWORD len;
+ bool canElevate =
+ GetTokenInformation(token, TokenElevationType, &elevationType,
+ sizeof(elevationType), &len) &&
+ (elevationType == TokenElevationTypeLimited);
+ CloseHandle(token);
+
+ return canElevate;
+}
diff --git a/toolkit/mozapps/update/common/uachelper.h b/toolkit/mozapps/update/common/uachelper.h
new file mode 100644
index 0000000000..c9915981a0
--- /dev/null
+++ b/toolkit/mozapps/update/common/uachelper.h
@@ -0,0 +1,24 @@
+/* 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 _UACHELPER_H_
+#define _UACHELPER_H_
+
+#include <windows.h>
+
+class UACHelper {
+ public:
+ static HANDLE OpenUserToken(DWORD sessionID);
+ static HANDLE OpenLinkedToken(HANDLE token);
+ static BOOL DisablePrivileges(HANDLE token);
+ static bool CanUserElevate();
+
+ private:
+ static BOOL SetPrivilege(HANDLE token, LPCTSTR privs, BOOL enable);
+ static BOOL DisableUnneededPrivileges(HANDLE token, LPCTSTR* unneededPrivs,
+ size_t count);
+ static LPCTSTR PrivsToDisable[];
+};
+
+#endif
diff --git a/toolkit/mozapps/update/common/updatecommon.cpp b/toolkit/mozapps/update/common/updatecommon.cpp
new file mode 100644
index 0000000000..9e00ac5716
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatecommon.cpp
@@ -0,0 +1,470 @@
+/* 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/. */
+
+#if defined(XP_WIN)
+# include <windows.h>
+# include <winioctl.h> // for FSCTL_GET_REPARSE_POINT
+# include <shlobj.h>
+# ifndef RRF_SUBKEY_WOW6464KEY
+# define RRF_SUBKEY_WOW6464KEY 0x00010000
+# endif
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "updatecommon.h"
+#ifdef XP_WIN
+# include "updatehelper.h"
+# include "nsWindowsHelpers.h"
+# include "mozilla/UniquePtr.h"
+# include "mozilla/WinHeaderOnlyUtils.h"
+
+// This struct isn't in any SDK header, so this definition was copied from:
+// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/ns-ntifs-_reparse_data_buffer
+typedef struct _REPARSE_DATA_BUFFER {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ } DUMMYUNIONNAME;
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+#endif
+
+UpdateLog::UpdateLog() : logFP(nullptr) {}
+
+void UpdateLog::Init(NS_tchar* logFilePath) {
+ if (logFP) {
+ return;
+ }
+
+ // 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.
+ logFP = tmpfile();
+#endif
+ }
+}
+
+void UpdateLog::Finish() {
+ if (!logFP) {
+ return;
+ }
+
+#if !defined(XP_WIN) && !defined(XP_MACOSX)
+ const int blockSize = 1024;
+ char buffer[blockSize];
+ fflush(logFP);
+ rewind(logFP);
+
+ FILE* updateLogFP = NS_tfopen(mDstFilePath, NS_T("wb+"));
+ while (!feof(logFP)) {
+ size_t read = fread(buffer, 1, blockSize, logFP);
+ if (ferror(logFP)) {
+ fclose(logFP);
+ logFP = nullptr;
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+ return;
+ }
+
+ size_t written = 0;
+
+ while (written < read) {
+ size_t chunkWritten = fwrite(buffer, 1, read - written, updateLogFP);
+ if (chunkWritten <= 0) {
+ fclose(logFP);
+ logFP = nullptr;
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+ return;
+ }
+
+ written += chunkWritten;
+ }
+ }
+ fclose(updateLogFP);
+ updateLogFP = nullptr;
+#endif
+
+ fclose(logFP);
+ logFP = nullptr;
+}
+
+void UpdateLog::Flush() {
+ if (!logFP) {
+ return;
+ }
+
+ fflush(logFP);
+}
+
+void UpdateLog::Printf(const char* fmt, ...) {
+ if (!logFP) {
+ return;
+ }
+
+ time_t rawtime = time(nullptr);
+ struct tm* timeinfo = localtime(&rawtime);
+
+ if (nullptr != timeinfo) {
+ // attempt to format the time similar to rfc-3339 so that it works with
+ // sort(1). xxxx-xx-xx xx:xx:xx+xxxx -> 24 chars + 1 NUL
+ const size_t buffer_size = 25;
+ char buffer[buffer_size] = {0};
+
+ if (0 == strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S%z", timeinfo)) {
+ buffer[0] = '\0'; // reset buffer into a defined state and try posix ts
+ if (0 > snprintf(buffer, buffer_size, "%d", (int)mktime(timeinfo))) {
+ buffer[0] = '\0'; // reset and give up
+ }
+ }
+
+ fprintf(logFP, "%s: ", buffer);
+ }
+
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(logFP, fmt, ap);
+ va_end(ap);
+
+ fprintf(logFP, "\n");
+#if defined(XP_WIN) && defined(MOZ_DEBUG)
+ // When the updater crashes on Windows the log file won't be flushed and this
+ // can make it easier to debug what is going on.
+ fflush(logFP);
+#endif
+}
+
+void UpdateLog::WarnPrintf(const char* fmt, ...) {
+ if (!logFP) {
+ return;
+ }
+
+ va_list ap;
+ va_start(ap, fmt);
+ fprintf(logFP, "*** Warning: ");
+ vfprintf(logFP, fmt, ap);
+ fprintf(logFP, "***\n");
+ va_end(ap);
+#if defined(XP_WIN) && defined(MOZ_DEBUG)
+ // When the updater crashes on Windows the log file won't be flushed and this
+ // can make it easier to debug what is going on.
+ fflush(logFP);
+#endif
+}
+
+#ifdef XP_WIN
+/**
+ * Determine if a path contains symlinks or junctions to disallowed locations
+ *
+ * @param fullPath The full path to check.
+ * @return true if the path contains invalid links or on errors,
+ * false if the check passes and the path can be used
+ */
+bool PathContainsInvalidLinks(wchar_t* const fullPath) {
+ wchar_t pathCopy[MAXPATHLEN + 1] = L"";
+ wcsncpy(pathCopy, fullPath, MAXPATHLEN);
+ wchar_t* remainingPath = nullptr;
+ wchar_t* nextToken = wcstok_s(pathCopy, L"\\", &remainingPath);
+ wchar_t* partialPath = nextToken;
+
+ while (nextToken) {
+ if ((GetFileAttributesW(partialPath) & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+ nsAutoHandle h(CreateFileW(
+ partialPath, 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
+ if (h == INVALID_HANDLE_VALUE) {
+ if (GetLastError() == ERROR_FILE_NOT_FOUND) {
+ // The path can't be an invalid link if it doesn't exist.
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ mozilla::UniquePtr<UINT8[]> byteBuffer =
+ mozilla::MakeUnique<UINT8[]>(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+ ZeroMemory(byteBuffer.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+ REPARSE_DATA_BUFFER* buffer = (REPARSE_DATA_BUFFER*)byteBuffer.get();
+ DWORD bytes = 0;
+ if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, nullptr, 0, buffer,
+ MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bytes, nullptr)) {
+ return true;
+ }
+
+ wchar_t* reparseTarget = nullptr;
+ switch (buffer->ReparseTag) {
+ case IO_REPARSE_TAG_MOUNT_POINT:
+ reparseTarget =
+ buffer->MountPointReparseBuffer.PathBuffer +
+ (buffer->MountPointReparseBuffer.SubstituteNameOffset /
+ sizeof(wchar_t));
+ if (buffer->MountPointReparseBuffer.SubstituteNameLength <
+ ARRAYSIZE(L"\\??\\")) {
+ return false;
+ }
+ break;
+ case IO_REPARSE_TAG_SYMLINK:
+ reparseTarget =
+ buffer->SymbolicLinkReparseBuffer.PathBuffer +
+ (buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset /
+ sizeof(wchar_t));
+ if (buffer->SymbolicLinkReparseBuffer.SubstituteNameLength <
+ ARRAYSIZE(L"\\??\\")) {
+ return false;
+ }
+ break;
+ default:
+ return true;
+ break;
+ }
+
+ if (!reparseTarget) {
+ return false;
+ }
+ if (wcsncmp(reparseTarget, L"\\??\\", ARRAYSIZE(L"\\??\\") - 1) != 0) {
+ return true;
+ }
+ }
+
+ nextToken = wcstok_s(nullptr, L"\\", &remainingPath);
+ PathAppendW(partialPath, nextToken);
+ }
+
+ return false;
+}
+
+/**
+ * Determine if a path is located within Program Files, either native or x86
+ *
+ * @param fullPath The full path to check.
+ * @return true if fullPath begins with either Program Files directory,
+ * false if it does not or if an error is encountered
+ */
+bool IsProgramFilesPath(NS_tchar* fullPath) {
+ // Make sure we don't try to compare against a short path.
+ DWORD longInstallPathChars = GetLongPathNameW(fullPath, nullptr, 0);
+ if (longInstallPathChars == 0) {
+ return false;
+ }
+ mozilla::UniquePtr<wchar_t[]> longInstallPath =
+ mozilla::MakeUnique<wchar_t[]>(longInstallPathChars);
+ if (!GetLongPathNameW(fullPath, longInstallPath.get(),
+ longInstallPathChars)) {
+ return false;
+ }
+
+ // First check for Program Files (x86).
+ {
+ PWSTR programFiles32PathRaw = nullptr;
+ // FOLDERID_ProgramFilesX86 gets native Program Files directory on a 32-bit
+ // OS or the (x86) directory on a 64-bit OS regardless of this binary's
+ // bitness.
+ if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, 0, nullptr,
+ &programFiles32PathRaw))) {
+ // That call should never fail on any supported OS version.
+ return false;
+ }
+ mozilla::UniquePtr<wchar_t, mozilla::CoTaskMemFreeDeleter>
+ programFiles32Path(programFiles32PathRaw);
+ // We need this path to have a trailing slash so our prefix test doesn't
+ // match on a different folder which happens to have a name beginning with
+ // the prefix we're looking for but then also more characters after that.
+ size_t length = wcslen(programFiles32Path.get());
+ if (length == 0) {
+ return false;
+ }
+ if (programFiles32Path.get()[length - 1] == L'\\') {
+ if (wcsnicmp(longInstallPath.get(), programFiles32Path.get(), length) ==
+ 0) {
+ return true;
+ }
+ } else {
+ // Allocate space for a copy of the string along with a terminator and one
+ // extra character for the trailing backslash.
+ length += 1;
+ mozilla::UniquePtr<wchar_t[]> programFiles32PathWithSlash =
+ mozilla::MakeUnique<wchar_t[]>(length + 1);
+
+ NS_tsnprintf(programFiles32PathWithSlash.get(), length + 1, NS_T("%s\\"),
+ programFiles32Path.get());
+
+ if (wcsnicmp(longInstallPath.get(), programFiles32PathWithSlash.get(),
+ length) == 0) {
+ return true;
+ }
+ }
+ }
+
+ // If we didn't find (x86), check for the native Program Files.
+ {
+ // In case we're a 32-bit binary on 64-bit Windows, we now have a problem
+ // getting the right "native" Program Files path, which is that there is no
+ // FOLDERID_* value that returns that path. So we always read that one out
+ // of its canonical registry location instead. If we're on a 32-bit OS, this
+ // will be the same path that we just checked. First get the buffer size to
+ // allocate for the path.
+ DWORD length = 0;
+ if (RegGetValueW(HKEY_LOCAL_MACHINE,
+ L"Software\\Microsoft\\Windows\\CurrentVersion",
+ L"ProgramFilesDir", RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY,
+ nullptr, nullptr, &length) != ERROR_SUCCESS) {
+ return false;
+ }
+ // RegGetValue returns the length including the terminator, but it's in
+ // bytes, so convert that to characters.
+ DWORD lengthChars = (length / sizeof(wchar_t));
+ if (lengthChars <= 1) {
+ return false;
+ }
+ mozilla::UniquePtr<wchar_t[]> programFilesNativePath =
+ mozilla::MakeUnique<wchar_t[]>(lengthChars);
+
+ // Now actually read the value.
+ if (RegGetValueW(
+ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion",
+ L"ProgramFilesDir", RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, nullptr,
+ programFilesNativePath.get(), &length) != ERROR_SUCCESS) {
+ return false;
+ }
+ size_t nativePathStrLen =
+ wcsnlen_s(programFilesNativePath.get(), lengthChars);
+ if (nativePathStrLen == 0) {
+ return false;
+ }
+
+ // As before, append a backslash if there isn't one already.
+ if (programFilesNativePath.get()[nativePathStrLen - 1] == L'\\') {
+ if (wcsnicmp(longInstallPath.get(), programFilesNativePath.get(),
+ nativePathStrLen) == 0) {
+ return true;
+ }
+ } else {
+ // Allocate space for a copy of the string along with a terminator and one
+ // extra character for the trailing backslash.
+ nativePathStrLen += 1;
+ mozilla::UniquePtr<wchar_t[]> programFilesNativePathWithSlash =
+ mozilla::MakeUnique<wchar_t[]>(nativePathStrLen + 1);
+
+ NS_tsnprintf(programFilesNativePathWithSlash.get(), nativePathStrLen + 1,
+ NS_T("%s\\"), programFilesNativePath.get());
+
+ if (wcsnicmp(longInstallPath.get(), programFilesNativePathWithSlash.get(),
+ nativePathStrLen) == 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+#endif
+
+/**
+ * Performs checks of a full path for validity for this application.
+ *
+ * @param origFullPath
+ * The full path to check.
+ * @return true if the path is valid for this application and false otherwise.
+ */
+bool IsValidFullPath(NS_tchar* origFullPath) {
+ // Subtract 1 from MAXPATHLEN for null termination.
+ if (NS_tstrlen(origFullPath) > MAXPATHLEN - 1) {
+ // The path is longer than acceptable for this application.
+ return false;
+ }
+
+#ifdef XP_WIN
+ NS_tchar testPath[MAXPATHLEN] = {NS_T('\0')};
+ // GetFullPathNameW will replace / with \ which PathCanonicalizeW requires.
+ if (GetFullPathNameW(origFullPath, MAXPATHLEN, testPath, nullptr) == 0) {
+ // Unable to get the full name for the path (e.g. invalid path).
+ return false;
+ }
+
+ NS_tchar canonicalPath[MAXPATHLEN] = {NS_T('\0')};
+ if (!PathCanonicalizeW(canonicalPath, testPath)) {
+ // Path could not be canonicalized (e.g. invalid path).
+ return false;
+ }
+
+ // Check if the path passed in resolves to a differerent path.
+ if (NS_tstricmp(origFullPath, canonicalPath) != 0) {
+ // Case insensitive string comparison between the supplied path and the
+ // canonical path are not equal. This will prevent directory traversal and
+ // the use of / in paths since they are converted to \.
+ return false;
+ }
+
+ NS_tstrncpy(testPath, origFullPath, MAXPATHLEN);
+ if (!PathStripToRootW(testPath)) {
+ // It should always be possible to strip a valid path to its root.
+ return false;
+ }
+
+ if (origFullPath[0] == NS_T('\\')) {
+ // Only allow UNC server share paths.
+ if (!PathIsUNCServerShareW(testPath)) {
+ return false;
+ }
+ }
+
+ if (PathContainsInvalidLinks(canonicalPath)) {
+ return false;
+ }
+#else
+ // Only allow full paths.
+ if (origFullPath[0] != NS_T('/')) {
+ return false;
+ }
+
+ // The path must not traverse directories
+ if (NS_tstrstr(origFullPath, NS_T("/../")) != nullptr) {
+ return false;
+ }
+
+ // The path shall not have a path traversal suffix
+ const NS_tchar invalidSuffix[] = NS_T("/..");
+ size_t pathLen = NS_tstrlen(origFullPath);
+ size_t invalidSuffixLen = NS_tstrlen(invalidSuffix);
+ if (invalidSuffixLen <= pathLen &&
+ NS_tstrncmp(origFullPath + pathLen - invalidSuffixLen, invalidSuffix,
+ invalidSuffixLen) == 0) {
+ return false;
+ }
+#endif
+ return true;
+}
diff --git a/toolkit/mozapps/update/common/updatecommon.h b/toolkit/mozapps/update/common/updatecommon.h
new file mode 100644
index 0000000000..e317bdcb1f
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatecommon.h
@@ -0,0 +1,43 @@
+/* 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 UPDATECOMMON_H
+#define UPDATECOMMON_H
+
+#include "updatedefines.h"
+#include <stdio.h>
+#include <time.h>
+#include "mozilla/Attributes.h"
+
+class UpdateLog {
+ public:
+ static UpdateLog& GetPrimaryLog() {
+ static UpdateLog primaryLog;
+ return primaryLog;
+ }
+
+ 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 mDstFilePath[MAXPATHLEN];
+};
+
+bool IsValidFullPath(NS_tchar* fullPath);
+bool IsProgramFilesPath(NS_tchar* fullPath);
+
+#define LOG_WARN(args) UpdateLog::GetPrimaryLog().WarnPrintf args
+#define LOG(args) UpdateLog::GetPrimaryLog().Printf args
+#define LogInit(FILEPATH_) UpdateLog::GetPrimaryLog().Init(FILEPATH_)
+#define LogFinish() UpdateLog::GetPrimaryLog().Finish()
+#define LogFlush() UpdateLog::GetPrimaryLog().Flush()
+
+#endif
diff --git a/toolkit/mozapps/update/common/updatedefines.h b/toolkit/mozapps/update/common/updatedefines.h
new file mode 100644
index 0000000000..f716e10f66
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatedefines.h
@@ -0,0 +1,164 @@
+/* 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 UPDATEDEFINES_H
+#define UPDATEDEFINES_H
+
+#include <stdio.h>
+#include <stdarg.h>
+#include "readstrings.h"
+
+#if defined(XP_WIN)
+# include <windows.h>
+# include <shlwapi.h>
+# include <direct.h>
+# include <io.h>
+
+# ifndef F_OK
+# define F_OK 00
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef R_OK
+# define R_OK 04
+# endif
+# define S_ISDIR(s) (((s)&_S_IFMT) == _S_IFDIR)
+# define S_ISREG(s) (((s)&_S_IFMT) == _S_IFREG)
+
+# define access _access
+
+# define putenv _putenv
+# if defined(_MSC_VER) && _MSC_VER < 1900
+# define stat _stat
+# endif
+# define DELETE_DIR L"tobedeleted"
+# define CALLBACK_BACKUP_EXT L".moz-callback"
+
+# define LOG_S "%S"
+# define NS_CONCAT(x, y) x##y
+// The extra layer of indirection here allows this macro to be passed macros
+# define NS_T(str) NS_CONCAT(L, str)
+# define NS_SLASH NS_T('\\')
+static inline int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt,
+ ...) {
+ size_t _count = count - 1;
+ va_list varargs;
+ va_start(varargs, fmt);
+ int result = _vsnwprintf(dest, count - 1, fmt, varargs);
+ va_end(varargs);
+ dest[_count] = L'\0';
+ return result;
+}
+# define NS_tsnprintf mywcsprintf
+# define NS_taccess _waccess
+# define NS_tatoi _wtoi64
+# define NS_tchdir _wchdir
+# define NS_tchmod _wchmod
+# define NS_tfopen _wfopen
+# define NS_tmkdir(path, perms) _wmkdir(path)
+# define NS_tpid __int64
+# define NS_tremove _wremove
+// _wrename is used to avoid the link tracking service.
+# define NS_trename _wrename
+# define NS_trmdir _wrmdir
+# define NS_tstat _wstat
+# define NS_tlstat _wstat // No symlinks on Windows
+# define NS_tstat_t _stat
+# define NS_tstrcat wcscat
+# define NS_tstrcmp wcscmp
+# define NS_tstricmp wcsicmp
+# define NS_tstrncmp wcsncmp
+# define NS_tstrcpy wcscpy
+# define NS_tstrncpy wcsncpy
+# define NS_tstrlen wcslen
+# define NS_tstrchr wcschr
+# define NS_tstrrchr wcsrchr
+# define NS_tstrstr wcsstr
+# include "updateutils_win.h"
+# define NS_tDIR DIR
+# define NS_tdirent dirent
+# define NS_topendir opendir
+# define NS_tclosedir closedir
+# define NS_treaddir readdir
+#else
+# include <sys/wait.h>
+# include <unistd.h>
+
+# ifdef HAVE_FTS_H
+# include <fts.h>
+# else
+# include <sys/stat.h>
+# endif
+# include <dirent.h>
+
+# ifdef XP_MACOSX
+# include <sys/time.h>
+# endif
+
+# define LOG_S "%s"
+# define NS_T(str) str
+# define NS_SLASH NS_T('/')
+# define NS_tsnprintf snprintf
+# define NS_taccess access
+# define NS_tatoi atoi
+# define NS_tchdir chdir
+# define NS_tchmod chmod
+# define NS_tfopen fopen
+# define NS_tmkdir mkdir
+# define NS_tpid int
+# define NS_tremove remove
+# define NS_trename rename
+# define NS_trmdir rmdir
+# define NS_tstat stat
+# define NS_tstat_t stat
+# define NS_tlstat lstat
+# define NS_tstrcat strcat
+# define NS_tstrcmp strcmp
+# define NS_tstricmp strcasecmp
+# define NS_tstrncmp strncmp
+# define NS_tstrcpy strcpy
+# define NS_tstrncpy strncpy
+# define NS_tstrlen strlen
+# define NS_tstrrchr strrchr
+# define NS_tstrstr strstr
+# define NS_tDIR DIR
+# define NS_tdirent dirent
+# define NS_topendir opendir
+# define NS_tclosedir closedir
+# define NS_treaddir readdir
+#endif
+
+#define BACKUP_EXT NS_T(".moz-backup")
+
+#ifndef MAXPATHLEN
+# ifdef PATH_MAX
+# define MAXPATHLEN PATH_MAX
+# elif defined(MAX_PATH)
+# define MAXPATHLEN MAX_PATH
+# elif defined(_MAX_PATH)
+# define MAXPATHLEN _MAX_PATH
+# elif defined(CCHMAXPATH)
+# define MAXPATHLEN CCHMAXPATH
+# else
+# define MAXPATHLEN 1024
+# endif
+#endif
+
+static inline bool NS_tvsnprintf(NS_tchar* dest, size_t count,
+ const NS_tchar* fmt, ...) {
+ va_list varargs;
+ va_start(varargs, fmt);
+#if defined(XP_WIN)
+ int result = _vsnwprintf(dest, count, fmt, varargs);
+#else
+ int result = vsnprintf(dest, count, fmt, varargs);
+#endif
+ va_end(varargs);
+ // The size_t cast of result is safe because result can only be positive after
+ // the first check.
+ return result >= 0 && (size_t)result < count;
+}
+
+#endif
diff --git a/toolkit/mozapps/update/common/updatehelper.cpp b/toolkit/mozapps/update/common/updatehelper.cpp
new file mode 100644
index 0000000000..b094d9eb75
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatehelper.cpp
@@ -0,0 +1,763 @@
+/* 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>
+
+// Needed for CreateToolhelp32Snapshot
+#include <tlhelp32.h>
+
+#include <stdio.h>
+#include <direct.h>
+#include "shlobj.h"
+
+// Needed for PathAppendW
+#include <shlwapi.h>
+
+#include "updatehelper.h"
+#include "updateutils_win.h"
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+# include "mozilla/UniquePtr.h"
+# include "pathhash.h"
+# include "registrycertificates.h"
+# include "uachelper.h"
+
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+#endif
+
+BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
+ LPCWSTR newFileName);
+
+/**
+ * Obtains the path of a file in the same directory as the specified file.
+ *
+ * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result.
+ * @param siblingFilePath The path of another file in the same directory
+ * @param newFileName The filename of another file in the same directory
+ * @return TRUE if successful
+ */
+BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
+ LPCWSTR newFileName) {
+ if (wcslen(siblingFilePath) > MAX_PATH) {
+ return FALSE;
+ }
+
+ wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH + 1);
+ if (!PathRemoveFileSpecW(destinationBuffer)) {
+ return FALSE;
+ }
+
+ 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;
+ }
+
+ 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);
+ }
+}
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+/**
+ * Starts the upgrade process for update of the service if it is
+ * already installed.
+ *
+ * @param installDir the installation directory where
+ * maintenanceservice_installer.exe is located.
+ * @return TRUE if successful
+ */
+BOOL StartServiceUpdate(LPCWSTR installDir) {
+ // Get a handle to the local computer SCM database
+ SC_HANDLE manager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
+ if (!manager) {
+ return FALSE;
+ }
+
+ // Open the service
+ SC_HANDLE svc = OpenServiceW(manager, SVC_NAME, SERVICE_ALL_ACCESS);
+ if (!svc) {
+ CloseServiceHandle(manager);
+ return FALSE;
+ }
+
+ // If we reach here, then the service is installed, so
+ // proceed with upgrading it.
+
+ CloseServiceHandle(manager);
+
+ // The service exists and we opened it, get the config bytes needed
+ DWORD bytesNeeded;
+ if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) &&
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ CloseServiceHandle(svc);
+ return FALSE;
+ }
+
+ // Get the service config information, in particular we want the binary
+ // path of the service.
+ UniquePtr<char[]> serviceConfigBuffer = MakeUnique<char[]>(bytesNeeded);
+ if (!QueryServiceConfigW(
+ svc,
+ reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()),
+ bytesNeeded, &bytesNeeded)) {
+ CloseServiceHandle(svc);
+ return FALSE;
+ }
+
+ CloseServiceHandle(svc);
+
+ QUERY_SERVICE_CONFIGW& serviceConfig =
+ *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
+
+ PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
+
+ // Obtain the temp path of the maintenance service binary
+ WCHAR tmpService[MAX_PATH + 1] = {L'\0'};
+ if (!PathGetSiblingFilePath(tmpService, serviceConfig.lpBinaryPathName,
+ L"maintenanceservice_tmp.exe")) {
+ return FALSE;
+ }
+
+ 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)) {
+ return FALSE;
+ }
+
+ // Check that the copied file's certificate matches the expected name and
+ // issuer stored in the registry for this installation and that the
+ // certificate is trusted by the system's certificate store.
+ if (!DoesBinaryMatchAllowedCertificates(installDir, tmpService)) {
+ DeleteFileW(tmpService);
+ return FALSE;
+ }
+
+ // Start the upgrade comparison process
+ STARTUPINFOW si = {0};
+ si.cb = sizeof(STARTUPINFOW);
+ // No particular desktop because no UI
+ si.lpDesktop = const_cast<LPWSTR>(L""); // -Wwritable-strings
+ PROCESS_INFORMATION pi = {0};
+ WCHAR cmdLine[64] = {'\0'};
+ wcsncpy(cmdLine, L"dummyparam.exe upgrade",
+ sizeof(cmdLine) / sizeof(cmdLine[0]) - 1);
+ BOOL svcUpdateProcessStarted =
+ CreateProcessW(tmpService, cmdLine, nullptr, nullptr, FALSE, 0, nullptr,
+ installDir, &si, &pi);
+ if (svcUpdateProcessStarted) {
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+ return svcUpdateProcessStarted;
+}
+
+/**
+ * Executes a maintenance service command
+ *
+ * @param argc The total number of arguments in argv
+ * @param argv An array of null terminated strings to pass to the service,
+ * @return ERROR_SUCCESS if the service command was started.
+ * Less than 16000, a windows system error code from StartServiceW
+ * More than 20000, 20000 + the last state of the service constant if
+ * the last state is something other than stopped.
+ * 17001 if the SCM could not be opened
+ * 17002 if the service could not be opened
+ */
+DWORD
+StartServiceCommand(int argc, LPCWSTR* argv) {
+ DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
+ if (lastState != SERVICE_STOPPED) {
+ return 20000 + lastState;
+ }
+
+ // Get a handle to the SCM database.
+ SC_HANDLE serviceManager = OpenSCManager(
+ nullptr, nullptr, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
+ if (!serviceManager) {
+ return 17001;
+ }
+
+ // Get a handle to the service.
+ SC_HANDLE service = OpenServiceW(serviceManager, SVC_NAME, SERVICE_START);
+ if (!service) {
+ CloseServiceHandle(serviceManager);
+ return 17002;
+ }
+
+ // Wait at most 5 seconds trying to start the service in case of errors
+ // like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT.
+ const DWORD maxWaitMS = 5000;
+ DWORD currentWaitMS = 0;
+ DWORD lastError = ERROR_SUCCESS;
+ while (currentWaitMS < maxWaitMS) {
+ BOOL result = StartServiceW(service, argc, argv);
+ if (result) {
+ lastError = ERROR_SUCCESS;
+ break;
+ } else {
+ lastError = GetLastError();
+ }
+ Sleep(100);
+ currentWaitMS += 100;
+ }
+ CloseServiceHandle(service);
+ CloseServiceHandle(serviceManager);
+ return lastError;
+}
+
+/**
+ * Launch a service initiated action for a software update with the
+ * specified arguments.
+ *
+ * @param argc The total number of arguments in argv
+ * @param argv An array of null terminated strings to pass to the exePath,
+ * argv[0] must be the path to the updater.exe
+ * @return ERROR_SUCCESS if successful
+ */
+DWORD
+LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv) {
+ // The service command is the same as the updater.exe command line except
+ // it has 4 extra args:
+ // 0) The name of the service, automatically added by Windows
+ // 1) "MozillaMaintenance" (I think this is redundant with 0)
+ // 2) The command being executed, which is "software-update"
+ // 3) The path to updater.exe (from argv[0])
+ LPCWSTR* updaterServiceArgv = new LPCWSTR[argc + 2];
+ updaterServiceArgv[0] = L"MozillaMaintenance";
+ updaterServiceArgv[1] = L"software-update";
+
+ for (int i = 0; i < argc; ++i) {
+ updaterServiceArgv[i + 2] = argv[i];
+ }
+
+ // Execute the service command by starting the service with
+ // the passed in arguments.
+ DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv);
+ delete[] updaterServiceArgv;
+ return ret;
+}
+
+/**
+ * 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 patchDirPath
+ * The path of the update patch directory.
+ * @param errorCode
+ * Error code to set
+ * @return TRUE if successful
+ */
+BOOL WriteStatusFailure(LPCWSTR patchDirPath, int errorCode) {
+ WCHAR statusFilePath[MAX_PATH + 1] = {L'\0'};
+ if (!GetSecureOutputFilePath(patchDirPath, L".status", statusFilePath)) {
+ return FALSE;
+ }
+
+ 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(hStatusFile, failure, toWrite, &wrote, nullptr);
+ CloseHandle(hStatusFile);
+
+ if (!ok || wrote != toWrite) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Waits for a service to enter a stopped state.
+ * This function does not stop the service, it just blocks until the service
+ * is stopped.
+ *
+ * @param serviceName The service to wait for.
+ * @param maxWaitSeconds The maximum number of seconds to wait
+ * @return state of the service after a timeout or when stopped.
+ * A value of 255 is returned for an error. Typical values are:
+ * SERVICE_STOPPED 0x00000001
+ * SERVICE_START_PENDING 0x00000002
+ * SERVICE_STOP_PENDING 0x00000003
+ * SERVICE_RUNNING 0x00000004
+ * SERVICE_CONTINUE_PENDING 0x00000005
+ * SERVICE_PAUSE_PENDING 0x00000006
+ * SERVICE_PAUSED 0x00000007
+ * last status not set 0x000000CF
+ * Could no query status 0x000000DF
+ * Could not open service, access denied 0x000000EB
+ * Could not open service, invalid handle 0x000000EC
+ * Could not open service, invalid name 0x000000ED
+ * Could not open service, does not exist 0x000000EE
+ * Could not open service, other error 0x000000EF
+ * Could not open SCM, access denied 0x000000FD
+ * Could not open SCM, database does not exist 0x000000FE;
+ * Could not open SCM, other error 0x000000FF;
+ * Note: The strange choice of error codes above SERVICE_PAUSED are chosen
+ * in case Windows comes out with other service stats higher than 7, they
+ * would likely call it 8 and above. JS code that uses this in TestAUSHelper
+ * only handles values up to 255 so that's why we don't use GetLastError
+ * directly.
+ */
+DWORD
+WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds) {
+ // 0x000000CF is defined above to be not set
+ DWORD lastServiceState = 0x000000CF;
+
+ // Get a handle to the SCM database.
+ SC_HANDLE serviceManager = OpenSCManager(
+ nullptr, nullptr, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
+ if (!serviceManager) {
+ DWORD lastError = GetLastError();
+ switch (lastError) {
+ case ERROR_ACCESS_DENIED:
+ return 0x000000FD;
+ case ERROR_DATABASE_DOES_NOT_EXIST:
+ return 0x000000FE;
+ default:
+ return 0x000000FF;
+ }
+ }
+
+ // Get a handle to the service.
+ SC_HANDLE service =
+ OpenServiceW(serviceManager, serviceName, SERVICE_QUERY_STATUS);
+ if (!service) {
+ DWORD lastError = GetLastError();
+ CloseServiceHandle(serviceManager);
+ switch (lastError) {
+ case ERROR_ACCESS_DENIED:
+ return 0x000000EB;
+ case ERROR_INVALID_HANDLE:
+ return 0x000000EC;
+ case ERROR_INVALID_NAME:
+ return 0x000000ED;
+ case ERROR_SERVICE_DOES_NOT_EXIST:
+ return 0x000000EE;
+ default:
+ return 0x000000EF;
+ }
+ }
+
+ DWORD currentWaitMS = 0;
+ SERVICE_STATUS_PROCESS ssp;
+ ssp.dwCurrentState = lastServiceState;
+ while (currentWaitMS < maxWaitSeconds * 1000) {
+ DWORD bytesNeeded;
+ if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
+ sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
+ DWORD lastError = GetLastError();
+ switch (lastError) {
+ case ERROR_INVALID_HANDLE:
+ ssp.dwCurrentState = 0x000000D9;
+ break;
+ case ERROR_ACCESS_DENIED:
+ ssp.dwCurrentState = 0x000000DA;
+ break;
+ case ERROR_INSUFFICIENT_BUFFER:
+ ssp.dwCurrentState = 0x000000DB;
+ break;
+ case ERROR_INVALID_PARAMETER:
+ ssp.dwCurrentState = 0x000000DC;
+ break;
+ case ERROR_INVALID_LEVEL:
+ ssp.dwCurrentState = 0x000000DD;
+ break;
+ case ERROR_SHUTDOWN_IN_PROGRESS:
+ ssp.dwCurrentState = 0x000000DE;
+ break;
+ // These 3 errors can occur when the service is not yet stopped but
+ // it is stopping.
+ case ERROR_INVALID_SERVICE_CONTROL:
+ case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
+ case ERROR_SERVICE_NOT_ACTIVE:
+ currentWaitMS += 50;
+ Sleep(50);
+ continue;
+ default:
+ ssp.dwCurrentState = 0x000000DF;
+ }
+
+ // We couldn't query the status so just break out
+ break;
+ }
+
+ // The service is already in use.
+ if (ssp.dwCurrentState == SERVICE_STOPPED) {
+ break;
+ }
+ currentWaitMS += 50;
+ Sleep(50);
+ }
+
+ lastServiceState = ssp.dwCurrentState;
+ CloseServiceHandle(service);
+ CloseServiceHandle(serviceManager);
+ return lastServiceState;
+}
+#endif
+
+/**
+ * Determines if there is at least one process running for the specified
+ * application. A match will be found across any session for any user.
+ *
+ * @param process The process to check for existance
+ * @return ERROR_NOT_FOUND if the process was not found
+ * ERROR_SUCCESS if the process was found and there were no errors
+ * Other Win32 system error code for other errors
+ **/
+DWORD
+IsProcessRunning(LPCWSTR filename) {
+ // Take a snapshot of all processes in the system.
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (INVALID_HANDLE_VALUE == snapshot) {
+ return GetLastError();
+ }
+
+ PROCESSENTRY32W processEntry;
+ processEntry.dwSize = sizeof(PROCESSENTRY32W);
+ if (!Process32FirstW(snapshot, &processEntry)) {
+ DWORD lastError = GetLastError();
+ CloseHandle(snapshot);
+ return lastError;
+ }
+
+ do {
+ if (wcsicmp(filename, processEntry.szExeFile) == 0) {
+ CloseHandle(snapshot);
+ return ERROR_SUCCESS;
+ }
+ } while (Process32NextW(snapshot, &processEntry));
+ CloseHandle(snapshot);
+ return ERROR_NOT_FOUND;
+}
+
+/**
+ * Waits for the specified application to exit.
+ *
+ * @param filename The application to wait for.
+ * @param maxSeconds The maximum amount of seconds to wait for all
+ * instances of the application to exit.
+ * @return ERROR_SUCCESS if no instances of the application exist
+ * WAIT_TIMEOUT if the process is still running after maxSeconds.
+ * Any other Win32 system error code.
+ */
+DWORD
+WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds) {
+ DWORD applicationRunningError = WAIT_TIMEOUT;
+ for (DWORD i = 0; i < maxSeconds; i++) {
+ DWORD applicationRunningError = IsProcessRunning(filename);
+ if (ERROR_NOT_FOUND == applicationRunningError) {
+ return ERROR_SUCCESS;
+ }
+ Sleep(1000);
+ }
+
+ if (ERROR_SUCCESS == applicationRunningError) {
+ return WAIT_TIMEOUT;
+ }
+
+ return applicationRunningError;
+}
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+/**
+ * Determines if the fallback key exists or not
+ *
+ * @return TRUE if the fallback key exists and there was no error checking
+ */
+BOOL DoesFallbackKeyExist() {
+ HKEY testOnlyFallbackKey;
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEST_ONLY_FALLBACK_KEY_PATH, 0,
+ KEY_READ | KEY_WOW64_64KEY,
+ &testOnlyFallbackKey) != ERROR_SUCCESS) {
+ return FALSE;
+ }
+
+ RegCloseKey(testOnlyFallbackKey);
+ return TRUE;
+}
+
+/**
+ * Determines if the file system for the specified file handle is local
+ * @param file path to check the filesystem type for, must be at most MAX_PATH
+ * @param isLocal out parameter which will hold TRUE if the drive is local
+ * @return TRUE if the call succeeded
+ */
+BOOL IsLocalFile(LPCWSTR file, BOOL& isLocal) {
+ WCHAR rootPath[MAX_PATH + 1] = {L'\0'};
+ if (wcslen(file) > MAX_PATH) {
+ return FALSE;
+ }
+
+ wcsncpy(rootPath, file, MAX_PATH);
+ PathStripToRootW(rootPath);
+ isLocal = GetDriveTypeW(rootPath) == DRIVE_FIXED;
+ return TRUE;
+}
+
+/**
+ * Determines the DWORD value of a registry key value
+ *
+ * @param key The base key to where the value name exists
+ * @param valueName The name of the value
+ * @param retValue Out parameter which will hold the value
+ * @return TRUE on success
+ */
+static BOOL GetDWORDValue(HKEY key, LPCWSTR valueName, DWORD& retValue) {
+ DWORD regDWORDValueSize = sizeof(DWORD);
+ LONG retCode =
+ RegQueryValueExW(key, valueName, 0, nullptr,
+ reinterpret_cast<LPBYTE>(&retValue), &regDWORDValueSize);
+ return ERROR_SUCCESS == retCode;
+}
+
+/**
+ * Determines if the the system's elevation type allows
+ * unprmopted elevation.
+ *
+ * @param isUnpromptedElevation Out parameter which specifies if unprompted
+ * elevation is allowed.
+ * @return TRUE if the user can actually elevate and the value was obtained
+ * successfully.
+ */
+BOOL IsUnpromptedElevation(BOOL& isUnpromptedElevation) {
+ if (!UACHelper::CanUserElevate()) {
+ return FALSE;
+ }
+
+ LPCWSTR UACBaseRegKey =
+ L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
+ HKEY baseKey;
+ LONG retCode =
+ RegOpenKeyExW(HKEY_LOCAL_MACHINE, UACBaseRegKey, 0, KEY_READ, &baseKey);
+ if (retCode != ERROR_SUCCESS) {
+ return FALSE;
+ }
+
+ DWORD consent, secureDesktop;
+ BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin", consent);
+ success = success &&
+ GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop);
+
+ RegCloseKey(baseKey);
+ if (success) {
+ isUnpromptedElevation = !consent && !secureDesktop;
+ }
+
+ return success;
+}
+#endif
diff --git a/toolkit/mozapps/update/common/updatehelper.h b/toolkit/mozapps/update/common/updatehelper.h
new file mode 100644
index 0000000000..b346893835
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatehelper.h
@@ -0,0 +1,39 @@
+/* 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/. */
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+BOOL StartServiceUpdate(LPCWSTR installDir);
+DWORD LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv);
+BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
+DWORD WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds);
+BOOL DoesFallbackKeyExist();
+BOOL IsLocalFile(LPCWSTR file, BOOL& isLocal);
+DWORD StartServiceCommand(int argc, LPCWSTR* argv);
+BOOL IsUnpromptedElevation(BOOL& isUnpromptedElevation);
+#endif
+
+DWORD WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds);
+DWORD IsProcessRunning(LPCWSTR filename);
+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"
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+# 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
+// "This is a valid installation directory" check. This key also stores
+// the allowed name and issuer for cert checks so that the cert check
+// code can still be run unchanged.
+# define TEST_ONLY_FALLBACK_KEY_PATH \
+ BASE_SERVICE_REG_KEY L"\\3932ecacee736d366d6436db0f55bce4"
+#endif
diff --git a/toolkit/mozapps/update/common/updatererrors.h b/toolkit/mozapps/update/common/updatererrors.h
new file mode 100644
index 0000000000..f2663d5b57
--- /dev/null
+++ b/toolkit/mozapps/update/common/updatererrors.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 UPDATEERRORS_H
+#define UPDATEERRORS_H
+
+#define OK 0
+
+// Error codes that are no longer used should not be used again unless they
+// aren't used in client code (e.g. UpdateService.jsm, updates.js, etc.).
+
+#define MAR_ERROR_EMPTY_ACTION_LIST 1
+#define LOADSOURCE_ERROR_WRONG_SIZE 2
+
+// Error codes 3-16 are for general update problems.
+#define USAGE_ERROR 3
+#define CRC_ERROR 4
+#define PARSE_ERROR 5
+#define READ_ERROR 6
+#define WRITE_ERROR 7
+// #define UNEXPECTED_ERROR 8 // Replaced with errors 38-42
+#define ELEVATION_CANCELED 9
+
+// Error codes 10-14 are related to memory allocation failures.
+// Note: If more memory allocation error codes are added, the implementation of
+// isMemoryAllocationErrorCode in UpdateService.jsm should be updated to account
+// for them.
+#define READ_STRINGS_MEM_ERROR 10
+#define ARCHIVE_READER_MEM_ERROR 11
+#define BSPATCH_MEM_ERROR 12
+#define UPDATER_MEM_ERROR 13
+#define UPDATER_QUOTED_PATH_MEM_ERROR 14
+
+#define BAD_ACTION_ERROR 15
+#define STRING_CONVERSION_ERROR 16
+
+// Error codes 17-23 are related to security tasks for MAR
+// signing and MAR protection.
+#define CERT_LOAD_ERROR 17
+#define CERT_HANDLING_ERROR 18
+#define CERT_VERIFY_ERROR 19
+#define ARCHIVE_NOT_OPEN 20
+#define COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR 21
+#define MAR_CHANNEL_MISMATCH_ERROR 22
+#define VERSION_DOWNGRADE_ERROR 23
+
+// Error codes 24-33 and 49-58 are for the Windows maintenance service.
+// Note: If more maintenance service error codes are added, the implementations
+// of IsServiceSpecificErrorCode in updater.cpp and UpdateService.jsm should be
+// updated to account for them.
+#define SERVICE_UPDATER_COULD_NOT_BE_STARTED 24
+#define SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS 25
+#define SERVICE_UPDATER_SIGN_ERROR 26
+#define SERVICE_UPDATER_COMPARE_ERROR 27
+#define SERVICE_UPDATER_IDENTITY_ERROR 28
+#define SERVICE_STILL_APPLYING_ON_SUCCESS 29
+#define SERVICE_STILL_APPLYING_ON_FAILURE 30
+#define SERVICE_UPDATER_NOT_FIXED_DRIVE 31
+#define SERVICE_COULD_NOT_LOCK_UPDATER 32
+#define SERVICE_INSTALLDIR_ERROR 33
+
+#define NO_INSTALLDIR_ERROR 34
+#define WRITE_ERROR_ACCESS_DENIED 35
+// #define WRITE_ERROR_SHARING_VIOLATION 36 // Replaced with errors 46-48
+#define WRITE_ERROR_CALLBACK_APP 37
+#define UPDATE_SETTINGS_FILE_CHANNEL 38
+#define UNEXPECTED_XZ_ERROR 39
+#define UNEXPECTED_MAR_ERROR 40
+#define UNEXPECTED_BSPATCH_ERROR 41
+#define UNEXPECTED_FILE_OPERATION_ERROR 42
+#define UNEXPECTED_STAGING_ERROR 43
+#define DELETE_ERROR_STAGING_LOCK_FILE 44
+#define DELETE_ERROR_EXPECTED_DIR 46
+#define DELETE_ERROR_EXPECTED_FILE 47
+#define RENAME_ERROR_EXPECTED_FILE 48
+
+// Error codes 24-33 and 49-58 are for the Windows maintenance service.
+// Note: If more maintenance service error codes are added, the implementations
+// of IsServiceSpecificErrorCode in updater.cpp and UpdateService.jsm should be
+// updated to account for them.
+#define SERVICE_COULD_NOT_COPY_UPDATER 49
+#define SERVICE_STILL_APPLYING_TERMINATED 50
+#define SERVICE_STILL_APPLYING_NO_EXIT_CODE 51
+#define SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR 52
+#define SERVICE_CALC_REG_PATH_ERROR 53
+#define SERVICE_INVALID_APPLYTO_DIR_ERROR 54
+#define SERVICE_INVALID_INSTALL_DIR_PATH_ERROR 55
+#define SERVICE_INVALID_WORKING_DIR_PATH_ERROR 56
+#define SERVICE_INSTALL_DIR_REG_ERROR 57
+#define SERVICE_UPDATE_STATUS_UNCHANGED 58
+
+#define WRITE_ERROR_FILE_COPY 61
+#define WRITE_ERROR_DELETE_FILE 62
+#define WRITE_ERROR_OPEN_PATCH_FILE 63
+#define WRITE_ERROR_PATCH_FILE 64
+#define WRITE_ERROR_APPLY_DIR_PATH 65
+#define WRITE_ERROR_CALLBACK_PATH 66
+#define WRITE_ERROR_FILE_ACCESS_DENIED 67
+#define WRITE_ERROR_DIR_ACCESS_DENIED 68
+#define WRITE_ERROR_DELETE_BACKUP 69
+#define WRITE_ERROR_EXTRACT 70
+#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 UpdateService.jsm
+
+// 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
+#define FALLBACKKEY_SERVICE_NO_STOP_ERROR 103
+#define FALLBACKKEY_LAUNCH_ERROR 104
+
+#define SILENT_UPDATE_NEEDED_ELEVATION_ERROR 105
+#define WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION 106
+
+// Error codes 110 and 111 are reserved for UpdateService.jsm
+
+#endif // UPDATEERRORS_H
diff --git a/toolkit/mozapps/update/common/updateutils_win.cpp b/toolkit/mozapps/update/common/updateutils_win.cpp
new file mode 100644
index 0000000000..fc2554e569
--- /dev/null
+++ b/toolkit/mozapps/update/common/updateutils_win.cpp
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "updateutils_win.h"
+#include <errno.h>
+#include <shlwapi.h>
+#include <string.h>
+
+/**
+ * Note: The reason that these functions are separated from those in
+ * updatehelper.h/updatehelper.cpp is that those functions are strictly
+ * used within the updater, whereas changing functions in updateutils_win
+ * will have effects reaching beyond application update.
+ */
+
+// This section implements the minimum set of dirent APIs used by updater.cpp on
+// Windows. If updater.cpp is modified to use more of this API, we need to
+// implement those parts here too.
+static dirent gDirEnt;
+
+DIR::DIR(const WCHAR* path) : findHandle(INVALID_HANDLE_VALUE) {
+ memset(name, 0, sizeof(name));
+ wcsncpy(name, path, sizeof(name) / sizeof(name[0]));
+ wcsncat(name, L"\\*", sizeof(name) / sizeof(name[0]) - wcslen(name) - 1);
+}
+
+DIR::~DIR() {
+ if (findHandle != INVALID_HANDLE_VALUE) {
+ FindClose(findHandle);
+ }
+}
+
+dirent::dirent() { d_name[0] = L'\0'; }
+
+DIR* opendir(const WCHAR* path) { return new DIR(path); }
+
+int closedir(DIR* dir) {
+ delete dir;
+ return 0;
+}
+
+dirent* readdir(DIR* dir) {
+ WIN32_FIND_DATAW data;
+ if (dir->findHandle != INVALID_HANDLE_VALUE) {
+ BOOL result = FindNextFileW(dir->findHandle, &data);
+ if (!result) {
+ if (GetLastError() != ERROR_NO_MORE_FILES) {
+ errno = ENOENT;
+ }
+ return 0;
+ }
+ } else {
+ // Reading the first directory entry
+ dir->findHandle = FindFirstFileW(dir->name, &data);
+ if (dir->findHandle == INVALID_HANDLE_VALUE) {
+ if (GetLastError() == ERROR_FILE_NOT_FOUND) {
+ errno = ENOENT;
+ } else {
+ errno = EBADF;
+ }
+ return 0;
+ }
+ }
+ size_t direntBufferLength =
+ sizeof(gDirEnt.d_name) / sizeof(gDirEnt.d_name[0]);
+ wcsncpy(gDirEnt.d_name, data.cFileName, direntBufferLength);
+ // wcsncpy does not guarantee a null-terminated string if the source string is
+ // too long.
+ gDirEnt.d_name[direntBufferLength - 1] = '\0';
+ return &gDirEnt;
+}
+
+/**
+ * Joins a base directory path with a filename.
+ *
+ * @param base The base directory path of size MAX_PATH + 1
+ * @param extra The filename to append
+ * @return TRUE if the file name was successful appended to base
+ */
+BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra) {
+ 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) {
+ if (wcslen(prefix) > MAX_PATH) {
+ return FALSE;
+ }
+ wcsncpy(filename, prefix, MAX_PATH + 1);
+ }
+
+ WCHAR tmpFileNameString[MAX_PATH + 1] = {L"\0"};
+ if (!GetUUIDString(tmpFileNameString)) {
+ return FALSE;
+ }
+
+ size_t tmpFileNameStringLen = wcslen(tmpFileNameString);
+ if (wcslen(filename) + tmpFileNameStringLen > MAX_PATH) {
+ return FALSE;
+ }
+ wcsncat(filename, tmpFileNameString, tmpFileNameStringLen);
+
+ size_t basePathLen = wcslen(basePath);
+ if (basePathLen > MAX_PATH) {
+ return FALSE;
+ }
+ // Use basePathLen + 1 so wcsncpy will add null termination and if a caller
+ // doesn't allocate MAX_PATH + 1 for tmpPath this won't fail when there is
+ // actually enough space allocated.
+ wcsncpy(tmpPath, basePath, basePathLen + 1);
+ if (!PathAppendSafe(tmpPath, filename)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/toolkit/mozapps/update/common/updateutils_win.h b/toolkit/mozapps/update/common/updateutils_win.h
new file mode 100644
index 0000000000..9de5914741
--- /dev/null
+++ b/toolkit/mozapps/update/common/updateutils_win.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 WINDIRENT_H__
+#define WINDIRENT_H__
+
+/**
+ * Note: The reason that these functions are separated from those in
+ * updatehelper.h/updatehelper.cpp is that those functions are strictly
+ * used within the updater, whereas changing functions in updateutils_win
+ * will have effects reaching beyond application update.
+ */
+
+#ifndef XP_WIN
+# error This library should only be used on Windows
+#endif
+
+#include <windows.h>
+
+struct DIR {
+ explicit DIR(const WCHAR* path);
+ ~DIR();
+ HANDLE findHandle;
+ WCHAR name[MAX_PATH + 1];
+};
+
+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__