Skip to content

Commit

Permalink
Bug 1132743: Only allow CSS Unprefixing Service to be activated for h…
Browse files Browse the repository at this point in the history
…osts on a small, hardcoded whitelist. r=dbaron f=bz
  • Loading branch information
rmottola committed Aug 6, 2019
1 parent 95a0e6c commit 6afd0dd
Show file tree
Hide file tree
Showing 12 changed files with 719 additions and 209 deletions.
11 changes: 10 additions & 1 deletion caps/nsIPrincipal.idl
Expand Up @@ -20,7 +20,7 @@ interface nsIContentSecurityPolicy;
[ptr] native JSPrincipals(JSPrincipals);
[ptr] native PrincipalArray(nsTArray<nsCOMPtr<nsIPrincipal> >);

[scriptable, builtinclass, uuid(204555e7-04ad-4cc8-9f0e-840615cc43e8)]
[scriptable, builtinclass, uuid(264fe8ca-c382-11e4-95a6-782bcbaebb28)]
interface nsIPrincipal : nsISerializable
{
/**
Expand Down Expand Up @@ -230,6 +230,15 @@ interface nsIPrincipal : nsISerializable
* unknown, hence assumed minimally privileged, security context).
*/
[infallible] readonly attribute boolean isNullPrincipal;

/**
* Returns true if this principal's origin is recognized as being on the
* whitelist of sites that can use the CSS Unprefixing Service.
*
* (This interface provides a trivial implementation, just returning false;
* subclasses can implement something more complex as-needed.)
*/
[noscript,notxpcom,nostdcall] bool IsOnCSSUnprefixingWhitelist();
};

/**
Expand Down
6 changes: 6 additions & 0 deletions caps/nsNullPrincipal.cpp
Expand Up @@ -318,6 +318,12 @@ nsNullPrincipal::GetBaseDomain(nsACString& aBaseDomain)
return mURI->GetPath(aBaseDomain);
}

bool
nsNullPrincipal::IsOnCSSUnprefixingWhitelist()
{
return false;
}

/**
* nsISerializable implementation
*/
Expand Down
161 changes: 161 additions & 0 deletions caps/nsPrincipal.cpp
Expand Up @@ -14,12 +14,14 @@
#include "pratom.h"
#include "nsIURI.h"
#include "nsJSPrincipals.h"
#include "nsIEffectiveTLDService.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsIClassInfoImpl.h"
#include "nsIProtocolHandler.h"
#include "nsError.h"
#include "nsIContentSecurityPolicy.h"
#include "nsNetCID.h"
#include "jswrapper.h"

#include "mozilla/dom/ScriptSettings.h"
Expand All @@ -31,6 +33,8 @@

using namespace mozilla;

static bool gIsWhitelistingTestDomains = false;
// XXXdholbert Add to InitializeStatics():
static bool gCodeBasePrincipalSupport = false;
static bool gIsObservingCodeBasePrincipalSupport = false;

Expand Down Expand Up @@ -126,6 +130,15 @@ NS_IMPL_CI_INTERFACE_GETTER(nsPrincipal,
NS_IMPL_ADDREF_INHERITED(nsPrincipal, nsBasePrincipal)
NS_IMPL_RELEASE_INHERITED(nsPrincipal, nsBasePrincipal)

// Called at startup:
/* static */ void
nsPrincipal::InitializeStatics()
{
Preferences::AddBoolVarCache(
&gIsWhitelistingTestDomains,
"layout.css.unprefixing-service.include-test-domains");
}

nsPrincipal::nsPrincipal()
: mAppId(nsIScriptSecurityManager::UNKNOWN_APP_ID)
, mInMozBrowser(false)
Expand Down Expand Up @@ -608,6 +621,145 @@ nsPrincipal::GetAppStatus()
return nsScriptSecurityManager::AppStatusForPrincipal(this);
}

// Helper-function to indicate whether the CSS Unprefixing Service
// whitelist should include dummy domains that are only intended for
// use in testing. (Controlled by a pref.)
static inline bool
IsWhitelistingTestDomains()
{
return gIsWhitelistingTestDomains;
}

