From f24abd4ac3e9f369b7028d858b745bf08a97d6d9 Mon Sep 17 00:00:00 2001 From: Bob Owen Date: Mon, 22 May 2017 14:29:06 +0100 Subject: [PATCH] Bug 1339105 Part 1: Implement Windows Level 3 content process sandbox policy. r=jimm MozReview-Commit-ID: L8wcVhdLvFe --- ipc/glue/GeckoChildProcessHost.cpp | 108 +---------------- js/xpconnect/src/XPCShellImpl.cpp | 1 + .../win/src/sandboxbroker/sandboxBroker.cpp | 114 +++++++++++++++++- .../win/src/sandboxbroker/sandboxBroker.h | 6 + toolkit/xre/nsAppRunner.cpp | 4 + 5 files changed, 124 insertions(+), 109 deletions(-) diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index 4b00abe6807c..25bdc5394ba2 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -23,7 +23,7 @@ #include "prenv.h" #include "nsXPCOMPrivate.h" -#if defined(MOZ_CONTENT_SANDBOX) +#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_MACOSX) #include "nsAppDirectoryServiceDefs.h" #endif @@ -48,7 +48,6 @@ #if defined(MOZ_SANDBOX) #include "mozilla/Preferences.h" #include "mozilla/sandboxing/sandboxLogging.h" -#include "nsDirectoryServiceUtils.h" #endif #endif @@ -112,34 +111,6 @@ GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType, #endif { MOZ_COUNT_CTOR(GeckoChildProcessHost); - -#if defined(OS_WIN) && defined(MOZ_CONTENT_SANDBOX) - // Add $PROFILE/chrome to the white list because it may located on network - // drive. - if (mProcessType == GeckoProcessType_Content) { - nsCOMPtr directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); - NS_ASSERTION(directoryService, "Expected XPCOM to be available"); - if (directoryService) { - // Full path to the profile dir - nsCOMPtr profileDir; - nsresult rv = directoryService->Get(NS_APP_USER_PROFILE_50_DIR, - NS_GET_IID(nsIFile), - getter_AddRefs(profileDir)); - if (NS_SUCCEEDED(rv)) { - profileDir->Append(NS_LITERAL_STRING("chrome")); - profileDir->Append(NS_LITERAL_STRING("*")); - nsAutoCString path; - MOZ_ALWAYS_SUCCEEDS(profileDir->GetNativePath(path)); - std::wstring wpath = UTF8ToWide(path.get()); - // If the patch starts with "\\\\", it is a UNC path. - if (wpath.find(L"\\\\") == 0) { - wpath.insert(1, L"??\\UNC"); - } - mAllowedFilesRead.push_back(wpath); - } - } - } -#endif } GeckoChildProcessHost::~GeckoChildProcessHost() @@ -323,78 +294,6 @@ GeckoChildProcessHost::GetUniqueID() return sNextUniqueID++; } -#if defined(XP_WIN) && defined(MOZ_SANDBOX) - -// This is pretty much a duplicate of the function in PluginProcessParent. -// Simply copying for now due to uplift. I will address this duplication and for -// example the similar code in the Constructor in bug 1339105. -static void -AddSandboxAllowedFile(std::vector& aAllowedFiles, - nsIProperties* aDirSvc, const char* aDirKey, - const nsAString& aSuffix = EmptyString()) -{ - nsCOMPtr ruleDir; - nsresult rv = - aDirSvc->Get(aDirKey, NS_GET_IID(nsIFile), getter_AddRefs(ruleDir)); - if (NS_WARN_IF(NS_FAILED(rv))) { - return; - } - - nsAutoString rulePath; - rv = ruleDir->GetPath(rulePath); - if (NS_WARN_IF(NS_FAILED(rv))) { - return; - } - - // Convert network share path to format for sandbox policy. - if (Substring(rulePath, 0, 2).Equals(L"\\\\")) { - rulePath.InsertLiteral(u"??\\UNC", 1); - } - - if (!aSuffix.IsEmpty()) { - rulePath.Append(aSuffix); - } - - aAllowedFiles.push_back(std::wstring(rulePath.get())); - return; -} - -static void -AddContentSandboxAllowedFiles(int32_t aSandboxLevel, - std::vector& aAllowedFilesRead, - std::vector& aAllowedFilesReadWrite) -{ - if (aSandboxLevel < 1) { - return; - } - - nsresult rv; - nsCOMPtr dirSvc = - do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) { - return; - } - - // Add rule to allow read / write access to content temp dir. If for some - // reason the addition of the content temp failed, this will give write access - // to the normal TEMP dir. However such failures should be pretty rare and - // without this printing will not currently work. - AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, - NS_APP_CONTENT_PROCESS_TEMP_DIR, - NS_LITERAL_STRING("\\*")); - - if (aSandboxLevel < 2) { - return; - } - - // Add rule to allow read access to installation directory. At less than - // level 2 we already add a global read rule. - AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_GRE_DIR, - NS_LITERAL_STRING("\\*")); -} - -#endif - void GeckoChildProcessHost::PrepareLaunch() { @@ -415,11 +314,6 @@ GeckoChildProcessHost::PrepareLaunch() mSandboxLevel = Preferences::GetInt("security.sandbox.content.level"); mEnableSandboxLogging = Preferences::GetBool("security.sandbox.logging.enabled"); - - // This calls the directory service, which can also cause issues if called - // off main thread. - AddContentSandboxAllowedFiles(mSandboxLevel, mAllowedFilesRead, - mAllowedFilesReadWrite); } #endif diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp index 8cda123fd1a8..76d03b961d5e 100644 --- a/js/xpconnect/src/XPCShellImpl.cpp +++ b/js/xpconnect/src/XPCShellImpl.cpp @@ -1462,6 +1462,7 @@ XRE_XPCShellMain(int argc, char** argv, char** envp, // Required for sandboxed child processes. if (aShellData->sandboxBrokerServices) { SandboxBroker::Initialize(aShellData->sandboxBrokerServices); + SandboxBroker::CacheRulesDirectories(); } else { NS_WARNING("Failed to initialize broker services, sandboxed " "processes will fail to start."); diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp index ebd970bb2799..d35ffd309f3d 100644 --- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp +++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp @@ -8,8 +8,17 @@ #include "base/win/windows_version.h" #include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" #include "mozilla/Logging.h" #include "mozilla/NSPRLogModulesParser.h" +#include "mozilla/UniquePtr.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsCOMPtr.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" #include "sandbox/win/src/sandbox.h" #include "sandbox/win/src/security_level.h" @@ -18,6 +27,11 @@ namespace mozilla sandbox::BrokerServices *SandboxBroker::sBrokerService = nullptr; +// Cached special directories used for adding policy rules. +static UniquePtr sBinDir; +static UniquePtr sProfileDir; +static UniquePtr sContentTempDir; + static LazyLogModule sSandboxBrokerLog("SandboxBroker"); #define LOG_E(...) MOZ_LOG(sSandboxBrokerLog, LogLevel::Error, (__VA_ARGS__)) @@ -30,6 +44,50 @@ SandboxBroker::Initialize(sandbox::BrokerServices* aBrokerServices) sBrokerService = aBrokerServices; } +static void +CacheDirAndAutoClear(nsIProperties* aDirSvc, const char* aDirKey, + UniquePtr* cacheVar) +{ + 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; + } + + *cacheVar = MakeUnique(); + ClearOnShutdown(cacheVar); + MOZ_ALWAYS_SUCCEEDS(dirToCache->GetPath(**cacheVar)); + + // Convert network share path to format for sandbox policy. + if (Substring(**cacheVar, 0, 2).Equals(L"\\\\")) { + (*cacheVar)->InsertLiteral(u"??\\UNC", 1); + } +} + +/* static */ +void +SandboxBroker::CacheRulesDirectories() +{ + MOZ_ASSERT(NS_IsMainThread()); + + 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; + } + + CacheDirAndAutoClear(dirSvc, NS_GRE_DIR, &sBinDir); + CacheDirAndAutoClear(dirSvc, NS_APP_USER_PROFILE_50_DIR, &sProfileDir); + CacheDirAndAutoClear(dirSvc, NS_APP_CONTENT_PROCESS_TEMP_DIR, &sContentTempDir); +} + SandboxBroker::SandboxBroker() { if (sBrokerService) { @@ -133,7 +191,35 @@ SandboxBroker::LaunchApp(const wchar_t *aPath, return true; } +static void +AddCachedDirRule(sandbox::TargetPolicy* aPolicy, + sandbox::TargetPolicy::Semantics aAccess, + const UniquePtr& aBaseDir, + const nsAString& aRelativePath) +{ + 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", + aRelativePath, aAccess); + return; + } + + nsAutoString rulePath(*aBaseDir); + rulePath.Append(aRelativePath); + + sandbox::ResultCode result = + aPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, 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); + } +} + #if defined(MOZ_CONTENT_SANDBOX) + void SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel, base::ChildPrivileges aPrivs) @@ -159,7 +245,12 @@ SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel, accessTokenLevel = sandbox::USER_LIMITED; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; - } else if (aSandboxLevel >= 2) { + } else if (aSandboxLevel >= 3) { + jobLevel = sandbox::JOB_RESTRICTED; + accessTokenLevel = sandbox::USER_LIMITED; + initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + } else if (aSandboxLevel == 2) { jobLevel = sandbox::JOB_INTERACTIVE; accessTokenLevel = sandbox::USER_INTERACTIVE; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; @@ -204,7 +295,7 @@ SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel, MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "SetDelayedIntegrityLevel should never fail, what happened?"); - if (aSandboxLevel > 2) { + if (aSandboxLevel > 3) { result = mPolicy->SetAlternateDesktop(true); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "Failed to create alternate desktop for sandbox."); @@ -229,6 +320,13 @@ SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel, MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "Invalid flags for SetDelayedProcessMitigations."); + // Add rule to allow read / write access to content temp dir. If for some + // reason the addition of the content temp failed, this will give write access + // to the normal TEMP dir. However such failures should be pretty rare and + // without this printing will not currently work. + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_ANY, + sContentTempDir, NS_LITERAL_STRING("\\*")); + // 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 || @@ -238,6 +336,18 @@ SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel, L"*"); MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, "With these static arguments AddRule should never fail, what happened?"); + } else { + // Add rule to allow read access to installation directory. + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_READONLY, + sBinDir, NS_LITERAL_STRING("\\*")); + + // Add rule to allow read access chrome directory within profile. + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_READONLY, + sProfileDir, NS_LITERAL_STRING("\\chrome\\*")); + + // Add rule to allow read access extensions directory within profile. + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_READONLY, + sProfileDir, NS_LITERAL_STRING("\\extensions\\*")); } // Add the policy for the client side of a pipe. It is just a file diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.h b/security/sandbox/win/src/sandboxbroker/sandboxBroker.h index c95e788a237d..9f5f62bc0a5c 100644 --- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.h +++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.h @@ -26,6 +26,12 @@ public: static void Initialize(sandbox::BrokerServices* aBrokerServices); + /** + * Cache directory paths for use in policy rules. Must be called on main + * thread. + */ + static void CacheRulesDirectories(); + bool LaunchApp(const wchar_t *aPath, const wchar_t *aArguments, const bool aEnableLogging, diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 667c2eb1b365..e9a389ad7bf6 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -4441,6 +4441,10 @@ XREMain::XRE_mainRun() // We intentionally leak the string here since it is required by PR_SetEnv. PR_SetEnv(saved.release()); } + + // Call SandboxBroker to cache directories needed for policy rules, this must + // be called after mDirProvider.DoStartup as it needs the profile dir. + SandboxBroker::CacheRulesDirectories(); #endif SaveStateForAppInitiatedRestart();