/* 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 #include #include #include "city.h" #include "commonupdatedir.h" #include "updatedefines.h" #ifdef XP_WIN # include # include # include # include # include # include # include # include # include # include "nsWindowsHelpers.h" # include "updateutils_win.h" #endif #ifdef XP_WIN // This is the name of the old update directory // (i.e. C:\ProgramData\) # define OLD_ROOT_UPDATE_DIR_NAME "Mozilla" // This is the name of the current update directory // (i.e. C:\ProgramData\) // 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\\) # 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 mString; /** * Allocates enough space to store a string of the specified length. */ bool AllocLen(size_t len) { mString = mozilla::MakeUnique(len + 1); return mString.get() != nullptr; } /** * Allocates a buffer of the size given. */ bool AllocSize(size_t size) { mString = mozilla::MakeUnique(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& 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(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; mozilla::UniquePtr securityDescriptorBuffer; PSECURITY_DESCRIPTOR securityDescriptor; SECURITY_ATTRIBUTES securityAttributes; }; static bool GetCachedHash(const char16_t* installPath, HKEY rootKey, const SimpleAutoString& regPath, mozilla::UniquePtr& result); static HRESULT GetUpdateDirectory(const wchar_t* installPath, WhichUpdateDir whichDir, mozilla::UniquePtr& 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& result) { MOZ_ASSERT(installPath != nullptr, "Install path must not be null in GetInstallHash"); size_t pathSize = std::char_traits::length(installPath) * sizeof(*installPath); uint64_t hash = CityHash64(reinterpret_cast(installPath), pathSize); size_t hashStrSize = sizeof(hash) * 2 + 1; // 2 hex digits per byte + null result = mozilla::MakeUnique(hashStrSize); int charsWritten = NS_tsnprintf(result.get(), hashStrSize, NS_T("%") NS_T(PRIX64), hash); return !(charsWritten < 1 || static_cast(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& 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(installPath), RRF_RT_REG_SZ, nullptr, nullptr, &bufferSize); if (lrv != ERROR_SUCCESS) { return false; } result = mozilla::MakeUnique(bufferSize); // Now read the actual value from the registry. lrv = RegGetValueW(rootKey, regPath.String(), reinterpret_cast(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& 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& 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 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& 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 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 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(installPath), HKEY_LOCAL_MACHINE, regPath, hash); if (!gotHash) { gotHash = GetCachedHash(reinterpret_cast(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(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(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(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(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(SECURITY_DESCRIPTOR_MIN_LENGTH); if (!result.securityDescriptorBuffer) { return E_OUTOFMEMORY; } result.securityDescriptor = reinterpret_cast( 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(&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