// Checks if the given URI's host is on our "full domain" whitelist
// (i.e. if it's an exact match against a domain that needs unprefixing)
static bool
IsOnFullDomainWhitelist(nsIURI* aURI)
{
nsAutoCString hostStr;
nsresult rv = aURI->GetHost(hostStr);
NS_ENSURE_SUCCESS(rv, false);

// NOTE: This static whitelist is expected to be short. If that changes,
// we should consider a different representation; e.g. hash-set, prefix tree.
static const nsLiteralCString sFullDomainsOnWhitelist[] = {
// 0th entry only active when testing:
NS_LITERAL_CSTRING("test1.example.org"),
NS_LITERAL_CSTRING("map.baidu.com"),
NS_LITERAL_CSTRING("music.baidu.com"),
NS_LITERAL_CSTRING("3g.163.com"),
NS_LITERAL_CSTRING("3glogo.gtimg.com"), // for 3g.163.com
NS_LITERAL_CSTRING("info.3g.qq.com"), // for 3g.qq.com
NS_LITERAL_CSTRING("3gimg.qq.com"), // for 3g.qq.com
NS_LITERAL_CSTRING("img.m.baidu.com"), // for [shucheng|ks].baidu.com
NS_LITERAL_CSTRING("m.mogujie.com"),
NS_LITERAL_CSTRING("touch.qunar.com"),
};
static const size_t sNumFullDomainsOnWhitelist =
MOZ_ARRAY_LENGTH(sFullDomainsOnWhitelist);

// Skip 0th (dummy) entry in whitelist, unless a pref is enabled.
const size_t firstWhitelistIdx = IsWhitelistingTestDomains() ? 0 : 1;

for (size_t i = firstWhitelistIdx; i < sNumFullDomainsOnWhitelist; ++i) {
if (hostStr == sFullDomainsOnWhitelist[i]) {
return true;
}
}
return false;
}

// Checks if the given URI's host is on our "base domain" whitelist
// (i.e. if it's a subdomain of some host that we've whitelisted as needing
// unprefixing for all its subdomains)
static bool
IsOnBaseDomainWhitelist(nsIURI* aURI)
{
static const nsLiteralCString sBaseDomainsOnWhitelist[] = {
// 0th entry only active when testing:
NS_LITERAL_CSTRING("test2.example.org"),
NS_LITERAL_CSTRING("tbcdn.cn"), // for m.taobao.com
NS_LITERAL_CSTRING("dpfile.com"), // for m.dianping.com
NS_LITERAL_CSTRING("hao123img.com"), // for hao123.com
};
static const size_t sNumBaseDomainsOnWhitelist =
MOZ_ARRAY_LENGTH(sBaseDomainsOnWhitelist);

nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);

if (tldService) {
// Skip 0th test-entry in whitelist, unless the testing pref is enabled.
const size_t firstWhitelistIdx = IsWhitelistingTestDomains() ? 0 : 1;

// Right now, the test base-domain "test2.example.org" is the only entry in
// its whitelist with a nonzero "depth". So we'll only bother going beyond
// 0 depth (to 1) if that entry is enabled. (No point in slowing down the
// normal codepath, for the benefit of a disabled test domain.) If we add a
// "real" base-domain with a depth of >= 1 to our whitelist, we can get rid
// of this conditional & just make this a static variable.
const uint32_t maxSubdomainDepth = IsWhitelistingTestDomains() ? 1 : 0;

for (uint32_t subdomainDepth = 0;
subdomainDepth <= maxSubdomainDepth; ++subdomainDepth) {

// Get the base domain (to depth |subdomainDepth|) from passed-in URI:
nsAutoCString baseDomainStr;
nsresult rv = tldService->GetBaseDomain(aURI, subdomainDepth,
baseDomainStr);
if (NS_FAILED(rv)) {
// aURI doesn't have |subdomainDepth| levels of subdomains. If we got
// here without a match yet, then aURI is not on our whitelist.
return false;
}

// Compare the base domain against each entry in our whitelist:
for (size_t i = firstWhitelistIdx; i < sNumBaseDomainsOnWhitelist; ++i) {
if (baseDomainStr == sBaseDomainsOnWhitelist[i]) {
return true;
}
}
}
}

return false;
}

// The actual (non-cached) implementation of IsOnCSSUnprefixingWhitelist():
static bool
IsOnCSSUnprefixingWhitelistImpl(nsIURI* aURI)
{
// Check scheme, so we can drop any non-HTTP/HTTPS URIs right away
nsAutoCString schemeStr;
nsresult rv = aURI->GetScheme(schemeStr);
NS_ENSURE_SUCCESS(rv, false);

// Only proceed if scheme is "http" or "https"
if (!(StringBeginsWith(schemeStr, NS_LITERAL_CSTRING("http")) &&
(schemeStr.Length() == 4 ||
(schemeStr.Length() == 5 && schemeStr[4] == 's')))) {
return false;
}

return (IsOnFullDomainWhitelist(aURI) ||
IsOnBaseDomainWhitelist(aURI));
}


bool
nsPrincipal::IsOnCSSUnprefixingWhitelist()
{
if (mIsOnCSSUnprefixingWhitelist.isNothing()) {
// Value not cached -- perform our lazy whitelist-check.
// (NOTE: If our URI is mutable, we just assume it's not on the whitelist,
// since our caching strategy won't work. This isn't expected to be common.)
mIsOnCSSUnprefixingWhitelist.emplace(
mCodebaseImmutable &&
IsOnCSSUnprefixingWhitelistImpl(mCodebase));
}

return *mIsOnCSSUnprefixingWhitelist;
}

/************************************************************************************************************************/

static const char EXPANDED_PRINCIPAL_SPEC[] = "[Expanded Principal]";
Expand Down Expand Up @@ -823,6 +975,15 @@ nsExpandedPrincipal::GetBaseDomain(nsACString& aBaseDomain)
return NS_ERROR_NOT_AVAILABLE;
}

