Bug 1487032 - Store origin/site info in CompartmentPrivate. r=bholley

This will let us answer the following questions (in a performant way):

1) What's the compartment's origin? Necessary to implement compartment-per-origin.
2) What's the origin's site? Necessary for the new Wrap() algorithm.
3) Has any realm in the compartment set document.domain? Necessary for the new Wrap() algorithm.

Differential Revision: https://phabricator.services.mozilla.com/D5423

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jan de Mooij 2018-09-11 09:01:14 +00:00
Родитель f4441de2f2
Коммит cb90b553cd
16 изменённых файлов: 255 добавлений и 37 удалений

Просмотреть файл

@ -534,4 +534,12 @@ BasePrincipal::FinishInit(const nsACString& aOriginNoSuffix,
mOriginNoSuffix = NS_Atomize(aOriginNoSuffix);
}
bool
SiteIdentifier::Equals(const SiteIdentifier& aOther) const
{
MOZ_ASSERT(IsInitialized());
MOZ_ASSERT(aOther.IsInitialized());
return mPrincipal->FastEquals(aOther.mPrincipal);
}
} // namespace mozilla

Просмотреть файл

@ -25,6 +25,39 @@ namespace extensions {
class WebExtensionPolicy;
}
class BasePrincipal;
// Codebase principals (and codebase principals embedded within expanded
// principals) stored in SiteIdentifier are guaranteed to contain only the
// eTLD+1 part of the original domain. This is used to determine whether two
// origins are same-site: if it's possible for two origins to access each other
// (maybe after mutating document.domain), then they must have the same site
// identifier.
class SiteIdentifier
{
public:
void Init(BasePrincipal* aPrincipal)
{
MOZ_ASSERT(aPrincipal);
mPrincipal = aPrincipal;
}
bool IsInitialized() const { return !!mPrincipal; }
bool Equals(const SiteIdentifier& aOther) const;
private:
friend class ::ExpandedPrincipal;
BasePrincipal* GetPrincipal() const
{
MOZ_ASSERT(IsInitialized());
return mPrincipal;
}
RefPtr<BasePrincipal> mPrincipal;
};
/*
* Base class from which all nsIPrincipal implementations inherit. Use this for
* default implementations and other commonalities between principal
@ -166,6 +199,8 @@ public:
uint32_t GetOriginNoSuffixHash() const { return mOriginNoSuffix->hash(); }
virtual nsresult GetSiteIdentifier(SiteIdentifier& aSite) = 0;
protected:
virtual ~BasePrincipal();

Просмотреть файл

@ -367,15 +367,27 @@ ContentPrincipal::SetDomain(nsIURI* aDomain)
js::ContentCompartmentsOnly());
NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
// Set the changed-document-domain flag on compartments containing realms
// using this principal.
auto cb = [](JSContext*, void*, JS::Handle<JS::Realm*> aRealm) {
JS::Compartment* comp = JS::GetCompartmentForRealm(aRealm);
xpc::SetCompartmentChangedDocumentDomain(comp);
};
JS::IterateRealmsWithPrincipals(cx, principals, nullptr, cb);
return NS_OK;
}
NS_IMETHODIMP
ContentPrincipal::GetBaseDomain(nsACString& aBaseDomain)
static nsresult
GetBaseDomainHelper(const nsCOMPtr<nsIURI>& aCodebase,
bool* aHasBaseDomain,
nsACString& aBaseDomain)
{
*aHasBaseDomain = false;
// For a file URI, we return the file path.
if (NS_URIIsLocalFile(mCodebase)) {
nsCOMPtr<nsIURL> url = do_QueryInterface(mCodebase);
if (NS_URIIsLocalFile(aCodebase)) {
nsCOMPtr<nsIURL> url = do_QueryInterface(aCodebase);
if (url) {
return url->GetFilePath(aBaseDomain);
@ -383,7 +395,7 @@ ContentPrincipal::GetBaseDomain(nsACString& aBaseDomain)
}
bool hasNoRelativeFlag;
nsresult rv = NS_URIChainHasFlags(mCodebase,
nsresult rv = NS_URIChainHasFlags(aCodebase,
nsIProtocolHandler::URI_NORELATIVE,
&hasNoRelativeFlag);
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -391,34 +403,41 @@ ContentPrincipal::GetBaseDomain(nsACString& aBaseDomain)
}
if (hasNoRelativeFlag) {
return mCodebase->GetSpec(aBaseDomain);
return aCodebase->GetSpec(aBaseDomain);
}
*aHasBaseDomain = true;
// For everything else, we ask the TLD service via
// the ThirdPartyUtil.
nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
do_GetService(THIRDPARTYUTIL_CONTRACTID);
if (thirdPartyUtil) {
return thirdPartyUtil->GetBaseDomain(mCodebase, aBaseDomain);
return thirdPartyUtil->GetBaseDomain(aCodebase, aBaseDomain);
}
return NS_OK;
}
NS_IMETHODIMP
ContentPrincipal::GetBaseDomain(nsACString& aBaseDomain)
{
bool hasBaseDomain;
return GetBaseDomainHelper(mCodebase, &hasBaseDomain, aBaseDomain);
}
NS_IMETHODIMP
ContentPrincipal::GetSiteOrigin(nsACString& aSiteOrigin)
{
// Get the eTLDService & determine our base domain. If we don't have a valid
// BaseDomain, we can fall-back to GetOrigin.
nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
if (NS_WARN_IF(!tldService)) {
return GetOrigin(aSiteOrigin);
}
// Determine our base domain.
bool hasBaseDomain;
nsAutoCString baseDomain;
nsresult rv = tldService->GetBaseDomain(mCodebase, 0, baseDomain);
if (NS_FAILED(rv)) {
nsresult rv = GetBaseDomainHelper(mCodebase, &hasBaseDomain, baseDomain);
NS_ENSURE_SUCCESS(rv, rv);
if (!hasBaseDomain) {
// This is a special URI ("file:", "about:", "view-source:", etc). Just
// return the origin.
return GetOrigin(aSiteOrigin);
}
@ -431,27 +450,38 @@ ContentPrincipal::GetSiteOrigin(nsACString& aSiteOrigin)
.SetHostPort(baseDomain)
.Finalize(siteUri);
MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create siteUri");
if (NS_FAILED(rv)) {
return GetOrigin(aSiteOrigin);
}
NS_ENSURE_SUCCESS(rv, rv);
rv = GenerateOriginNoSuffixFromURI(siteUri, aSiteOrigin);
MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create siteOriginNoSuffix");
if (NS_FAILED(rv)) {
return GetOrigin(aSiteOrigin);
}
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString suffix;
rv = GetOriginSuffix(suffix);
MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create suffix");
if (NS_FAILED(rv)) {
return GetOrigin(aSiteOrigin);
}
NS_ENSURE_SUCCESS(rv, rv);
aSiteOrigin.Append(suffix);
return NS_OK;
}
nsresult
ContentPrincipal::GetSiteIdentifier(SiteIdentifier& aSite)
{
nsCString siteOrigin;
nsresult rv = GetSiteOrigin(siteOrigin);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<BasePrincipal> principal = CreateCodebasePrincipal(siteOrigin);
if (!principal) {
NS_WARNING("could not instantiate codebase principal");
return NS_ERROR_FAILURE;
}
aSite.Init(principal);
return NS_OK;
}
WebExtensionPolicy*
ContentPrincipal::AddonPolicy()
{

Просмотреть файл

@ -43,6 +43,8 @@ public:
virtual nsresult GetScriptLocation(nsACString& aStr) override;
nsresult GetSiteIdentifier(SiteIdentifier& aSite) override;
static nsresult
GenerateOriginNoSuffixFromURI(nsIURI* aURI, nsACString& aOrigin);

Просмотреть файл

@ -309,3 +309,25 @@ ExpandedPrincipal::Write(nsIObjectOutputStream* aStream)
return NS_OK;
}
nsresult
ExpandedPrincipal::GetSiteIdentifier(SiteIdentifier& aSite)
{
// Call GetSiteIdentifier on each of our principals and return a new
// ExpandedPrincipal.
nsTArray<nsCOMPtr<nsIPrincipal>> whitelist;
for (const auto& principal : mPrincipals) {
SiteIdentifier site;
nsresult rv = Cast(principal)->GetSiteIdentifier(site);
NS_ENSURE_SUCCESS(rv, rv);
whitelist.AppendElement(site.GetPrincipal());
}
RefPtr<ExpandedPrincipal> expandedPrincipal =
ExpandedPrincipal::Create(whitelist, OriginAttributesRef());
MOZ_ASSERT(expandedPrincipal, "ExpandedPrincipal::Create returned nullptr?");
aSite.Init(expandedPrincipal);
return NS_OK;
}

Просмотреть файл

@ -47,6 +47,8 @@ public:
// URL. See BasePrincipal::PrincipalToInherit.
nsIPrincipal* PrincipalToInherit(nsIURI* aRequestedURI = nullptr);
nsresult GetSiteIdentifier(mozilla::SiteIdentifier& aSite) override;
protected:
explicit ExpandedPrincipal(nsTArray<nsCOMPtr<nsIPrincipal>> &aWhiteList);

Просмотреть файл

@ -77,6 +77,11 @@ public:
virtual nsresult GetScriptLocation(nsACString &aStr) override;
nsresult GetSiteIdentifier(SiteIdentifier& aSite) override {
aSite.Init(this);
return NS_OK;
}
protected:
virtual ~NullPrincipal() = default;

Просмотреть файл

@ -49,6 +49,11 @@ public:
virtual nsresult GetScriptLocation(nsACString &aStr) override;
nsresult GetSiteIdentifier(SiteIdentifier& aSite) override {
aSite.Init(this);
return NS_OK;
}
protected:
virtual ~SystemPrincipal(void) {}

Просмотреть файл

@ -3377,9 +3377,19 @@ bool
CreateGlobalOptionsWithXPConnect::PostCreateGlobal(JSContext* aCx,
JS::Handle<JSObject*> aGlobal)
{
JSPrincipals* principals =
JS::GetRealmPrincipals(js::GetNonCCWObjectRealm(aGlobal));
nsIPrincipal* principal = nsJSPrincipals::get(principals);
// We create the SiteIdentifier here instead of in the XPCWrappedNativeScope
// constructor because this is fallible.
SiteIdentifier site;
nsresult rv = BasePrincipal::Cast(principal)->GetSiteIdentifier(site);
NS_ENSURE_SUCCESS(rv, false);
// Invoking the XPCWrappedNativeScope constructor automatically hooks it
// up to the compartment of aGlobal.
(void) new XPCWrappedNativeScope(aCx, aGlobal);
// up to the realm of aGlobal.
(void) new XPCWrappedNativeScope(aCx, aGlobal, site);
return true;
}

Просмотреть файл

@ -239,6 +239,24 @@ JS::IterateRealms(JSContext* cx, void* data, JS::IterateRealmCallback realmCallb
}
}
JS_PUBLIC_API(void)
JS::IterateRealmsWithPrincipals(JSContext* cx, JSPrincipals* principals, void* data,
JS::IterateRealmCallback realmCallback)
{
MOZ_ASSERT(principals);
AutoTraceSession session(cx->runtime());
Rooted<Realm*> realm(cx);
for (RealmsIter r(cx->runtime()); !r.done(); r.next()) {
if (r->principals() != principals) {
continue;
}
realm = r;
(*realmCallback)(cx, data, realm);
}
}
JS_PUBLIC_API(void)
JS::IterateRealmsInCompartment(JSContext* cx, JS::Compartment* compartment, void* data,
JS::IterateRealmCallback realmCallback)

Просмотреть файл

@ -903,6 +903,13 @@ using IterateRealmCallback = void (*)(JSContext* cx, void* data, Handle<Realm*>
extern JS_PUBLIC_API(void)
IterateRealms(JSContext* cx, void* data, IterateRealmCallback realmCallback);
/**
* Like IterateRealms, but only call the callback for realms using |principals|.
*/
extern JS_PUBLIC_API(void)
IterateRealmsWithPrincipals(JSContext* cx, JSPrincipals* principals, void* data,
IterateRealmCallback realmCallback);
/**
* Like IterateRealms, but only iterates realms in |compartment|.
*/

