/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 sw=2 et 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 "mozilla/BasePrincipal.h" #include "nsDocShell.h" #ifdef MOZ_CRASHREPORTER #include "nsExceptionHandler.h" #endif #include "nsIAddonPolicyService.h" #include "nsIContentSecurityPolicy.h" #include "nsIEffectiveTLDService.h" #include "nsIObjectInputStream.h" #include "nsIObjectOutputStream.h" #include "nsPrincipal.h" #include "nsNetUtil.h" #include "nsIURIWithPrincipal.h" #include "nsNullPrincipal.h" #include "nsScriptSecurityManager.h" #include "nsServiceManagerUtils.h" #include "mozilla/dom/ChromeUtils.h" #include "mozilla/dom/CSPDictionariesBinding.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/dom/URLSearchParams.h" namespace mozilla { using dom::URLParams; bool OriginAttributes::sFirstPartyIsolation = false; bool OriginAttributes::sRestrictedOpenerAccess = false; void OriginAttributes::InitPrefs() { MOZ_ASSERT(NS_IsMainThread()); static bool sInited = false; if (!sInited) { sInited = true; Preferences::AddBoolVarCache(&sFirstPartyIsolation, "privacy.firstparty.isolate"); Preferences::AddBoolVarCache(&sRestrictedOpenerAccess, "privacy.firstparty.isolate.restrict_opener_access"); } } void OriginAttributes::Inherit(const OriginAttributes& aAttrs) { mAppId = aAttrs.mAppId; mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser; StripAttributes(STRIP_ADDON_ID); mUserContextId = aAttrs.mUserContextId; mPrivateBrowsingId = aAttrs.mPrivateBrowsingId; mFirstPartyDomain = aAttrs.mFirstPartyDomain; } void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument, nsIURI* aURI) { bool isFirstPartyEnabled = IsFirstPartyEnabled(); // When the pref is on, we also compute the firstPartyDomain attribute // if this is for top-level document. if (isFirstPartyEnabled && aIsTopLevelDocument) { nsCOMPtr tldService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); MOZ_ASSERT(tldService); if (!tldService) { return; } nsAutoCString baseDomain; tldService->GetBaseDomain(aURI, 0, baseDomain); mFirstPartyDomain = NS_ConvertUTF8toUTF16(baseDomain); } } void OriginAttributes::CreateSuffix(nsACString& aStr) const { UniquePtr params(new URLParams()); nsAutoString value; // // Important: While serializing any string-valued attributes, perform a // release-mode assertion to make sure that they don't contain characters that // will break the quota manager when it uses the serialization for file // naming (see addonId below). // if (mAppId != nsIScriptSecurityManager::NO_APP_ID) { value.AppendInt(mAppId); params->Set(NS_LITERAL_STRING("appId"), value); } if (mInIsolatedMozBrowser) { params->Set(NS_LITERAL_STRING("inBrowser"), NS_LITERAL_STRING("1")); } if (!mAddonId.IsEmpty()) { if (mAddonId.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) != kNotFound) { #ifdef MOZ_CRASHREPORTER CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Crash_AddonId"), NS_ConvertUTF16toUTF8(mAddonId)); #endif MOZ_CRASH(); } params->Set(NS_LITERAL_STRING("addonId"), mAddonId); } if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) { value.Truncate(); value.AppendInt(mUserContextId); params->Set(NS_LITERAL_STRING("userContextId"), value); } if (mPrivateBrowsingId) { value.Truncate(); value.AppendInt(mPrivateBrowsingId); params->Set(NS_LITERAL_STRING("privateBrowsingId"), value); } if (!mFirstPartyDomain.IsEmpty()) { MOZ_RELEASE_ASSERT(mFirstPartyDomain.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) == kNotFound); params->Set(NS_LITERAL_STRING("firstPartyDomain"), mFirstPartyDomain); } aStr.Truncate(); params->Serialize(value); if (!value.IsEmpty()) { aStr.AppendLiteral("^"); aStr.Append(NS_ConvertUTF16toUTF8(value)); } // In debug builds, check the whole string for illegal characters too (just in case). #ifdef DEBUG nsAutoCString str; str.Assign(aStr); MOZ_ASSERT(str.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) == kNotFound); #endif } void OriginAttributes::CreateAnonymizedSuffix(nsACString& aStr) const { OriginAttributes attrs = *this; if (!attrs.mFirstPartyDomain.IsEmpty()) { attrs.mFirstPartyDomain.AssignLiteral("_anonymizedFirstPartyDomain_"); } attrs.CreateSuffix(aStr); } namespace { class MOZ_STACK_CLASS PopulateFromSuffixIterator final : public URLParams::ForEachIterator { public: explicit PopulateFromSuffixIterator(OriginAttributes* aOriginAttributes) : mOriginAttributes(aOriginAttributes) { MOZ_ASSERT(aOriginAttributes); // If mPrivateBrowsingId is passed in as >0 and is not present in the suffix, // then it will remain >0 when it should be 0 according to the suffix. Set to 0 before // iterating to fix this. mOriginAttributes->mPrivateBrowsingId = 0; } bool URLParamsIterator(const nsString& aName, const nsString& aValue) override { if (aName.EqualsLiteral("appId")) { nsresult rv; int64_t val = aValue.ToInteger64(&rv); NS_ENSURE_SUCCESS(rv, false); NS_ENSURE_TRUE(val <= UINT32_MAX, false); mOriginAttributes->mAppId = static_cast(val); return true; } if (aName.EqualsLiteral("inBrowser")) { if (!aValue.EqualsLiteral("1")) { return false; } mOriginAttributes->mInIsolatedMozBrowser = true; return true; } if (aName.EqualsLiteral("addonId")) { MOZ_RELEASE_ASSERT(mOriginAttributes->mAddonId.IsEmpty()); mOriginAttributes->mAddonId.Assign(aValue); return true; } if (aName.EqualsLiteral("userContextId")) { nsresult rv; int64_t val = aValue.ToInteger64(&rv); NS_ENSURE_SUCCESS(rv, false); NS_ENSURE_TRUE(val <= UINT32_MAX, false); mOriginAttributes->mUserContextId = static_cast(val); return true; } if (aName.EqualsLiteral("privateBrowsingId")) { nsresult rv; int64_t val = aValue.ToInteger64(&rv); NS_ENSURE_SUCCESS(rv, false); NS_ENSURE_TRUE(val >= 0 && val <= UINT32_MAX, false); mOriginAttributes->mPrivateBrowsingId = static_cast(val); return true; } if (aName.EqualsLiteral("firstPartyDomain")) { MOZ_RELEASE_ASSERT(mOriginAttributes->mFirstPartyDomain.IsEmpty()); mOriginAttributes->mFirstPartyDomain.Assign(aValue); return true; } // No other attributes are supported. return false; } private: OriginAttributes* mOriginAttributes; }; } // namespace bool OriginAttributes::PopulateFromSuffix(const nsACString& aStr) { if (aStr.IsEmpty()) { return true; } if (aStr[0] != '^') { return false; } UniquePtr params(new URLParams()); params->ParseInput(Substring(aStr, 1, aStr.Length() - 1)); PopulateFromSuffixIterator iterator(this); return params->ForEach(iterator); } bool OriginAttributes::PopulateFromOrigin(const nsACString& aOrigin, nsACString& aOriginNoSuffix) { // RFindChar is only available on nsCString. nsCString origin(aOrigin); int32_t pos = origin.RFindChar('^'); if (pos == kNotFound) { aOriginNoSuffix = origin; return true; } aOriginNoSuffix = Substring(origin, 0, pos); return PopulateFromSuffix(Substring(origin, pos)); } void OriginAttributes::SyncAttributesWithPrivateBrowsing(bool aInPrivateBrowsing) { mPrivateBrowsingId = aInPrivateBrowsing ? 1 : 0; } /* static */ bool OriginAttributes::IsPrivateBrowsing(const nsACString& aOrigin) { nsAutoCString dummy; OriginAttributes attrs; if (NS_WARN_IF(!attrs.PopulateFromOrigin(aOrigin, dummy))) { return false; } return !!attrs.mPrivateBrowsingId; } BasePrincipal::BasePrincipal() {} BasePrincipal::~BasePrincipal() {} NS_IMETHODIMP BasePrincipal::GetOrigin(nsACString& aOrigin) { nsresult rv = GetOriginInternal(aOrigin); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString suffix; mOriginAttributes.CreateSuffix(suffix); aOrigin.Append(suffix); return NS_OK; } NS_IMETHODIMP BasePrincipal::GetOriginNoSuffix(nsACString& aOrigin) { return GetOriginInternal(aOrigin); } bool BasePrincipal::Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) { MOZ_ASSERT(aOther); // Expanded principals handle origin attributes for each of their // sub-principals individually, null principals do only simple checks for // pointer equality, and system principals are immune to origin attributes // checks, so only do this check for codebase principals. if (Kind() == eCodebasePrincipal && OriginAttributesRef() != Cast(aOther)->OriginAttributesRef()) { return false; } return SubsumesInternal(aOther, aConsideration); } NS_IMETHODIMP BasePrincipal::Equals(nsIPrincipal *aOther, bool *aResult) { NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); *aResult = Subsumes(aOther, DontConsiderDocumentDomain) && Cast(aOther)->Subsumes(this, DontConsiderDocumentDomain); return NS_OK; } NS_IMETHODIMP BasePrincipal::EqualsConsideringDomain(nsIPrincipal *aOther, bool *aResult) { NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); *aResult = Subsumes(aOther, ConsiderDocumentDomain) && Cast(aOther)->Subsumes(this, ConsiderDocumentDomain); return NS_OK; } bool BasePrincipal::EqualsIgnoringAddonId(nsIPrincipal *aOther) { MOZ_ASSERT(aOther); // Note that this will not work for expanded principals, nor is it intended // to. if (!dom::ChromeUtils::IsOriginAttributesEqualIgnoringAddonId( OriginAttributesRef(), Cast(aOther)->OriginAttributesRef())) { return false; } return SubsumesInternal(aOther, DontConsiderDocumentDomain) && Cast(aOther)->SubsumesInternal(this, DontConsiderDocumentDomain); } NS_IMETHODIMP BasePrincipal::Subsumes(nsIPrincipal *aOther, bool *aResult) { NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); *aResult = Subsumes(aOther, DontConsiderDocumentDomain); return NS_OK; } NS_IMETHODIMP BasePrincipal::SubsumesConsideringDomain(nsIPrincipal *aOther, bool *aResult) { NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); *aResult = Subsumes(aOther, ConsiderDocumentDomain); return NS_OK; } NS_IMETHODIMP BasePrincipal::SubsumesConsideringDomainIgnoringFPD(nsIPrincipal *aOther, bool *aResult) { NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); if (Kind() == eCodebasePrincipal && !dom::ChromeUtils::IsOriginAttributesEqualIgnoringFPD( OriginAttributesRef(), aOther->OriginAttributesRef())) { *aResult = false; return NS_OK; } *aResult = SubsumesInternal(aOther, ConsiderDocumentDomain); return NS_OK; } NS_IMETHODIMP BasePrincipal::CheckMayLoad(nsIURI* aURI, bool aReport, bool aAllowIfInheritsPrincipal) { // Check the internal method first, which allows us to quickly approve loads // for the System Principal. if (MayLoadInternal(aURI)) { return NS_OK; } nsresult rv; if (aAllowIfInheritsPrincipal) { // If the caller specified to allow loads of URIs that inherit // our principal, allow the load if this URI inherits its principal. bool doesInheritSecurityContext; rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &doesInheritSecurityContext); if (NS_SUCCEEDED(rv) && doesInheritSecurityContext) { return NS_OK; } } bool fetchableByAnyone; rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FETCHABLE_BY_ANYONE, &fetchableByAnyone); if (NS_SUCCEEDED(rv) && fetchableByAnyone) { return NS_OK; } if (aReport) { nsCOMPtr prinURI; rv = GetURI(getter_AddRefs(prinURI)); if (NS_SUCCEEDED(rv) && prinURI) { nsScriptSecurityManager::ReportError(nullptr, NS_LITERAL_STRING("CheckSameOriginError"), prinURI, aURI); } } return NS_ERROR_DOM_BAD_URI; } NS_IMETHODIMP BasePrincipal::GetCsp(nsIContentSecurityPolicy** aCsp) { NS_IF_ADDREF(*aCsp = mCSP); return NS_OK; } NS_IMETHODIMP BasePrincipal::EnsureCSP(nsIDOMDocument* aDocument, nsIContentSecurityPolicy** aCSP) { if (mCSP) { // if there is a CSP already associated with this principal // then just return that - do not overwrite it!!! NS_IF_ADDREF(*aCSP = mCSP); return NS_OK; } nsresult rv = NS_OK; mCSP = do_CreateInstance("@mozilla.org/cspcontext;1", &rv); NS_ENSURE_SUCCESS(rv, rv); // Store the request context for violation reports rv = aDocument ? mCSP->SetRequestContext(aDocument, nullptr) : mCSP->SetRequestContext(nullptr, this); NS_ENSURE_SUCCESS(rv, rv); NS_IF_ADDREF(*aCSP = mCSP); return NS_OK; } NS_IMETHODIMP BasePrincipal::GetPreloadCsp(nsIContentSecurityPolicy** aPreloadCSP) { NS_IF_ADDREF(*aPreloadCSP = mPreloadCSP); return NS_OK; } NS_IMETHODIMP BasePrincipal::EnsurePreloadCSP(nsIDOMDocument* aDocument, nsIContentSecurityPolicy** aPreloadCSP) { if (mPreloadCSP) { // if there is a speculative CSP already associated with this principal // then just return that - do not overwrite it!!! NS_IF_ADDREF(*aPreloadCSP = mPreloadCSP); return NS_OK; } nsresult rv = NS_OK; mPreloadCSP = do_CreateInstance("@mozilla.org/cspcontext;1", &rv); NS_ENSURE_SUCCESS(rv, rv); // Store the request context for violation reports rv = aDocument ? mPreloadCSP->SetRequestContext(aDocument, nullptr) : mPreloadCSP->SetRequestContext(nullptr, this); NS_ENSURE_SUCCESS(rv, rv); NS_IF_ADDREF(*aPreloadCSP = mPreloadCSP); return NS_OK; } NS_IMETHODIMP BasePrincipal::GetCspJSON(nsAString& outCSPinJSON) { outCSPinJSON.Truncate(); dom::CSPPolicies jsonPolicies; if (!mCSP) { jsonPolicies.ToJSON(outCSPinJSON); return NS_OK; } return mCSP->ToJSON(outCSPinJSON); } NS_IMETHODIMP BasePrincipal::GetIsNullPrincipal(bool* aResult) { *aResult = Kind() == eNullPrincipal; return NS_OK; } NS_IMETHODIMP BasePrincipal::GetIsCodebasePrincipal(bool* aResult) { *aResult = Kind() == eCodebasePrincipal; return NS_OK; } NS_IMETHODIMP BasePrincipal::GetIsExpandedPrincipal(bool* aResult) { *aResult = Kind() == eExpandedPrincipal; return NS_OK; } NS_IMETHODIMP BasePrincipal::GetIsSystemPrincipal(bool* aResult) { *aResult = Kind() == eSystemPrincipal; return NS_OK; } NS_IMETHODIMP BasePrincipal::GetOriginAttributes(JSContext* aCx, JS::MutableHandle aVal) { if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) { return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP BasePrincipal::GetOriginSuffix(nsACString& aOriginAttributes) { mOriginAttributes.CreateSuffix(aOriginAttributes); return NS_OK; } NS_IMETHODIMP BasePrincipal::GetAppStatus(uint16_t* aAppStatus) { // TODO: Remove GetAppStatus. *aAppStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED; return NS_OK; } NS_IMETHODIMP BasePrincipal::GetAppId(uint32_t* aAppId) { if (AppId() == nsIScriptSecurityManager::UNKNOWN_APP_ID) { MOZ_ASSERT(false); *aAppId = nsIScriptSecurityManager::NO_APP_ID; return NS_OK; } *aAppId = AppId(); return NS_OK; } NS_IMETHODIMP BasePrincipal::GetAddonId(nsAString& aAddonId) { aAddonId.Assign(mOriginAttributes.mAddonId); return NS_OK; } NS_IMETHODIMP BasePrincipal::GetUserContextId(uint32_t* aUserContextId) { *aUserContextId = UserContextId(); return NS_OK; } NS_IMETHODIMP BasePrincipal::GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) { *aPrivateBrowsingId = PrivateBrowsingId(); return NS_OK; } NS_IMETHODIMP BasePrincipal::GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement) { *aIsInIsolatedMozBrowserElement = IsInIsolatedMozBrowserElement(); return NS_OK; } NS_IMETHODIMP BasePrincipal::GetUnknownAppId(bool* aUnknownAppId) { *aUnknownAppId = AppId() == nsIScriptSecurityManager::UNKNOWN_APP_ID; return NS_OK; } bool BasePrincipal::AddonHasPermission(const nsAString& aPerm) { if (mOriginAttributes.mAddonId.IsEmpty()) { return false; } nsCOMPtr aps = do_GetService("@mozilla.org/addons/policy-service;1"); NS_ENSURE_TRUE(aps, false); bool retval = false; nsresult rv = aps->AddonHasPermission(mOriginAttributes.mAddonId, aPerm, &retval); NS_ENSURE_SUCCESS(rv, false); return retval; } already_AddRefed BasePrincipal::CreateCodebasePrincipal(nsIURI* aURI, const OriginAttributes& aAttrs) { // If the URI is supposed to inherit the security context of whoever loads it, // we shouldn't make a codebase principal for it. bool inheritsPrincipal; nsresult rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &inheritsPrincipal); if (NS_FAILED(rv) || inheritsPrincipal) { return nsNullPrincipal::Create(aAttrs); } // Check whether the URI knows what its principal is supposed to be. nsCOMPtr uriPrinc = do_QueryInterface(aURI); if (uriPrinc) { nsCOMPtr principal; uriPrinc->GetPrincipal(getter_AddRefs(principal)); if (!principal) { return nsNullPrincipal::Create(aAttrs); } RefPtr concrete = Cast(principal); return concrete.forget(); } // Mint a codebase principal. RefPtr codebase = new nsPrincipal(); rv = codebase->Init(aURI, aAttrs); NS_ENSURE_SUCCESS(rv, nullptr); return codebase.forget(); } already_AddRefed BasePrincipal::CreateCodebasePrincipal(const nsACString& aOrigin) { MOZ_ASSERT(!StringBeginsWith(aOrigin, NS_LITERAL_CSTRING("[")), "CreateCodebasePrincipal does not support System and Expanded principals"); MOZ_ASSERT(!StringBeginsWith(aOrigin, NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME ":")), "CreateCodebasePrincipal does not support nsNullPrincipal"); nsAutoCString originNoSuffix; mozilla::OriginAttributes attrs; if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) { return nullptr; } nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); NS_ENSURE_SUCCESS(rv, nullptr); return BasePrincipal::CreateCodebasePrincipal(uri, attrs); } already_AddRefed BasePrincipal::CloneStrippingUserContextIdAndFirstPartyDomain() { OriginAttributes attrs = OriginAttributesRef(); attrs.StripAttributes(OriginAttributes::STRIP_USER_CONTEXT_ID | OriginAttributes::STRIP_FIRST_PARTY_DOMAIN); nsAutoCString originNoSuffix; nsresult rv = GetOriginNoSuffix(originNoSuffix); NS_ENSURE_SUCCESS(rv, nullptr); nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); NS_ENSURE_SUCCESS(rv, nullptr); return BasePrincipal::CreateCodebasePrincipal(uri, attrs); } bool BasePrincipal::AddonAllowsLoad(nsIURI* aURI, bool aExplicit /* = false */) { if (mOriginAttributes.mAddonId.IsEmpty()) { return false; } nsCOMPtr aps = do_GetService("@mozilla.org/addons/policy-service;1"); NS_ENSURE_TRUE(aps, false); bool allowed = false; nsresult rv = aps->AddonMayLoadURI(mOriginAttributes.mAddonId, aURI, aExplicit, &allowed); return NS_SUCCEEDED(rv) && allowed; } } // namespace mozilla