/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* 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/. */ #define MOZ_USE_LAUNCHER_ERROR #include "sandboxBroker.h" #include #include #include #include "base/win/windows_version.h" #include "base/win/sid.h" #include "ConfigHelpers.h" #include "GfxDriverInfo.h" #include "mozilla/Assertions.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Components.h" #include "mozilla/ImportDir.h" #include "mozilla/Logging.h" #include "mozilla/NSPRLogModulesParser.h" #include "mozilla/Omnijar.h" #include "mozilla/Preferences.h" #include "mozilla/SandboxSettings.h" #include "mozilla/SHA1.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/StaticPrefs_security.h" #include "mozilla/StaticPrefs_widget.h" #include "mozilla/StaticPtr.h" #include "mozilla/UniquePtr.h" #include "mozilla/glean/SecuritySandboxMetrics.h" #include "mozilla/WinDllServices.h" #include "mozilla/WindowsVersion.h" #include "mozilla/ipc/LaunchError.h" #include "nsAppDirectoryServiceDefs.h" #include "nsCOMPtr.h" #include "nsDirectoryServiceDefs.h" #include "nsIFile.h" #include "nsIGfxInfo.h" #include "nsIProperties.h" #include "nsIXULRuntime.h" #include "nsServiceManagerUtils.h" #include "nsString.h" #include "nsTArray.h" #include "nsTHashtable.h" #include "sandbox/win/src/app_container.h" #include "sandbox/win/src/sandbox.h" #include "sandbox/win/src/security_level.h" #include "WinUtils.h" #define SANDBOX_SUCCEED_OR_CRASH(x) \ do { \ sandbox::ResultCode result = (x); \ MOZ_RELEASE_ASSERT(result == sandbox::SBOX_ALL_OK, #x " failed"); \ } while (0) namespace mozilla { constexpr wchar_t kLpacFirefoxInstallFiles[] = L"lpacFirefoxInstallFiles"; sandbox::BrokerServices* sBrokerService = nullptr; // This is set to true in Initialize when our exe file name has a drive type of // DRIVE_REMOTE, so that we can tailor the sandbox policy as some settings break // fundamental things when running from a network drive. We default to false in // case those checks fail as that gives us the strongest policy. bool SandboxBroker::sRunningFromNetworkDrive = false; // Cached special directories used for adding policy rules. static StaticAutoPtr sBinDir; static StaticAutoPtr sProfileDir; static StaticAutoPtr sWindowsProfileDir; static StaticAutoPtr sLocalAppDataDir; static StaticAutoPtr sSystemFontsDir; static StaticAutoPtr sWindowsSystemDir; static StaticAutoPtr sLocalAppDataLowDir; static StaticAutoPtr sLocalAppDataLowParentDir; #ifdef ENABLE_SYSTEM_EXTENSION_DIRS static StaticAutoPtr sUserExtensionsDir; #endif LazyLogModule sSandboxBrokerLog("SandboxBroker"); #define LOG_E(...) MOZ_LOG(sSandboxBrokerLog, LogLevel::Error, (__VA_ARGS__)) #define LOG_W(...) MOZ_LOG(sSandboxBrokerLog, LogLevel::Warning, (__VA_ARGS__)) #define LOG_D(...) MOZ_LOG(sSandboxBrokerLog, LogLevel::Debug, (__VA_ARGS__)) // Used to store whether we have accumulated an error combination for this // session. static StaticAutoPtr> sLaunchErrors; // This helper function is our version of SandboxWin::AddWin32kLockdownConfig // of Chromium, making sure the MITIGATION_WIN32K_DISABLE flag is set before // adding the SUBSYS_WIN32K_LOCKDOWN rule which is required by // PolicyBase::AddRuleInternal. static sandbox::ResultCode AddWin32kLockdownConfig( sandbox::TargetConfig* aConfig) { sandbox::MitigationFlags flags = aConfig->GetProcessMitigations(); MOZ_ASSERT(flags, "Mitigations should be set before AddWin32kLockdownConfig."); MOZ_ASSERT(!(flags & sandbox::MITIGATION_WIN32K_DISABLE), "Check not enabling twice. Should not happen."); flags |= sandbox::MITIGATION_WIN32K_DISABLE; sandbox::ResultCode result = aConfig->SetProcessMitigations(flags); if (result != sandbox::SBOX_ALL_OK) { return result; } result = aConfig->SetFakeGdiInit(); if (result != sandbox::SBOX_ALL_OK) { return result; } return result; } static void CacheAndStandardizeDir(const nsAString& aDir, StaticAutoPtr& aCacheVar) { MOZ_ASSERT(!aCacheVar); aCacheVar = new nsString(aDir); // Convert network share path to format for sandbox policy. if (Substring(*aCacheVar, 0, 2).Equals(u"\\\\"_ns)) { aCacheVar->InsertLiteral(u"??\\UNC", 1); } } /* static */ void SandboxBroker::Initialize(sandbox::BrokerServices* aBrokerServices, const nsAString& aBinDir) { sBrokerService = aBrokerServices; sRunningFromNetworkDrive = widget::WinUtils::RunningFromANetworkDrive(); if (!aBinDir.IsEmpty()) { CacheAndStandardizeDir(aBinDir, sBinDir); } // Clear statics on shutdown. RunOnShutdown([] { sLaunchErrors = nullptr; sBinDir = nullptr; sProfileDir = nullptr; sWindowsProfileDir = nullptr; sLocalAppDataDir = nullptr; sSystemFontsDir = nullptr; sWindowsSystemDir = nullptr; sLocalAppDataLowDir = nullptr; sLocalAppDataLowParentDir = nullptr; #ifdef ENABLE_SYSTEM_EXTENSION_DIRS sUserExtensionsDir = nullptr; #endif }); } static void CacheDirectoryServiceDir(nsIProperties* aDirSvc, const char* aDirKey, StaticAutoPtr& aCacheVar) { nsCOMPtr dirToCache; nsresult rv = aDirSvc->Get(aDirKey, NS_GET_IID(nsIFile), getter_AddRefs(dirToCache)); if (NS_FAILED(rv)) { // This can only be an NS_WARNING, because it can fail for xpcshell tests. NS_WARNING("Failed to get directory to cache."); LOG_E("Failed to get directory to cache, key: %s.", aDirKey); return; } nsAutoString dirPath; MOZ_ALWAYS_SUCCEEDS(dirToCache->GetPath(dirPath)); CacheAndStandardizeDir(dirPath, aCacheVar); } template static void AddCachedDirRule(TC* aConfig, sandbox::FileSemantics aAccess, const StaticAutoPtr& aBaseDir, const nsLiteralString& aRelativePath = u""_ns) { if (!aBaseDir) { // This can only be an NS_WARNING, because it can null for xpcshell tests. NS_WARNING("Tried to add rule with null base dir."); LOG_E("Tried to add rule with null base dir. Relative path: %S, Access: %d", static_cast(aRelativePath.get()), aAccess); return; } nsAutoString rulePath(*aBaseDir); rulePath.Append(aRelativePath); sandbox::ResultCode result = aConfig->AllowFileAccess(aAccess, rulePath.get()); if (sandbox::SBOX_ALL_OK != result) { NS_ERROR("Failed to add file policy rule."); LOG_E("Failed (ResultCode %d) to add %d access to: %S", result, aAccess, rulePath.getW()); } } static void EnsureWindowsDirCached( GUID aFolderID, StaticAutoPtr& aCacheVar, const char* aErrMsg, StaticAutoPtr* aParentCacheVar = nullptr) { if (aCacheVar) { return; } UniquePtr dirPath; if (FAILED(::SHGetKnownFolderPath(aFolderID, 0, nullptr, getter_Transfers(dirPath)))) { NS_ERROR(aErrMsg); LOG_E("%s", aErrMsg); return; } nsDependentString dirString(dirPath.get()); CacheAndStandardizeDir(dirString, aCacheVar); if (aParentCacheVar) { nsCOMPtr dirFile; nsCOMPtr parentDir; if (NS_FAILED(NS_NewLocalFile(dirString, getter_AddRefs(dirFile))) || NS_FAILED(dirFile->GetParent(getter_AddRefs(parentDir)))) { NS_WARNING("Failed to get parent directory to cache."); LOG_E("%s parent", aErrMsg); return; } nsString parentPath; MOZ_ALWAYS_SUCCEEDS(parentDir->GetPath(parentPath)); CacheAndStandardizeDir(parentPath, *aParentCacheVar); } } template static void AddCachedWindowsDirRule( TC* aConfig, sandbox::FileSemantics aAccess, GUID aFolderID, const nsLiteralString& aRelativePath = u""_ns) { if (aFolderID == FOLDERID_Fonts) { EnsureWindowsDirCached(FOLDERID_Fonts, sSystemFontsDir, "Failed to get Windows Fonts folder"); AddCachedDirRule(aConfig, aAccess, sSystemFontsDir, aRelativePath); return; } if (aFolderID == FOLDERID_System) { EnsureWindowsDirCached(FOLDERID_System, sWindowsSystemDir, "Failed to get Windows System folder"); AddCachedDirRule(aConfig, aAccess, sWindowsSystemDir, aRelativePath); return; } if (aFolderID == FOLDERID_LocalAppDataLow) { // For LocalAppDataLow we also require the parent dir. EnsureWindowsDirCached(FOLDERID_LocalAppDataLow, sLocalAppDataLowDir, "Failed to get Windows LocalAppDataLow folder", &sLocalAppDataLowParentDir); AddCachedDirRule(aConfig, aAccess, sLocalAppDataLowDir, aRelativePath); return; } if (aFolderID == FOLDERID_Profile) { EnsureWindowsDirCached(FOLDERID_Profile, sWindowsProfileDir, "Failed to get Windows Profile folder"); AddCachedDirRule(aConfig, aAccess, sWindowsProfileDir, aRelativePath); return; } MOZ_CRASH("Unhandled FOLDERID guid."); } /* static */ void SandboxBroker::GeckoDependentInitialize() { MOZ_ASSERT(NS_IsMainThread()); // Cache directory paths for use in policy rules, because the directory // service must be called on the main thread. nsresult rv; nsCOMPtr dirSvc = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); if (NS_FAILED(rv)) { MOZ_ASSERT(false, "Failed to get directory service, cannot cache directories " "for rules."); LOG_E( "Failed to get directory service, cannot cache directories for " "rules."); return; } CacheDirectoryServiceDir(dirSvc, NS_APP_USER_PROFILE_50_DIR, sProfileDir); CacheDirectoryServiceDir(dirSvc, NS_WIN_LOCAL_APPDATA_DIR, sLocalAppDataDir); #ifdef ENABLE_SYSTEM_EXTENSION_DIRS CacheDirectoryServiceDir(dirSvc, XRE_USER_SYS_EXTENSION_DIR, sUserExtensionsDir); #endif } SandboxBroker::SandboxBroker() { if (sBrokerService) { mPolicy = sBrokerService->CreatePolicy(); if (sRunningFromNetworkDrive) { mPolicy->GetConfig()->SetDoNotUseRestrictingSIDs(); } } } #define WSTRING(STRING) L"" STRING static void AddMozLogRulesToConfig(sandbox::TargetConfig* aConfig, const base::EnvironmentMap& aEnvironment) { auto it = aEnvironment.find(ENVIRONMENT_LITERAL("MOZ_LOG_FILE")); if (it == aEnvironment.end()) { it = aEnvironment.find(ENVIRONMENT_LITERAL("NSPR_LOG_FILE")); } if (it == aEnvironment.end()) { return; } char const* logFileModules = getenv("MOZ_LOG"); if (!logFileModules) { return; } // MOZ_LOG files have a standard file extension appended. std::wstring logFileName(it->second); logFileName.append(WSTRING(MOZ_LOG_FILE_EXTENSION)); // Allow for rotation number if rotate is on in the MOZ_LOG settings. bool rotate = false; NSPRLogModulesParser( logFileModules, [&rotate](const char* aName, LogLevel aLevel, int32_t aValue) { if (strcmp(aName, "rotate") == 0) { // Less or eq zero means to turn rotate off. rotate = aValue > 0; } }); if (rotate) { logFileName.append(L".?"); } // Allow for %PID token in the filename. We don't allow it in the dir path, if // specified, because we have to use a wildcard as we don't know the PID yet. auto pidPos = logFileName.find(WSTRING(MOZ_LOG_PID_TOKEN)); auto lastSlash = logFileName.find_last_of(L"/\\"); if (pidPos != std::wstring::npos && (lastSlash == std::wstring::npos || lastSlash < pidPos)) { logFileName.replace(pidPos, strlen(MOZ_LOG_PID_TOKEN), L"*"); } auto result = aConfig->AllowFileAccess(sandbox::FileSemantics::kAllowAny, logFileName.c_str()); if (result != sandbox::SBOX_ALL_OK) { NS_WARNING("Failed to add rule for MOZ_LOG files."); LOG_W("Failed (ResultCode %d) to add rule for MOZ_LOG files", result); } } static void AddDeveloperRepoDirToConfig(sandbox::TargetConfig* aConfig) { const wchar_t* developer_repo_dir = _wgetenv(WSTRING("MOZ_DEVELOPER_REPO_DIR")); if (!developer_repo_dir) { return; } std::wstring repoPath(developer_repo_dir); std::replace(repoPath.begin(), repoPath.end(), '/', '\\'); repoPath.append(WSTRING("\\*")); auto result = aConfig->AllowFileAccess(sandbox::FileSemantics::kAllowReadonly, repoPath.c_str()); if (result != sandbox::SBOX_ALL_OK) { NS_ERROR("Failed to add rule for developer repo dir."); LOG_E("Failed (ResultCode %d) to add read access to developer repo dir", result); } // The following is required if the process is using a USER_RESTRICTED or // lower access token level. result = aConfig->AllowFileAccess(sandbox::FileSemantics::kAllowReadonly, L"\\??\\MountPointManager"); if (result != sandbox::SBOX_ALL_OK) { NS_ERROR("Failed to add rule for MountPointManager."); LOG_E("Failed (ResultCode %d) to add read access to MountPointManager", result); } } #if defined(MOZ_PROFILE_GENERATE) // It should only be allowed on instrumented builds, never on production // builds. static void AddLLVMProfilePathDirectoryToPolicy( sandbox::TargetConfig* aConfig) { std::wstring parentPath; if (GetLlvmProfileDir(parentPath)) { Unused << aConfig->AllowFileAccess(sandbox::FileSemantics::kAllowAny, parentPath.c_str()); } } #endif #undef WSTRING static void EnsureAppLockerAccess(sandbox::TargetConfig* aConfig) { if (aConfig->GetLockdownTokenLevel() < sandbox::USER_LIMITED) { // The following rules are to allow DLLs to be loaded when the token level // blocks access to AppLocker. If the sandbox does not allow access to the // DLL or the AppLocker rules specifically block it, then it will not load. auto result = aConfig->AllowFileAccess( sandbox::FileSemantics::kAllowReadonly, L"\\Device\\SrpDevice"); if (sandbox::SBOX_ALL_OK != result) { NS_ERROR("Failed to add rule for SrpDevice."); LOG_E("Failed (ResultCode %d) to add read access to SrpDevice", result); } result = aConfig->AllowRegistryRead( L"HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Srp\\GP\\"); if (sandbox::SBOX_ALL_OK != result) { NS_ERROR("Failed to add rule for Srp\\GP."); LOG_E("Failed (ResultCode %d) to add read access to Srp\\GP", result); } // On certain Windows versions there is a double slash before GP. result = aConfig->AllowRegistryRead( L"HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Srp\\\\GP\\"); if (sandbox::SBOX_ALL_OK != result) { NS_ERROR("Failed to add rule for Srp\\\\GP."); LOG_E("Failed (ResultCode %d) to add read access to Srp\\\\GP", result); } } } Result SandboxBroker::LaunchApp( const wchar_t* aPath, const wchar_t* aArguments, base::EnvironmentMap& aEnvironment, GeckoProcessType aProcessType, const bool aEnableLogging, const IMAGE_THUNK_DATA* aCachedNtdllThunk, void** aProcessHandle) { if (!sBrokerService) { return Err(mozilla::ipc::LaunchError("SB::LA::sBrokerService")); } if (!mPolicy) { return Err(mozilla::ipc::LaunchError("SB::LA::mPolicy")); } // Set stdout and stderr, to allow inheritance for logging. mPolicy->SetStdoutHandle(::GetStdHandle(STD_OUTPUT_HANDLE)); mPolicy->SetStderrHandle(::GetStdHandle(STD_ERROR_HANDLE)); auto* config = mPolicy->GetConfig(); // If we're running from a network drive then we can't block loading from // remote locations. Strangely using MITIGATION_IMAGE_LOAD_NO_LOW_LABEL in // this situation also means the process fails to start (bug 1423296). if (sRunningFromNetworkDrive) { sandbox::MitigationFlags mitigations = config->GetProcessMitigations(); mitigations &= ~(sandbox::MITIGATION_IMAGE_LOAD_NO_REMOTE | sandbox::MITIGATION_IMAGE_LOAD_NO_LOW_LABEL); MOZ_RELEASE_ASSERT( config->SetProcessMitigations(mitigations) == sandbox::SBOX_ALL_OK, "Setting the reduced set of flags should always succeed"); } // Bug 1936749: MpDetours.dll injection is incompatible with ACG. constexpr sandbox::MitigationFlags kDynamicCodeFlags = sandbox::MITIGATION_DYNAMIC_CODE_DISABLE | sandbox::MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT; sandbox::MitigationFlags delayedMitigations = config->GetDelayedProcessMitigations(); if ((delayedMitigations & kDynamicCodeFlags) && ::GetModuleHandleW(L"MpDetours.dll")) { delayedMitigations &= ~kDynamicCodeFlags; SANDBOX_SUCCEED_OR_CRASH( config->SetDelayedProcessMitigations(delayedMitigations)); } EnsureAppLockerAccess(config); // If logging enabled, set up the policy. if (aEnableLogging) { ApplyLoggingConfig(); } #if defined(DEBUG) // Allow write access to TEMP directory in debug builds for logging purposes. // The path from GetTempPathW can have a length up to MAX_PATH + 1, including // the null, so we need MAX_PATH + 2, so we can add an * to the end. wchar_t tempPath[MAX_PATH + 2]; uint32_t pathLen = ::GetTempPathW(MAX_PATH + 1, tempPath); if (pathLen > 0) { // GetTempPath path ends with \ and returns the length without the null. tempPath[pathLen] = L'*'; tempPath[pathLen + 1] = L'\0'; auto result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, tempPath); if (result != sandbox::SBOX_ALL_OK) { NS_WARNING("Failed to add rule for TEMP debug logging."); LOG_W("Failed (ResultCode %d) to add rule for TEMP debug logging", result); } } #endif // Enable the child process to write log files when setup AddMozLogRulesToConfig(config, aEnvironment); #if defined(MOZ_PROFILE_GENERATE) AddLLVMProfilePathDirectoryToPolicy(config); #endif if (!mozilla::IsPackagedBuild()) { AddDeveloperRepoDirToConfig(config); } // Create the sandboxed process PROCESS_INFORMATION targetInfo = {0}; sandbox::ResultCode result; DWORD last_error = ERROR_SUCCESS; result = sBrokerService->SpawnTarget(aPath, aArguments, aEnvironment, std::move(mPolicy), &last_error, &targetInfo); if (sandbox::SBOX_ALL_OK != result) { nsAutoCString key; key.AppendASCII(XRE_GeckoProcessTypeToString(aProcessType)); key.AppendLiteral("/0x"); key.AppendInt(static_cast(last_error), 16); // Only accumulate for each combination once per session. if (!sLaunchErrors) { sLaunchErrors = new nsTHashtable(); } if (!sLaunchErrors->Contains(key)) { glean::sandbox::failed_launch_keyed.Get(key).AccumulateSingleSample( result); sLaunchErrors->PutEntry(key); } LOG_E("Failed (ResultCode %d) to SpawnTarget with last_error=%lu", result, last_error); return Err(mozilla::ipc::LaunchError::FromWin32Error("SB::LA::SpawnTarget", last_error)); } #ifdef MOZ_THUNDERBIRD // In Thunderbird, mInitDllBlocklistOOP is null, so InitDllBlocklistOOP would // hit MOZ_RELEASE_ASSERT. constexpr bool isThunderbird = true; #else constexpr bool isThunderbird = false; #endif if (!isThunderbird && XRE_GetChildProcBinPathType(aProcessType) == BinPathType::Self) { RefPtr dllSvc(DllServices::Get()); LauncherVoidResultWithLineInfo blocklistInitOk = dllSvc->InitDllBlocklistOOP(aPath, targetInfo.hProcess, aCachedNtdllThunk, aProcessType); if (blocklistInitOk.isErr()) { dllSvc->HandleLauncherError(blocklistInitOk.unwrapErr(), XRE_GeckoProcessTypeToString(aProcessType)); LOG_E("InitDllBlocklistOOP failed at %s:%d with HRESULT 0x%08lX", blocklistInitOk.unwrapErr().mFile, blocklistInitOk.unwrapErr().mLine, blocklistInitOk.unwrapErr().mError.AsHResult()); TerminateProcess(targetInfo.hProcess, 1); CloseHandle(targetInfo.hThread); CloseHandle(targetInfo.hProcess); return Err(mozilla::ipc::LaunchError( "InitDllBlocklistOOP", blocklistInitOk.unwrapErr().mError.AsHResult())); } } else { // Load the child executable as a datafile so that we can examine its // headers without doing a full load with dependencies and such. nsModuleHandle moduleHandle( ::LoadLibraryExW(aPath, nullptr, LOAD_LIBRARY_AS_DATAFILE)); if (moduleHandle) { nt::CrossExecTransferManager transferMgr(targetInfo.hProcess, moduleHandle); if (!!transferMgr) { LauncherVoidResult importsRestored = RestoreImportDirectory(aPath, transferMgr); if (importsRestored.isErr()) { RefPtr dllSvc(DllServices::Get()); dllSvc->HandleLauncherError( importsRestored.unwrapErr(), XRE_GeckoProcessTypeToString(aProcessType)); LOG_E("Failed to restore import directory with HRESULT 0x%08lX", importsRestored.unwrapErr().mError.AsHResult()); TerminateProcess(targetInfo.hProcess, 1); CloseHandle(targetInfo.hThread); CloseHandle(targetInfo.hProcess); return Err(mozilla::ipc::LaunchError( "RestoreImportDirectory", importsRestored.unwrapErr().mError.AsHResult())); } } } } // The sandboxed process is started in a suspended state, resume it now that // we've set things up. ResumeThread(targetInfo.hThread); CloseHandle(targetInfo.hThread); // Return the process handle to the caller *aProcessHandle = targetInfo.hProcess; return Ok(); } // This function caches and returns an array of NT paths of the executable's // dependent modules. // If this returns Nothing(), it means the retrieval of the modules failed // (e.g. when the launcher process is disabled), so the process should not // enable pre-spawn CIG. static const Maybe>& GetPrespawnCigExceptionModules() { // The shared section contains a list of dependent modules as a // null-delimited string. We convert it to a string vector and // cache it to avoid converting the same data every time. static Maybe> sDependentModules = []() -> Maybe> { RefPtr dllSvc(DllServices::Get()); auto sharedSection = dllSvc->GetSharedSection(); if (!sharedSection) { return Nothing(); } return sharedSection->GetDependentModules(); }(); return sDependentModules; } static sandbox::ResultCode AllowProxyLoadFromBinDir( sandbox::TargetConfig* aConfig) { // Allow modules in the directory containing the executable such as // mozglue.dll, nss3.dll, etc. nsAutoString rulePath(*sBinDir); rulePath.Append(u"\\*"_ns); return aConfig->AllowExtraDlls(rulePath.get()); } static sandbox::ResultCode AddCigToConfig( sandbox::TargetConfig* aConfig, bool aAlwaysProxyBinDirLoading = false) { if (StaticPrefs::security_sandbox_cig_prespawn_enabled()) { const Maybe>& exceptionModules = GetPrespawnCigExceptionModules(); if (exceptionModules.isSome()) { sandbox::MitigationFlags mitigations = aConfig->GetProcessMitigations(); MOZ_ASSERT(mitigations, "Mitigations should be set before AddCigToPolicy."); MOZ_ASSERT(!(mitigations & sandbox::MITIGATION_FORCE_MS_SIGNED_BINS), "AddCigToPolicy should not be called twice."); mitigations |= sandbox::MITIGATION_FORCE_MS_SIGNED_BINS; sandbox::ResultCode result = aConfig->SetProcessMitigations(mitigations); if (result != sandbox::SBOX_ALL_OK) { return result; } result = AllowProxyLoadFromBinDir(aConfig); if (result != sandbox::SBOX_ALL_OK) { return result; } for (const wchar_t* path : exceptionModules.ref()) { result = aConfig->AllowExtraDlls(path); if (result != sandbox::SBOX_ALL_OK) { return result; } } return sandbox::SBOX_ALL_OK; } } sandbox::MitigationFlags delayedMitigations = aConfig->GetDelayedProcessMitigations(); MOZ_ASSERT(delayedMitigations, "Delayed mitigations should be set before AddCigToPolicy."); MOZ_ASSERT(!(delayedMitigations & sandbox::MITIGATION_FORCE_MS_SIGNED_BINS), "AddCigToPolicy should not be called twice."); delayedMitigations |= sandbox::MITIGATION_FORCE_MS_SIGNED_BINS; sandbox::ResultCode result = aConfig->SetDelayedProcessMitigations(delayedMitigations); if (result != sandbox::SBOX_ALL_OK) { return result; } if (aAlwaysProxyBinDirLoading) { result = AllowProxyLoadFromBinDir(aConfig); } return result; } // Returns the most strict dynamic code mitigation flag that is compatible with // system libraries MSAudDecMFT.dll and msmpeg2vdec.dll. This depends on the // Windows version and the architecture. See bug 1783223 comment 27. // // Use the result with SetDelayedProcessMitigations. Using non-delayed ACG // results in incompatibility with third-party antivirus software, the Windows // internal Shim Engine mechanism, parts of our own DLL blocklist code, and // AddressSanitizer initialization code. See bug 1783223. static sandbox::MitigationFlags DynamicCodeFlagForSystemMediaLibraries() { static auto dynamicCodeFlag = []() { #ifdef _M_X64 if (IsWin10CreatorsUpdateOrLater()) { return sandbox::MITIGATION_DYNAMIC_CODE_DISABLE; } #endif // _M_X64 if (IsWin10AnniversaryUpdateOrLater()) { return sandbox::MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT; } return sandbox::MitigationFlags{}; }(); return dynamicCodeFlag; } // Process fails to start in LPAC with ASan build #if !defined(MOZ_ASAN) static void HexEncode(const Span& aBytes, nsACString& aEncoded) { static const char kHexChars[] = "0123456789abcdef"; // Each input byte creates two output hex characters. char* encodedPtr; aEncoded.GetMutableData(&encodedPtr, aBytes.size() * 2); for (auto byte : aBytes) { *(encodedPtr++) = kHexChars[byte >> 4]; *(encodedPtr++) = kHexChars[byte & 0xf]; } } // This is left as a void because we might fail to set the permission for some // reason and yet the LPAC permission is already granted. So returning success // or failure isn't really that useful. /* static */ void SandboxBroker::EnsureLpacPermsissionsOnDir(const nsString& aDir) { // For MSIX packages we get access through the packageContents capability and // we probably won't have access to add the permission either way. if (widget::WinUtils::HasPackageIdentity()) { return; } BYTE sidBytes[SECURITY_MAX_SID_SIZE]; PSID lpacFirefoxInstallFilesSid = static_cast(sidBytes); if (!sBrokerService->DeriveCapabilitySidFromName(kLpacFirefoxInstallFiles, lpacFirefoxInstallFilesSid, sizeof(sidBytes))) { LOG_E("Failed to derive Firefox install files capability SID."); return; } HANDLE hDir = ::CreateFileW(aDir.get(), WRITE_DAC | READ_CONTROL, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hDir == INVALID_HANDLE_VALUE) { LOG_W("Unable to get directory handle for %s", NS_ConvertUTF16toUTF8(aDir).get()); return; } UniquePtr autoHandleCloser(hDir); PACL pBinDirAcl = nullptr; PSECURITY_DESCRIPTOR pSD = nullptr; DWORD result = ::GetSecurityInfo(hDir, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &pBinDirAcl, nullptr, &pSD); if (result != ERROR_SUCCESS) { LOG_E("Failed to get DACL for %s", NS_ConvertUTF16toUTF8(aDir).get()); return; } UniquePtr autoFreeSecDesc(pSD); if (!pBinDirAcl) { LOG_E("DACL was null for %s", NS_ConvertUTF16toUTF8(aDir).get()); return; } for (DWORD i = 0; i < pBinDirAcl->AceCount; ++i) { VOID* pAce = nullptr; if (!::GetAce(pBinDirAcl, i, &pAce) || static_cast(pAce)->AceType != ACCESS_ALLOWED_ACE_TYPE) { continue; } auto* pAllowedAce = static_cast(pAce); if ((pAllowedAce->Mask & (GENERIC_READ | GENERIC_EXECUTE)) != (GENERIC_READ | GENERIC_EXECUTE)) { continue; } PSID aceSID = reinterpret_cast(&(pAllowedAce->SidStart)); if (::EqualSid(aceSID, lpacFirefoxInstallFilesSid)) { LOG_D("Firefox install files permission found on %s", NS_ConvertUTF16toUTF8(aDir).get()); return; } } EXPLICIT_ACCESS_W newAccess = {0}; newAccess.grfAccessMode = GRANT_ACCESS; newAccess.grfAccessPermissions = GENERIC_READ | GENERIC_EXECUTE; newAccess.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ::BuildTrusteeWithSidW(&newAccess.Trustee, lpacFirefoxInstallFilesSid); PACL newDacl = nullptr; if (ERROR_SUCCESS != ::SetEntriesInAclW(1, &newAccess, pBinDirAcl, &newDacl)) { LOG_E("Failed to create new DACL with Firefox install files SID."); return; } UniquePtr autoFreeAcl(newDacl); if (ERROR_SUCCESS != ::SetSecurityInfo(hDir, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, newDacl, nullptr)) { LOG_E("Failed to set new DACL on %s", NS_ConvertUTF16toUTF8(aDir).get()); } LOG_D("Firefox install files permission granted on %s", NS_ConvertUTF16toUTF8(aDir).get()); } static bool IsLowPrivilegedAppContainerSupported() { // Chromium doesn't support adding an LPAC before this version due to // incompatibility with some process mitigations. return IsWin10Sep2018UpdateOrLater(); } // AddAndConfigureAppContainerProfile deliberately fails if it is called on an // unsupported version. This is because for some process types the LPAC is // required to provide a sufficiently strong sandbox. Processes where the use of // an LPAC is an optional extra should use IsLowPrivilegedAppContainerSupported // to check support first. static sandbox::ResultCode AddAndConfigureAppContainerProfile( sandbox::TargetConfig* aConfig, const nsAString& aPackagePrefix, const nsTArray& aWellKnownCapabilites, const nsTArray& aNamedCapabilites) { // CreateAppContainerProfile requires that the profile name is at most 64 // characters but 50 on WCOS systems. The size of sha1 is a constant 40, // so validate that the base names are sufficiently short that the total // length is valid on all systems. MOZ_ASSERT(aPackagePrefix.Length() <= 10U, "AppContainer Package prefix too long."); if (!IsLowPrivilegedAppContainerSupported()) { return sandbox::SBOX_ERROR_UNSUPPORTED; } static nsAutoString uniquePackageStr = []() { // userenv.dll may not have been loaded and some of the chromium sandbox // AppContainer code assumes that it is. Done here to load once. ::LoadLibraryW(L"userenv.dll"); // Done during the package string initialization so we only do it once. SandboxBroker::EnsureLpacPermsissionsOnDir(*sBinDir.get()); // This mirrors Edge's use of the exe path for the SHA1 hash to give a // machine unique name per install. nsAutoString ret; char exePathBuf[MAX_PATH]; DWORD pathSize = ::GetModuleFileNameA(nullptr, exePathBuf, MAX_PATH); if (!pathSize) { return ret; } SHA1Sum sha1Sum; SHA1Sum::Hash sha1Hash; sha1Sum.update(exePathBuf, pathSize); sha1Sum.finish(sha1Hash); nsAutoCString hexEncoded; HexEncode(sha1Hash, hexEncoded); ret = NS_ConvertUTF8toUTF16(hexEncoded); return ret; }(); if (uniquePackageStr.IsEmpty()) { return sandbox::SBOX_ERROR_CREATE_APPCONTAINER; } // The bool parameter is called create_profile, but in fact it tries to create // and then opens if it already exists. So always passing true is fine. bool createOrOpenProfile = true; nsAutoString packageName = aPackagePrefix + uniquePackageStr; sandbox::ResultCode result = aConfig->AddAppContainerProfile(packageName.get(), createOrOpenProfile); if (result != sandbox::SBOX_ALL_OK) { return result; } // This looks odd, but unfortunately holding a scoped_refptr and // dereferencing has DCHECKs that cause a linking problem. sandbox::AppContainer* appContainer = aConfig->GetAppContainer().get(); appContainer->SetEnableLowPrivilegeAppContainer(true); for (auto wkCap : aWellKnownCapabilites) { appContainer->AddCapability(wkCap); } for (auto namedCap : aNamedCapabilites) { appContainer->AddCapability(namedCap); } return sandbox::SBOX_ALL_OK; } #endif void AddShaderCachesToPolicy(sandboxing::SizeTrackingConfig* aConfig, int32_t aSandboxLevel) { // The GPU process needs to write to a shader cache for performance reasons if (sProfileDir) { // Currently the GPU process creates the shader-cache directory if it // doesn't exist, so we have to give FILES_ALLOW_ANY access. // FILES_ALLOW_DIR_ANY has been seen to fail on an existing profile although // the root cause hasn't been found. FILES_ALLOW_DIR_ANY has also been // removed from the sandbox code upstream. // It is possible that we might be able to use FILES_ALLOW_READONLY for the // dir if it is already created, bug 1966157 has been filed to track. AddCachedDirRule(aConfig, sandbox::FileSemantics::kAllowAny, sProfileDir, u"\\shader-cache"_ns); AddCachedDirRule(aConfig, sandbox::FileSemantics::kAllowAny, sProfileDir, u"\\shader-cache\\*"_ns); } // Add GPU specific shader cache rules. const nsCOMPtr gfxInfo = components::GfxInfo::Service(); MOZ_ASSERT(gfxInfo); nsAutoString vendorID; if (NS_FAILED(gfxInfo->GetAdapterVendorID(vendorID))) { NS_WARNING("Failed to get GPU Vendor ID."); return; } if (aSandboxLevel >= 2 && vendorID == widget::GfxDriverInfo::GetDeviceVendor( widget::DeviceVendor::Intel)) { // Add rules to allow Intel's shader cache. AddCachedWindowsDirRule(aConfig, sandbox::FileSemantics::kAllowAny, FOLDERID_LocalAppDataLow, u"\\Intel\\ShaderCache\\*"_ns); AddCachedWindowsDirRule(aConfig, sandbox::FileSemantics::kAllowQuery, FOLDERID_LocalAppDataLow, u"\\Intel\\ShaderCache"_ns); AddCachedWindowsDirRule(aConfig, sandbox::FileSemantics::kAllowQuery, FOLDERID_LocalAppDataLow, u"\\Intel"_ns); AddCachedWindowsDirRule(aConfig, sandbox::FileSemantics::kAllowQuery, FOLDERID_LocalAppDataLow); // The parent of LocalAppDataLow is cached by AddCachedWindowsDirRule. if (sLocalAppDataLowParentDir) { AddCachedDirRule(aConfig, sandbox::FileSemantics::kAllowQuery, sLocalAppDataLowParentDir); } } } void SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel, bool aIsFileProcess) { MOZ_RELEASE_ASSERT(mPolicy, "mPolicy must be set before this call."); sandbox::JobLevel jobLevel; sandbox::TokenLevel accessTokenLevel; sandbox::IntegrityLevel initialIntegrityLevel; sandbox::IntegrityLevel delayedIntegrityLevel; auto* config = mPolicy->GetConfig(); // The setting of these levels is pretty arbitrary, but they are a useful (if // crude) tool while we are tightening the policy. Gaps are left to try and // avoid changing their meaning. MOZ_RELEASE_ASSERT(aSandboxLevel >= 1, "Should not be called with aSandboxLevel < 1"); if (aSandboxLevel >= 20) { jobLevel = sandbox::JobLevel::kLockdown; accessTokenLevel = sandbox::USER_LOCKDOWN; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_UNTRUSTED; } else if (aSandboxLevel >= 8) { jobLevel = sandbox::JobLevel::kLockdown; accessTokenLevel = sandbox::USER_RESTRICTED; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_UNTRUSTED; } else if (aSandboxLevel >= 7) { jobLevel = sandbox::JobLevel::kLockdown; accessTokenLevel = sandbox::USER_LIMITED; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_UNTRUSTED; } else if (aSandboxLevel >= 4) { jobLevel = sandbox::JobLevel::kLockdown; accessTokenLevel = sandbox::USER_LIMITED; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; } else if (aSandboxLevel >= 3) { jobLevel = sandbox::JobLevel::kLockdown; accessTokenLevel = sandbox::USER_LIMITED; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; } else if (aSandboxLevel == 2) { jobLevel = sandbox::JobLevel::kInteractive; accessTokenLevel = sandbox::USER_INTERACTIVE; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; } else { MOZ_ASSERT(aSandboxLevel == 1); jobLevel = sandbox::JobLevel::kUnprotected; accessTokenLevel = sandbox::USER_RESTRICTED_NON_ADMIN; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; } // If the process will handle file: URLs, don't allow settings that // block reads. if (aIsFileProcess) { if (accessTokenLevel < sandbox::USER_RESTRICTED_NON_ADMIN) { accessTokenLevel = sandbox::USER_RESTRICTED_NON_ADMIN; } if (delayedIntegrityLevel > sandbox::INTEGRITY_LEVEL_LOW) { delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; } } #if defined(DEBUG) // This is required for a MOZ_ASSERT check in WindowsMessageLoop.cpp // WinEventHook, see bug 1366694 for details. DWORD uiExceptions = JOB_OBJECT_UILIMIT_HANDLES; #else DWORD uiExceptions = 0; #endif sandbox::ResultCode result = config->SetJobLevel(jobLevel, uiExceptions); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "Setting job level failed, have you set memory limit when " "jobLevel == JOB_NONE?"); result = config->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS, accessTokenLevel); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "Lockdown level cannot be USER_UNPROTECTED or USER_LAST " "if initial level was USER_RESTRICTED_SAME_ACCESS"); result = config->SetIntegrityLevel(initialIntegrityLevel); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "SetIntegrityLevel should never fail, what happened?"); config->SetDelayedIntegrityLevel(delayedIntegrityLevel); if (aSandboxLevel > 5) { config->SetLockdownDefaultDacl(); config->AddRestrictingRandomSid(); } if (aSandboxLevel > 4) { config->SetDesktop(sandbox::Desktop::kAlternateWinstation); } sandbox::MitigationFlags mitigations = sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_DEP | sandbox::MITIGATION_EXTENSION_POINT_DISABLE | sandbox::MITIGATION_IMAGE_LOAD_NO_REMOTE | sandbox::MITIGATION_IMAGE_LOAD_NO_LOW_LABEL | sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32; #if defined(_M_ARM64) // Disable CFG on older versions of ARM64 Windows to avoid a crash in COM. if (!IsWin10Sep2018UpdateOrLater()) { mitigations |= sandbox::MITIGATION_CONTROL_FLOW_GUARD_DISABLE; } #endif if (StaticPrefs::security_sandbox_content_shadow_stack_enabled()) { mitigations |= sandbox::MITIGATION_CET_COMPAT_MODE; } result = config->SetProcessMitigations(mitigations); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "Invalid flags for SetProcessMitigations."); nsIXULRuntime::ContentWin32kLockdownState win32kLockdownState = GetContentWin32kLockdownState(); LOG_W("Win32k Lockdown State: '%s'", ContentWin32kLockdownStateToString(win32kLockdownState)); if (GetContentWin32kLockdownEnabled()) { result = AddWin32kLockdownConfig(config); MOZ_RELEASE_ASSERT(result == sandbox::SBOX_ALL_OK, "Failed to add the win32k lockdown config"); } mitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | sandbox::MITIGATION_DLL_SEARCH_ORDER; result = config->SetDelayedProcessMitigations(mitigations); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "Invalid flags for SetDelayedProcessMitigations."); // We still have edge cases where the child at low integrity can't read some // files, so add a rule to allow read access to everything when required. if (aSandboxLevel == 1 || aIsFileProcess) { result = config->AllowFileAccess(sandbox::FileSemantics::kAllowReadonly, L"*"); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "With these static arguments AddRule should never fail, " "what happened?"); } else { // Add rule to allow access to user specific fonts. AddCachedDirRule(config, sandbox::FileSemantics::kAllowReadonly, sLocalAppDataDir, u"\\Microsoft\\Windows\\Fonts\\*"_ns); // Add rule to allow read access to installation directory. AddCachedDirRule(config, sandbox::FileSemantics::kAllowReadonly, sBinDir, u"\\*"_ns); // Add rule to allow read access to the chrome directory within profile. AddCachedDirRule(config, sandbox::FileSemantics::kAllowReadonly, sProfileDir, u"\\chrome\\*"_ns); // Add rule to allow read access to the extensions directory within profile. AddCachedDirRule(config, sandbox::FileSemantics::kAllowReadonly, sProfileDir, u"\\extensions\\*"_ns); #ifdef ENABLE_SYSTEM_EXTENSION_DIRS // Add rule to allow read access to the per-user extensions directory. AddCachedDirRule(config, sandbox::FileSemantics::kAllowReadonly, sUserExtensionsDir, u"\\*"_ns); #endif } // Add the policy for the client side of a pipe. It is just a file // in the \pipe\ namespace. We restrict it to pipes that start with // "chrome." so the sandboxed process cannot connect to system services. result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\chrome.*"); MOZ_RELEASE_ASSERT( sandbox::SBOX_ALL_OK == result, "With these static arguments AddRule should never fail, what happened?"); // Add the policy for the client side of the crash server pipe. result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\gecko-crash-server-pipe.*"); MOZ_RELEASE_ASSERT( sandbox::SBOX_ALL_OK == result, "With these static arguments AddRule should never fail, what happened?"); // Allow content processes to use complex line breaking brokering. result = config->AllowLineBreaking(); MOZ_RELEASE_ASSERT( sandbox::SBOX_ALL_OK == result, "With these static arguments AddRule should never fail, what happened?"); if (aSandboxLevel >= 8) { // Content process still needs to be able to read fonts. AddCachedWindowsDirRule(config, sandbox::FileSemantics::kAllowReadonly, FOLDERID_Fonts); AddCachedWindowsDirRule(config, sandbox::FileSemantics::kAllowReadonly, FOLDERID_Fonts, u"\\*"_ns); // Add access to Windows system binary dir to allow DLLs that are not // required in all content processes to load later. AddCachedWindowsDirRule(config, sandbox::FileSemantics::kAllowReadonly, FOLDERID_System, u"\\*"_ns); // USER_RESTRCITED will also block access to the KnownDlls list, so we force // that path to fall-back to the normal loading path. config->SetForceKnownDllLoadingFallback(); // Read access for MF Media Source Activate and subkeys/values. result = config->AllowRegistryRead( L"HKEY_LOCAL_MACHINE\\Software\\Classes\\CLSID" L"\\{e79167d7-1b85-4d78-b603-798e0e1a4c67}*"); if (sandbox::SBOX_ALL_OK != result) { NS_ERROR("Failed to add rule for MFStartup CLSID."); LOG_E("Failed (ResultCode %d) to add rule for MFStartup CLSID.", result); } // Read access for other Media Foundation Classes. result = config->AllowRegistryRead( L"HKEY_LOCAL_MACHINE\\Software\\Classes\\MediaFoundation\\*"); if (sandbox::SBOX_ALL_OK != result) { NS_ERROR("Failed to add rule for MFStartup CLSID."); LOG_E("Failed (ResultCode %d) to add rule for MFStartup CLSID.", result); } // Read access for MF H264 Encoder and subkeys/values. result = config->AllowRegistryRead( L"HKEY_LOCAL_MACHINE\\Software\\Classes\\CLSID" L"\\{6CA50344-051A-4DED-9779-A43305165E35}*"); if (sandbox::SBOX_ALL_OK != result) { NS_ERROR("Failed to add rule for MF H264 Encoder CLSID."); LOG_E("Failed (ResultCode %d) to add rule for MF H264 Encoder CLSID.", result); } #if !defined(_WIN64) BOOL isWow64Process; if (::IsWow64Process(::GetCurrentProcess(), &isWow64Process) && isWow64Process) { // Read access for other Media Foundation Classes for WOW64. result = config->AllowRegistryRead( L"HKEY_LOCAL_MACHINE\\" L"Software\\Classes\\WOW6432Node\\MediaFoundation\\*"); if (sandbox::SBOX_ALL_OK != result) { NS_ERROR("Failed to add rule for MFStartup CLSID."); LOG_E("Failed (ResultCode %d) to add rule for MFStartup CLSID.", result); } // Read access for MF H264 Encoder and subkeys/values for WOW64. result = config->AllowRegistryRead( L"HKEY_LOCAL_MACHINE\\Software\\Classes\\WOW6432Node\\CLSID" L"\\{6CA50344-051A-4DED-9779-A43305165E35}*"); if (sandbox::SBOX_ALL_OK != result) { NS_ERROR("Failed to add rule for MF H264 Encoder CLSID."); LOG_E("Failed (ResultCode %d) to add rule for MF H264 Encoder CLSID.", result); } } #endif // We still currently create IPC named pipes in the content process. result = config->AllowNamedPipes(L"\\\\.\\pipe\\chrome.*"); MOZ_RELEASE_ASSERT( sandbox::SBOX_ALL_OK == result, "With these static arguments AddRule should never fail."); } } void SandboxBroker::SetSecurityLevelForGPUProcess(int32_t aSandboxLevel) { MOZ_RELEASE_ASSERT(mPolicy, "mPolicy must be set before this call."); MOZ_RELEASE_ASSERT(aSandboxLevel >= 1); sandbox::TokenLevel initialTokenLevel = sandbox::USER_RESTRICTED_SAME_ACCESS; sandbox::TokenLevel lockdownTokenLevel = (aSandboxLevel >= 2) ? sandbox::USER_LIMITED : sandbox::USER_RESTRICTED_NON_ADMIN; sandbox::IntegrityLevel initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; sandbox::IntegrityLevel delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; sandbox::JobLevel jobLevel = sandbox::JobLevel::kLimitedUser; uint32_t uiExceptions = JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | JOB_OBJECT_UILIMIT_DESKTOP | JOB_OBJECT_UILIMIT_EXITWINDOWS | JOB_OBJECT_UILIMIT_DISPLAYSETTINGS; sandbox::MitigationFlags initialMitigations = sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_IMAGE_LOAD_NO_REMOTE | sandbox::MITIGATION_IMAGE_LOAD_NO_LOW_LABEL | sandbox::MITIGATION_DEP; if (StaticPrefs::security_sandbox_gpu_shadow_stack_enabled()) { initialMitigations |= sandbox::MITIGATION_CET_COMPAT_MODE; } sandbox::MitigationFlags delayedMitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | sandbox::MITIGATION_DLL_SEARCH_ORDER; auto* config = mPolicy->GetConfig(); SANDBOX_SUCCEED_OR_CRASH(config->SetJobLevel(jobLevel, uiExceptions)); SANDBOX_SUCCEED_OR_CRASH( config->SetTokenLevel(initialTokenLevel, lockdownTokenLevel)); SANDBOX_SUCCEED_OR_CRASH(config->SetIntegrityLevel(initialIntegrityLevel)); config->SetDelayedIntegrityLevel(delayedIntegrityLevel); SANDBOX_SUCCEED_OR_CRASH(config->SetProcessMitigations(initialMitigations)); SANDBOX_SUCCEED_OR_CRASH( config->SetDelayedProcessMitigations(delayedMitigations)); config->SetLockdownDefaultDacl(); config->AddRestrictingRandomSid(); // Policy wrapper to keep track of available rule space. The full policy has // 14 pages, so 13 allows one page for generic process rules. sandboxing::SizeTrackingConfig trackingConfig(config, 13); // Add the policy for the client side of a pipe. It is just a file // in the \pipe\ namespace. We restrict it to pipes that start with // "chrome." so the sandboxed process cannot connect to system services. SANDBOX_SUCCEED_OR_CRASH(trackingConfig.AllowFileAccess( sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\chrome.*")); // Add the policy for the client side of the crash server pipe. SANDBOX_SUCCEED_OR_CRASH( trackingConfig.AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\gecko-crash-server-pipe.*")); // Add rule to allow read access to installation directory. AddCachedDirRule(&trackingConfig, sandbox::FileSemantics::kAllowReadonly, sBinDir, u"\\*"_ns); AddShaderCachesToPolicy(&trackingConfig, aSandboxLevel); // The GPU process is launched without GeckoDependentInitialize for the // profile picker making sLocalAppDataDir null. if (aSandboxLevel >= 2 && sLocalAppDataDir) { // We don't want to add a rule directly here but use the same retrieval and // caching mechanism to get the Windows user profile dir. EnsureWindowsDirCached(FOLDERID_Profile, sWindowsProfileDir, "Failed to get Windows Profile folder"); sandboxing::UserFontConfigHelper configHelper( LR"(Software\Microsoft\Windows NT\CurrentVersion\Fonts)", *sWindowsProfileDir, *sLocalAppDataDir); configHelper.AddRules(trackingConfig); } } #define SANDBOX_ENSURE_SUCCESS(result, message) \ do { \ MOZ_ASSERT(sandbox::SBOX_ALL_OK == result, message); \ if (sandbox::SBOX_ALL_OK != result) return false; \ } while (0) bool SandboxBroker::SetSecurityLevelForRDDProcess() { if (!mPolicy) { return false; } auto* config = mPolicy->GetConfig(); auto result = config->SetJobLevel(sandbox::JobLevel::kLockdown, 0 /* ui_exceptions */); SANDBOX_ENSURE_SUCCESS( result, "SetJobLevel should never fail with these arguments, what happened?"); result = config->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS, sandbox::USER_LIMITED); SANDBOX_ENSURE_SUCCESS( result, "SetTokenLevel should never fail with these arguments, what happened?"); config->SetDesktop(sandbox::Desktop::kAlternateWinstation); result = config->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW); SANDBOX_ENSURE_SUCCESS(result, "SetIntegrityLevel should never fail with these " "arguments, what happened?"); config->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW); config->SetLockdownDefaultDacl(); config->AddRestrictingRandomSid(); sandbox::MitigationFlags mitigations = sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_EXTENSION_POINT_DISABLE | sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_DEP | sandbox::MITIGATION_NONSYSTEM_FONT_DISABLE | sandbox::MITIGATION_IMAGE_LOAD_NO_REMOTE | sandbox::MITIGATION_IMAGE_LOAD_NO_LOW_LABEL | sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32; if (StaticPrefs::security_sandbox_rdd_shadow_stack_enabled()) { mitigations |= sandbox::MITIGATION_CET_COMPAT_MODE; } result = config->SetProcessMitigations(mitigations); SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetProcessMitigations."); mitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | sandbox::MITIGATION_DLL_SEARCH_ORDER; if (StaticPrefs::security_sandbox_rdd_acg_enabled()) { // The RDD process depends on msmpeg2vdec.dll. mitigations |= DynamicCodeFlagForSystemMediaLibraries(); } result = config->SetDelayedProcessMitigations(mitigations); SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetDelayedProcessMitigations."); result = AddCigToConfig(config); SANDBOX_ENSURE_SUCCESS(result, "Failed to initialize signed policy rules."); // Add the policy for the client side of a pipe. It is just a file // in the \pipe\ namespace. We restrict it to pipes that start with // "chrome." so the sandboxed process cannot connect to system services. result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\chrome.*"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); // Add the policy for the client side of the crash server pipe. result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\gecko-crash-server-pipe.*"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); return true; } bool SandboxBroker::SetSecurityLevelForSocketProcess() { if (!mPolicy) { return false; } auto* config = mPolicy->GetConfig(); auto result = config->SetJobLevel(sandbox::JobLevel::kLockdown, 0 /* ui_exceptions */); SANDBOX_ENSURE_SUCCESS( result, "SetJobLevel should never fail with these arguments, what happened?"); result = config->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS, sandbox::USER_LIMITED); SANDBOX_ENSURE_SUCCESS( result, "SetTokenLevel should never fail with these arguments, what happened?"); config->SetDesktop(sandbox::Desktop::kAlternateWinstation); result = config->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW); SANDBOX_ENSURE_SUCCESS(result, "SetIntegrityLevel should never fail with these " "arguments, what happened?"); config->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_UNTRUSTED); config->SetLockdownDefaultDacl(); config->AddRestrictingRandomSid(); sandbox::MitigationFlags mitigations = sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_EXTENSION_POINT_DISABLE | sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_DEP | sandbox::MITIGATION_NONSYSTEM_FONT_DISABLE | sandbox::MITIGATION_IMAGE_LOAD_NO_REMOTE | sandbox::MITIGATION_IMAGE_LOAD_NO_LOW_LABEL | sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32; if (StaticPrefs::security_sandbox_socket_shadow_stack_enabled()) { mitigations |= sandbox::MITIGATION_CET_COMPAT_MODE; } result = config->SetProcessMitigations(mitigations); SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetProcessMitigations."); if (StaticPrefs::security_sandbox_socket_win32k_disable()) { result = AddWin32kLockdownConfig(config); SANDBOX_ENSURE_SUCCESS(result, "Failed to add the win32k lockdown config"); } mitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | sandbox::MITIGATION_DLL_SEARCH_ORDER | sandbox::MITIGATION_DYNAMIC_CODE_DISABLE; result = config->SetDelayedProcessMitigations(mitigations); SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetDelayedProcessMitigations."); result = AddCigToConfig(config); SANDBOX_ENSURE_SUCCESS(result, "Failed to initialize signed policy rules."); // Add the policy for the client side of a pipe. It is just a file // in the \pipe\ namespace. We restrict it to pipes that start with // "chrome." so the sandboxed process cannot connect to system services. result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\chrome.*"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); // Add the policy for the client side of the crash server pipe. result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\gecko-crash-server-pipe.*"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); return true; } // A strict base sandbox for utility sandboxes to adapt. struct UtilitySandboxProps { sandbox::JobLevel mJobLevel = sandbox::JobLevel::kLockdown; sandbox::TokenLevel mInitialTokenLevel = sandbox::USER_RESTRICTED_SAME_ACCESS; sandbox::TokenLevel mDelayedTokenLevel = sandbox::USER_LOCKDOWN; sandbox::IntegrityLevel mInitialIntegrityLevel = // before lockdown sandbox::INTEGRITY_LEVEL_LOW; sandbox::IntegrityLevel mDelayedIntegrityLevel = // after lockdown sandbox::INTEGRITY_LEVEL_UNTRUSTED; sandbox::Desktop mDesktop = sandbox::Desktop::kAlternateWinstation; bool mLockdownDefaultDacl = true; bool mAddRestrictingRandomSid = true; bool mUseWin32kLockdown = true; bool mUseCig = true; sandbox::MitigationFlags mInitialMitigations = sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_EXTENSION_POINT_DISABLE | sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_DEP | sandbox::MITIGATION_NONSYSTEM_FONT_DISABLE | sandbox::MITIGATION_IMAGE_LOAD_NO_REMOTE | sandbox::MITIGATION_IMAGE_LOAD_NO_LOW_LABEL | sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32 | sandbox::MITIGATION_CET_COMPAT_MODE; sandbox::MitigationFlags mDelayedMitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | sandbox::MITIGATION_DLL_SEARCH_ORDER | sandbox::MITIGATION_DYNAMIC_CODE_DISABLE; // Low Privileged Application Container settings; nsString mPackagePrefix; nsTArray mWellKnownCapabilites; nsTArray mNamedCapabilites; }; struct GenericUtilitySandboxProps : public UtilitySandboxProps {}; struct UtilityAudioDecodingWmfSandboxProps : public UtilitySandboxProps { UtilityAudioDecodingWmfSandboxProps() { mDelayedTokenLevel = sandbox::USER_LIMITED; mDelayedMitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | sandbox::MITIGATION_DLL_SEARCH_ORDER; #ifdef MOZ_WMF if (StaticPrefs::security_sandbox_utility_wmf_acg_enabled()) { mDelayedMitigations |= DynamicCodeFlagForSystemMediaLibraries(); } #else mDelayedMitigations |= sandbox::MITIGATION_DYNAMIC_CODE_DISABLE; #endif // MOZ_WMF } }; #ifdef MOZ_WMF_MEDIA_ENGINE struct UtilityMfMediaEngineCdmSandboxProps : public UtilitySandboxProps { UtilityMfMediaEngineCdmSandboxProps() { mJobLevel = sandbox::JobLevel::kInteractive; mInitialTokenLevel = sandbox::USER_UNPROTECTED; mDelayedTokenLevel = sandbox::USER_UNPROTECTED; mDesktop = sandbox::Desktop::kDefault; mLockdownDefaultDacl = false; mAddRestrictingRandomSid = false; mUseCig = false; // When we have an LPAC we can't set an integrity level and the process will // default to low integrity anyway. Without an LPAC using low integrity // causes problems with the CDMs. mInitialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LAST; mDelayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LAST; if (StaticPrefs::security_sandbox_utility_wmf_cdm_lpac_enabled()) { mPackagePrefix = u"fx.sb.cdm"_ns; mWellKnownCapabilites = { base::win::WellKnownCapability::kPrivateNetworkClientServer, base::win::WellKnownCapability::kInternetClient, }; mNamedCapabilites = { L"lpacCom", L"lpacIdentityServices", L"lpacMedia", L"lpacPnPNotifications", L"lpacServicesManagement", L"lpacSessionManagement", L"lpacAppExperience", L"lpacInstrumentation", L"lpacCryptoServices", L"lpacEnterprisePolicyChangeNotifications", L"mediaFoundationCdmFiles", L"lpacMediaFoundationCdmData", L"registryRead", kLpacFirefoxInstallFiles, L"lpacDeviceAccess", }; // For MSIX packages we need access to the package contents. if (widget::WinUtils::HasPackageIdentity()) { mNamedCapabilites.AppendElement(L"packageContents"); } } mUseWin32kLockdown = false; mDelayedMitigations = sandbox::MITIGATION_DLL_SEARCH_ORDER; } }; #endif struct WindowsUtilitySandboxProps : public UtilitySandboxProps { WindowsUtilitySandboxProps() { mJobLevel = sandbox::JobLevel::kInteractive; mDelayedTokenLevel = sandbox::USER_RESTRICTED_SAME_ACCESS; mDesktop = sandbox::Desktop::kDefault; mInitialIntegrityLevel = sandbox::INTEGRITY_LEVEL_MEDIUM; mDelayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_MEDIUM; mUseWin32kLockdown = false; mUseCig = false; mDelayedMitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | sandbox::MITIGATION_DLL_SEARCH_ORDER; } }; static const char* WellKnownCapabilityNames[] = { "InternetClient", "InternetClientServer", "PrivateNetworkClientServer", "PicturesLibrary", "VideosLibrary", "MusicLibrary", "DocumentsLibrary", "EnterpriseAuthentication", "SharedUserCertificates", "RemovableStorage", "Appointments", "Contacts", }; void LogUtilitySandboxProps(const UtilitySandboxProps& us) { if (!static_cast(sSandboxBrokerLog)->ShouldLog(LogLevel::Debug)) { return; } nsAutoCString logMsg; logMsg.AppendPrintf("Building sandbox for utility process:\n"); logMsg.AppendPrintf("\tJob Level: %d\n", static_cast(us.mJobLevel)); logMsg.AppendPrintf("\tInitial Token Level: %d\n", static_cast(us.mInitialTokenLevel)); logMsg.AppendPrintf("\tDelayed Token Level: %d\n", static_cast(us.mDelayedTokenLevel)); logMsg.AppendPrintf("\tInitial Integrity Level: %d\n", static_cast(us.mInitialIntegrityLevel)); logMsg.AppendPrintf("\tDelayed Integrity Level: %d\n", static_cast(us.mDelayedIntegrityLevel)); logMsg.AppendPrintf("\tDesktop: %d\n", static_cast(us.mDesktop)); logMsg.AppendPrintf("\tLockdown Default Dacl: %s\n", us.mLockdownDefaultDacl ? "yes" : "no"); logMsg.AppendPrintf("\tAdd Random Restricting SID: %s\n", us.mAddRestrictingRandomSid ? "yes" : "no"); logMsg.AppendPrintf("\tUse Win32k Lockdown: %s\n", us.mUseWin32kLockdown ? "yes" : "no"); logMsg.AppendPrintf("\tUse CIG: %s\n", us.mUseCig ? "yes" : "no"); logMsg.AppendPrintf("\tInitial mitigations: %016llx\n", static_cast(us.mInitialMitigations)); logMsg.AppendPrintf("\tDelayed mitigations: %016llx\n", static_cast(us.mDelayedMitigations)); if (us.mPackagePrefix.IsEmpty()) { logMsg.AppendPrintf("\tNo Low Privileged Application Container\n"); } else { logMsg.AppendPrintf("\tLow Privileged Application Container Settings:\n"); logMsg.AppendPrintf("\t\tPackage Name Prefix: %S\n", static_cast(us.mPackagePrefix.get())); logMsg.AppendPrintf("\t\tWell Known Capabilities:\n"); for (auto wkCap : us.mWellKnownCapabilites) { logMsg.AppendPrintf("\t\t\t%s\n", WellKnownCapabilityNames[static_cast(wkCap)]); } logMsg.AppendPrintf("\t\tNamed Capabilities:\n"); for (auto namedCap : us.mNamedCapabilites) { logMsg.AppendPrintf("\t\t\t%S\n", namedCap); } } LOG_D("%s", logMsg.get()); } bool BuildUtilitySandbox(sandbox::TargetConfig* config, const UtilitySandboxProps& us) { LogUtilitySandboxProps(us); auto result = config->SetJobLevel(us.mJobLevel, 0 /* ui_exceptions */); SANDBOX_ENSURE_SUCCESS( result, "SetJobLevel should never fail with these arguments, what happened?"); result = config->SetTokenLevel(us.mInitialTokenLevel, us.mDelayedTokenLevel); SANDBOX_ENSURE_SUCCESS( result, "SetTokenLevel should never fail with these arguments, what happened?"); if (us.mInitialIntegrityLevel != sandbox::INTEGRITY_LEVEL_LAST) { result = config->SetIntegrityLevel(us.mInitialIntegrityLevel); SANDBOX_ENSURE_SUCCESS(result, "SetIntegrityLevel should never fail with these " "arguments, what happened?"); } if (us.mDelayedIntegrityLevel != sandbox::INTEGRITY_LEVEL_LAST) { config->SetDelayedIntegrityLevel(us.mDelayedIntegrityLevel); } config->SetDesktop(us.mDesktop); if (us.mLockdownDefaultDacl) { config->SetLockdownDefaultDacl(); } if (us.mAddRestrictingRandomSid) { config->AddRestrictingRandomSid(); } result = config->SetProcessMitigations(us.mInitialMitigations); SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetProcessMitigations."); result = config->SetDelayedProcessMitigations(us.mDelayedMitigations); SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetDelayedProcessMitigations."); // Win32k lockdown might not work on earlier versions // Bug 1719212, 1769992 if (us.mUseWin32kLockdown && IsWin10FallCreatorsUpdateOrLater()) { result = AddWin32kLockdownConfig(config); SANDBOX_ENSURE_SUCCESS(result, "Failed to add the win32k lockdown config"); } if (us.mUseCig) { bool alwaysProxyBinDirLoading = mozilla::HasPackageIdentity(); result = AddCigToConfig(config, alwaysProxyBinDirLoading); SANDBOX_ENSURE_SUCCESS(result, "Failed to initialize signed policy rules."); } // Process fails to start in LPAC with ASan build #if !defined(MOZ_ASAN) if (!us.mPackagePrefix.IsEmpty()) { MOZ_ASSERT(us.mInitialIntegrityLevel == sandbox::INTEGRITY_LEVEL_LAST, "Initial integrity level cannot be specified if using an LPAC."); result = AddAndConfigureAppContainerProfile(config, us.mPackagePrefix, us.mWellKnownCapabilites, us.mNamedCapabilites); SANDBOX_ENSURE_SUCCESS(result, "Failed to configure AppContainer profile."); } #endif // Add the policy for the client side of a pipe. It is just a file // in the \pipe\ namespace. We restrict it to pipes that start with // "chrome." so the sandboxed process cannot connect to system services. result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\chrome.*"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); // Add the policy for the client side of the crash server pipe. result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\gecko-crash-server-pipe.*"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); return true; } bool SandboxBroker::SetSecurityLevelForUtilityProcess( mozilla::ipc::SandboxingKind aSandbox) { if (!mPolicy) { return false; } auto* config = mPolicy->GetConfig(); switch (aSandbox) { case mozilla::ipc::SandboxingKind::GENERIC_UTILITY: return BuildUtilitySandbox(config, GenericUtilitySandboxProps()); case mozilla::ipc::SandboxingKind::UTILITY_AUDIO_DECODING_WMF: return BuildUtilitySandbox(config, UtilityAudioDecodingWmfSandboxProps()); #ifdef MOZ_WMF_MEDIA_ENGINE case mozilla::ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM: return BuildUtilitySandbox(config, UtilityMfMediaEngineCdmSandboxProps()); #endif case mozilla::ipc::SandboxingKind::WINDOWS_UTILS: return BuildUtilitySandbox(config, WindowsUtilitySandboxProps()); case mozilla::ipc::SandboxingKind::WINDOWS_FILE_DIALOG: // This process type is not sandboxed. (See commentary in // `ipc::IsUtilitySandboxEnabled()`.) MOZ_ASSERT_UNREACHABLE("No sandboxing for this process type"); return false; default: MOZ_ASSERT_UNREACHABLE("Unknown sandboxing value"); return false; } } bool SandboxBroker::SetSecurityLevelForGMPlugin( GMPSandboxKind aGMPSandboxKind) { if (!mPolicy) { return false; } auto* config = mPolicy->GetConfig(); auto result = config->SetJobLevel(sandbox::JobLevel::kLockdown, 0 /* ui_exceptions */); SANDBOX_ENSURE_SUCCESS( result, "SetJobLevel should never fail with these arguments, what happened?"); // The Widevine CDM on Windows can only load at USER_RESTRICTED auto level = (aGMPSandboxKind == Widevine) ? sandbox::USER_RESTRICTED : sandbox::USER_LOCKDOWN; result = config->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS, level); SANDBOX_ENSURE_SUCCESS( result, "SetTokenLevel should never fail with these arguments, what happened?"); config->SetDesktop(sandbox::Desktop::kAlternateWinstation); result = config->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW); MOZ_ASSERT(sandbox::SBOX_ALL_OK == result, "SetIntegrityLevel should never fail with these arguments, what " "happened?"); config->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_UNTRUSTED); config->SetLockdownDefaultDacl(); config->AddRestrictingRandomSid(); sandbox::MitigationFlags mitigations = sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_EXTENSION_POINT_DISABLE | sandbox::MITIGATION_NONSYSTEM_FONT_DISABLE | sandbox::MITIGATION_IMAGE_LOAD_NO_REMOTE | sandbox::MITIGATION_IMAGE_LOAD_NO_LOW_LABEL | sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_DEP; if (StaticPrefs::security_sandbox_gmp_shadow_stack_enabled()) { mitigations |= sandbox::MITIGATION_CET_COMPAT_MODE; } result = config->SetProcessMitigations(mitigations); SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetProcessMitigations."); // win32k is currently not disabled for clearkey due to WMF decoding or // widevine due to intermittent test failures, where the GMP process fails // very early. See bug 1449348. // The sandbox doesn't provide Output Protection Manager API brokering // anymore, so we can't use this for the Fake plugin that is used to partially // test it. This brokering was only used in the tests anyway. if (StaticPrefs::security_sandbox_gmp_win32k_disable() && aGMPSandboxKind != Widevine && aGMPSandboxKind != Clearkey && aGMPSandboxKind != Fake) { result = AddWin32kLockdownConfig(config); SANDBOX_ENSURE_SUCCESS(result, "Failed to add the win32k lockdown policy"); } mitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | sandbox::MITIGATION_DLL_SEARCH_ORDER; if (StaticPrefs::security_sandbox_gmp_acg_enabled()) { auto acgMitigation = sandbox::MITIGATION_DYNAMIC_CODE_DISABLE; if (aGMPSandboxKind == Widevine) { // We can't guarantee that widevine won't use dynamic code. acgMitigation = 0; } else if (aGMPSandboxKind == Clearkey) { // Clearkey uses system decoding libraries. acgMitigation = DynamicCodeFlagForSystemMediaLibraries(); } mitigations |= acgMitigation; } result = config->SetDelayedProcessMitigations(mitigations); SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetDelayedProcessMitigations."); // Add the policy for the client side of a pipe. It is just a file // in the \pipe\ namespace. We restrict it to pipes that start with // "chrome." so the sandboxed process cannot connect to system services. result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\chrome.*"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); // Add the policy for the client side of the crash server pipe. result = config->AllowFileAccess(sandbox::FileSemantics::kAllowAny, L"\\??\\pipe\\gecko-crash-server-pipe.*"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); // The following rules were added because, during analysis of an EME // plugin during development, these registry keys were accessed when // loading the plugin. Commenting out these policy exceptions caused // plugin loading to fail, so they are necessary for proper functioning // of at least one EME plugin. result = config->AllowRegistryRead(L"HKEY_CURRENT_USER"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); result = config->AllowRegistryRead(L"HKEY_CURRENT_USER\\Control Panel\\Desktop"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); result = config->AllowRegistryRead( L"HKEY_CURRENT_USER\\Control Panel\\Desktop\\LanguageConfiguration"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); result = config->AllowRegistryRead( L"HKEY_LOCAL_MACHINE" L"\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\SideBySide"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); // The following rules were added because, during analysis of an EME // plugin during development, these registry keys were accessed when // loading the plugin. Commenting out these policy exceptions did not // cause anything to break during initial testing, but might cause // unforeseen issues down the road. result = config->AllowRegistryRead( L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\MUI\\Settings"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); result = config->AllowRegistryRead( L"HKEY_CURRENT_USER" L"\\Software\\Policies\\Microsoft\\Control Panel\\Desktop"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); result = config->AllowRegistryRead( L"HKEY_CURRENT_USER\\Control Panel\\Desktop\\PreferredUILanguages"); SANDBOX_ENSURE_SUCCESS( result, "With these static arguments AddRule should never fail, what happened?"); result = config->AllowRegistryRead( L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion" L"\\SideBySide\\PreferExternalManifest"); SANDBOX_ENSURE_SUCCESS( result, "with these static arguments addrule should never fail, what happened?"); return true; } #undef SANDBOX_ENSURE_SUCCESS bool SandboxBroker::AllowReadFile(wchar_t const* file) { if (!mPolicy) { return false; } auto result = mPolicy->GetConfig()->AllowFileAccess( sandbox::FileSemantics::kAllowReadonly, file); if (sandbox::SBOX_ALL_OK != result) { LOG_E("Failed (ResultCode %d) to add read access to: %S", result, file); return false; } return true; } void SandboxBroker::AddHandleToShare(HANDLE aHandle) { mPolicy->AddHandleToShare(aHandle); } bool SandboxBroker::IsWin32kLockedDown() { return mPolicy->GetConfig()->GetProcessMitigations() & sandbox::MITIGATION_WIN32K_DISABLE; } void SandboxBroker::ApplyLoggingConfig() { MOZ_ASSERT(mPolicy); auto* config = mPolicy->GetConfig(); // Add dummy rules, so that we can log in the interception code. // We already have a file interception set up for the client side of pipes. // Also, passing just "dummy" for file system policy causes win_utils.cc // IsReparsePoint() to loop. Unused << config->AllowNamedPipes(L"dummy"); Unused << config->AllowRegistryRead(L"HKEY_CURRENT_USER\\dummy"); } SandboxBroker::~SandboxBroker() = default; } // namespace mozilla