Просмотреть файл

@ -180,8 +180,10 @@ public:
namespace xpc {
CompartmentPrivate::CompartmentPrivate(JS::Compartment* c)
: wantXrays(false)
CompartmentPrivate::CompartmentPrivate(JS::Compartment* c, mozilla::BasePrincipal* origin,
const SiteIdentifier& site)
: originInfo(origin, site)
, wantXrays(false)
, allowWaivers(true)
, isWebExtensionContentScript(false)
, allowCPOWs(false)
@ -569,6 +571,20 @@ EnableUniversalXPConnect(JSContext* cx)
return scope->AttachComponentsObject(cx);
}
bool
CompartmentOriginInfo::IsSameOrigin(nsIPrincipal* aOther) const
{
return mOrigin->FastEquals(aOther);
}
void
SetCompartmentChangedDocumentDomain(JS::Compartment* compartment)
{
CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
MOZ_ASSERT(priv);
priv->originInfo.SetChangedDocumentDomain();
}
JSObject*
UnprivilegedJunkScope()
{

Просмотреть файл

@ -57,7 +57,8 @@ RemoteXULForbidsXBLScope(nsIPrincipal* aPrincipal, HandleObject aGlobal)
}
XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx,
JS::HandleObject aGlobal)
JS::HandleObject aGlobal,
const mozilla::SiteIdentifier& aSite)
: mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)),
mWrappedNativeProtoMap(ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)),
mComponents(nullptr),
@ -83,10 +84,13 @@ XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx,
MOZ_COUNT_CTOR(XPCWrappedNativeScope);
nsIPrincipal* principal = GetPrincipal();
// Create the compartment private.
JS::Compartment* c = js::GetObjectCompartment(aGlobal);
MOZ_ASSERT(!JS_GetCompartmentPrivate(c));
CompartmentPrivate* priv = new CompartmentPrivate(c);
CompartmentPrivate* priv =
new CompartmentPrivate(c, BasePrincipal::Cast(principal), aSite);
JS_SetCompartmentPrivate(c, priv);
// Attach ourselves to the realm private.
@ -98,7 +102,6 @@ XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx,
// Determine whether we would allow an XBL scope in this situation.
// In addition to being pref-controlled, we also disable XBL scopes for
// remote XUL domains, _except_ if we have an additional pref override set.
nsIPrincipal* principal = GetPrincipal();
mAllowContentXBLScope = !RemoteXULForbidsXBLScope(principal, aGlobal);
// Determine whether to use an XBL scope.

