author | Gabor Krizsanits <gkrizsanits@mozilla.com> |
Tue, 24 Mar 2015 15:29:16 +0100 | |
changeset 235274 | 90d9af9b861a816df7da099934525c8ead0ae270 |
parent 235273 | 80be62413c65e036ca6c2a1e30c993edfb88fff0 |
child 235275 | 7386c130fb784279ee3e4805caa08a97d7e7058e |
push id | 57390 |
push user | gkrizsanits@mozilla.com |
push date | Tue, 24 Mar 2015 14:29:53 +0000 |
treeherder | mozilla-inbound@90d9af9b861a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mrbkap |
bugs | 1126014 |
milestone | 39.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/caps/DomainPolicy.cpp +++ b/caps/DomainPolicy.cpp @@ -1,26 +1,59 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set ts=4 et sw=4 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/. */ #include "DomainPolicy.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/unused.h" +#include "nsIMessageManager.h" #include "nsScriptSecurityManager.h" namespace mozilla { +using namespace ipc; +using namespace dom; + NS_IMPL_ISUPPORTS(DomainPolicy, nsIDomainPolicy) -DomainPolicy::DomainPolicy() : mBlacklist(new DomainSet()) - , mSuperBlacklist(new DomainSet()) - , mWhitelist(new DomainSet()) - , mSuperWhitelist(new DomainSet()) -{} +static nsresult +BroadcastDomainSetChange(DomainSetType aSetType, DomainSetChangeType aChangeType, + nsIURI* aDomain = nullptr) +{ + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default, + "DomainPolicy should only be exposed to the chrome process."); + + nsTArray<ContentParent*> parents; + ContentParent::GetAll(parents); + if (!parents.Length()) { + return NS_OK; + } + + OptionalURIParams uri; + SerializeURI(aDomain, uri); + + for (uint32_t i = 0; i < parents.Length(); i++) { + unused << parents[i]->SendDomainSetChanged(aSetType, aChangeType, uri); + } + return NS_OK; +} + +DomainPolicy::DomainPolicy() : mBlacklist(new DomainSet(BLACKLIST)) + , mSuperBlacklist(new DomainSet(SUPER_BLACKLIST)) + , mWhitelist(new DomainSet(WHITELIST)) + , mSuperWhitelist(new DomainSet(SUPER_WHITELIST)) +{ + if (XRE_GetProcessType() == GeckoProcessType_Default) { + BroadcastDomainSetChange(NO_TYPE, ACTIVATE_POLICY); + } +} DomainPolicy::~DomainPolicy() { // The SSM holds a strong ref to the DomainPolicy until Deactivate() is // invoked, so we should never hit the destructor until that happens. MOZ_ASSERT(!mBlacklist && !mSuperBlacklist && !mWhitelist && !mSuperWhitelist); } @@ -70,20 +103,57 @@ DomainPolicy::Deactivate() // Null them out. mBlacklist = nullptr; mSuperBlacklist = nullptr; mWhitelist = nullptr; mSuperWhitelist = nullptr; // Inform the SSM. - nsScriptSecurityManager::GetScriptSecurityManager()->DeactivateDomainPolicy(); + nsScriptSecurityManager* ssm = nsScriptSecurityManager::GetScriptSecurityManager(); + if (ssm) { + ssm->DeactivateDomainPolicy(); + } + if (XRE_GetProcessType() == GeckoProcessType_Default) { + BroadcastDomainSetChange(NO_TYPE, DEACTIVATE_POLICY); + } return NS_OK; } +void +DomainPolicy::CloneDomainPolicy(DomainPolicyClone* aClone) +{ + aClone->active() = true; + static_cast<DomainSet*>(mBlacklist.get())->CloneSet(&aClone->blacklist()); + static_cast<DomainSet*>(mSuperBlacklist.get())->CloneSet(&aClone->superBlacklist()); + static_cast<DomainSet*>(mWhitelist.get())->CloneSet(&aClone->whitelist()); + static_cast<DomainSet*>(mSuperWhitelist.get())->CloneSet(&aClone->superWhitelist()); +} + +static +void +CopyURIs(const InfallibleTArray<URIParams>& aDomains, nsIDomainSet* aSet) +{ + for (uint32_t i = 0; i < aDomains.Length(); i++) { + nsCOMPtr<nsIURI> uri = DeserializeURI(aDomains[i]); + aSet->Add(uri); + } +} + +void +DomainPolicy::ApplyClone(DomainPolicyClone* aClone) +{ + nsCOMPtr<nsIDomainSet> list; + + CopyURIs(aClone->blacklist(), mBlacklist); + CopyURIs(aClone->whitelist(), mWhitelist); + CopyURIs(aClone->superBlacklist(), mSuperBlacklist); + CopyURIs(aClone->superWhitelist(), mSuperWhitelist); +} + static already_AddRefed<nsIURI> GetCanonicalClone(nsIURI* aURI) { nsCOMPtr<nsIURI> clone; nsresult rv = aURI->Clone(getter_AddRefs(clone)); NS_ENSURE_SUCCESS(rv, nullptr); rv = clone->SetUserPass(EmptyCString()); NS_ENSURE_SUCCESS(rv, nullptr); @@ -95,32 +165,41 @@ GetCanonicalClone(nsIURI* aURI) NS_IMPL_ISUPPORTS(DomainSet, nsIDomainSet) NS_IMETHODIMP DomainSet::Add(nsIURI* aDomain) { nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain); NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); mHashTable.PutEntry(clone); + if (XRE_GetProcessType() == GeckoProcessType_Default) + return BroadcastDomainSetChange(mType, ADD_DOMAIN, aDomain); + return NS_OK; } NS_IMETHODIMP DomainSet::Remove(nsIURI* aDomain) { nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain); NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); mHashTable.RemoveEntry(clone); + if (XRE_GetProcessType() == GeckoProcessType_Default) + return BroadcastDomainSetChange(mType, REMOVE_DOMAIN, aDomain); + return NS_OK; } NS_IMETHODIMP DomainSet::Clear() { mHashTable.Clear(); + if (XRE_GetProcessType() == GeckoProcessType_Default) + return BroadcastDomainSetChange(mType, CLEAR_DOMAINS); + return NS_OK; } NS_IMETHODIMP DomainSet::Contains(nsIURI* aDomain, bool* aContains) { *aContains = false; nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain); @@ -155,9 +234,36 @@ DomainSet::ContainsSuperDomain(nsIURI* a NS_ENSURE_SUCCESS(rv, rv); } // No match. return NS_OK; } +NS_IMETHODIMP +DomainSet::GetType(uint32_t* aType) +{ + *aType = mType; + return NS_OK; +} + +static +PLDHashOperator +DomainEnumerator(nsURIHashKey* aEntry, void* aUserArg) +{ + InfallibleTArray<URIParams>* uris = static_cast<InfallibleTArray<URIParams>*>(aUserArg); + nsIURI* key = aEntry->GetKey(); + + URIParams uri; + SerializeURI(key, uri); + + uris->AppendElement(uri); + return PL_DHASH_NEXT; +} + +void +DomainSet::CloneSet(InfallibleTArray<URIParams>* aDomains) +{ + mHashTable.EnumerateEntries(DomainEnumerator, aDomains); +} + } /* namespace mozilla */
--- a/caps/DomainPolicy.h +++ b/caps/DomainPolicy.h @@ -8,16 +8,40 @@ #define DomainPolicy_h__ #include "nsIDomainPolicy.h" #include "nsTHashtable.h" #include "nsURIHashKey.h" namespace mozilla { +namespace dom { +class nsIContentParent; +}; + +namespace ipc { +class URIParams; +}; + +enum DomainSetChangeType{ + ACTIVATE_POLICY, + DEACTIVATE_POLICY, + ADD_DOMAIN, + REMOVE_DOMAIN, + CLEAR_DOMAINS +}; + +enum DomainSetType{ + NO_TYPE, + BLACKLIST, + SUPER_BLACKLIST, + WHITELIST, + SUPER_WHITELIST +}; + class DomainPolicy : public nsIDomainPolicy { public: NS_DECL_ISUPPORTS NS_DECL_NSIDOMAINPOLICY DomainPolicy(); private: @@ -30,18 +54,23 @@ private: }; class DomainSet : public nsIDomainSet { public: NS_DECL_ISUPPORTS NS_DECL_NSIDOMAINSET - DomainSet() {} + explicit DomainSet(DomainSetType aType) + : mType(aType) + {} + + void CloneSet(InfallibleTArray<mozilla::ipc::URIParams>* aDomains); protected: virtual ~DomainSet() {} nsTHashtable<nsURIHashKey> mHashTable; + DomainSetType mType; }; } /* namespace mozilla */ #endif /* DomainPolicy_h__ */
--- a/caps/nsIDomainPolicy.idl +++ b/caps/nsIDomainPolicy.idl @@ -3,43 +3,61 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsISupports.idl" interface nsIURI; interface nsIDomainSet; +%{ C++ +namespace mozilla { +namespace dom { +class DomainPolicyClone; +} +} +%} + +[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone); + /* * When a domain policy is instantiated by invoking activateDomainPolicy() on * nsIScriptSecurityManager, these domain sets are consulted when each new * global is created (they have no effect on already-created globals). * If javascript is globally enabled with |javascript.enabled|, the blacklists * are consulted. If globally disabled, the whitelists are consulted. Lookups * on blacklist and whitelist happen with contains(), and lookups on * superBlacklist and superWhitelist happen with containsSuperDomain(). * * When deactivate() is invoked, the domain sets are emptied, and the * nsIDomainPolicy ceases to have any effect on the system. */ -[scriptable, builtinclass, uuid(27b10f54-f34b-42b7-8594-4348d3ad7953)] +[scriptable, builtinclass, uuid(82b24a20-6701-4d40-a0f9-f5dc7321b555)] interface nsIDomainPolicy : nsISupports { readonly attribute nsIDomainSet blacklist; readonly attribute nsIDomainSet superBlacklist; readonly attribute nsIDomainSet whitelist; readonly attribute nsIDomainSet superWhitelist; void deactivate(); + + [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone); + [noscript, notxpcom] void applyClone(in DomainPolicyClonePtr aClone); }; -[scriptable, builtinclass, uuid(946a01ff-6525-4007-a2c2-447ebe1875d3)] +[scriptable, builtinclass, uuid(665c981b-0a0f-4229-ac06-a826e02d4f69)] interface nsIDomainSet : nsISupports { /* + * The type of the set. See: DomainSetType + */ + [noscript] readonly attribute uint32_t type; + + /* * Add a domain to the set. No-op if it already exists. */ void add(in nsIURI aDomain); /* * Remove a domain from the set. No-op if it doesn't exist. */ void remove(in nsIURI aDomain);
--- a/caps/nsIScriptSecurityManager.idl +++ b/caps/nsIScriptSecurityManager.idl @@ -9,22 +9,29 @@ interface nsIURI; interface nsIChannel; interface nsIClassInfo; interface nsIDocShell; interface nsIDomainPolicy; interface nsILoadContext; %{ C++ #include "jspubtd.h" + +namespace mozilla { +namespace dom { +class DomainPolicyClone; +} +} %} [ptr] native JSContextPtr(JSContext); [ptr] native JSObjectPtr(JSObject); +[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone); -[scriptable, uuid(f649959d-dae3-4027-83fd-5b7f8c8a8815)] +[scriptable, uuid(ba602ca6-dc7a-457e-a57a-ee5b343fd863)] interface nsIScriptSecurityManager : nsISupports { /** * For each of these hooks returning NS_OK means 'let the action continue'. * Returning an error code means 'veto the action'. XPConnect will return * false to the js engine if the action is vetoed. The implementor of this * interface is responsible for setting a JS exception into the JSContext * if that is appropriate. @@ -236,16 +243,32 @@ interface nsIScriptSecurityManager : nsI * nsIDomainPolicy returned from activateDomainPolicy(). At this point, * domainPolicyActive becomes false again, and a new consumer may acquire * control of the system by invoking activateDomainPolicy(). */ nsIDomainPolicy activateDomainPolicy(); readonly attribute boolean domainPolicyActive; /** + * Only the parent process can directly access domain policies, child + * processes only have a read-only mirror to the one in the parent. + * For child processes the mirror is updated via messages + * and ContentChild will hold the DomainPolicy by calling + * ActivateDomainPolicyInternal directly. New consumer to this + * function should not be addded. + */ + [noscript] nsIDomainPolicy activateDomainPolicyInternal(); + + /** + * This function is for internal use only. Every time a child process is spawned, we + * must clone any active domain policies in the parent to the new child. + */ + [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone); + + /** * Query mechanism for the above policy. * * If domainPolicyEnabled is false, this simply returns the current value * of javascript.enabled. Otherwise, it returns the same value, but taking * the various blacklist/whitelist exceptions into account. */ bool policyAllowsScript(in nsIURI aDomain); };
--- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -1305,19 +1305,24 @@ nsresult nsScriptSecurityManager::Init() return NS_OK; } static StaticRefPtr<nsScriptSecurityManager> gScriptSecMan; nsScriptSecurityManager::~nsScriptSecurityManager(void) { Preferences::RemoveObservers(this, kObservedPrefs); - if (mDomainPolicy) + if (mDomainPolicy) { mDomainPolicy->Deactivate(); - MOZ_ASSERT(!mDomainPolicy); + } + // ContentChild might hold a reference to the domain policy, + // and it might release it only after the security manager is + // gone. But we can still assert this for the main process. + MOZ_ASSERT_IF(XRE_GetProcessType() == GeckoProcessType_Default, + !mDomainPolicy); } void nsScriptSecurityManager::Shutdown() { if (sRuntime) { JS_SetSecurityCallbacks(sRuntime, nullptr); JS_SetTrustedPrincipals(sRuntime, nullptr); @@ -1523,16 +1528,26 @@ nsScriptSecurityManager::GetDomainPolicy { *aRv = !!mDomainPolicy; return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::ActivateDomainPolicy(nsIDomainPolicy** aRv) { + if (XRE_GetProcessType() != GeckoProcessType_Default) { + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + + return ActivateDomainPolicyInternal(aRv); +} + +NS_IMETHODIMP +nsScriptSecurityManager::ActivateDomainPolicyInternal(nsIDomainPolicy** aRv) +{ // We only allow one domain policy at a time. The holder of the previous // policy must explicitly deactivate it first. if (mDomainPolicy) { return NS_ERROR_SERVICE_NOT_AVAILABLE; } mDomainPolicy = new DomainPolicy(); nsCOMPtr<nsIDomainPolicy> ptr = mDomainPolicy; @@ -1543,16 +1558,27 @@ nsScriptSecurityManager::ActivateDomainP // Intentionally non-scriptable. Script must have a reference to the // nsIDomainPolicy to deactivate it. void nsScriptSecurityManager::DeactivateDomainPolicy() { mDomainPolicy = nullptr; } +void +nsScriptSecurityManager::CloneDomainPolicy(DomainPolicyClone* aClone) +{ + MOZ_ASSERT(aClone); + if (mDomainPolicy) { + mDomainPolicy->CloneDomainPolicy(aClone); + } else { + aClone->active() = false; + } +} + NS_IMETHODIMP nsScriptSecurityManager::PolicyAllowsScript(nsIURI* aURI, bool *aRv) { nsresult rv; // Compute our rule. If we don't have any domain policy set up that might // provide exceptions to this rule, we're done. *aRv = mIsJavaScriptEnabled;
--- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -23,16 +23,17 @@ #ifdef ACCESSIBILITY #include "mozilla/a11y/DocAccessibleChild.h" #endif #include "mozilla/Preferences.h" #include "mozilla/ProcessHangMonitorIPC.h" #include "mozilla/docshell/OfflineCacheUpdateChild.h" #include "mozilla/dom/ContentBridgeChild.h" #include "mozilla/dom/ContentBridgeParent.h" +#include "mozilla/dom/ContentParent.h" #include "mozilla/dom/DOMStorageIPC.h" #include "mozilla/dom/ExternalHelperAppChild.h" #include "mozilla/dom/PCrashReporterChild.h" #include "mozilla/dom/ProcessGlobal.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/asmjscache/AsmJSCache.h" #include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h" #include "mozilla/dom/nsIContentChild.h" @@ -105,18 +106,19 @@ #include "nsChromeRegistryContent.h" #include "nsFrameMessageManager.h" #include "nsIGeolocationProvider.h" #include "mozilla/dom/PMemoryReportRequestChild.h" #include "mozilla/dom/PCycleCollectWithLogsChild.h" +#include "nsIScriptSecurityManager.h" + #ifdef MOZ_PERMISSIONS -#include "nsIScriptSecurityManager.h" #include "nsPermission.h" #include "nsPermissionManager.h" #endif #include "PermissionMessageUtils.h" #if defined(MOZ_WIDGET_ANDROID) #include "APKOpen.h" @@ -163,16 +165,17 @@ #include "ProcessUtils.h" #include "StructuredCloneUtils.h" #include "URIUtils.h" #include "nsContentUtils.h" #include "nsIPrincipal.h" #include "nsDeviceStorage.h" #include "AudioChannelService.h" +#include "DomainPolicy.h" #include "mozilla/dom/DataStoreService.h" #include "mozilla/dom/telephony/PTelephonyChild.h" #include "mozilla/dom/time/DateCacheCleaner.h" #include "mozilla/dom/voicemail/VoicemailIPCService.h" #include "mozilla/net/NeckoMessageUtils.h" #include "mozilla/RemoteSpellCheckEngineChild.h" using namespace mozilla; @@ -781,19 +784,31 @@ ContentChild::InitXPCOM() } mConsoleListener = new ConsoleListener(this); if (NS_FAILED(svc->RegisterListener(mConsoleListener))) NS_WARNING("Couldn't register console listener for child process"); bool isOffline; ClipboardCapabilities clipboardCaps; - SendGetXPCOMProcessAttributes(&isOffline, &mAvailableDictionaries, &clipboardCaps); + DomainPolicyClone domainPolicy; + + SendGetXPCOMProcessAttributes(&isOffline, &mAvailableDictionaries, &clipboardCaps, &domainPolicy); RecvSetOffline(isOffline); + if (domainPolicy.active()) { + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + MOZ_ASSERT(ssm); + ssm->ActivateDomainPolicyInternal(getter_AddRefs(mPolicy)); + if (!mPolicy) { + MOZ_CRASH("Failed to activate domain policy."); + } + mPolicy->ApplyClone(&domainPolicy); + } + nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1")); if (nsCOMPtr<nsIClipboardProxy> clipboardProxy = do_QueryInterface(clipboard)) { clipboardProxy->SetCapabilities(clipboardCaps); } DebugOnly<FileUpdateDispatcher*> observer = FileUpdateDispatcher::GetSingleton(); NS_ASSERTION(observer, "FileUpdateDispatcher is null"); @@ -2591,18 +2606,92 @@ bool ContentChild::RecvAssociatePluginId(const uint32_t& aPluginId, const base::ProcessId& aProcessId) { plugins::PluginModuleContentParent::AssociatePluginId(aPluginId, aProcessId); return true; } bool +ContentChild::RecvDomainSetChanged(const uint32_t& aSetType, const uint32_t& aChangeType, + const OptionalURIParams& aDomain) +{ + if (aChangeType == ACTIVATE_POLICY) { + if (mPolicy) { + return true; + } + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + MOZ_ASSERT(ssm); + ssm->ActivateDomainPolicyInternal(getter_AddRefs(mPolicy)); + return !!mPolicy; + } else if (!mPolicy) { + MOZ_ASSERT_UNREACHABLE("If the domain policy is not active yet," + " the first message should be ACTIVATE_POLICY"); + return false; + } + + NS_ENSURE_TRUE(mPolicy, false); + + if (aChangeType == DEACTIVATE_POLICY) { + mPolicy->Deactivate(); + mPolicy = nullptr; + return true; + } + + nsCOMPtr<nsIDomainSet> set; + switch(aSetType) { + case BLACKLIST: + mPolicy->GetBlacklist(getter_AddRefs(set)); + break; + case SUPER_BLACKLIST: + mPolicy->GetSuperBlacklist(getter_AddRefs(set)); + break; + case WHITELIST: + mPolicy->GetWhitelist(getter_AddRefs(set)); + break; + case SUPER_WHITELIST: + mPolicy->GetSuperWhitelist(getter_AddRefs(set)); + break; + default: + NS_NOTREACHED("Unexpected setType"); + return false; + } + + MOZ_ASSERT(set); + + nsCOMPtr<nsIURI> uri = DeserializeURI(aDomain); + + switch(aChangeType) { + case ADD_DOMAIN: + NS_ENSURE_TRUE(uri, false); + set->Add(uri); + break; + case REMOVE_DOMAIN: + NS_ENSURE_TRUE(uri, false); + set->Remove(uri); + break; + case CLEAR_DOMAINS: + set->Clear(); + break; + default: + NS_NOTREACHED("Unexpected changeType"); + return false; + } + + return true; +} + +bool ContentChild::RecvShutdown() { + if (mPolicy) { + mPolicy->Deactivate(); + mPolicy = nullptr; + } + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); if (os) { os->NotifyObservers(this, "content-child-shutdown", nullptr); } GetIPCChannel()->SetAbortOnError(false); // Ignore errors here. If this fails, the parent will kill us after a
--- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -19,16 +19,17 @@ #include "nsWeakPtr.h" struct ChromePackage; class nsIDOMBlob; class nsIObserver; struct ResourceMapping; struct OverrideMapping; +class nsIDomainPolicy; namespace mozilla { class RemoteSpellcheckEngineChild; namespace ipc { class OptionalURIParams; class PFileDescriptorSetChild; class URIParams; @@ -379,16 +380,18 @@ public: const bool& aResult) override; virtual bool RecvStartProfiler(const uint32_t& aEntries, const double& aInterval, nsTArray<nsCString>&& aFeatures, nsTArray<nsCString>&& aThreadNameFilters) override; virtual bool RecvStopProfiler() override; virtual bool RecvGetProfile(nsCString* aProfile) override; + virtual bool RecvDomainSetChanged(const uint32_t& aSetType, const uint32_t& aChangeType, + const OptionalURIParams& aDomain) override; virtual bool RecvShutdown() override; #ifdef ANDROID gfxIntSize GetScreenSize() { return mScreenSize; } #endif // Get the directory for IndexedDB files. We query the parent for this and // cache the value @@ -476,16 +479,18 @@ private: bool mIsForApp; bool mIsForBrowser; bool mCanOverrideProcessName; bool mIsAlive; nsString mProcessName; static ContentChild* sSingleton; + nsCOMPtr<nsIDomainPolicy> mPolicy; + DISALLOW_EVIL_CONSTRUCTORS(ContentChild); }; uint64_t NextWindowID(); } // namespace dom } // namespace mozilla
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -2777,18 +2777,19 @@ ContentParent::RecvAddNewProcess(const u for (size_t i = 0; i < numNuwaPrefUpdates; i++) { mozilla::unused << content->SendPreferenceUpdate(sNuwaPrefUpdates->ElementAt(i)); } // Update offline settings. bool isOffline; InfallibleTArray<nsString> unusedDictionaries; ClipboardCapabilities clipboardCaps; + DomainPolicyClone domainPolicy; RecvGetXPCOMProcessAttributes(&isOffline, &unusedDictionaries, - &clipboardCaps); + &clipboardCaps, &domainPolicy); mozilla::unused << content->SendSetOffline(isOffline); MOZ_ASSERT(!clipboardCaps.supportsSelectionClipboard() && !clipboardCaps.supportsFindClipboard(), "Unexpected values"); PreallocatedProcessManager::PublishSpareProcess(content); return true; #else @@ -3114,17 +3115,18 @@ ContentParent::RecvGetProcessAttributes( *aIsForBrowser = mIsForBrowser; return true; } bool ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline, InfallibleTArray<nsString>* dictionaries, - ClipboardCapabilities* clipboardCaps) + ClipboardCapabilities* clipboardCaps, + DomainPolicyClone* domainPolicy) { nsCOMPtr<nsIIOService> io(do_GetIOService()); MOZ_ASSERT(io, "No IO service?"); DebugOnly<nsresult> rv = io->GetOffline(aIsOffline); MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?"); nsCOMPtr<nsISpellChecker> spellChecker(do_GetService(NS_SPELLCHECKER_CONTRACTID)); MOZ_ASSERT(spellChecker, "No spell checker?"); @@ -3135,16 +3137,21 @@ ContentParent::RecvGetXPCOMProcessAttrib MOZ_ASSERT(clipboard, "No clipboard?"); rv = clipboard->SupportsSelectionClipboard(&clipboardCaps->supportsSelectionClipboard()); MOZ_ASSERT(NS_SUCCEEDED(rv)); rv = clipboard->SupportsFindClipboard(&clipboardCaps->supportsFindClipboard()); MOZ_ASSERT(NS_SUCCEEDED(rv)); + // Let's copy the domain policy from the parent to the child (if it's active). + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ENSURE_TRUE(ssm, false); + ssm->CloneDomainPolicy(domainPolicy); + return true; } mozilla::jsipc::PJavaScriptParent * ContentParent::AllocPJavaScriptParent() { MOZ_ASSERT(!ManagedPJavaScriptParent().Length()); return nsIContentParent::AllocPJavaScriptParent();
--- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -506,17 +506,18 @@ private: AllocPProcessHangMonitorParent(Transport* aTransport, ProcessId aOtherProcess) override; virtual bool RecvGetProcessAttributes(ContentParentId* aCpId, bool* aIsForApp, bool* aIsForBrowser) override; virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline, InfallibleTArray<nsString>* dictionaries, - ClipboardCapabilities* clipboardCaps) + ClipboardCapabilities* clipboardCaps, + DomainPolicyClone* domainPolicy) override; virtual bool DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*) override; virtual bool DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*) override; virtual PBrowserParent* AllocPBrowserParent(const TabId& aTabId, const IPCTabContext& aContext, const uint32_t& aChromeFlags,
--- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -341,16 +341,25 @@ union FileDescOrError { }; union OptionalContentId { ContentParentId; void_t; }; +struct DomainPolicyClone +{ + bool active; + URIParams[] blacklist; + URIParams[] whitelist; + URIParams[] superBlacklist; + URIParams[] superWhitelist; +}; + prio(normal upto urgent) sync protocol PContent { parent spawns PPluginModule; parent opens PCompositor; parent opens PProcessHangMonitor; parent opens PSharedBufferManager; parent opens PImageBridge; @@ -547,16 +556,18 @@ child: async StartProfiler(uint32_t aEntries, double aInterval, nsCString[] aFeatures, nsCString[] aThreadNameFilters); async StopProfiler(); prio(high) sync GetProfile() returns (nsCString aProfile); NuwaFreeze(); + async DomainSetChanged(uint32_t aSetType, uint32_t aChangeType, OptionalURIParams aDomain); + /** * Notify the child to shutdown. The child will in turn call FinishShutdown * and let the parent close the channel. */ async Shutdown(); async LoadProcessScript(nsString url); @@ -581,17 +592,18 @@ parent: * !isForBrowser|, we're probably loading <xul:browser remote>. * * Keep the return values in sync with PBrowser()! */ sync GetProcessAttributes() returns (ContentParentId cpId, bool isForApp, bool isForBrowser); sync GetXPCOMProcessAttributes() returns (bool isOffline, nsString[] dictionaries, - ClipboardCapabilities clipboardCaps); + ClipboardCapabilities clipboardCaps, + DomainPolicyClone domainPolicy); sync CreateChildProcess(IPCTabContext context, ProcessPriority priority, TabId openerTabId) returns (ContentParentId cpId, bool isForApp, bool isForBrowser, TabId tabId); sync BridgeToChildProcess(ContentParentId cpId); /**
--- a/dom/ipc/moz.build +++ b/dom/ipc/moz.build @@ -149,12 +149,13 @@ if CONFIG['MOZ_TOOLKIT_SEARCH']: DEFINES['MOZ_TOOLKIT_SEARCH'] = True for var in ('MOZ_PERMISSIONS', 'MOZ_CHILD_PERMISSIONS'): if CONFIG[var]: DEFINES[var] = True JAR_MANIFESTS += ['jar.mn'] +BROWSER_CHROME_MANIFESTS += ['tests/browser.ini'] MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini'] MOCHITEST_MANIFESTS += ['tests/mochitest.ini'] CXXFLAGS += CONFIG['TK_CFLAGS']
new file mode 100644 --- /dev/null +++ b/dom/ipc/tests/browser.ini @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = + file_disableScript.html + file_domainPolicy_base.html + +[browser_domainPolicy.js]
new file mode 100644 --- /dev/null +++ b/dom/ipc/tests/browser_domainPolicy.js @@ -0,0 +1,240 @@ +var policy; // To make sure we never leave up an activated domain policy after a failed test, let's make this global. +function activateDomainPolicy() { + const ssm = Services.scriptSecurityManager; + policy = ssm.activateDomainPolicy(); +} + +function deactivateDomainPolicy() { + if (policy) { + policy.deactivate(); + policy = null; + } +} + +function* test_domainPolicy() { + + XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm"); + let deferred = Promise.defer(); + let currentTask = deferred.promise; + SpecialPowers.pushPrefEnv( + {set: [["dom.ipc.browser_frames.oop_by_default", false], + ["browser.pagethumbnails.capturing_disabled", false], + ["dom.mozBrowserFramesEnabled", false]]}, + () => { return deferred.resolve()}); + yield currentTask; + + // Create tab + let tab; + + // Init test + function initProcess() { + tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + + let initPromise = ContentTask.spawn(tab.linkedBrowser, null, function() { + Cu.import("resource://gre/modules/PromiseUtils.jsm"); + function loadBase() { + let deferred = PromiseUtils.defer(); + let listener = (event) => { + removeEventListener("DOMDocElementInserted", listener, true); + let listener2 = (event) => { + content.removeEventListener('load', listener2); + deferred.resolve(); + } + content.addEventListener('load', listener2); + }; + addEventListener("DOMDocElementInserted", listener, true); + return deferred.promise; + } + + return loadBase(); + }); + tab.linkedBrowser.loadURI("http://mochi.test:8888/browser/dom/ipc/tests/file_domainPolicy_base.html"); + return initPromise; + } + + // We use ContentTask for the tests, but we also want to pass some data and some helper functions too. + // To do that, we serialize an input object via JSON |ipcArgs| and some shared helper functions |initUtils| + // and eval them in the content process. + var ipcArgs = {}; + function initUtils(obj) { + obj.checkScriptEnabled = function(win, expectEnabled) { + win.wrappedJSObject.gFiredOnclick = false; + win.document.body.dispatchEvent(new win.Event('click')); + return { passed: win.wrappedJSObject.gFiredOnclick == expectEnabled, + msg: `Checking script-enabled for ${win.name} (${win.location})`}; + } + + obj.navigateFrame = function(ifr, src) { + let deferred = PromiseUtils.defer(); + function onload() { + ifr.removeEventListener('load', onload); + deferred.resolve(); + } + ifr.addEventListener('load', onload, false); + ifr.setAttribute('src', src); + return deferred.promise; + } + }; + + function runTest(test) { + return ContentTask.spawn(tab.linkedBrowser, + 'ipcArgs = ' + JSON.stringify(ipcArgs) + '; (' + initUtils.toSource() + ')(utils)', test); + } + + function checkAndCleanup(result) { + result = [].concat(result); + for (var i in result) + ok(result[i].passed, result[i].msg); + gBrowser.removeTab(tab); + deactivateDomainPolicy(); + ipcArgs = {}; + } + + function testDomain(domain) { + ipcArgs.domain = domain; + return (aUtils) => { + Cu.import("resource://gre/modules/PromiseUtils.jsm"); + var ipcArgs; + var utils = {}; + eval(aUtils); + + let path = '/browser/dom/ipc/tests/file_disableScript.html'; + let deferred = PromiseUtils.defer(); + var rootFrame = content.document.getElementById('root'); + utils.navigateFrame(rootFrame, ipcArgs.domain + path).then(() => { + deferred.resolve(utils.checkScriptEnabled(rootFrame.contentWindow, false)); + }); + return deferred.promise; + } + } + + info("Testing simple blacklist policy"); + + info("Creating child process first, activating domainPolicy after"); + currentTask = initProcess(); + yield currentTask; + activateDomainPolicy(); + var bl = policy.blacklist; + bl.add(Services.io.newURI('http://example.com', null, null)); + currentTask = runTest(testDomain("http://example.com")); + checkAndCleanup(yield currentTask); + + info("Activating domainPolicy first, creating child process after"); + activateDomainPolicy(); + var bl = policy.blacklist; + bl.add(Services.io.newURI('http://example.com', null, null)); + currentTask = initProcess(); + yield currentTask; + currentTask = runTest(testDomain("http://example.com")); + checkAndCleanup(yield currentTask); + + function testList(expectEnabled, list) { + ipcArgs.expectEnabled = expectEnabled; + ipcArgs.list = list; + return (aUtils) => { + Cu.import("resource://gre/modules/PromiseUtils.jsm"); + var ipcArgs; + var utils = {}; + eval(aUtils); + + var results = []; + var testListInternal = function(expectEnabled, list, idx) { + idx = idx || 0; + let deferred = PromiseUtils.defer(); + let path = '/browser/dom/ipc/tests/file_disableScript.html'; + let target = list[idx] + path; + var rootFrame = content.document.getElementById('root'); + utils.navigateFrame(rootFrame, target).then(function() { + results.push(utils.checkScriptEnabled(rootFrame.contentWindow, expectEnabled)); + if (idx == list.length - 1) + deferred.resolve(results); + else + testListInternal(expectEnabled, list, idx + 1).then(function(retArg) { deferred.resolve(retArg); }); + }); + return deferred.promise; + } + return testListInternal(ipcArgs.expectEnabled, ipcArgs.list); + } + } + + let testPolicy = { + exceptions: ['http://test1.example.com', 'http://example.com'], + superExceptions: ['http://test2.example.org', 'https://test1.example.com'], + exempt: ['http://test1.example.com', 'http://example.com', + 'http://test2.example.org', 'http://sub1.test2.example.org', + 'https://sub1.test1.example.com'], + notExempt: ['http://test2.example.com', 'http://sub1.test1.example.com', + 'http://www.example.com', 'https://test2.example.com', + 'https://example.com', 'http://test1.example.org'], + }; + + function activate(isBlack, exceptions, superExceptions) { + activateDomainPolicy(); + let set = isBlack ? policy.blacklist : policy.whitelist; + let superSet = isBlack ? policy.superBlacklist : policy.superWhitelist; + for (var e of exceptions) + set.add(makeURI(e)); + for (var e of superExceptions) + superSet.add(makeURI(e)); + }; + + info("Testing Blacklist-style Domain Policy"); + info("Activating domainPolicy first, creating child process after"); + activate(true, testPolicy.exceptions, testPolicy.superExceptions); + currentTask = initProcess(); + yield currentTask; + let results = []; + currentTask = runTest(testList(true, testPolicy.notExempt)); + results = results.concat(yield currentTask); + currentTask = runTest(testList(false, testPolicy.exempt)); + results = results.concat(yield currentTask); + checkAndCleanup(results); + + info("Creating child process first, activating domainPolicy after"); + currentTask = initProcess(); + yield currentTask; + activate(true, testPolicy.exceptions, testPolicy.superExceptions); + results = []; + currentTask = runTest(testList(true, testPolicy.notExempt)); + results = results.concat(yield currentTask); + currentTask = runTest(testList(false, testPolicy.exempt)); + results = results.concat(yield currentTask); + checkAndCleanup(results); + + info("Testing Whitelist-style Domain Policy"); + deferred = Promise.defer(); + currentTask = deferred.promise; + SpecialPowers.pushPrefEnv({set:[["javascript.enabled", false]]}, () => { return deferred.resolve()}); + yield currentTask; + + info("Activating domainPolicy first, creating child process after"); + activate(false, testPolicy.exceptions, testPolicy.superExceptions); + currentTask = initProcess(); + yield currentTask; + results = []; + currentTask = runTest(testList(false, testPolicy.notExempt)); + results = results.concat(yield currentTask); + currentTask = runTest(testList(true, testPolicy.exempt)); + results = results.concat(yield currentTask); + checkAndCleanup(results); + + info("Creating child process first, activating domainPolicy after"); + currentTask = initProcess(); + yield currentTask; + activate(false, testPolicy.exceptions, testPolicy.superExceptions); + results = []; + currentTask = runTest(testList(false, testPolicy.notExempt)); + results = results.concat(yield currentTask); + currentTask = runTest(testList(true, testPolicy.exempt)); + results = results.concat(yield currentTask); + checkAndCleanup(results); + finish(); +} + + +add_task(test_domainPolicy); + +registerCleanupFunction(()=>{ + deactivateDomainPolicy(); +}) \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/dom/ipc/tests/file_disableScript.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> +<script> +var gFiredOnload = false; +var gFiredOnclick = false; +</script> +</head> +<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;"> +</body> +</html>