From 2cabfdd5a8419a1298b4cd97d43c5151d722d7cf Mon Sep 17 00:00:00 2001 From: Steve Workman Date: Thu, 17 Jul 2014 11:08:20 -0700 Subject: [PATCH] Bug 354493 - Add nsINetworkZonePolicy to protect resources loaded from private IPs r=mcmanus --- modules/libpref/src/init/all.js | 7 + netwerk/base/public/moz.build | 1 + netwerk/base/public/nsIIOService2.idl | 8 +- netwerk/base/public/nsILoadGroup.idl | 8 +- netwerk/base/public/nsINetworkZonePolicy.idl | 53 ++ netwerk/base/public/nsISocketTransport.idl | 6 + netwerk/base/public/nsNetUtil.h | 22 + netwerk/base/src/moz.build | 1 + netwerk/base/src/nsIOService.cpp | 41 ++ netwerk/base/src/nsIOService.h | 20 + netwerk/base/src/nsLoadGroup.cpp | 15 + netwerk/base/src/nsLoadGroup.h | 3 + netwerk/base/src/nsNetworkZonePolicy.cpp | 361 +++++++++++ netwerk/base/src/nsNetworkZonePolicy.h | 80 +++ netwerk/base/src/nsSocketTransport2.cpp | 32 +- netwerk/build/nsNetCID.h | 10 + netwerk/build/nsNetModule.cpp | 8 + netwerk/cache/nsDiskCache.h | 2 +- netwerk/dns/DNS.cpp | 19 +- netwerk/dns/DNS.h | 4 + netwerk/dns/nsDNSService2.cpp | 87 ++- netwerk/dns/nsIDNSService.idl | 12 + netwerk/protocol/http/HttpBaseChannel.cpp | 2 + netwerk/protocol/http/nsAHttpConnection.h | 7 + netwerk/protocol/http/nsHttp.h | 3 + netwerk/protocol/http/nsHttpChannel.cpp | 137 +++- netwerk/protocol/http/nsHttpChannel.h | 7 + netwerk/protocol/http/nsHttpConnection.cpp | 17 + netwerk/protocol/http/nsHttpConnection.h | 3 + netwerk/protocol/http/nsHttpConnectionMgr.cpp | 12 +- netwerk/protocol/http/nsHttpHandler.cpp | 30 +- netwerk/protocol/http/nsHttpHandler.h | 7 +- netwerk/protocol/http/nsHttpTransaction.cpp | 9 + netwerk/test/unit/test_httpResponseTimeout.js | 11 +- netwerk/test/unit/test_networkZonePolicy.js | 613 ++++++++++++++++++ netwerk/test/unit/test_speculative_connect.js | 16 +- netwerk/test/unit/xpcshell.ini | 3 + 37 files changed, 1618 insertions(+), 59 deletions(-) create mode 100644 netwerk/base/public/nsINetworkZonePolicy.idl create mode 100644 netwerk/base/src/nsNetworkZonePolicy.cpp create mode 100644 netwerk/base/src/nsNetworkZonePolicy.h create mode 100644 netwerk/test/unit/test_networkZonePolicy.js diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index b432d45c7eb..ec7f01b3bfc 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -1109,6 +1109,9 @@ pref("network.http.connection-timeout", 90); // when starting a new speculative connection. pref("network.http.speculative-parallel-limit", 6); +// Allow/Forbid speculative connections on loopback. +pref("network.http.speculative.allowLoopback", false); + // Whether or not to block requests for non head js/css items (e.g. media) // while those elements load. pref("network.http.rendering-critical-requests-prioritization", true); @@ -1528,6 +1531,10 @@ pref("network.proxy.autoconfig_retry_interval_max", 300); // 5 minutes // Use the HSTS preload list by default pref("network.stricttransportsecurity.preloadlist", true); +// Prohibit resource loads from private networks (e.g. RFC1918 like IP +// addresses) by documents which were loaded from public networks. +pref("network.zonepolicy.enabled", true); + pref("converter.html2txt.structs", true); // Output structured phrases (strong, em, code, sub, sup, b, i, u) pref("converter.html2txt.header_strategy", 1); // 0 = no indention; 1 = indention, increased with header level; 2 = numbering and slight indention diff --git a/netwerk/base/public/moz.build b/netwerk/base/public/moz.build index 21f0c6be991..6c0c5f657bf 100644 --- a/netwerk/base/public/moz.build +++ b/netwerk/base/public/moz.build @@ -61,6 +61,7 @@ XPIDL_SOURCES += [ 'nsINetworkPredictor.idl', 'nsINetworkPredictorVerifier.idl', 'nsINetworkProperties.idl', + 'nsINetworkZonePolicy.idl', 'nsINSSErrorsService.idl', 'nsIParentChannel.idl', 'nsIParentRedirectingChannel.idl', diff --git a/netwerk/base/public/nsIIOService2.idl b/netwerk/base/public/nsIIOService2.idl index e46df20cafc..eeafd76606f 100644 --- a/netwerk/base/public/nsIIOService2.idl +++ b/netwerk/base/public/nsIIOService2.idl @@ -10,7 +10,7 @@ /** * nsIIOService2 extends nsIIOService */ -[scriptable, uuid(9a7dc724-0b5c-4b78-9722-1037074c02de)] +[scriptable, uuid(b2344926-d194-41d5-b749-e6591a257428)] interface nsIIOService2 : nsIIOService { /** @@ -40,4 +40,10 @@ interface nsIIOService2 : nsIIOService nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI, in nsIURI aProxyURI, in unsigned long aProxyFlags); + + /** + * Returns a string ID for the current network. This ID should be + * refreshed when the network link changes. + */ + readonly attribute ACString networkLinkID; }; diff --git a/netwerk/base/public/nsILoadGroup.idl b/netwerk/base/public/nsILoadGroup.idl index 72439ebf55a..a10cfc47536 100644 --- a/netwerk/base/public/nsILoadGroup.idl +++ b/netwerk/base/public/nsILoadGroup.idl @@ -15,7 +15,7 @@ typedef unsigned long nsLoadFlags; /** * A load group maintains a collection of nsIRequest objects. */ -[scriptable, uuid(afb57ac2-bce5-4ee3-bb34-385089a9ba5c)] +[scriptable, uuid(8531ccd9-f12f-4674-ae07-05b625cebf8b)] interface nsILoadGroup : nsIRequest { /** @@ -93,6 +93,12 @@ interface nsILoadGroup : nsIRequest * the docShell has created the default request.) */ attribute nsLoadFlags defaultLoadFlags; + + /** + * Allow/Deny this loadgroup to load resources from private, RFC1918-like + * addresses. See nsINetworkZonePolicy for more information. + */ + attribute bool allowLoadsFromPrivateNetworks; }; %{C++ diff --git a/netwerk/base/public/nsINetworkZonePolicy.idl b/netwerk/base/public/nsINetworkZonePolicy.idl new file mode 100644 index 00000000000..563bb8f108b --- /dev/null +++ b/netwerk/base/public/nsINetworkZonePolicy.idl @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#include "nsISupports.idl" + +interface nsIRequest; + +/** + * NetworkZonePolicy is used to permit or deny nsIRequests loading from private + * IP addresses. Private in this case means RFC1918-like addresses and + * localhost. Primarily, this is to protect resources held on private + * networks from being loaded by documents which were loaded from the public + * internet. + */ + +[scriptable, uuid(639a0797-7870-4a37-9110-57ea9f14783f)] +interface nsINetworkZonePolicy : nsISupports +{ + /** + * Returns true if aRequest can connect to private, RFC1918-like addresses. + * Implementing classes should query the loadgroup of the request, along with + * the loadgroup's owning/parent hierarchy, to determine if permission is + * granted. This should result in requests generated by a public document + * being restricted to public loads only. + * + * @param aRequest The nsIRequest which is trying to load from a private IP + * address. + * @returns True if aRequest has permission to load from a private IP + * address; false if it does not. + */ + bool checkPrivateNetworkPermission(in nsIRequest aRequest); + + /** + * Tries to set permission in the loadgroup of aRequest with respect to + * loading from private IP addresses. Permission will only be set if the + * loadgroup's ancestors (parent-, owning- and docshell parent loadgroups) all + * allow private network loads. If permission cannot be set, the function + * returns silently. + * Note: only documents should be allowed to set permission for other + * resource loads in the loadgroup, so aRequest should represent a document + * load. Non document loads will be ignored and the function will return + * silently. + * + * @param aRequest The request which is trying to set private load + * permission for its loadgroup. + * @param aAllowed True if permission is to be granted; false if not. + */ + void setPrivateNetworkPermission(in nsIRequest aRequest, + in bool aAllowed); +}; diff --git a/netwerk/base/public/nsISocketTransport.idl b/netwerk/base/public/nsISocketTransport.idl index 8b75e39ccc7..ceeb7778692 100644 --- a/netwerk/base/public/nsISocketTransport.idl +++ b/netwerk/base/public/nsISocketTransport.idl @@ -182,6 +182,12 @@ interface nsISocketTransport : nsITransport */ const unsigned long DISABLE_RFC1918 = (1 << 5); + /** + * If set, indicates that the socket should not connect if the hostname + * resolves to a loopback address. + */ + const unsigned long DISABLE_LOOPBACK = (1 << 6); + /** * Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or * IPTOS_CLASS_CSx (or IPTOS_DSCP_EF, but currently no supported diff --git a/netwerk/base/public/nsNetUtil.h b/netwerk/base/public/nsNetUtil.h index 746960927dc..10cf2af82f9 100644 --- a/netwerk/base/public/nsNetUtil.h +++ b/netwerk/base/public/nsNetUtil.h @@ -2399,4 +2399,26 @@ NS_IsSrcdocChannel(nsIChannel *aChannel) return false; } +/** + * Provides 32 bits of PRNG; workaround for platform variances of RAND_MAX. + */ +inline uint32_t +NS_Get32BitsOfPseudoRandom() +{ + // rand() provides different amounts of PRNG on different platforms. + // 15 or 31 bits are common amounts. + + PR_STATIC_ASSERT(RAND_MAX >= 0xfff); + +#if RAND_MAX < 0xffffU + return ((uint16_t) rand() << 20) | + (((uint16_t) rand() & 0xfff) << 8) | + ((uint16_t) rand() & 0xff); +#elif RAND_MAX < 0xffffffffU + return ((uint16_t) rand() << 16) | ((uint16_t) rand() & 0xffff); +#else + return (uint32_t) rand(); +#endif +} + #endif // !nsNetUtil_h__ diff --git a/netwerk/base/src/moz.build b/netwerk/base/src/moz.build index 311cdb9d223..625d41a1cd9 100644 --- a/netwerk/base/src/moz.build +++ b/netwerk/base/src/moz.build @@ -47,6 +47,7 @@ UNIFIED_SOURCES += [ 'nsMIMEInputStream.cpp', 'nsNetAddr.cpp', 'nsNetStrings.cpp', + 'nsNetworkZonePolicy.cpp', 'nsPACMan.cpp', 'nsPreloadedStream.cpp', 'nsProtocolProxyService.cpp', diff --git a/netwerk/base/src/nsIOService.cpp b/netwerk/base/src/nsIOService.cpp index 02c0e9c2c34..e78bf0fd5b6 100644 --- a/netwerk/base/src/nsIOService.cpp +++ b/netwerk/base/src/nsIOService.cpp @@ -149,6 +149,15 @@ nsIOService::nsIOService() , mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY) , mAutoDialEnabled(false) { + // XXX May need to remove this once Bug 939319 and associated bugs for + // NS_NETWORK_LINK_DATA_CHANGED are complete on all supported platforms. + + // Subfields of unions cannot be targeted in an initializer list + mNetworkLinkSelfAddr.raw.family = PR_AF_UNSPEC; + + // Right now, the network link ID is just 64bits of pseudo-randonmess. + mNetworkLinkID = NS_Get32BitsOfPseudoRandom(); + mNetworkLinkID = (mNetworkLinkID << 32) | NS_Get32BitsOfPseudoRandom(); } nsresult @@ -1084,6 +1093,38 @@ nsIOService::GetManageOfflineStatus(bool* aManage) { return NS_OK; } +uint64_t +nsIOService::GetNetworkLinkID() const +{ + return mNetworkLinkID; +} + +NS_IMETHODIMP +nsIOService::GetNetworkLinkID(nsACString &aNetworkLinkID) +{ + char temp[21]; + PR_snprintf(temp, sizeof(temp), "%llu", mNetworkLinkID); + aNetworkLinkID.Append(temp); + + return NS_OK; +} + +// XXX Remove this once Bug 939319 and associated bugs for +// NS_NETWORK_LINK_DATA_CHANGED are complete on all supported platforms. +// Right now, the network link ID is just 64bits of pseudo-randonmess. +void +nsIOService::UpdateNetworkLinkID(const mozilla::net::NetAddr aCurrentSelfAddr) +{ + if (IsLoopBackAddress(&aCurrentSelfAddr) || + mNetworkLinkSelfAddr.EqualsIP(aCurrentSelfAddr)) { + return; + } + mNetworkLinkSelfAddr = aCurrentSelfAddr; + + mNetworkLinkID = NS_Get32BitsOfPseudoRandom(); + mNetworkLinkID = (mNetworkLinkID << 32) | NS_Get32BitsOfPseudoRandom(); +} + nsresult nsIOService::TrackNetworkLinkStatusForOffline() { diff --git a/netwerk/base/src/nsIOService.h b/netwerk/base/src/nsIOService.h index 1d91615bcf4..830aa07dc11 100644 --- a/netwerk/base/src/nsIOService.h +++ b/netwerk/base/src/nsIOService.h @@ -18,6 +18,7 @@ #include "nsCategoryCache.h" #include "nsISpeculativeConnect.h" #include "mozilla/Attributes.h" +#include "mozilla/net/DNS.h" #define NS_N(x) (sizeof(x)/sizeof(*x)) @@ -74,6 +75,15 @@ public: return mOffline && mSettingOffline && !mSetOfflineValue; } + // Returns the NetworkLinkID. + uint64_t GetNetworkLinkID() const; + + // XXX Remove this once Bug 939319 and associated bugs for + // NS_NETWORK_LINK_DATA_CHANGED are complete on all supported + // platforms. + // Primes the network link ID with the current self address of this host. + void UpdateNetworkLinkID(const mozilla::net::NetAddr aCurrentSelfAddr); + private: // These shouldn't be called directly: // - construct using GetInstance @@ -133,6 +143,16 @@ public: // Used for all default buffer sizes that necko allocates. static uint32_t gDefaultSegmentSize; static uint32_t gDefaultSegmentCount; + +private: + // XXX Maybe remove these once Bug 939319 and associated bugs for + // NS_NETWORK_LINK_DATA_CHANGED are complete on all supported platforms. + // Right now, the network link ID is just 64bits of pseudo-randonmess. + // + // ID of the current network link. + uint64_t mNetworkLinkID; + // IP address of this host for the current network link. + mozilla::net::NetAddr mNetworkLinkSelfAddr; }; /** diff --git a/netwerk/base/src/nsLoadGroup.cpp b/netwerk/base/src/nsLoadGroup.cpp index c238207925b..b97233abd5c 100644 --- a/netwerk/base/src/nsLoadGroup.cpp +++ b/netwerk/base/src/nsLoadGroup.cpp @@ -118,6 +118,7 @@ nsLoadGroup::nsLoadGroup(nsISupports* outer) , mStatus(NS_OK) , mPriority(PRIORITY_NORMAL) , mIsCanceling(false) + , mAllowLoadsFromPrivateNetworks(true) , mDefaultLoadIsTimed(false) , mTimedRequests(0) , mCachedRequests(0) @@ -1094,6 +1095,20 @@ nsresult nsLoadGroup::MergeLoadFlags(nsIRequest *aRequest, nsLoadFlags& outFlags return rv; } +NS_IMETHODIMP +nsLoadGroup::GetAllowLoadsFromPrivateNetworks(bool *aAllowed) +{ + *aAllowed = mAllowLoadsFromPrivateNetworks; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetAllowLoadsFromPrivateNetworks(bool aAllowed) +{ + mAllowLoadsFromPrivateNetworks = aAllowed; + return NS_OK; +} + // nsLoadGroupConnectionInfo class nsLoadGroupConnectionInfo MOZ_FINAL : public nsILoadGroupConnectionInfo diff --git a/netwerk/base/src/nsLoadGroup.h b/netwerk/base/src/nsLoadGroup.h index 988f3dbd07a..b9bee92f30e 100644 --- a/netwerk/base/src/nsLoadGroup.h +++ b/netwerk/base/src/nsLoadGroup.h @@ -82,6 +82,9 @@ protected: int32_t mPriority; bool mIsCanceling; + // Set if this loadgroup allows loads from private networks (RFC1918 etc). + bool mAllowLoadsFromPrivateNetworks; + /* Telemetry */ mozilla::TimeStamp mDefaultRequestCreationTime; bool mDefaultLoadIsTimed; diff --git a/netwerk/base/src/nsNetworkZonePolicy.cpp b/netwerk/base/src/nsNetworkZonePolicy.cpp new file mode 100644 index 00000000000..d7fe8163a16 --- /dev/null +++ b/netwerk/base/src/nsNetworkZonePolicy.cpp @@ -0,0 +1,361 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#include "prlog.h" +#include "nsAutoPtr.h" +#include "nsNetworkZonePolicy.h" +#include "nsIChannel.h" +#include "nsIDocShell.h" +#include "nsIDocumentLoader.h" +#include "nsILoadGroup.h" +#include "nsILoadGroupChild.h" +#include "nsIObserverService.h" +#include "nsIRequestObserver.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#ifdef PR_LOGGING +#include "nsString.h" +#endif + +namespace mozilla +{ +namespace net +{ + +#ifdef PR_LOGGING +static PRLogModuleInfo *gNZPLog; +#define NZPLOG(msg, ...) \ + PR_LOG(gNZPLog, PR_LOG_DEBUG, ("[NZP] %p: " msg, this, ##__VA_ARGS__)) +#else +#define NZPLOG(msg, ...) +#endif + +/* Keeps track of whether or not NZP is enabled */ +bool nsNetworkZonePolicy::sNZPEnabled = true; + +/* True if shutdown notification has been received. */ +bool nsNetworkZonePolicy::sShutdown = false; + +/* Singleton pointer. */ +StaticRefPtr nsNetworkZonePolicy::sSingleton; + +nsNetworkZonePolicy::nsNetworkZonePolicy() +{ + Preferences::AddBoolVarCache(&sNZPEnabled, "network.zonepolicy.enabled"); + + // Register for shutdown notification. + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + } else { + NS_WARNING("failed to get observer service"); + } + +#ifdef PR_LOGGING + if (!gNZPLog) { + gNZPLog = PR_NewLogModule("NZP"); + } +#endif +} + +already_AddRefed +nsNetworkZonePolicy::GetSingleton() +{ + if (sShutdown) { + return nullptr; + } + + if (!sSingleton) { + sSingleton = new nsNetworkZonePolicy(); + } + + // Return a ref ptr to the singleton. + nsRefPtr nzp = sSingleton.get(); + return nzp.forget(); +} + +nsNetworkZonePolicy::~nsNetworkZonePolicy() {} + +NS_IMPL_ISUPPORTS(nsNetworkZonePolicy, + nsINetworkZonePolicy, + nsIObserver) + +// nsIObserver interface +NS_IMETHODIMP +nsNetworkZonePolicy::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + sShutdown = true; + sSingleton = nullptr; + } + return NS_OK; +} + +already_AddRefed +nsNetworkZonePolicy::GetLoadGroupParent(nsILoadGroup *aLoadGroup) +{ + if (NS_WARN_IF(!aLoadGroup)) { + return nullptr; + } + MOZ_ASSERT(aLoadGroup); + + DebugOnly rv = NS_OK; + nsCOMPtr parent; + nsCOMPtr loadGroupAsChild = do_QueryInterface(aLoadGroup); + if (!loadGroupAsChild) { + return nullptr; + } + rv = loadGroupAsChild->GetParentLoadGroup(getter_AddRefs(parent)); + if (!parent) { + return nullptr; + } + NZPLOG("loadgroup %p getting parent loadgroup %p", aLoadGroup, + parent.get()); + return parent.forget(); +} + +already_AddRefed +nsNetworkZonePolicy::GetOwningLoadGroup(nsILoadGroup *aLoadGroup) +{ + if (NS_WARN_IF(!aLoadGroup)) { + return nullptr; + } + MOZ_ASSERT(aLoadGroup); + + DebugOnly rv = NS_OK; + nsCOMPtr owner; + rv = aLoadGroup->GetLoadGroup(getter_AddRefs(owner)); + if (!owner) { + return nullptr; + } + NZPLOG("loadgroup %p getting owning loadgroup %p", aLoadGroup, owner.get()); + return owner.forget(); +} + +already_AddRefed +nsNetworkZonePolicy::GetParentDocShellsLoadGroup(nsILoadGroup *aLoadGroup) +{ + if (NS_WARN_IF(!aLoadGroup)) { + return nullptr; + } + MOZ_ASSERT(aLoadGroup); + + DebugOnly rv = NS_OK; + nsCOMPtr observer; + rv = aLoadGroup->GetGroupObserver(getter_AddRefs(observer)); + if (!observer) { + return nullptr; + } + nsCOMPtr docShell = do_QueryInterface(observer); + if (!docShell) { + return nullptr; + } + nsCOMPtr parentAsTreeItem; + docShell->GetSameTypeParent(getter_AddRefs(parentAsTreeItem)); + if (!parentAsTreeItem) { + return nullptr; + } + nsCOMPtr parentAsDocLoader = + do_QueryInterface(parentAsTreeItem); + if (!parentAsDocLoader) { + return nullptr; + } + nsCOMPtr dsParent; + rv = parentAsDocLoader->GetLoadGroup(getter_AddRefs(dsParent)); + if (!dsParent) { + return nullptr; + } + NZPLOG("loadgroup %p getting docshell parent's loadgroup %p", + aLoadGroup, dsParent.get()); + return dsParent.forget(); +} + +bool +nsNetworkZonePolicy::CheckLoadGroupAncestorHierarchies(nsILoadGroup *aLoadGroup) +{ + if (NS_WARN_IF(!aLoadGroup)) { + return false; + } + MOZ_ASSERT(aLoadGroup); + + // Check the hierarchies of appropriate ancestors. + // 1. Parent loadgroup. + nsCOMPtr ancestor = GetLoadGroupParent(aLoadGroup); + if (ancestor) { + bool ancestorAllows = CheckLoadGroupHierarchy(ancestor); + + NZPLOG("Loadgroup %p's parent loadgroup hierarchy %s private loads.", + aLoadGroup, ancestorAllows ? "allows" : "forbids"); + if (!ancestorAllows) { + return false; + } + } + + // 2. Owning loadgroup. + ancestor = GetOwningLoadGroup(aLoadGroup); + if (ancestor) { + bool ancestorAllows = CheckLoadGroupHierarchy(ancestor); + + NZPLOG("Loadgroup %p's owning loadgroup hierarchy %s private loads.", + aLoadGroup, ancestorAllows ? "allows" : "forbids"); + if (!ancestorAllows) { + return false; + } + } + + // 3. Parent docshell's loadgroup. + ancestor = GetParentDocShellsLoadGroup(aLoadGroup); + if (ancestor) { + bool ancestorAllows = CheckLoadGroupHierarchy(ancestor); + + NZPLOG("Loadgroup %p's parent docshell's loadgroup hierarchy %s private " + "loads.", + aLoadGroup, ancestorAllows ? "allows" : "forbids"); + if (!ancestorAllows) { + return false; + } + } + + // If there is no ancestor or they all have permission to load from private + // networks, return true. + NZPLOG("Loadgroup %p: no ancestor forbids loads from private networks.", + aLoadGroup); + return true; +} + +bool +nsNetworkZonePolicy::CheckLoadGroupHierarchy(nsILoadGroup *aLoadGroup) +{ + if (NS_WARN_IF(!aLoadGroup)) { + return false; + } + MOZ_ASSERT(aLoadGroup); + + // Recurse until root load group of same type, or until ancestor forbids + // private loads. + + // If current loadgroup does not allow private loads, just return. + bool allowed = false; + aLoadGroup->GetAllowLoadsFromPrivateNetworks(&allowed); + if (!allowed) { + NZPLOG("Loadgroup %p forbids loads from private networks.", aLoadGroup); + return false; + } + + // Else, check the hierarchies of appropriate ancestors. + return CheckLoadGroupAncestorHierarchies(aLoadGroup); +} + +/* + * nsNetworkZonePolicy : nsINetworkZonePolicy + */ +NS_IMETHODIMP +nsNetworkZonePolicy::CheckPrivateNetworkPermission(nsIRequest *aRequest, + bool *aAllowed) +{ + if (NS_WARN_IF(!aRequest)) { + return NS_ERROR_NULL_POINTER; + } + if (NS_WARN_IF(!aAllowed)) { + return NS_ERROR_NULL_POINTER; + } + + if (NS_WARN_IF(!aRequest || !aAllowed)) { + return NS_ERROR_NULL_POINTER; + } + + if (!sNZPEnabled) { + *aAllowed = true; + return NS_OK; + } + +#ifdef PR_LOGGING + nsAutoCString nameStr; + aRequest->GetName(nameStr); +#endif + NZPLOG("CheckPrivateNetworkPermission for request %p [%s].", aRequest, + nameStr.get()); + + nsCOMPtr loadGroup; + nsresult rv = aRequest->GetLoadGroup(getter_AddRefs(loadGroup)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!loadGroup) { + NZPLOG("No loadgroup for request %p [%s]; private networks allowed.", + aRequest, nameStr.get()); + *aAllowed = true; + return NS_OK; + } + + // Find out if this loadgroup's hierarchy allows private loads. + bool hierarchyAllows = CheckLoadGroupHierarchy(loadGroup); + + NZPLOG("LoadGroup %p for request %p [%s] is %s private loads.", + loadGroup.get(), aRequest, nameStr.get(), + hierarchyAllows ? "allowed" : "forbidden"); + + *aAllowed = hierarchyAllows; + return NS_OK; +} + +NS_IMETHODIMP +nsNetworkZonePolicy::SetPrivateNetworkPermission(nsIRequest *aRequest, + bool aAllowed) +{ + if (NS_WARN_IF(!aRequest)) { + return NS_ERROR_NULL_POINTER; + } + + if (!sNZPEnabled) { + return NS_OK; + } + + nsCOMPtr loadGroup; + nsresult rv = aRequest->GetLoadGroup(getter_AddRefs(loadGroup)); + if (!loadGroup || NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifdef PR_LOGGING + nsAutoCString nameStr; + aRequest->GetName(nameStr); +#endif + NZPLOG("SetPrivateNetworkPermission: try to %s for loadgroup %p of request " + "%p [%s].", + aAllowed ? "allow" : "forbid", loadGroup.get(), aRequest, + nameStr.get()); + + // Only allow a document request to set its loadgroup's permissions. + nsLoadFlags flags; + aRequest->GetLoadFlags(&flags); + + if (!(flags & nsIChannel::LOAD_DOCUMENT_URI)) { + NZPLOG("Skipping request %p [%s] - not a document load", aRequest, + nameStr.get()); + return NS_OK; + } + + // Do NOT allow a document load to override the loadgroup's hierarchy. + bool ancestorsAllow = CheckLoadGroupAncestorHierarchies(loadGroup); + + NZPLOG("LoadGroup %p ancestors for request %p [%s] %s private loads.", + loadGroup.get(), aRequest, nameStr.get(), + ancestorsAllow ? "allows" : "forbids"); + + if (!ancestorsAllow) { + NZPLOG("Request %p [%s] can't override hierarchy.", aRequest, + nameStr.get()); + return NS_OK; + } + + return loadGroup->SetAllowLoadsFromPrivateNetworks(aAllowed); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/src/nsNetworkZonePolicy.h b/netwerk/base/src/nsNetworkZonePolicy.h new file mode 100644 index 00000000000..68ff86e80d5 --- /dev/null +++ b/netwerk/base/src/nsNetworkZonePolicy.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#ifndef __nznetworkzonepolicy_h__ +#define __nznetworkzonepolicy_h__ + +#include "nsCOMPtr.h" +#include "mozilla/StaticPtr.h" +#include "nsINetworkZonePolicy.h" +#include "nsIObserver.h" + +class nsILoadGroup; + +namespace mozilla +{ +namespace net +{ + +/** + * class nsNetworkZonePolicy + * + * Implements nsINetworkZonePolicy: used by nsIRequest objects to check if + * they have permission to load from private, RFC1918-like IP addresses. + * See nsINetworkZonePolicy for more info. + */ +class nsNetworkZonePolicy : public nsINetworkZonePolicy + , public nsIObserver +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSINETWORKZONEPOLICY + NS_DECL_NSIOBSERVER + + static already_AddRefed GetSingleton(); + +private: + nsNetworkZonePolicy(); + virtual ~nsNetworkZonePolicy(); + + // Returns the parent loadgroup of aLoadGroup, or nullptr if none exists. + already_AddRefed GetLoadGroupParent(nsILoadGroup *aLoadGroup); + + // Returns the owning loadgroup of aLoadGroup, or nullptr if none exists. + already_AddRefed GetOwningLoadGroup(nsILoadGroup *aLoadGroup); + + // Returns the loadgroup of the parent docshell of aLoadGroup's docshell, or + // nullptr if none exists. + already_AddRefed + GetParentDocShellsLoadGroup(nsILoadGroup *aLoadGroup); + + // Checks aLoadGroup and its ancestors (parent-, owning- and docshell + // parent's loadgroups) to check for permission to load from private IP + // addresses. The function follows the ancestor hierarchy to the root + // loadgroup, or until a loadgroup is forbidden to load from private + // networks. In this way, the loadgroup and all of its ancestor loadgroups + // must have permission for this function to return true. + bool CheckLoadGroupHierarchy(nsILoadGroup *aLoadGroup); + + // Similar to CheckLoadGroupHierarchy, except this function checks the + // ancestor hierarchy only for permission to load from private networks; + // aLoadGroup is not checked. + bool CheckLoadGroupAncestorHierarchies(nsILoadGroup *aLoadGroup); + + // Keeps track of whether or not NZP is enabled. + static bool sNZPEnabled; + + // True if shutdown notification has been received. + static bool sShutdown; + + // Singleton pointer. + static StaticRefPtr sSingleton; +}; + +} // namespace net +} // namespace mozilla + +#endif /* __nznetworkzonepolicy_h__ */ diff --git a/netwerk/base/src/nsSocketTransport2.cpp b/netwerk/base/src/nsSocketTransport2.cpp index fef6579b35a..147a7ae2d36 100644 --- a/netwerk/base/src/nsSocketTransport2.cpp +++ b/netwerk/base/src/nsSocketTransport2.cpp @@ -1030,6 +1030,10 @@ nsSocketTransport::ResolveHost() dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6; if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4; + if (mConnectionFlags & nsSocketTransport::DISABLE_RFC1918) + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_RFC1918; + if (mConnectionFlags & nsSocketTransport::DISABLE_LOOPBACK) + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_LOOPBACK; NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) || !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4), @@ -1199,10 +1203,12 @@ nsSocketTransport::InitiateSocket() } } - // Hosts/Proxy Hosts that are Local IP Literals should not be speculatively - // connected - Bug 853423. - if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 && - IsIPAddrLocal(&mNetAddr)) { + // Ensure that we're not using a private IP if such addresses are disabled. + bool disableRFC1918 = mConnectionFlags & nsISocketTransport::DISABLE_RFC1918; + bool disableLoopback = mConnectionFlags & nsISocketTransport::DISABLE_LOOPBACK; + + if ((disableRFC1918 && IsIPAddrLocal(&mNetAddr)) || + (disableLoopback && IsLoopBackAddress(&mNetAddr))) { #ifdef PR_LOGGING if (SOCKET_LOG_ENABLED()) { nsAutoCString netAddrCString; @@ -1211,13 +1217,16 @@ nsSocketTransport::InitiateSocket() netAddrCString.BeginWriting(), kIPv6CStrBufSize)) netAddrCString = NS_LITERAL_CSTRING(""); - SOCKET_LOG(("nsSocketTransport::InitiateSocket skipping " - "speculative connection for host [%s:%d] proxy " - "[%s:%d] with Local IP address [%s]", + SOCKET_LOG(("nsSocketTransport::InitiateSocket refusing to " + "connect to %s host [%s:%d] proxy [%s:%d] with IP " + "address [%s]", + IsIPAddrLocal(&mNetAddr) ? "private" : "loopback", mHost.get(), mPort, mProxyHost.get(), mProxyPort, netAddrCString.get())); } #endif + MOZ_ASSERT(false, + "Local or Loopback IP addresses disabled for this socket!"); return NS_ERROR_CONNECTION_REFUSED; } @@ -1699,8 +1708,15 @@ nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status, nsISupports *pa SOCKET_LOG((" MSG_DNS_LOOKUP_COMPLETE\n")); mDNSRequest = 0; if (param) { + MOZ_ASSERT(NS_SUCCEEDED(status), + "Shouldn't have DNS record if request failed."); mDNSRecord = static_cast(param); - mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr); + nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr); + if (NS_FAILED(rv)) { + NS_WARNING("Unable to get address after name resolution " + "succeeded."); + status = rv; + } } // status contains DNS lookup status if (NS_FAILED(status)) { diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index 130170d9b78..bd7e194a05f 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -457,6 +457,16 @@ { 0xae, 0xcf, 0x05, 0xf8, 0xfa, 0xf0, 0x0c, 0x9b } \ } +// service implementing nsINetworkZonePolicy +#define NS_NETWORKZONEPOLICY_CONTRACTID "@mozilla.org/network/networkzonepolicy;1" +#define NS_NETWORKZONEPOLICY_CID \ +{ /* {afcabf86-d401-41f0-a511-7a444ce31c71} */ \ + 0xafcabf86, \ + 0xd401, \ + 0x41f0, \ + { 0xa5, 0x11, 0x7a, 0x44, 0x4c, 0xe3, 0x1c, 0x71 } \ +} + /****************************************************************************** * netwerk/cache/ classes */ diff --git a/netwerk/build/nsNetModule.cpp b/netwerk/build/nsNetModule.cpp index 34ee5852e62..536089ab22f 100644 --- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -38,6 +38,7 @@ #include "nsCategoryCache.h" #include "nsIContentSniffer.h" #include "Predictor.h" +#include "nsNetworkZonePolicy.h" #include "nsNetUtil.h" #include "nsIThreadPool.h" #include "mozilla/net/NeckoChild.h" @@ -446,6 +447,10 @@ static const mozilla::Module::CategoryEntry kNeckoCategories[] = { NS_GENERIC_FACTORY_CONSTRUCTOR(nsBinHexDecoder) #endif +typedef mozilla::net::nsNetworkZonePolicy nsNetworkZonePolicy; +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNetworkZonePolicy, + nsNetworkZonePolicy::GetSingleton) + static nsresult CreateNewStreamConvServiceFactory(nsISupports* aOuter, REFNSIID aIID, void **aResult) { @@ -798,6 +803,7 @@ NS_DEFINE_NAMED_CID(NS_SERIALIZATION_HELPER_CID); NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELREGISTRAR_CID); NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_NETWORKPREDICTOR_CID); +NS_DEFINE_NAMED_CID(NS_NETWORKZONEPOLICY_CID); static const mozilla::Module::CIDEntry kNeckoCIDs[] = { { &kNS_IOSERVICE_CID, false, nullptr, nsIOServiceConstructor }, @@ -941,6 +947,7 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = { { &kNS_REDIRECTCHANNELREGISTRAR_CID, false, nullptr, RedirectChannelRegistrarConstructor }, { &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor }, { &kNS_NETWORKPREDICTOR_CID, false, nullptr, mozilla::net::Predictor::Create }, + { &kNS_NETWORKZONEPOLICY_CID, false, nullptr, nsNetworkZonePolicyConstructor }, { nullptr } }; @@ -1088,6 +1095,7 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = { { NS_CACHE_STORAGE_SERVICE_CONTRACTID, &kNS_CACHE_STORAGE_SERVICE_CID }, { NS_CACHE_STORAGE_SERVICE_CONTRACTID2, &kNS_CACHE_STORAGE_SERVICE_CID }, { NS_NETWORKPREDICTOR_CONTRACTID, &kNS_NETWORKPREDICTOR_CID }, + { NS_NETWORKZONEPOLICY_CONTRACTID, &kNS_NETWORKZONEPOLICY_CID }, { nullptr } }; diff --git a/netwerk/cache/nsDiskCache.h b/netwerk/cache/nsDiskCache.h index cc91553fa8d..05c30e08276 100644 --- a/netwerk/cache/nsDiskCache.h +++ b/netwerk/cache/nsDiskCache.h @@ -11,7 +11,7 @@ #include "nsCacheEntry.h" #ifdef XP_WIN -#include // for htonl/ntohl +#include // for htonl/ntohl #endif diff --git a/netwerk/dns/DNS.cpp b/netwerk/dns/DNS.cpp index 0275b14fc55..227ccfc2af6 100644 --- a/netwerk/dns/DNS.cpp +++ b/netwerk/dns/DNS.cpp @@ -204,6 +204,11 @@ bool IsIPAddrLocal(const NetAddr *addr) return false; } +bool IsIPAddrPrivate(const NetAddr *addr) +{ + return IsIPAddrLocal(addr) || IsLoopBackAddress(addr); +} + bool NetAddr::operator == (const NetAddr& other) const { @@ -227,7 +232,19 @@ NetAddr::operator == (const NetAddr& other) const return false; } - +bool +NetAddr::EqualsIP(const NetAddr& other) const +{ + if (this->raw.family != other.raw.family) { + return false; + } else if (this->raw.family == AF_INET) { + return (this->inet.ip == other.inet.ip); + } else if (this->raw.family == AF_INET6) { + return (memcmp(&this->inet6.ip, &other.inet6.ip, + sizeof(this->inet6.ip)) == 0); + } + return false; +} NetAddrElement::NetAddrElement(const PRNetAddr *prNetAddr) { diff --git a/netwerk/dns/DNS.h b/netwerk/dns/DNS.h index 5f639a8a27d..b8d55358a8f 100644 --- a/netwerk/dns/DNS.h +++ b/netwerk/dns/DNS.h @@ -111,6 +111,8 @@ union NetAddr { #endif // introduced to support nsTArray (for DNSRequestParent.cpp) bool operator == (const NetAddr& other) const; + // Compares ip fields only; excludes port and other data; IPv4 and v6 only. + bool EqualsIP(const NetAddr& other) const; }; // This class wraps a NetAddr union to provide C++ linked list @@ -164,6 +166,8 @@ bool IsIPAddrAny(const NetAddr *addr); bool IsIPAddrV4Mapped(const NetAddr *addr); +bool IsIPAddrPrivate(const NetAddr *addr); + bool IsIPAddrLocal(const NetAddr *addr); } // namespace net diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp index 573a70e2da9..0cf31d6ed7c 100644 --- a/netwerk/dns/nsDNSService2.cpp +++ b/netwerk/dns/nsDNSService2.cpp @@ -61,8 +61,19 @@ public: : mHostRecord(hostRecord) , mIter(nullptr) , mIterGenCnt(-1) + , mHideLocalIPAddresses(false) + , mHideLoopbackIPAddresses(false) , mDone(false) {} + // Do not return private, RFC1918-like IP addresses. + void HideLocalIPAddresses(); + + // Do not return loopback addresses. + void HideLoopbackIPAddresses(); + + // Convenience function for nsIDNSRecord.hasMore(). + bool HasMore(); + private: virtual ~nsDNSRecord() {} @@ -71,11 +82,27 @@ private: int mIterGenCnt; // the generation count of // mHostRecord->addr_info when we // start iterating + // True if private, RFC1918-like IP addresses should be hidden. + bool mHideLocalIPAddresses; + // True if loopback addresses should be hidden. + bool mHideLoopbackIPAddresses; bool mDone; }; NS_IMPL_ISUPPORTS(nsDNSRecord, nsIDNSRecord) +void +nsDNSRecord::HideLocalIPAddresses() +{ + mHideLocalIPAddresses = true; +} + +void +nsDNSRecord::HideLoopbackIPAddresses() +{ + mHideLoopbackIPAddresses = true; +} + NS_IMETHODIMP nsDNSRecord::GetCanonicalName(nsACString &result) { @@ -122,8 +149,11 @@ nsDNSRecord::GetNextAddr(uint16_t port, NetAddr *addr) } else { mIter = mIter->getNext(); } - } - while (mIter && mHostRecord->Blacklisted(&mIter->mAddress)); + } while (mIter && (mHostRecord->Blacklisted(&mIter->mAddress) || + (mHideLocalIPAddresses && + IsIPAddrLocal(&mIter->mAddress)) || + (mHideLoopbackIPAddresses && + IsLoopBackAddress(&mIter->mAddress)))); if (!mIter && startedFresh) { // If everything was blacklisted we want to reset the blacklist (and @@ -131,6 +161,14 @@ nsDNSRecord::GetNextAddr(uint16_t port, NetAddr *addr) // than nothing. mHostRecord->ResetBlacklist(); mIter = mHostRecord->addr_info->mAddresses.getFirst(); + + // If Private IPs are hidden, return the first public address. + while (mIter && ((mHideLocalIPAddresses && + IsIPAddrLocal(&mIter->mAddress)) || + (mHideLoopbackIPAddresses && + IsLoopBackAddress(&mIter->mAddress)))) { + mIter = mIter->getNext(); + } } if (mIter) { @@ -147,7 +185,9 @@ nsDNSRecord::GetNextAddr(uint16_t port, NetAddr *addr) else { mHostRecord->addr_info_lock.Unlock(); - if (!mHostRecord->addr) { + if (!mHostRecord->addr || + (mHideLocalIPAddresses && IsIPAddrLocal(mHostRecord->addr)) || + (mHideLoopbackIPAddresses && IsLoopBackAddress(mHostRecord->addr))) { // Both mHostRecord->addr_info and mHostRecord->addr are null. // This can happen if mHostRecord->addr_info expired and the // attempt to reresolve it failed. @@ -216,6 +256,15 @@ nsDNSRecord::HasMore(bool *result) return NS_OK; } +bool +nsDNSRecord::HasMore() +{ + bool more; + DebugOnly rv = HasMore(&more); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return more; +} + NS_IMETHODIMP nsDNSRecord::Rewind() { @@ -293,9 +342,18 @@ nsDNSAsyncRequest::OnLookupComplete(nsHostResolver *resolver, nsCOMPtr rec; if (NS_SUCCEEDED(status)) { NS_ASSERTION(hostRecord, "no host record"); - rec = new nsDNSRecord(hostRecord); - if (!rec) - status = NS_ERROR_OUT_OF_MEMORY; + nsRefPtr recImpl = new nsDNSRecord(hostRecord); + if (mFlags & nsIDNSService::RESOLVE_DISABLE_RFC1918) { + recImpl->HideLocalIPAddresses(); + } + if (mFlags & nsIDNSService::RESOLVE_DISABLE_LOOPBACK) { + recImpl->HideLoopbackIPAddresses(); + } + if (!recImpl->HasMore()) { + status = NS_ERROR_UNKNOWN_HOST; + recImpl = nullptr; + } + rec = recImpl.forget(); } MOZ_EVENT_TRACER_DONE(this, "net::dns::lookup"); @@ -837,11 +895,18 @@ nsDNSService::Resolve(const nsACString &hostname, rv = syncReq.mStatus; else { NS_ASSERTION(syncReq.mHostRecord, "no host record"); - nsDNSRecord *rec = new nsDNSRecord(syncReq.mHostRecord); - if (!rec) - rv = NS_ERROR_OUT_OF_MEMORY; - else - NS_ADDREF(*result = rec); + nsRefPtr rec = new nsDNSRecord(syncReq.mHostRecord); + if (flags & nsIDNSService::RESOLVE_DISABLE_RFC1918) { + rec->HideLocalIPAddresses(); + } + if (flags & nsIDNSService::RESOLVE_DISABLE_LOOPBACK) { + rec->HideLoopbackIPAddresses(); + } + if (!rec->HasMore()) { + rv = NS_ERROR_UNKNOWN_HOST; + rec = nullptr; + } + rec.forget(result); } } diff --git a/netwerk/dns/nsIDNSService.idl b/netwerk/dns/nsIDNSService.idl index 2d5a218e207..952dd5269ae 100644 --- a/netwerk/dns/nsIDNSService.idl +++ b/netwerk/dns/nsIDNSService.idl @@ -138,4 +138,16 @@ interface nsIDNSService : nsISupports * If set, only IPv6 addresses will be returned from resolve/asyncResolve. */ const unsigned long RESOLVE_DISABLE_IPV4 = (1 << 7); + + /** + * If set, local (RFC1918) addresses will NOT be returned from + * resolve/asyncResolve. + */ + const unsigned long RESOLVE_DISABLE_RFC1918 = (1 << 8); + + /** + * If set, loopback addresses will NOT be returned from + * resolve/asyncResolve. + */ + const unsigned long RESOLVE_DISABLE_LOOPBACK = (1 << 9); }; diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index dc4cdd34813..04fa5d7b3dc 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -76,7 +76,9 @@ HttpBaseChannel::HttpBaseChannel() // Subfields of unions cannot be targeted in an initializer list mSelfAddr.raw.family = PR_AF_UNSPEC; + memset(&mSelfAddr, 0, sizeof(mSelfAddr.raw.data)); mPeerAddr.raw.family = PR_AF_UNSPEC; + memset(&mPeerAddr, 0, sizeof(mPeerAddr.raw.data)); } HttpBaseChannel::~HttpBaseChannel() diff --git a/netwerk/protocol/http/nsAHttpConnection.h b/netwerk/protocol/http/nsAHttpConnection.h index 7060f004442..20464ee862b 100644 --- a/netwerk/protocol/http/nsAHttpConnection.h +++ b/netwerk/protocol/http/nsAHttpConnection.h @@ -140,6 +140,9 @@ public: // Update the callbacks used to provide security info. May be called on // any thread. virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0; + + // Returns true if the socket peer has a private (RFC1918-like) address. + virtual bool PeerHasPrivateIP() = 0; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID) @@ -230,6 +233,10 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID) { \ if (fwdObject) \ (fwdObject)->SetSecurityCallbacks(aCallbacks); \ + } \ + bool PeerHasPrivateIP() \ + { \ + return fwdObject ? (fwdObject)->PeerHasPrivateIP() : false; \ } }} // namespace mozilla::net diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h index f509af37fb5..e0ecf21ecff 100644 --- a/netwerk/protocol/http/nsHttp.h +++ b/netwerk/protocol/http/nsHttp.h @@ -83,6 +83,9 @@ typedef uint8_t nsHttpVersion; // weaker security profiles based on past history #define NS_HTTP_ALLOW_RSA_FALSESTART (1<<9) +// Allows a transaction to use a connection to a private, RFC1918-like address. +#define NS_HTTP_ALLOW_PRIVATE_IP_ADDRESSES (1<<10) + //----------------------------------------------------------------------------- // some default values //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 2943d3bef55..56ed10f073e 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -20,6 +20,7 @@ #include "nsIStreamListenerTee.h" #include "nsISeekableStream.h" #include "nsILoadGroupChild.h" +#include "nsINetworkZonePolicy.h" #include "nsIProtocolProxyService2.h" #include "nsMimeTypes.h" #include "nsNetUtil.h" @@ -2707,6 +2708,34 @@ nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appC } buf.Adopt(0); + // If the entry was loaded from a private/RFC1918 network, verify that + // private loads are allowed for this loadgroup and that the network + // link ID matches. + rv = entry->GetMetaDataElement("loaded-from-private-network", + getter_Copies(buf)); + if (NS_SUCCEEDED(rv) && !buf.IsEmpty()) { + bool privateIPAddrOK = true; + nsCString currentNetworkIDString; + rv = gIOService->GetNetworkLinkID(currentNetworkIDString); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!buf.Equals(currentNetworkIDString)) { + LOG(("nsHttpChannel::OnCacheEntryCheck %p private entry " + "does not match network link ID - not wanted.", this)); + privateIPAddrOK = false; + } else { + privateIPAddrOK = mCaps & NS_HTTP_ALLOW_PRIVATE_IP_ADDRESSES; + LOG(("nsHttpChannel::OnCacheEntryCheck %p private entry %s.", + this, privateIPAddrOK ? "allowed" : "forbidden")); + } + if (!privateIPAddrOK) { + *aResult = ENTRY_NOT_WANTED; + return NS_OK; + } + } + buf.Adopt(0); + // We'll need this value in later computations... uint32_t lastModifiedTime; rv = entry->GetLastModified(&lastModifiedTime); @@ -3123,6 +3152,30 @@ nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry *aEntry, Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, false); } + + // For cached doument loads, we must set Private/Public Network load + // permissions for the document's sub-resources. + // -- Forbid private loads if the doc was loaded on a public network. + // -- Allow private loads if the doc was loaded from a private IP. + // Note: Private docs should only be loaded from the same network. + // This check should already have been done in OnCacheEntryCheck. + if (mNZP && !aNew && mLoadFlags & nsIChannel::LOAD_DOCUMENT_URI) { + nsXPIDLCString buf; + nsresult rv = + mCacheEntry->GetMetaDataElement("loaded-from-private-network", + getter_Copies(buf)); + bool privateIPAddrOK = NS_SUCCEEDED(rv) && !buf.IsEmpty(); + + LOG(("nsHttpChannel::OnNormalCacheEntryAvailable %p document " + "load: %s sub-resource loads from private networks.", + this, privateIPAddrOK ? "allows" : "forbids")); + + rv = mNZP->SetPrivateNetworkPermission(this, privateIPAddrOK); + if (NS_FAILED(rv)) { + LOG(("nsHttpChannel::OnNormalCacheEntryAvailable %p failed " + "SetPrivateNetworkPermission rv=0x%x", this, rv)); + } + } } return NS_OK; @@ -3867,6 +3920,23 @@ nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry *entry) rv = entry->SetMetaDataElement("response-head", head.get()); if (NS_FAILED(rv)) return rv; + // If the response was loaded from a private/RFC1918 network, store + // the networkLinkID in a metadata header. + if (IsIPAddrPrivate(&mPeerAddr)) { + char privateNetworkIDString[21]; + PR_snprintf(privateNetworkIDString, sizeof(privateNetworkIDString), + "%llu", mPrivateNetworkID); + MOZ_ASSERT(strlen(privateNetworkIDString) > 0); + + LOG(("nsHttpChannel::AddCacheEntryHeaders %p setting loaded-from-" + "private-network=%s", this, privateNetworkIDString)); + rv = entry->SetMetaDataElement("loaded-from-private-network", + privateNetworkIDString); + if (NS_FAILED(rv)) { + return rv; + } + } + // Indicate we have successfully finished setting metadata on the cache entry. rv = entry->MetaDataReady(); @@ -4473,8 +4543,32 @@ nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context) // add ourselves to the load group. from this point forward, we'll report // all failures asynchronously. - if (mLoadGroup) + if (mLoadGroup) { mLoadGroup->AddRequest(this, nullptr); + } + + // Check if the channel is allowed to load from private addresses. + mNZP = do_GetService(NS_NETWORKZONEPOLICY_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && mNZP) { + bool privateIPAddrOK = false; + rv = mNZP->CheckPrivateNetworkPermission(this, &privateIPAddrOK); + if (NS_SUCCEEDED(rv) && privateIPAddrOK) { + mCaps |= NS_HTTP_ALLOW_PRIVATE_IP_ADDRESSES; + } else if (NS_FAILED(rv)) { + LOG(("nsHttpChannel::AsyncOpen %p CheckPrivateNetworkPermission " + "failed with rv=0x%x", this, rv)); + } +#ifdef PR_LOGGING + nsAutoCString host; + rv = mURI->GetAsciiHost(host); + LOG(("nsHttpChannel::AsyncOpen %p private addresses %s for " + "%s", this, privateIPAddrOK ? "allowed" : "forbidden", + host.get())); +#endif + } else { + LOG(("nsHttpChannel::AsyncOpen %p No NetworkZonePolicy object rv=0x%x", + this, rv)); + } // record asyncopen time unconditionally and clear it if we // don't want it after OnModifyRequest() weighs in. But waiting for @@ -5421,8 +5515,45 @@ nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status, nsCOMPtr socketTransport = do_QueryInterface(trans); if (socketTransport) { - socketTransport->GetSelfAddr(&mSelfAddr); - socketTransport->GetPeerAddr(&mPeerAddr); + nsresult selfRv = socketTransport->GetSelfAddr(&mSelfAddr); + nsresult peerRv = socketTransport->GetPeerAddr(&mPeerAddr); + + // XXX Remove this call to UpdateNetworkLinkID once Bug 939319 and + // associated bugs for NS_NETWORK_LINK_DATA_CHANGED are complete on + // all supported platforms. For now, update the network link ID with + // mSelfAddr, assuming we were able to get it successfully. + if (NS_SUCCEEDED(selfRv)) { + gIOService->UpdateNetworkLinkID(mSelfAddr); + } + + // If the peer is private, store the network link ID now to be + // saved in the cache headers later. Also check permissions. + bool peerHasPrivateAddr = NS_SUCCEEDED(peerRv) && + IsIPAddrPrivate(&mPeerAddr); + if (peerHasPrivateAddr) { + mPrivateNetworkID = gIOService->GetNetworkLinkID(); + + if (!(mCaps & NS_HTTP_ALLOW_PRIVATE_IP_ADDRESSES)) { + LOG(("nsHttpChannel::OnTransportStatus %p not permitted " + "to load from private IP address! Canceling.", this)); + return Cancel(NS_ERROR_CONNECTION_REFUSED); + } + } + + // If this is a document load, set permissions for the rest of the + // loadgroup's requests. + if (mNZP && NS_SUCCEEDED(peerRv) && + mLoadFlags & nsIChannel::LOAD_DOCUMENT_URI) { + LOG(("nsHttpChannel::OnTransportStatus %p document load: " + "%s sub-resource loads from private networks.", + this, peerHasPrivateAddr ? "allows" : "forbids")); + nsresult rv = + mNZP->SetPrivateNetworkPermission(this, peerHasPrivateAddr); + if (NS_FAILED(rv)) { + LOG(("nsHttpChannel::OnTransportStatus %p failed " + "SetPrivateNetworkPermission rv=0x%x", this, rv)); + } + } } } diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index cf567a88868..52baef9b73f 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -19,6 +19,7 @@ #include "nsIAsyncVerifyRedirectCallback.h" #include "nsIThreadRetargetableRequest.h" #include "nsIThreadRetargetableStreamListener.h" +#include "nsINetworkZonePolicy.h" #include "nsWeakReference.h" #include "TimingStruct.h" #include "AutoClose.h" @@ -422,6 +423,12 @@ protected: private: // cache telemetry bool mDidReval; + + // The network link ID generated by nsIOService at the time of connection. + uint64_t mPrivateNetworkID; + + // Set during AsyncOpen + nsCOMPtr mNZP; }; } } // namespace mozilla::net diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index ba08dcb98fb..c86afd8e1a9 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -1712,6 +1712,23 @@ nsHttpConnection::OnSocketReadable() return rv; } +bool +nsHttpConnection::PeerHasPrivateIP() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + if (!mSocketTransport) { + return false; + } + + NetAddr peerAddr; + nsresult rv = mSocketTransport->GetPeerAddr(&peerAddr); + if (NS_FAILED(rv)) { + return false; + } + + return IsIPAddrPrivate(&peerAddr); +} + void nsHttpConnection::SetupSecondaryTLS() { diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h index bf2aea537a2..2131a5e528f 100644 --- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -200,6 +200,9 @@ public: void SetupSecondaryTLS(); void SetInSpdyTunnel(bool arg); + // Returns true if the socket peer has a private (RFC1918-like) address. + bool PeerHasPrivateIP(); + private: // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use. enum TCPKeepaliveConfig { diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index 2a3c16801a1..931dff3c290 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -2938,10 +2938,20 @@ nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport, tmpFlags |= nsISocketTransport::DISABLE_IPV6; } - if (IsSpeculative()) { + // Allow speculative connections for loopback so we can run tests. + if (IsSpeculative() && gHttpHandler->AllowSpeculativeConnectOnLoopback()) { tmpFlags |= nsISocketTransport::DISABLE_RFC1918; + LOG(("nsHalfOpenSocket::SetupStreams %p Disable private IPs for " + "speculative connections", this)); + } else if (IsSpeculative() || + !(mCaps & NS_HTTP_ALLOW_PRIVATE_IP_ADDRESSES)) { + tmpFlags |= nsISocketTransport::DISABLE_LOOPBACK | + nsISocketTransport::DISABLE_RFC1918; + LOG(("nsHalfOpenSocket::SetupStreams %p Disable loopback and private " + "IPs", this)); } + socketTransport->SetConnectionFlags(tmpFlags); socketTransport->SetQoSBits(gHttpHandler->GetQoSBits()); diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index 8fed7b450fd..404bc1a366a 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -196,6 +196,7 @@ nsHttpHandler::nsHttpHandler() , mConnectTimeout(90000) , mBypassCacheLockThreshold(250.0) , mParallelSpeculativeConnectLimit(6) + , mAllowSpeculativeConnectOnLoopback(false) , mRequestTokenBucketEnabled(true) , mRequestTokenBucketMinParallelism(6) , mRequestTokenBucketHz(100) @@ -520,28 +521,6 @@ nsHttpHandler::GetIOService(nsIIOService** result) return NS_OK; } -uint32_t -nsHttpHandler::Get32BitsOfPseudoRandom() -{ - // only confirm rand seeding on socket thread - MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); - - // rand() provides different amounts of PRNG on different platforms. - // 15 or 31 bits are common amounts. - - PR_STATIC_ASSERT(RAND_MAX >= 0xfff); - -#if RAND_MAX < 0xffffU - return ((uint16_t) rand() << 20) | - (((uint16_t) rand() & 0xfff) << 8) | - ((uint16_t) rand() & 0xff); -#elif RAND_MAX < 0xffffffffU - return ((uint16_t) rand() << 16) | ((uint16_t) rand() & 0xffff); -#else - return (uint32_t) rand(); -#endif -} - void nsHttpHandler::NotifyObservers(nsIHttpChannel *chan, const char *event) { @@ -1256,6 +1235,13 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref) mParallelSpeculativeConnectLimit = (uint32_t) clamped(val, 0, 1024); } + if (PREF_CHANGED(HTTP_PREF("speculative.allowLoopback"))) { + rv = prefs->GetBoolPref(HTTP_PREF("speculative.allowLoopback"), &cVar); + if (NS_SUCCEEDED(rv)) { + mAllowSpeculativeConnectOnLoopback = cVar; + } + } + // Whether or not to block requests for non head js/css items (e.g. media) // while those elements load. if (PREF_CHANGED(HTTP_PREF("rendering-critical-requests-prioritization"))) { diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index 9799131c47b..46bf0fe89af 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -109,6 +109,7 @@ public: bool AllowPush() { return mAllowPush; } uint32_t ConnectTimeout() { return mConnectTimeout; } uint32_t ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; } + bool AllowSpeculativeConnectOnLoopback() { return mAllowSpeculativeConnectOnLoopback; } bool CriticalRequestPrioritization() { return mCriticalRequestPrioritization; } double BypassCacheLockThreshold() { return mBypassCacheLockThreshold; } @@ -228,9 +229,6 @@ public: nsICookieService * GetCookieService(); // not addrefed nsISiteSecurityService * GetSSService(); - // callable from socket thread only - uint32_t Get32BitsOfPseudoRandom(); - // Called by the channel synchronously during asyncOpen void OnOpeningRequest(nsIHttpChannel *chan) { @@ -482,6 +480,9 @@ private: // when starting a new speculative connection. uint32_t mParallelSpeculativeConnectLimit; + // Allow speculative connections on loopback. Primarily for testing. + bool mAllowSpeculativeConnectOnLoopback; + // For Rate Pacing of HTTP/1 requests through a netwerk/base/src/EventTokenBucket // Active requests <= *MinParallelism are not subject to the rate pacing bool mRequestTokenBucketEnabled; diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 61a1b95c320..a2dd272fb41 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -632,6 +632,15 @@ nsHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader, mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); } + // Verify permission to load from private (RFC1918-like) addresses. + if (!(mCaps & NS_HTTP_ALLOW_PRIVATE_IP_ADDRESSES) && + mConnection->PeerHasPrivateIP()) { + LOG(("nsHttpTransaction::ReadSegments %p private IPs forbidden; " + "closing transaction.", this)); + Close(NS_ERROR_CONNECTION_REFUSED); + return NS_ERROR_CONNECTION_REFUSED; + } + mReader = reader; nsresult rv = mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead); diff --git a/netwerk/test/unit/test_httpResponseTimeout.js b/netwerk/test/unit/test_httpResponseTimeout.js index 169754500fc..96b47fe067e 100644 --- a/netwerk/test/unit/test_httpResponseTimeout.js +++ b/netwerk/test/unit/test_httpResponseTimeout.js @@ -43,10 +43,6 @@ TimeoutListener.prototype = { }, }; -function serverStopListener() { - do_test_finished(); -} - function testTimeout(timeoutEnabled, expectResponse) { // Set timeout pref. if (timeoutEnabled) { @@ -137,8 +133,11 @@ function setup_tests() { function setup_http_server() { // Start server; will be stopped at test cleanup time. server.start(-1); - baseURL = "http://localhost:" + server.identity.primaryPort + "/"; + baseURL = server.identity.primaryScheme + "://" + + server.identity.primaryHost + ":" + + server.identity.primaryPort + "/"; do_print("Using baseURL: " + baseURL); + server.registerPathHandler('/', function(metadata, response) { // Wait until the timeout should have passed, then respond. response.processAsync(); @@ -150,7 +149,7 @@ function setup_http_server() { }); }); do_register_cleanup(function() { - server.stop(serverStopListener); + server.stop(function() {}); }); } diff --git a/netwerk/test/unit/test_networkZonePolicy.js b/netwerk/test/unit/test_networkZonePolicy.js new file mode 100644 index 00000000000..2420b0f2f07 --- /dev/null +++ b/netwerk/test/unit/test_networkZonePolicy.js @@ -0,0 +1,613 @@ +/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +"use strict"; + +Cu.import("resource://testing-common/httpd.js"); + +var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); +var nzp = Cc["@mozilla.org/network/networkzonepolicy;1"] + .createInstance(Ci.nsINetworkZonePolicy); +// HTTP Server for 'network' requests. +var httpServ; +// URI Base for requests. +var uriBase; + +// Listener implements nsIStreamListener. +// +// @param expectSuccess If true, Listener will check for request success. +// If false, Listener will check for failure and ensure +// no onDataAvailable calls are made. +// @param loadGroupAllows +// Indicates if loadGroup should allow or forbid private +// loads AFTER the response is received. This may be +// changed by the channel based on the type of channel +// load. +function Listener(expectSuccess, loadGroupAllows) { + this._expectSuccess = expectSuccess; + this._loadGroupAllows = loadGroupAllows; +} + +Listener.prototype = { + _expectSuccess: false, + _loadGroupAllows: true, + _buffer: null, + + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsIStreamListener) || + iid.equals(Components.interfaces.nsIRequestObserver) || + iid.equals(Components.interfaces.nsISupports)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + onStartRequest: function(request, ctx) { + do_check_true(request instanceof Ci.nsIHttpChannel); + if (this._expectSuccess) { + do_check_true(Components.isSuccessCode(request.status)); + do_check_eq(request.requestSucceeded, true); + do_check_eq(request.responseStatus, 200); + request.visitResponseHeaders({ visitHeader: function(aHeader, aValue) { + do_print(aHeader + ": " + aValue); + }}); + do_print(request.responseStatus + ": " + request.responseStatusText); + this._buffer = ""; + } else { + do_check_false(Components.isSuccessCode(request.status)); + } + }, + + onDataAvailable: function(request, ctx, stream, off, cnt) { + do_check_true(request instanceof Ci.nsIHttpChannel); + if (!this._expectSuccess) { + do_throw("Should not get data; private load forbidden!"); + } + this._buffer = this._buffer.concat(read_stream(stream, cnt)); + }, + + onStopRequest: function(request, ctx, status) { + do_check_true(request instanceof Ci.nsIHttpChannel); + // Check loadgroup permission has not changed. + do_check_eq(request.loadGroup.allowLoadsFromPrivateNetworks, + this._loadGroupAllows); + + if (this._expectSuccess) { + do_check_true(Components.isSuccessCode(status)); + } else { + do_check_false(Components.isSuccessCode(status)); + } + run_next_test(); + } +}; + +// +// Test Functions +// + +// Ensure that the pref enables and disables private load restrictions. +function test_basic_NetworkZonePolicy_pref() { + // Create loadgroup and channel for non-doc load. + var loadGroup = Cc["@mozilla.org/network/load-group;1"] + .createInstance(Ci.nsILoadGroup); + var chan = ios.newChannel("http://localhost/failme/", null, null) + .QueryInterface(Ci.nsIHttpChannel); + chan.loadGroup = loadGroup; + + // Verify that we're starting with the pref enabled. This should have been set + // during this scripts setup phase. + var prefs = Cc["@mozilla.org/preferences-service;1"] + .getService(Ci.nsIPrefBranch); + var nzpEnabled = prefs.getBoolPref("network.zonepolicy.enabled"); + do_check_true(nzpEnabled); + + // Verify defaults + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, true); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), true); + + // Set permission for doc load; verify permission changed. + chan.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI; + + nzp.setPrivateNetworkPermission(chan, false); + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, false); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), false); + + // Disable pref; ensure restrictions lifted. + prefs.setBoolPref("network.zonepolicy.enabled", false); + // Loadgroup permission will still be "forbid private loads". + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, false); + // NZP will report that private loads are ok. + do_check_eq(nzp.checkPrivateNetworkPermission(chan), true); + + // Enable pref again; ensure restrictions are again in play. + prefs.setBoolPref("network.zonepolicy.enabled", true); + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, false); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), false); + + // Leaving pref on for the remainder of the tests. + + run_next_test(); +} + +// Ensure that NetworkZonePolicy can manage permissions for a channel's +// loadgroup; no loadgroup ancestors. +function test_basic_NetworkZonePolicy_and_loadGroup() { + // Create loadgroup and channel for non-doc load. + var loadGroup = Cc["@mozilla.org/network/load-group;1"] + .createInstance(Ci.nsILoadGroup); + var chan = ios.newChannel("http://localhost/failme/", null, null) + .QueryInterface(Ci.nsIHttpChannel); + chan.loadGroup = loadGroup; + + // Verify defaults + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, true); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), true); + + // Set permission for non-doc load; verify no changes. + nzp.setPrivateNetworkPermission(chan, false); + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, true); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), true); + + // Set permission for doc load; verify permission changed. + chan.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI; + + nzp.setPrivateNetworkPermission(chan, false); + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, false); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), false); + + run_next_test(); +} + +// Ensure that NetworkZonePolicy can manage permissions for a channel's +// loadgroup and one of its ancestors. Ancestor is specified by calling +// function. +function test_loadGroup_and_ancestor(loadGroup, ancestor, chan) { + // Verify permission defaults + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, true); + do_check_eq(ancestor.allowLoadsFromPrivateNetworks, true); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), true); + + // Set permission for non-doc load; verify no changes. + nzp.setPrivateNetworkPermission(chan, false); + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, true); + do_check_eq(ancestor.allowLoadsFromPrivateNetworks, true); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), true); + + // Set permission for doc load; verify permission changed for loadgroup, but + // not for ancestor loadgroup. + chan.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI; + + nzp.setPrivateNetworkPermission(chan, false); + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, false); + do_check_eq(ancestor.allowLoadsFromPrivateNetworks, true); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), false); + + // Verify we can set permission allowed again. + nzp.setPrivateNetworkPermission(chan, true); + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, true); + do_check_eq(ancestor.allowLoadsFromPrivateNetworks, true); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), true); + + // Set ancestor to forbid private loads; verify chan permission forbidden. + ancestor.allowLoadsFromPrivateNetworks = false; + do_check_eq(nzp.checkPrivateNetworkPermission(chan), false); + // ... loadgroup's own persmission should still be allow. + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, true); + // ... nzp should not be able to set permission to true. + nzp.setPrivateNetworkPermission(chan, true); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), false); + + // Reset ancestor and verify. + ancestor.allowLoadsFromPrivateNetworks = true; + do_check_eq(nzp.checkPrivateNetworkPermission(chan), true); + nzp.setPrivateNetworkPermission(chan, false); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), false); +} + +// Ensure that NetworkZonePolicy can manage permissions for a channel's +// loadgroup; loadgroup has a parent loadgroup. +function test_basic_NetworkZonePolicy_loadGroup_and_parent() { + // Create loadgroup, parent loadgroup and channel for non-doc load. + var loadGroup = Cc["@mozilla.org/network/load-group;1"] + .createInstance(Ci.nsILoadGroup); + var loadGroupAsChild = loadGroup.QueryInterface(Ci.nsILoadGroupChild) + var chan = ios.newChannel("http://localhost/failme/", null, null) + .QueryInterface(Ci.nsIHttpChannel); + chan.loadGroup = loadGroup; + + var parent = Cc["@mozilla.org/network/load-group;1"] + .createInstance(Ci.nsILoadGroup); + loadGroupAsChild.parentLoadGroup = parent; + do_check_eq(parent, loadGroupAsChild.parentLoadGroup); + + test_loadGroup_and_ancestor(loadGroup, parent, chan); + + run_next_test(); +} + +// Ensure that NetworkZonePolicy can manage permissions for a channel's +// loadgroup; loadgroup is member of another loadgroup. +function test_basic_NetworkZonePolicy_loadGroup_and_owner() { + // Create loadgroup, parent loadgroup and channel for non-doc load. + var loadGroup = Cc["@mozilla.org/network/load-group;1"] + .createInstance(Ci.nsILoadGroup); + var chan = ios.newChannel("http://localhost/failme/", null, null) + .QueryInterface(Ci.nsIHttpChannel); + chan.loadGroup = loadGroup; + + var owner = Cc["@mozilla.org/network/load-group;1"] + .createInstance(Ci.nsILoadGroup); + loadGroup.loadGroup = owner; + do_check_eq(owner, loadGroup.loadGroup); + + test_loadGroup_and_ancestor(loadGroup, owner, chan); + + run_next_test(); +} + +// Ensure that NetworkZonePolicy can manage permissions for a channel's +// loadgroup; loadgroup is a docshell loadgroup that has a parent docshell. +function test_basic_NetworkZonePolicy_loadGroup_and_docshell() { + // Create docshell and docshell parent, and get their loadgroups. + var docShell = Cc["@mozilla.org/docshell;1"].createInstance(Ci.nsIDocShell); + + var docShellParent = Cc["@mozilla.org/docshell;1"] + .createInstance(Ci.nsIDocShell); + docShellParent.addChild(docShell); + + var loadGroup = docShell.QueryInterface(Ci.nsIDocumentLoader).loadGroup; + var dsParent = docShellParent.QueryInterface(Ci.nsIDocumentLoader).loadGroup; + + // Create a channel for non-doc load. + var chan = ios.newChannel("http://localhost/failme/", null, null) + .QueryInterface(Ci.nsIHttpChannel); + chan.loadGroup = loadGroup; + + test_loadGroup_and_ancestor(loadGroup, dsParent, chan); + + run_next_test(); +} + +// Ensure that a loadgroup's immediate ancestors dictate its private load +// permissions. +function test_loadGroup_immediate_ancestors() { + // Create docshell and docshell parent, and get their loadgroups. + var docShell = Cc["@mozilla.org/docshell;1"].createInstance(Ci.nsIDocShell); + var docShellParent = Cc["@mozilla.org/docshell;1"] + .createInstance(Ci.nsIDocShell); + docShellParent.addChild(docShell); + + var loadGroup = docShell.QueryInterface(Ci.nsIDocumentLoader).loadGroup; + var dsParent = docShellParent.QueryInterface(Ci.nsIDocumentLoader).loadGroup; + + // Add owning loadgroup. + var owner = Cc["@mozilla.org/network/load-group;1"] + .createInstance(Ci.nsILoadGroup); + loadGroup.loadGroup = owner; + do_check_eq(owner, loadGroup.loadGroup); + + // Add parent loadgroup. + var loadGroupAsChild = loadGroup.QueryInterface(Ci.nsILoadGroupChild) + var parent = Cc["@mozilla.org/network/load-group;1"] + .createInstance(Ci.nsILoadGroup); + loadGroupAsChild.parentLoadGroup = parent; + do_check_eq(parent, loadGroupAsChild.parentLoadGroup); + + // Create a channel for non-doc load. + var chan = ios.newChannel("http://localhost/failme/", null, null) + .QueryInterface(Ci.nsIHttpChannel); + chan.loadGroup = loadGroup; + + // Verify permission defaults + do_check_eq(loadGroup.allowLoadsFromPrivateNetworks, true); + do_check_eq(dsParent.allowLoadsFromPrivateNetworks, true); + do_check_eq(owner.allowLoadsFromPrivateNetworks, true); + do_check_eq(parent.allowLoadsFromPrivateNetworks, true); + do_check_eq(nzp.checkPrivateNetworkPermission(chan), true); + + // Set ancestors to forbid. + for (var i = 0; i < 8; i++) { + dsParent.allowLoadsFromPrivateNetworks = !!(i & 1); + owner.allowLoadsFromPrivateNetworks = !!(i & 2); + parent.allowLoadsFromPrivateNetworks = !!(i & 4); + // Permission allowed only when all ancestors allow private loads. + do_check_eq(nzp.checkPrivateNetworkPermission(chan), (i == 7)); + } + + run_next_test(); +} + +// Checks a channel load based on its loadgroup private load permissions. +// +// @param allowPrivateLoads Indicates if private loads should be allowed on +// the channel's loadgroup or not. +// @param expectSuccessfulResponse +// Indicates if the nsIStreamListener for the channel +// load should expect a response with success or +// failure. +// @param urlStr The url that should be loaded by the channel. +// +function test_single_loadGroup(allowPrivateLoads, + expectSuccessfulResponse, + urlStr) { + // Create loadgroup and channel for non-doc load. + var loadGroup = Cc["@mozilla.org/network/load-group;1"] + .createInstance(Ci.nsILoadGroup); + var chan = ios.newChannel(urlStr, null, null) + .QueryInterface(Ci.nsIHttpChannel); + chan.loadGroup = loadGroup; + + do_print("Setting loadgroup permission: " + + (allowPrivateLoads ? "Allowing" : "Forbidding") + + " private loads for " + urlStr + "."); + loadGroup.allowLoadsFromPrivateNetworks = allowPrivateLoads; + + // Try to load channel with IP literal. For non-doc loads like this, load + // group permissions should not change after the response is received. + var listener = new Listener(expectSuccessfulResponse, allowPrivateLoads); + chan.asyncOpen(listener, null); +} + +// Same as test_single_loadGroup but for a document load. +function test_single_loadGroup_doc_load(allowPrivateLoads, + expectSuccessfulResponse, + loadGroupAllowsAfter, + urlStr) { + // Create loadgroup and channel for document load. + var loadGroup = Cc["@mozilla.org/network/load-group;1"] + .createInstance(Ci.nsILoadGroup); + var chan = ios.newChannel(urlStr, null, null) + .QueryInterface(Ci.nsIHttpChannel); + chan.loadGroup = loadGroup; + chan.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI; + + do_print("Setting loadgroup permission: " + + (allowPrivateLoads ? "Allowing" : "Forbidding") + + " private loads for doc load " + urlStr + "."); + loadGroup.allowLoadsFromPrivateNetworks = allowPrivateLoads; + + // Try to load channel with IP literal. + var listener = new Listener(expectSuccessfulResponse, loadGroupAllowsAfter); + chan.asyncOpen(listener, null); +} + +function prime_cache_entry(uri, isPrivate, networkID, onEntryPrimed) { + do_print("Priming cache with a " + (isPrivate ? "private" : "public") + + " entry."); + + var fakeResponseHead = "HTTP/1.1 200 OK\r\n" + + "Content-Type: text/plain\r\n" + + "Server: httpd.js\r\n" + + "Date: " + (new Date()).toString() + "\r\n"; + + asyncOpenCacheEntry(uri, "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, null, + new OpenCallback(NEW, "a1m", "a1d", function(entry) { + do_print("Created " + (isPrivate ? "private" : "public") + " entry"); + entry.setMetaDataElement("request-method", "GET"); + entry.setMetaDataElement("response-head", fakeResponseHead); + if (isPrivate) { + entry.setMetaDataElement("loaded-from-private-network", networkID); + } + do_print("Added metadata"); + asyncOpenCacheEntry(uri, "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null, + new OpenCallback(NORMAL, "a1m", "a1d", function(entry) { + do_print("Verifying " + (isPrivate ? "private" : "public") + + " entry created"); + if (isPrivate) { + do_check_eq(entry.getMetaDataElement("loaded-from-private-network"), + networkID); + } + + do_print((isPrivate ? "Private" : "Public") + " cache entry primed."); + onEntryPrimed(); + }) + ); + }) + ); +} + +// Create a private cache entry; if private loads are allowed, the entry will +// be accepted. If not, the entry will be rejected, but since the server is +// localhost the network load should also be rejected. +function test_private_cached_entry_same_network(privateLoadAllowed) { + var uri = uriBase + "/failme/"; + prime_cache_entry(uri, true, ios.networkLinkID, function() { + test_single_loadGroup(privateLoadAllowed, privateLoadAllowed, uri); + }); +} + +// Create a private cache entry; if private loads are allowed, the entry will +// be accepted. If not, the entry will be rejected, but since the server is +// localhost the network load should also be rejected. +// LoadGroup permissions should not change after response. +function test_private_cached_entry_same_network_doc_load(privateLoadAllowed) { + var uri = uriBase + "/failme/"; + prime_cache_entry(uri, true, ios.networkLinkID, function() { + test_single_loadGroup_doc_load(privateLoadAllowed, + privateLoadAllowed, + privateLoadAllowed, + uri); + }); +} + +// UUID to fake a load on a different private network. +var fakeNetworkID = "{86437A10-658B-4637-8D41-9B3693F72762}"; + +// Ensure that privately cached entries from different networks than the current +// one are not loaded. +function test_private_cached_entry_diff_network(privateLoadAllowed) { + // We should bypass the cache entry since it was created on a different + // network. As such, use /passme for private loads allowed, and /failme for + // private loads forbidden. + var uri = uriBase + (privateLoadAllowed ? "/passme/" : "/failme/"); + prime_cache_entry(uri, true, fakeNetworkID, function() { + test_single_loadGroup(privateLoadAllowed, privateLoadAllowed, uri); + }); +} + +// Ensure that privately cached entries from different networks than the current +// one are not loaded; doc load. +function test_private_cached_entry_diff_network_doc_load(privateLoadAllowed) { + // We should bypass the cache entry since it was created on a different + // network. As such, use /passme for private loads allowed, and /failme for + // private loads forbidden. + // LoadGroup permissions should not change after response. + var uri = uriBase + (privateLoadAllowed ? "/passme/" : "/failme/"); + prime_cache_entry(uri, true, fakeNetworkID, function() { + test_single_loadGroup_doc_load(privateLoadAllowed, + privateLoadAllowed, + privateLoadAllowed, + uri); + }); +} + +// Ensure that publicly cached entries are always loaded. +function test_public_cached_entry(privateCacheEntryAllowed) { + var uri = uriBase + "/failme/"; + prime_cache_entry(uri, false, null, function() { + test_single_loadGroup(privateCacheEntryAllowed, true, uri); + }); +} + +// Ensure that publicly cached entries are always loaded; doc loads. +function test_public_cached_entry_doc_load(privateCacheEntryAllowed) { + // Create a public cache entry for /failme; the entry should be accepted and + // we should not go to the network. + // LoadGroup permissions should forbid private loads after response. + var uri = uriBase + "/failme/"; + prime_cache_entry(uri, false, null, function() { + test_single_loadGroup_doc_load(privateCacheEntryAllowed, true, false, uri); + }); +} + +// +// Initialization +// + +// Setup HTTP server to handle for http://localhost/passme/ and +// http://localhost/failme/. Requests received by /failme/ will cause the test +// script to fail. Thus, it is used to verify that specific request do not go +// to the network. +function setup_http_server() { + httpServ = new HttpServer(); + + httpServ.registerPathHandler("/passme/", function (metadata, response) { + do_print("Received request on http://localhost/passme/"); + var httpbody = "0123456789"; + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/plain", false); + response.bodyOutputStream.write(httpbody, httpbody.length); + }); + + httpServ.registerPathHandler("/failme/", function (metadata, response) { + do_throw("http://localhost/failme/ should not not receive requests!"); + }); + httpServ.start(-1); + + do_register_cleanup(function() { + httpServ.stop(function() {}); + }); + + do_print("Started HTTP Server on " + + httpServ.identity.primaryScheme + "://" + + httpServ.identity.primaryHost + ":" + + httpServ.identity.primaryPort); +} + +function setup_and_add_tests() { + // Get profile for disk caching. + do_get_profile(); + + // Run tests with NetworkZonePolicy enabled; set pref back to default after + // tests complete. + { + var prefs = Cc["@mozilla.org/preferences-service;1"] + .getService(Ci.nsIPrefBranch); + if (!prefs.getBoolPref("network.zonepolicy.enabled")) { + prefs.setBoolPref("network.zonepolicy.enabled", true); + do_register_cleanup(function() { + prefs.setBoolPref("network.zonepolicy.enabled", false); + }); + } + } + + uriBase = httpServ.identity.primaryScheme + "://" + + httpServ.identity.primaryHost + ":" + + httpServ.identity.primaryPort; + + var tests = [ + // Basic pref test. + test_basic_NetworkZonePolicy_pref, + + // Verify NetworkZonePolicy can manage loadgroup permisssions. + test_basic_NetworkZonePolicy_and_loadGroup, + test_basic_NetworkZonePolicy_loadGroup_and_parent, + test_basic_NetworkZonePolicy_loadGroup_and_owner, + test_basic_NetworkZonePolicy_loadGroup_and_docshell, + test_loadGroup_immediate_ancestors, + + // Private (localhost) network requests. + function test_network_private_allowed() { + test_single_loadGroup(true, true, uriBase + "/passme/"); }, + function test_network_private_forbidden() { + test_single_loadGroup(false, false, uriBase + "/failme/"); }, + + // Private (localhost) network requests for document loads. + function test_network_private_allowed_doc_load() { + test_single_loadGroup_doc_load(true, true, true, uriBase + "/passme/"); }, + function test_network_private_forbidden_doc_load() { + test_single_loadGroup_doc_load(false, false, false, uriBase + "/failme/"); }, + + // Private cache entries; same network. + function test_private_cache_same_network_private_allowed() { + test_private_cached_entry_same_network(true); }, + function test_private_cache_same_network_private_forbidden() { + test_private_cached_entry_same_network(false); }, + + // Private cache entries; same network; doc load. + function test_private_cache_same_network_private_allowed_doc_load() { + test_private_cached_entry_same_network_doc_load(true); }, + function test_private_cache_same_network_private_forbidden_doc_load() { + test_private_cached_entry_same_network_doc_load(false); }, + + // Private cache entries, different network. + function test_private_cache_diff_network_private_allowed() { + test_private_cached_entry_diff_network(true); }, + function test_private_cache_diff_network_private_forbidden() { + test_private_cached_entry_diff_network(false); }, + + // Private cache entries, different network; doc load. + function test_private_cache_diff_network_private_allowed_doc_load() { + test_private_cached_entry_diff_network_doc_load(true); }, + function test_private_cache_diff_network_private_forbidden_doc_load() { + test_private_cached_entry_diff_network_doc_load(false); }, + + // Public cache entries. + function test_public_cache_private_allowed() { + test_public_cached_entry(true); }, + function test_public_cache_private_forbidden() { + test_public_cached_entry(false); }, + + // Public cache entries for document loads. + function test_public_cache_private_allowed_doc_load() { + test_public_cached_entry_doc_load(true); }, + function test_public_cache_private_forbidden_doc_load() { + test_public_cached_entry_doc_load(false); } + ]; + + tests.forEach(function(test) { + add_test(test); + }); + + do_print("Added tests."); +} + +function run_test() { + setup_http_server(); + + setup_and_add_tests(); + + run_next_test(); +} diff --git a/netwerk/test/unit/test_speculative_connect.js b/netwerk/test/unit/test_speculative_connect.js index 35d01371b8a..092369f85e0 100644 --- a/netwerk/test/unit/test_speculative_connect.js +++ b/netwerk/test/unit/test_speculative_connect.js @@ -119,7 +119,9 @@ TestOutputStreamCallback.prototype = { } } else { // A refusal to connect speculatively should throw an error. - do_check_eq(e.result, Cr.NS_ERROR_CONNECTION_REFUSED); + do_check_true(e.result == Cr.NS_ERROR_UNKNOWN_HOST || + e.result == Cr.NS_ERROR_UNKNOWN_PROXY_HOST || + e.result == Cr.NS_ERROR_CONNECTION_REFUSED); } this.transport.close(Cr.NS_BINDING_ABORTED); this.next(); @@ -321,6 +323,18 @@ function next_test() { * Main entry function for test execution. */ function run_test() { + // Enable speculative connects on loopback for testing. + { + var prefs = Cc["@mozilla.org/preferences-service;1"] + .getService(Ci.nsIPrefBranch); + if (!prefs.getBoolPref("network.http.speculative.allowLoopback")) { + prefs.setBoolPref("network.http.speculative.allowLoopback", true); + do_register_cleanup(function() { + prefs.setBoolPref("network.http.speculative.allowLoopback", false); + }); + } + } + ios = Cc["@mozilla.org/network/io-service;1"] .getService(Ci.nsIIOService); diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index 2168531beb8..30958048d86 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -236,6 +236,8 @@ skip-if = os == "android" [test_httpauth.js] [test_httpcancel.js] [test_httpResponseTimeout.js] +# Bug 354493: fails on Android, but feature disabled by default. +skip-if = os == "android" [test_httpsuspend.js] [test_idnservice.js] [test_idn_urls.js] @@ -251,6 +253,7 @@ skip-if = os == "android" [test_net_addr.js] # Bug 732363: test fails on windows for unknown reasons. skip-if = os == "win" +[test_networkZonePolicy.js] [test_nojsredir.js] [test_offline_status.js] [test_parse_content_type.js]