Bug 354493 - Add nsINetworkZonePolicy to protect resources loaded from private IPs r=mcmanus

This commit is contained in:
Steve Workman 2014-07-17 11:08:20 -07:00
parent ed98140f75
commit 2cabfdd5a8
37 changed files with 1618 additions and 59 deletions

View File

@ -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

View File

@ -61,6 +61,7 @@ XPIDL_SOURCES += [
'nsINetworkPredictor.idl',
'nsINetworkPredictorVerifier.idl',
'nsINetworkProperties.idl',
'nsINetworkZonePolicy.idl',
'nsINSSErrorsService.idl',
'nsIParentChannel.idl',
'nsIParentRedirectingChannel.idl',

View File

@ -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;
};

View File

@ -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++

View File

@ -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);
};

View File

@ -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

View File

@ -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__

View File

@ -47,6 +47,7 @@ UNIFIED_SOURCES += [
'nsMIMEInputStream.cpp',
'nsNetAddr.cpp',
'nsNetStrings.cpp',
'nsNetworkZonePolicy.cpp',
'nsPACMan.cpp',
'nsPreloadedStream.cpp',
'nsProtocolProxyService.cpp',

View File

@ -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()
{

View File

@ -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;
};
/**

View File

@ -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

View File

@ -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;

View File

@ -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> nsNetworkZonePolicy::sSingleton;
nsNetworkZonePolicy::nsNetworkZonePolicy()
{
Preferences::AddBoolVarCache(&sNZPEnabled, "network.zonepolicy.enabled");
// Register for shutdown notification.
nsCOMPtr<nsIObserverService> 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>
nsNetworkZonePolicy::GetSingleton()
{
if (sShutdown) {
return nullptr;
}
if (!sSingleton) {
sSingleton = new nsNetworkZonePolicy();
}
// Return a ref ptr to the singleton.
nsRefPtr<nsNetworkZonePolicy> 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<nsILoadGroup>
nsNetworkZonePolicy::GetLoadGroupParent(nsILoadGroup *aLoadGroup)
{
if (NS_WARN_IF(!aLoadGroup)) {
return nullptr;
}
MOZ_ASSERT(aLoadGroup);
DebugOnly<nsresult> rv = NS_OK;
nsCOMPtr<nsILoadGroup> parent;
nsCOMPtr<nsILoadGroupChild> 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<nsILoadGroup>
nsNetworkZonePolicy::GetOwningLoadGroup(nsILoadGroup *aLoadGroup)
{
if (NS_WARN_IF(!aLoadGroup)) {
return nullptr;
}
MOZ_ASSERT(aLoadGroup);
DebugOnly<nsresult> rv = NS_OK;
nsCOMPtr<nsILoadGroup> 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<nsILoadGroup>
nsNetworkZonePolicy::GetParentDocShellsLoadGroup(nsILoadGroup *aLoadGroup)
{
if (NS_WARN_IF(!aLoadGroup)) {
return nullptr;
}
MOZ_ASSERT(aLoadGroup);
DebugOnly<nsresult> rv = NS_OK;
nsCOMPtr<nsIRequestObserver> observer;
rv = aLoadGroup->GetGroupObserver(getter_AddRefs(observer));
if (!observer) {
return nullptr;
}
nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(observer);
if (!docShell) {
return nullptr;
}
nsCOMPtr<nsIDocShellTreeItem> parentAsTreeItem;
docShell->GetSameTypeParent(getter_AddRefs(parentAsTreeItem));
if (!parentAsTreeItem) {
return nullptr;
}
nsCOMPtr<nsIDocumentLoader> parentAsDocLoader =
do_QueryInterface(parentAsTreeItem);
if (!parentAsDocLoader) {
return nullptr;
}
nsCOMPtr<nsILoadGroup> 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<nsILoadGroup> 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<nsILoadGroup> 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<nsILoadGroup> 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

View File

@ -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<nsNetworkZonePolicy> GetSingleton();
private:
nsNetworkZonePolicy();
virtual ~nsNetworkZonePolicy();
// Returns the parent loadgroup of aLoadGroup, or nullptr if none exists.
already_AddRefed<nsILoadGroup> GetLoadGroupParent(nsILoadGroup *aLoadGroup);
// Returns the owning loadgroup of aLoadGroup, or nullptr if none exists.
already_AddRefed<nsILoadGroup> GetOwningLoadGroup(nsILoadGroup *aLoadGroup);
// Returns the loadgroup of the parent docshell of aLoadGroup's docshell, or
// nullptr if none exists.
already_AddRefed<nsILoadGroup>
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<nsNetworkZonePolicy> sSingleton;
};
} // namespace net
} // namespace mozilla
#endif /* __nznetworkzonepolicy_h__ */

View File

@ -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("<IP-to-string failed>");
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<nsIDNSRecord *>(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)) {

View File

@ -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
*/

View File

@ -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 }
};

View File

@ -11,7 +11,7 @@
#include "nsCacheEntry.h"
#ifdef XP_WIN
#include <winsock.h> // for htonl/ntohl
#include <winsock2.h> // for htonl/ntohl
#endif

View File

@ -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)
{

View File

@ -111,6 +111,8 @@ union NetAddr {
#endif
// introduced to support nsTArray<NetAddr> (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

View File

@ -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<nsresult> rv = HasMore(&more);
MOZ_ASSERT(NS_SUCCEEDED(rv));
return more;
}
NS_IMETHODIMP
nsDNSRecord::Rewind()
{
@ -293,9 +342,18 @@ nsDNSAsyncRequest::OnLookupComplete(nsHostResolver *resolver,
nsCOMPtr<nsIDNSRecord> rec;
if (NS_SUCCEEDED(status)) {
NS_ASSERTION(hostRecord, "no host record");
rec = new nsDNSRecord(hostRecord);
if (!rec)
status = NS_ERROR_OUT_OF_MEMORY;
nsRefPtr<nsDNSRecord> 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<nsDNSRecord> 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);
}
}

View File

@ -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);
};

View File

@ -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()

View File

@ -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

View File

@ -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
//-----------------------------------------------------------------------------

View File

@ -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<nsISocketTransport> 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));
}
}
}
}

View File

@ -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<nsINetworkZonePolicy> mNZP;
};
} } // namespace mozilla::net

View File

@ -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()
{

View File

@ -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 {

View File

@ -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());

View File

@ -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"))) {

View File

@ -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;

View File

@ -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);

View File

@ -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() {});
});
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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]