bool
nsExpandedPrincipal::IsOnCSSUnprefixingWhitelist()
{
// CSS Unprefixing Whitelist is a per-origin thing; doesn't really make sense
// for an expanded principal. (And probably shouldn't be needed.)
return false;
}


void
nsExpandedPrincipal::GetScriptLocation(nsACString& aStr)
{
Expand Down
8 changes: 8 additions & 0 deletions caps/nsPrincipal.h
Expand Up @@ -66,6 +66,7 @@ class nsPrincipal final : public nsBasePrincipal
NS_IMETHOD GetUnknownAppId(bool* aUnknownAppId) override;
NS_IMETHOD GetIsNullPrincipal(bool* aIsNullPrincipal) override;
NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
virtual bool IsOnCSSUnprefixingWhitelist() override;
#ifdef DEBUG
virtual void dumpImpl() override;
#endif
Expand Down Expand Up @@ -102,6 +103,11 @@ class nsPrincipal final : public nsBasePrincipal
*/
static nsresult GetOriginForURI(nsIURI* aURI, char **aOrigin);

/**
* Called at startup to setup static data, e.g. about:config pref-observers.
*/
static void InitializeStatics();

nsCOMPtr<nsIURI> mDomain;
nsCOMPtr<nsIURI> mCodebase;
uint32_t mAppId;
Expand All @@ -110,6 +116,7 @@ class nsPrincipal final : public nsBasePrincipal
bool mCodebaseImmutable;
bool mDomainImmutable;
bool mInitialized;
mozilla::Maybe<bool> mIsOnCSSUnprefixingWhitelist; // Lazily-computed

protected:
virtual ~nsPrincipal();
Expand Down Expand Up @@ -149,6 +156,7 @@ class nsExpandedPrincipal : public nsIExpandedPrincipal, public nsBasePrincipal
NS_IMETHOD GetUnknownAppId(bool* aUnknownAppId) override;
NS_IMETHOD GetIsNullPrincipal(bool* aIsNullPrincipal) override;
NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
virtual bool IsOnCSSUnprefixingWhitelist() override;
#ifdef DEBUG
virtual void dumpImpl() override;
#endif
Expand Down
7 changes: 7 additions & 0 deletions caps/nsSystemPrincipal.cpp
Expand Up @@ -201,6 +201,13 @@ nsSystemPrincipal::GetBaseDomain(nsACString& aBaseDomain)
return NS_OK;
}

bool
nsSystemPrincipal::IsOnCSSUnprefixingWhitelist()
{
// chrome stylesheets should not be fed to the CSS Unprefixing Service.
return false;
}

//////////////////////////////////////////
// Methods implementing nsISerializable //
//////////////////////////////////////////
Expand Down
2 changes: 2 additions & 0 deletions layout/build/nsLayoutStatics.cpp
Expand Up @@ -30,6 +30,7 @@
#include "nsGkAtoms.h"
#include "nsImageFrame.h"
#include "nsLayoutStylesheetCache.h"
#include "nsPrincipal.h"
#include "nsRange.h"
#include "nsRegion.h"
#include "nsRepeatService.h"
Expand Down Expand Up @@ -267,6 +268,7 @@ nsLayoutStatics::Initialize()
nsIPresShell::InitializeStatics();
TouchManager::InitializeStatics();
nsRefreshDriver::InitializeStatics();
nsPrincipal::InitializeStatics();

nsCORSListenerProxy::Startup();

Expand Down
5 changes: 3 additions & 2 deletions layout/style/nsCSSParser.cpp
Expand Up @@ -6646,11 +6646,12 @@ bool
CSSParserImpl::ShouldUseUnprefixingService()
{
if (!sUnprefixingServiceEnabled) {
// Unprefixing is globally disabled.
return false;
}

// XXXdholbert Bug 1132743: Check if stylesheet URI is on fixlist here.
return true;
// Unprefixing enabled; see if our principal is whitelisted for unprefixing.
return mSheetPrincipal && mSheetPrincipal->IsOnCSSUnprefixingWhitelist();
}

bool
Expand Down
3 changes: 3 additions & 0 deletions layout/style/test/mochitest.ini
Expand Up @@ -224,6 +224,9 @@ skip-if = buildapp == 'b2g' || toolkit == 'android' #bug 775227 # b2g(times out,
[test_units_length.html]
[test_units_time.html]
[test_unprefixing_service.html]
support-files = unprefixing_service_iframe.html unprefixing_service_utils.js
[test_unprefixing_service_prefs.html]
support-files = unprefixing_service_iframe.html unprefixing_service_utils.js
[test_value_cloning.html]
skip-if = (toolkit == 'gonk' && debug) || toolkit == 'android' #bug 775227 #debug-only failure; timed out
[test_value_computation.html]
Expand Down

0 comments on commit 6afd0dd

Please sign in to comment.