Просмотреть файл

@ -466,6 +466,10 @@ CreateGlobalObject(JSContext* cx, const JSClass* clasp, nsIPrincipal* principal,
MOZ_RELEASE_ASSERT(principal != nsContentUtils::GetNullSubjectPrincipal(),
"The null subject principal is getting inherited - fix that!");
SiteIdentifier site;
nsresult rv = BasePrincipal::Cast(principal)->GetSiteIdentifier(site);
NS_ENSURE_SUCCESS(rv, nullptr);
RootedObject global(cx,
JS_NewGlobalObject(cx, clasp, nsJSPrincipals::get(principal),
JS::DontFireOnNewGlobalHook, aOptions));
@ -474,9 +478,9 @@ CreateGlobalObject(JSContext* cx, const JSClass* clasp, nsIPrincipal* principal,
}
JSAutoRealm ar(cx, global);
// The constructor automatically attaches the scope to the compartment private
// The constructor automatically attaches the scope to the realm private
// of |global|.
(void) new XPCWrappedNativeScope(cx, global);
(void) new XPCWrappedNativeScope(cx, global, site);
if (clasp->flags & JSCLASS_DOM_GLOBAL) {
#ifdef DEBUG

Просмотреть файл

@ -935,7 +935,8 @@ public:
// object is wrapped into the compartment of the global.
JSObject* EnsureContentXBLScope(JSContext* cx);
XPCWrappedNativeScope(JSContext* cx, JS::HandleObject aGlobal);
XPCWrappedNativeScope(JSContext* cx, JS::HandleObject aGlobal,
const mozilla::SiteIdentifier& aSite);
nsAutoPtr<JSObject2JSObjectMap> mWaiverWrapperMap;
@ -2914,6 +2915,51 @@ enum WrapperDenialType {
};
bool ReportWrapperDenial(JSContext* cx, JS::HandleId id, WrapperDenialType type, const char* reason);
class CompartmentOriginInfo
{
public:
CompartmentOriginInfo(const CompartmentOriginInfo&) = delete;
CompartmentOriginInfo(mozilla::BasePrincipal* aOrigin,
const mozilla::SiteIdentifier& aSite)
: mOrigin(aOrigin)
, mSite(aSite)
{
MOZ_ASSERT(aOrigin);
MOZ_ASSERT(aSite.IsInitialized());
}
bool IsSameOrigin(nsIPrincipal* aOther) const;
const mozilla::SiteIdentifier& SiteRef() const {
return mSite;
}
bool HasChangedDocumentDomain() const {
return mChangedDocumentDomain;
}
void SetChangedDocumentDomain() {
mChangedDocumentDomain = true;
}
private:
// All globals in the compartment must have this origin. Note that
// individual globals and principals can have their domain changed via
// document.domain, so this principal must not be used for things like
// subsumesConsideringDomain or equalsConsideringDomain. Use the realm's
// principal for that.
RefPtr<mozilla::BasePrincipal> mOrigin;
// In addition to the origin we also store the SiteIdentifier. When realms
// in different compartments can become same-origin (via document.domain),
// these compartments must have equal SiteIdentifiers. (This is derived from
// mOrigin but we cache it here for performance reasons.)
mozilla::SiteIdentifier mSite;
// True if any global in this compartment mutated document.domain.
bool mChangedDocumentDomain = false;
};
// The CompartmentPrivate contains XPConnect-specific stuff related to each JS
// compartment. Since compartments are trust domains, this means mostly
// information needed to select the right security policy for cross-compartment
@ -2924,7 +2970,8 @@ class CompartmentPrivate
CompartmentPrivate(const CompartmentPrivate&) = delete;
public:
explicit CompartmentPrivate(JS::Compartment* c);
CompartmentPrivate(JS::Compartment* c, mozilla::BasePrincipal* origin,
const mozilla::SiteIdentifier& site);
~CompartmentPrivate();
@ -2941,6 +2988,8 @@ public:
return Get(compartment);
}
CompartmentOriginInfo originInfo;
// Controls whether this compartment gets Xrays to same-origin. This behavior
// is deprecated, but is still the default for sandboxes for compatibity
// reasons.

Просмотреть файл

@ -92,6 +92,8 @@ bool IsInUAWidgetScope(JSObject* obj);
bool IsInSandboxCompartment(JSObject* obj);
void SetCompartmentChangedDocumentDomain(JS::Compartment* compartment);
// Return a raw XBL scope object corresponding to contentScope, which must
// be an object whose global is a DOM window.
//