diff options
Diffstat (limited to 'caps')
59 files changed, 12037 insertions, 0 deletions
diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp new file mode 100644 index 0000000000..97f066255e --- /dev/null +++ b/caps/BasePrincipal.cpp @@ -0,0 +1,1534 @@ +/* -*- 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" + +#include "ExpandedPrincipal.h" +#include "nsNetUtil.h" +#include "nsContentUtils.h" +#include "nsIOService.h" +#include "nsIURIWithSpecialOrigin.h" +#include "nsScriptSecurityManager.h" +#include "nsServiceManagerUtils.h" +#include "nsAboutProtocolUtils.h" +#include "ThirdPartyUtil.h" +#include "mozilla/ContentPrincipal.h" +#include "mozilla/ExtensionPolicyService.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/dom/ChromeUtils.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/nsMixedContentBlocker.h" +#include "mozilla/Components.h" +#include "mozilla/dom/StorageUtils.h" +#include "mozilla/dom/StorageUtils.h" +#include "nsIURL.h" +#include "nsEffectiveTLDService.h" +#include "nsIURIMutator.h" +#include "mozilla/StaticPrefs_permissions.h" +#include "nsIURIMutator.h" +#include "nsMixedContentBlocker.h" +#include "prnetdb.h" +#include "nsIURIFixup.h" +#include "mozilla/dom/StorageUtils.h" +#include "mozilla/StorageAccess.h" +#include "nsPIDOMWindow.h" +#include "nsIURIMutator.h" +#include "mozilla/PermissionManager.h" + +#include "json/json.h" +#include "nsSerializationHelper.h" + +namespace mozilla { + +BasePrincipal::BasePrincipal(PrincipalKind aKind, + const nsACString& aOriginNoSuffix, + const OriginAttributes& aOriginAttributes) + : mOriginNoSuffix(NS_Atomize(aOriginNoSuffix)), + mOriginSuffix(aOriginAttributes.CreateSuffixAtom()), + mOriginAttributes(aOriginAttributes), + mKind(aKind), + mHasExplicitDomain(false) {} + +BasePrincipal::BasePrincipal(BasePrincipal* aOther, + const OriginAttributes& aOriginAttributes) + : mOriginNoSuffix(aOther->mOriginNoSuffix), + mOriginSuffix(aOriginAttributes.CreateSuffixAtom()), + mOriginAttributes(aOriginAttributes), + mKind(aOther->mKind), + mHasExplicitDomain(aOther->mHasExplicitDomain.load()) {} + +BasePrincipal::~BasePrincipal() = default; + +NS_IMETHODIMP +BasePrincipal::GetOrigin(nsACString& aOrigin) { + nsresult rv = GetOriginNoSuffix(aOrigin); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString suffix; + rv = GetOriginSuffix(suffix); + NS_ENSURE_SUCCESS(rv, rv); + aOrigin.Append(suffix); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetAsciiOrigin(nsACString& aOrigin) { + aOrigin.Truncate(); + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_ERROR_NOT_AVAILABLE; + } + return nsContentUtils::GetASCIIOrigin(prinURI, aOrigin); +} + +NS_IMETHODIMP +BasePrincipal::GetHostPort(nsACString& aRes) { + aRes.Truncate(); + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + return prinURI->GetHostPort(aRes); +} + +NS_IMETHODIMP +BasePrincipal::GetHost(nsACString& aRes) { + aRes.Truncate(); + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + return prinURI->GetHost(aRes); +} + +NS_IMETHODIMP +BasePrincipal::GetOriginNoSuffix(nsACString& aOrigin) { + mOriginNoSuffix->ToUTF8String(aOrigin); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetSiteOrigin(nsACString& aSiteOrigin) { + nsresult rv = GetSiteOriginNoSuffix(aSiteOrigin); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString suffix; + rv = GetOriginSuffix(suffix); + NS_ENSURE_SUCCESS(rv, rv); + aSiteOrigin.Append(suffix); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetSiteOriginNoSuffix(nsACString& aSiteOrigin) { + return GetOriginNoSuffix(aSiteOrigin); +} + +// Returns the inner Json::value of the serialized principal +// Example input and return values: +// Null principal: +// {"0":{"0":"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}"}} -> +// {"0":"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}"} +// +// Content principal: +// {"1":{"0":"https://mozilla.com"}} -> {"0":"https://mozilla.com"} +// +// Expanded principal: +// {"2":{"0":"<base64principal1>,<base64principal2>"}} -> +// {"0":"<base64principal1>,<base64principal2>"} +// +// System principal: +// {"3":{}} -> {} +// The aKey passed in also returns the corresponding PrincipalKind enum +// +// Warning: The Json::Value* pointer is into the aRoot object +static const Json::Value* GetPrincipalObject(const Json::Value& aRoot, + int& aOutPrincipalKind) { + const Json::Value::Members members = aRoot.getMemberNames(); + // We only support one top level key in the object + if (members.size() != 1) { + return nullptr; + } + // members[0] here is the "0", "1", "2", "3" principalKind + // that is the top level of the serialized JSON principal + const std::string stringPrincipalKind = members[0]; + + // Next we take the string value from the JSON + // and convert it into the int for the BasePrincipal::PrincipalKind enum + + // Verify that the key is within the valid range + int principalKind = std::stoi(stringPrincipalKind); + MOZ_ASSERT(BasePrincipal::eNullPrincipal == 0, + "We need to rely on 0 being a bounds check for the first " + "principal kind."); + if (principalKind < 0 || principalKind > BasePrincipal::eKindMax) { + return nullptr; + } + MOZ_ASSERT(principalKind == BasePrincipal::eNullPrincipal || + principalKind == BasePrincipal::eContentPrincipal || + principalKind == BasePrincipal::eExpandedPrincipal || + principalKind == BasePrincipal::eSystemPrincipal); + aOutPrincipalKind = principalKind; + + if (!aRoot[stringPrincipalKind].isObject()) { + return nullptr; + } + + // Return the inner value of the principal object + return &aRoot[stringPrincipalKind]; +} + +// Accepts the JSON inner object without the wrapping principalKind +// (See GetPrincipalObject for the inner object response examples) +// Creates an array of KeyVal objects that are all defined on the principal +// Each principal type (null, content, expanded) has a KeyVal that stores the +// fields of the JSON +// +// This simplifies deserializing elsewhere as we do the checking for presence +// and string values here for the complete set of serializable keys that the +// corresponding principal supports. +// +// The KeyVal object has the following fields: +// - valueWasSerialized: is true if the deserialized JSON contained a string +// value +// - value: The string that was serialized for this key +// - key: an SerializableKeys enum value specific to the principal. +// For example content principal is an enum of: eURI, eDomain, +// eSuffix, eCSP +// +// +// Given an inner content principal: +// {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"} +// | | | | +// ----------------------------- | +// | | | +// Key ---------------------- +// | +// Value +// +// They Key "0" corresponds to ContentPrincipal::eURI +// They Key "1" corresponds to ContentPrincipal::eSuffix +template <typename T> +static nsTArray<typename T::KeyVal> GetJSONKeys(const Json::Value* aInput) { + int size = T::eMax + 1; + nsTArray<typename T::KeyVal> fields; + for (int i = 0; i != size; i++) { + typename T::KeyVal* field = fields.AppendElement(); + // field->valueWasSerialized returns if the field was found in the + // deserialized code. This simplifies the consumers from having to check + // length. + field->valueWasSerialized = false; + field->key = static_cast<typename T::SerializableKeys>(i); + const std::string key = std::to_string(field->key); + if (aInput->isMember(key)) { + const Json::Value& val = (*aInput)[key]; + if (val.isString()) { + field->value.Append(nsDependentCString(val.asCString())); + field->valueWasSerialized = true; + } + } + } + return fields; +} + +// Takes a JSON string and parses it turning it into a principal of the +// corresponding type +// +// Given a content principal: +// +// inner JSON object +// | +// --------------------------------------------------------- +// | | +// {"1": {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}} +// | | | | | +// | ----------------------------- | +// | | | | +// PrincipalKind | | | +// | ---------------------------- +// SerializableKeys | +// Value +// +// The string is first deserialized with jsoncpp to get the Json::Value of the +// object. The inner JSON object is parsed with GetPrincipalObject which returns +// a KeyVal array of the inner object's fields. PrincipalKind is returned by +// GetPrincipalObject which is then used to decide which principal +// implementation of FromProperties to call. The corresponding FromProperties +// call takes the KeyVal fields and turns it into a principal. +already_AddRefed<BasePrincipal> BasePrincipal::FromJSON( + const nsACString& aJSON) { + Json::Value root; + Json::CharReaderBuilder builder; + std::unique_ptr<Json::CharReader> const reader(builder.newCharReader()); + bool parseSuccess = + reader->parse(aJSON.BeginReading(), aJSON.EndReading(), &root, nullptr); + if (!parseSuccess) { + MOZ_ASSERT(false, + "Unable to parse string as JSON to deserialize as a principal"); + return nullptr; + } + + int principalKind = -1; + const Json::Value* value = GetPrincipalObject(root, principalKind); + if (!value) { +#ifdef DEBUG + fprintf(stderr, "Unexpected JSON principal %s\n", + root.toStyledString().c_str()); +#endif + MOZ_ASSERT(false, "Unexpected JSON to deserialize as a principal"); + + return nullptr; + } + MOZ_ASSERT(principalKind != -1, + "PrincipalKind should always be >=0 by this point"); + + if (principalKind == eSystemPrincipal) { + RefPtr<BasePrincipal> principal = + BasePrincipal::Cast(nsContentUtils::GetSystemPrincipal()); + return principal.forget(); + } + + if (principalKind == eNullPrincipal) { + nsTArray<NullPrincipal::KeyVal> res = GetJSONKeys<NullPrincipal>(value); + return NullPrincipal::FromProperties(res); + } + + if (principalKind == eContentPrincipal) { + nsTArray<ContentPrincipal::KeyVal> res = + GetJSONKeys<ContentPrincipal>(value); + return ContentPrincipal::FromProperties(res); + } + + if (principalKind == eExpandedPrincipal) { + nsTArray<ExpandedPrincipal::KeyVal> res = + GetJSONKeys<ExpandedPrincipal>(value); + return ExpandedPrincipal::FromProperties(res); + } + + MOZ_RELEASE_ASSERT(false, "Unexpected enum to deserialize as a principal"); +} + +nsresult BasePrincipal::PopulateJSONObject(Json::Value& aObject) { + return NS_OK; +} + +// Returns a JSON representation of the principal. +// Calling BasePrincipal::FromJSON will deserialize the JSON into +// the corresponding principal type. +nsresult BasePrincipal::ToJSON(nsACString& aResult) { + MOZ_ASSERT(aResult.IsEmpty(), "ToJSON only supports an empty result input"); + aResult.Truncate(); + + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + Json::Value innerJSONObject = Json::objectValue; + + nsresult rv = PopulateJSONObject(innerJSONObject); + NS_ENSURE_SUCCESS(rv, rv); + + Json::Value root = Json::objectValue; + std::string key = std::to_string(Kind()); + root[key] = innerJSONObject; + std::string result = Json::writeString(builder, root); + aResult.Append(result); + if (aResult.Length() == 0) { + MOZ_ASSERT(false, "JSON writer failed to output a principal serialization"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +bool BasePrincipal::FastSubsumesIgnoringFPD( + nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) { + MOZ_ASSERT(aOther); + + if (Kind() == eContentPrincipal && + !dom::ChromeUtils::IsOriginAttributesEqualIgnoringFPD( + mOriginAttributes, Cast(aOther)->mOriginAttributes)) { + return false; + } + + return SubsumesInternal(aOther, aConsideration); +} + +bool BasePrincipal::Subsumes(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration) { + MOZ_ASSERT(aOther); + MOZ_ASSERT_IF(Kind() == eContentPrincipal, mOriginSuffix); + + // 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 content principals. + if (Kind() == eContentPrincipal && + mOriginSuffix != Cast(aOther)->mOriginSuffix) { + return false; + } + + return SubsumesInternal(aOther, aConsideration); +} + +NS_IMETHODIMP +BasePrincipal::Equals(nsIPrincipal* aOther, bool* aResult) { + NS_ENSURE_ARG_POINTER(aOther); + + *aResult = FastEquals(aOther); + + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::EqualsForPermission(nsIPrincipal* aOther, bool aExactHost, + bool* aResult) { + *aResult = false; + NS_ENSURE_ARG_POINTER(aOther); + NS_ENSURE_ARG_POINTER(aResult); + + auto* other = Cast(aOther); + if (Kind() != other->Kind()) { + // Principals of different kinds can't be equal. + return NS_OK; + } + + if (Kind() == eSystemPrincipal) { + *aResult = this == other; + return NS_OK; + } + + if (Kind() == eNullPrincipal) { + // We don't store permissions for NullPrincipals. + return NS_OK; + } + + MOZ_ASSERT(Kind() == eExpandedPrincipal || Kind() == eContentPrincipal); + + // Certain origin attributes should not be used to isolate permissions. + // Create a stripped copy of both OA sets to compare. + mozilla::OriginAttributes ourAttrs = mOriginAttributes; + PermissionManager::MaybeStripOriginAttributes(false, ourAttrs); + mozilla::OriginAttributes theirAttrs = aOther->OriginAttributesRef(); + PermissionManager::MaybeStripOriginAttributes(false, theirAttrs); + + if (ourAttrs != theirAttrs) { + return NS_OK; + } + + if (mOriginNoSuffix == other->mOriginNoSuffix) { + *aResult = true; + return NS_OK; + } + + // If we are matching with an exact host, we're done now - the permissions + // don't match otherwise, we need to start comparing subdomains! + if (aExactHost) { + return NS_OK; + } + + nsCOMPtr<nsIURI> ourURI; + nsresult rv = GetURI(getter_AddRefs(ourURI)); + NS_ENSURE_SUCCESS(rv, rv); + // Some principal types may indicate success, but still return nullptr for + // URI. + NS_ENSURE_TRUE(ourURI, NS_ERROR_FAILURE); + + nsCOMPtr<nsIURI> otherURI; + rv = other->GetURI(getter_AddRefs(otherURI)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(otherURI, NS_ERROR_FAILURE); + + // Compare schemes + nsAutoCString otherScheme; + rv = otherURI->GetScheme(otherScheme); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString ourScheme; + rv = ourURI->GetScheme(ourScheme); + NS_ENSURE_SUCCESS(rv, rv); + + if (otherScheme != ourScheme) { + return NS_OK; + } + + // Compare ports + int32_t otherPort; + rv = otherURI->GetPort(&otherPort); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t ourPort; + rv = ourURI->GetPort(&ourPort); + NS_ENSURE_SUCCESS(rv, rv); + + if (otherPort != ourPort) { + return NS_OK; + } + + // Check if the host or any subdomain of their host matches. + nsAutoCString otherHost; + rv = otherURI->GetHost(otherHost); + if (NS_FAILED(rv) || otherHost.IsEmpty()) { + return NS_OK; + } + + nsAutoCString ourHost; + rv = ourURI->GetHost(ourHost); + if (NS_FAILED(rv) || ourHost.IsEmpty()) { + return NS_OK; + } + + nsCOMPtr<nsIEffectiveTLDService> tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + if (!tldService) { + NS_ERROR("Should have a tld service!"); + return NS_ERROR_FAILURE; + } + + // This loop will not loop forever, as GetNextSubDomain will eventually fail + // with NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS. + while (otherHost != ourHost) { + rv = tldService->GetNextSubDomain(otherHost, otherHost); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + return NS_OK; + } + return rv; + } + } + + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::EqualsConsideringDomain(nsIPrincipal* aOther, bool* aResult) { + NS_ENSURE_ARG_POINTER(aOther); + + *aResult = FastEqualsConsideringDomain(aOther); + + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::EqualsURI(nsIURI* aOtherURI, bool* aResult) { + *aResult = false; + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + return prinURI->EqualsExceptRef(aOtherURI, aResult); +} + +NS_IMETHODIMP +BasePrincipal::Subsumes(nsIPrincipal* aOther, bool* aResult) { + NS_ENSURE_ARG_POINTER(aOther); + + *aResult = FastSubsumes(aOther); + + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::SubsumesConsideringDomain(nsIPrincipal* aOther, bool* aResult) { + NS_ENSURE_ARG_POINTER(aOther); + + *aResult = FastSubsumesConsideringDomain(aOther); + + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::SubsumesConsideringDomainIgnoringFPD(nsIPrincipal* aOther, + bool* aResult) { + NS_ENSURE_ARG_POINTER(aOther); + + *aResult = FastSubsumesConsideringDomainIgnoringFPD(aOther); + + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::CheckMayLoad(nsIURI* aURI, bool aAllowIfInheritsPrincipal) { + AssertIsOnMainThread(); + return CheckMayLoadHelper(aURI, aAllowIfInheritsPrincipal, false, 0); +} + +NS_IMETHODIMP +BasePrincipal::CheckMayLoadWithReporting(nsIURI* aURI, + bool aAllowIfInheritsPrincipal, + uint64_t aInnerWindowID) { + AssertIsOnMainThread(); + return CheckMayLoadHelper(aURI, aAllowIfInheritsPrincipal, true, + aInnerWindowID); +} + +nsresult BasePrincipal::CheckMayLoadHelper(nsIURI* aURI, + bool aAllowIfInheritsPrincipal, + bool aReport, + uint64_t aInnerWindowID) { + AssertIsOnMainThread(); // Accesses non-threadsafe URI flags and the + // non-threadsafe ExtensionPolicyService + NS_ENSURE_ARG_POINTER(aURI); + MOZ_ASSERT( + aReport || aInnerWindowID == 0, + "Why do we have an inner window id if we're not supposed to report?"); + + // 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; + } + } + + // Web Accessible Resources in MV2 Extensions are marked with + // URI_FETCHABLE_BY_ANYONE + bool fetchableByAnyone; + rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FETCHABLE_BY_ANYONE, + &fetchableByAnyone); + if (NS_SUCCEEDED(rv) && fetchableByAnyone) { + return NS_OK; + } + + // Get the principal uri for the last flag check or error. + nsCOMPtr<nsIURI> prinURI; + rv = GetURI(getter_AddRefs(prinURI)); + if (!(NS_SUCCEEDED(rv) && prinURI)) { + return NS_ERROR_DOM_BAD_URI; + } + + // If MV3 Extension uris are web accessible by this principal it is allowed to + // load. + bool maybeWebAccessible = false; + NS_URIChainHasFlags(aURI, nsIProtocolHandler::WEBEXT_URI_WEB_ACCESSIBLE, + &maybeWebAccessible); + NS_ENSURE_SUCCESS(rv, rv); + if (maybeWebAccessible) { + bool isWebAccessible = false; + rv = ExtensionPolicyService::GetSingleton().SourceMayLoadExtensionURI( + prinURI, aURI, &isWebAccessible); + if (NS_SUCCEEDED(rv) && isWebAccessible) { + return NS_OK; + } + } + + if (aReport) { + nsScriptSecurityManager::ReportError( + "CheckSameOriginError", prinURI, aURI, + mOriginAttributes.mPrivateBrowsingId > 0, aInnerWindowID); + } + + return NS_ERROR_DOM_BAD_URI; +} + +NS_IMETHODIMP +BasePrincipal::IsThirdPartyURI(nsIURI* aURI, bool* aRes) { + if (IsSystemPrincipal() || (AddonPolicyCore() && AddonAllowsLoad(aURI))) { + *aRes = false; + return NS_OK; + } + + *aRes = true; + // If we do not have a URI its always 3rd party. + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); + return thirdPartyUtil->IsThirdPartyURI(prinURI, aURI, aRes); +} + +NS_IMETHODIMP +BasePrincipal::IsThirdPartyPrincipal(nsIPrincipal* aPrin, bool* aRes) { + *aRes = true; + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + return aPrin->IsThirdPartyURI(prinURI, aRes); +} + +NS_IMETHODIMP +BasePrincipal::IsThirdPartyChannel(nsIChannel* aChan, bool* aRes) { + AssertIsOnMainThread(); + if (IsSystemPrincipal()) { + // Nothing is 3rd party to the system principal. + *aRes = false; + return NS_OK; + } + + nsCOMPtr<nsIURI> prinURI; + GetURI(getter_AddRefs(prinURI)); + ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); + return thirdPartyUtil->IsThirdPartyChannel(aChan, prinURI, aRes); +} + +NS_IMETHODIMP +BasePrincipal::IsSameOrigin(nsIURI* aURI, bool* aRes) { + *aRes = false; + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + // Note that expanded and system principals return here, because they have + // no URI. + return NS_OK; + } + *aRes = nsScriptSecurityManager::SecurityCompareURIs(prinURI, aURI); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::IsL10nAllowed(nsIURI* aURI, bool* aRes) { + AssertIsOnMainThread(); // URI_DANGEROUS_TO_LOAD is not threadsafe to query. + *aRes = false; + + if (nsContentUtils::IsErrorPage(aURI)) { + *aRes = true; + return NS_OK; + } + + // The system principal is always allowed. + if (IsSystemPrincipal()) { + *aRes = true; + return NS_OK; + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, NS_OK); + + bool hasFlags; + + // Allow access to uris that cannot be loaded by web content. + rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD, + &hasFlags); + NS_ENSURE_SUCCESS(rv, NS_OK); + if (hasFlags) { + *aRes = true; + return NS_OK; + } + + // UI resources also get access. + rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, NS_OK); + if (hasFlags) { + *aRes = true; + return NS_OK; + } + + auto policy = AddonPolicyCore(); + *aRes = (policy && policy->IsPrivileged()); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::AllowsRelaxStrictFileOriginPolicy(nsIURI* aURI, bool* aRes) { + *aRes = false; + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + *aRes = NS_RelaxStrictFileOriginPolicy(aURI, prinURI); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetPrefLightCacheKey(nsIURI* aURI, bool aWithCredentials, + const OriginAttributes& aOriginAttributes, + nsACString& _retval) { + _retval.Truncate(); + constexpr auto space = " "_ns; + + nsCOMPtr<nsIURI> uri; + nsresult rv = GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString scheme, host, port; + if (uri) { + uri->GetScheme(scheme); + uri->GetHost(host); + port.AppendInt(NS_GetRealPort(uri)); + } + + if (aWithCredentials) { + _retval.AssignLiteral("cred"); + } else { + _retval.AssignLiteral("nocred"); + } + + nsAutoCString spec; + rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString originAttributesSuffix; + aOriginAttributes.CreateSuffix(originAttributesSuffix); + + _retval.Append(space + scheme + space + host + space + port + space + spec + + space + originAttributesSuffix); + + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::HasFirstpartyStorageAccess(mozIDOMWindow* aCheckWindow, + uint32_t* aRejectedReason, + bool* aOutAllowed) { + AssertIsOnMainThread(); + *aRejectedReason = 0; + *aOutAllowed = false; + + nsPIDOMWindowInner* win = nsPIDOMWindowInner::From(aCheckWindow); + nsCOMPtr<nsIURI> uri; + nsresult rv = GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return rv; + } + *aOutAllowed = ShouldAllowAccessFor(win, uri, aRejectedReason); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsNullPrincipal(bool* aResult) { + *aResult = Kind() == eNullPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsContentPrincipal(bool* aResult) { + *aResult = Kind() == eContentPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsExpandedPrincipal(bool* aResult) { + *aResult = Kind() == eExpandedPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetAsciiSpec(nsACString& aSpec) { + aSpec.Truncate(); + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + return prinURI->GetAsciiSpec(aSpec); +} + +NS_IMETHODIMP +BasePrincipal::GetSpec(nsACString& aSpec) { + aSpec.Truncate(); + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + return prinURI->GetSpec(aSpec); +} + +NS_IMETHODIMP +BasePrincipal::GetAsciiHost(nsACString& aHost) { + aHost.Truncate(); + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + return prinURI->GetAsciiHost(aHost); +} + +NS_IMETHODIMP +BasePrincipal::GetExposablePrePath(nsACString& aPrepath) { + aPrepath.Truncate(); + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(prinURI); + return exposableURI->GetDisplayPrePath(aPrepath); +} + +NS_IMETHODIMP +BasePrincipal::GetExposableSpec(nsACString& aSpec) { + aSpec.Truncate(); + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + nsCOMPtr<nsIURI> clone; + rv = NS_MutateURI(prinURI) + .SetQuery(""_ns) + .SetRef(""_ns) + .SetUserPass(""_ns) + .Finalize(clone); + NS_ENSURE_SUCCESS(rv, rv); + return clone->GetAsciiSpec(aSpec); +} + +NS_IMETHODIMP +BasePrincipal::GetPrePath(nsACString& aPath) { + aPath.Truncate(); + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + return prinURI->GetPrePath(aPath); +} + +NS_IMETHODIMP +BasePrincipal::GetFilePath(nsACString& aPath) { + aPath.Truncate(); + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + return prinURI->GetFilePath(aPath); +} + +NS_IMETHODIMP +BasePrincipal::GetIsSystemPrincipal(bool* aResult) { + *aResult = IsSystemPrincipal(); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsAddonOrExpandedAddonPrincipal(bool* aResult) { + *aResult = AddonPolicyCore() || ContentScriptAddonPolicyCore(); + return NS_OK; +} + +NS_IMETHODIMP BasePrincipal::GetIsOnion(bool* aIsOnion) { + *aIsOnion = false; + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + nsAutoCString host; + rv = prinURI->GetHost(host); + if (NS_FAILED(rv)) { + return NS_OK; + } + *aIsOnion = StringEndsWith(host, ".onion"_ns); + return NS_OK; +} + +NS_IMETHODIMP BasePrincipal::GetIsIpAddress(bool* aIsIpAddress) { + *aIsIpAddress = false; + + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + nsAutoCString host; + rv = prinURI->GetHost(host); + if (NS_FAILED(rv)) { + return NS_OK; + } + + PRNetAddr prAddr; + memset(&prAddr, 0, sizeof(prAddr)); + + if (PR_StringToNetAddr(host.get(), &prAddr) == PR_SUCCESS) { + *aIsIpAddress = true; + } + + return NS_OK; +} + +NS_IMETHODIMP BasePrincipal::GetIsLocalIpAddress(bool* aIsIpAddress) { + *aIsIpAddress = false; + + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); + if (NS_FAILED(rv) || !ioService) { + return NS_OK; + } + rv = ioService->HostnameIsLocalIPAddress(prinURI, aIsIpAddress); + if (NS_FAILED(rv)) { + *aIsIpAddress = false; + } + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetScheme(nsACString& aScheme) { + aScheme.Truncate(); + + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + return prinURI->GetScheme(aScheme); +} + +NS_IMETHODIMP +BasePrincipal::SchemeIs(const char* aScheme, bool* aResult) { + *aResult = false; + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_WARN_IF(NS_FAILED(rv)) || !prinURI) { + return NS_OK; + } + *aResult = prinURI->SchemeIs(aScheme); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::IsURIInPrefList(const char* aPref, bool* aResult) { + AssertIsOnMainThread(); + *aResult = false; + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + *aResult = nsContentUtils::IsURIInPrefList(prinURI, aPref); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::IsURIInList(const nsACString& aList, bool* aResult) { + *aResult = false; + nsCOMPtr<nsIURI> prinURI; + + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + *aResult = nsContentUtils::IsURIInList(prinURI, nsCString(aList)); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsOriginPotentiallyTrustworthy(bool* aResult) { + AssertIsOnMainThread(); + *aResult = false; + + nsCOMPtr<nsIURI> uri; + nsresult rv = GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv) || !uri) { + return NS_OK; + } + + *aResult = nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsLoopbackHost(bool* aRes) { + AssertIsOnMainThread(); + *aRes = false; + nsAutoCString host; + nsresult rv = GetHost(host); + // Swallow potential failure as this method is infallible. + NS_ENSURE_SUCCESS(rv, NS_OK); + + *aRes = nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(host); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetAboutModuleFlags(uint32_t* flags) { + AssertIsOnMainThread(); + *flags = 0; + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_ERROR_NOT_AVAILABLE; + } + if (!prinURI->SchemeIs("about")) { + return NS_OK; + } + + nsCOMPtr<nsIAboutModule> aboutModule; + rv = NS_GetAboutModule(prinURI, getter_AddRefs(aboutModule)); + if (NS_FAILED(rv) || !aboutModule) { + return rv; + } + return aboutModule->GetURIFlags(prinURI, flags); +} + +NS_IMETHODIMP +BasePrincipal::GetOriginAttributes(JSContext* aCx, + JS::MutableHandle<JS::Value> aVal) { + if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetOriginSuffix(nsACString& aOriginAttributes) { + MOZ_ASSERT(mOriginSuffix); + mOriginSuffix->ToUTF8String(aOriginAttributes); + 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; +} + +nsresult BasePrincipal::GetAddonPolicy( + extensions::WebExtensionPolicy** aResult) { + AssertIsOnMainThread(); + RefPtr<extensions::WebExtensionPolicy> policy(AddonPolicy()); + policy.forget(aResult); + return NS_OK; +} + +nsresult BasePrincipal::GetContentScriptAddonPolicy( + extensions::WebExtensionPolicy** aResult) { + RefPtr<extensions::WebExtensionPolicy> policy(ContentScriptAddonPolicy()); + policy.forget(aResult); + return NS_OK; +} + +extensions::WebExtensionPolicy* BasePrincipal::AddonPolicy() { + AssertIsOnMainThread(); + RefPtr<extensions::WebExtensionPolicyCore> core = AddonPolicyCore(); + return core ? core->GetMainThreadPolicy() : nullptr; +} + +RefPtr<extensions::WebExtensionPolicyCore> BasePrincipal::AddonPolicyCore() { + if (Is<ContentPrincipal>()) { + return As<ContentPrincipal>()->AddonPolicyCore(); + } + return nullptr; +} + +bool BasePrincipal::AddonHasPermission(const nsAtom* aPerm) { + if (auto policy = AddonPolicyCore()) { + return policy->HasPermission(aPerm); + } + return false; +} + +nsIPrincipal* BasePrincipal::PrincipalToInherit(nsIURI* aRequestedURI) { + if (Is<ExpandedPrincipal>()) { + return As<ExpandedPrincipal>()->PrincipalToInherit(aRequestedURI); + } + return this; +} + +bool BasePrincipal::OverridesCSP(nsIPrincipal* aDocumentPrincipal) { + MOZ_ASSERT(aDocumentPrincipal); + + // Expanded principals override CSP if and only if they subsume the document + // principal. + if (mKind == eExpandedPrincipal) { + return FastSubsumes(aDocumentPrincipal); + } + // Extension principals always override the CSP of non-extension principals. + // This is primarily for the sake of their stylesheets, which are usually + // loaded from channels and cannot have expanded principals. + return (AddonPolicyCore() && + !BasePrincipal::Cast(aDocumentPrincipal)->AddonPolicyCore()); +} + +already_AddRefed<BasePrincipal> BasePrincipal::CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs, nsIURI* aInitialDomain) { + MOZ_ASSERT(aURI); + + nsAutoCString originNoSuffix; + nsresult rv = + ContentPrincipal::GenerateOriginNoSuffixFromURI(aURI, originNoSuffix); + if (NS_FAILED(rv)) { + // If the generation of the origin fails, we still want to have a valid + // principal. Better to return a null principal here. + return NullPrincipal::Create(aAttrs); + } + + return CreateContentPrincipal(aURI, aAttrs, originNoSuffix, aInitialDomain); +} + +already_AddRefed<BasePrincipal> BasePrincipal::CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs, + const nsACString& aOriginNoSuffix, nsIURI* aInitialDomain) { + MOZ_ASSERT(aURI); + MOZ_ASSERT(!aOriginNoSuffix.IsEmpty()); + + // If the URI is supposed to inherit the security context of whoever loads it, + // we shouldn't make a content principal for it. + bool inheritsPrincipal; + nsresult rv = NS_URIChainHasFlags( + aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, + &inheritsPrincipal); + if (NS_FAILED(rv) || inheritsPrincipal) { + return NullPrincipal::Create(aAttrs); + } + + // Check whether the URI knows what its principal is supposed to be. +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) + nsCOMPtr<nsIURIWithSpecialOrigin> uriWithSpecialOrigin = + do_QueryInterface(aURI); + if (uriWithSpecialOrigin) { + nsCOMPtr<nsIURI> origin; + rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + MOZ_ASSERT(origin); + OriginAttributes attrs; + RefPtr<BasePrincipal> principal = + CreateContentPrincipal(origin, attrs, aInitialDomain); + return principal.forget(); + } +#endif + + nsCOMPtr<nsIPrincipal> blobPrincipal; + if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal( + aURI, getter_AddRefs(blobPrincipal))) { + MOZ_ASSERT(blobPrincipal); + MOZ_ASSERT(!aInitialDomain, + "an initial domain for a blob URI makes no sense"); + RefPtr<BasePrincipal> principal = Cast(blobPrincipal); + return principal.forget(); + } + + // Mint a content principal. + RefPtr<ContentPrincipal> principal = + new ContentPrincipal(aURI, aAttrs, aOriginNoSuffix, aInitialDomain); + return principal.forget(); +} + +already_AddRefed<BasePrincipal> BasePrincipal::CreateContentPrincipal( + const nsACString& aOrigin) { + MOZ_ASSERT(!StringBeginsWith(aOrigin, "["_ns), + "CreateContentPrincipal does not support System and Expanded " + "principals"); + + MOZ_ASSERT( + !StringBeginsWith(aOrigin, nsLiteralCString(NS_NULLPRINCIPAL_SCHEME ":")), + "CreateContentPrincipal does not support NullPrincipal"); + + nsAutoCString originNoSuffix; + OriginAttributes attrs; + if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) { + return nullptr; + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + return BasePrincipal::CreateContentPrincipal(uri, attrs); +} + +already_AddRefed<BasePrincipal> BasePrincipal::CloneForcingOriginAttributes( + const OriginAttributes& aOriginAttributes) { + if (NS_WARN_IF(!IsContentPrincipal())) { + return nullptr; + } + + nsAutoCString originNoSuffix; + nsresult rv = GetOriginNoSuffix(originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIURI> uri; + MOZ_ALWAYS_SUCCEEDS(GetURI(getter_AddRefs(uri))); + + // XXX: This does not copy over the domain. Should it? + RefPtr<ContentPrincipal> copy = + new ContentPrincipal(uri, aOriginAttributes, originNoSuffix, nullptr); + return copy.forget(); +} + +extensions::WebExtensionPolicy* BasePrincipal::ContentScriptAddonPolicy() { + AssertIsOnMainThread(); + RefPtr<extensions::WebExtensionPolicyCore> core = + ContentScriptAddonPolicyCore(); + return core ? core->GetMainThreadPolicy() : nullptr; +} + +RefPtr<extensions::WebExtensionPolicyCore> +BasePrincipal::ContentScriptAddonPolicyCore() { + if (!Is<ExpandedPrincipal>()) { + return nullptr; + } + + auto* expanded = As<ExpandedPrincipal>(); + for (const auto& prin : expanded->AllowList()) { + if (RefPtr<extensions::WebExtensionPolicyCore> policy = + BasePrincipal::Cast(prin)->AddonPolicyCore()) { + return policy; + } + } + + return nullptr; +} + +bool BasePrincipal::AddonAllowsLoad(nsIURI* aURI, + bool aExplicit /* = false */) { + if (Is<ExpandedPrincipal>()) { + return As<ExpandedPrincipal>()->AddonAllowsLoad(aURI, aExplicit); + } + if (auto policy = AddonPolicyCore()) { + return policy->CanAccessURI(aURI, aExplicit); + } + return false; +} + +NS_IMETHODIMP +BasePrincipal::GetLocalStorageQuotaKey(nsACString& aKey) { + aKey.Truncate(); + + nsCOMPtr<nsIURI> uri; + nsresult rv = GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED); + + // The special handling of the file scheme should be consistent with + // GetStorageOriginKey. + + nsAutoCString baseDomain; + rv = uri->GetAsciiHost(baseDomain); + NS_ENSURE_SUCCESS(rv, rv); + + if (baseDomain.IsEmpty() && uri->SchemeIs("file")) { + nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = url->GetDirectory(baseDomain); + NS_ENSURE_SUCCESS(rv, rv); + } else { + nsCOMPtr<nsIEffectiveTLDService> eTLDService( + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString eTLDplusOne; + rv = eTLDService->GetBaseDomain(uri, 0, eTLDplusOne); + if (NS_SUCCEEDED(rv)) { + baseDomain = eTLDplusOne; + } else if (rv == NS_ERROR_HOST_IS_IP_ADDRESS || + rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + rv = NS_OK; + } + NS_ENSURE_SUCCESS(rv, rv); + } + + OriginAttributesRef().CreateSuffix(aKey); + + nsAutoCString subdomainsDBKey; + rv = dom::StorageUtils::CreateReversedDomain(baseDomain, subdomainsDBKey); + NS_ENSURE_SUCCESS(rv, rv); + + aKey.Append(':'); + aKey.Append(subdomainsDBKey); + + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetNextSubDomainPrincipal( + nsIPrincipal** aNextSubDomainPrincipal) { + nsCOMPtr<nsIURI> uri; + nsresult rv = GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv) || !uri) { + return NS_OK; + } + + nsAutoCString host; + rv = uri->GetHost(host); + if (NS_FAILED(rv) || host.IsEmpty()) { + return NS_OK; + } + + nsCString subDomain; + rv = nsEffectiveTLDService::GetInstance()->GetNextSubDomain(host, subDomain); + + if (NS_FAILED(rv) || subDomain.IsEmpty()) { + return NS_OK; + } + + nsCOMPtr<nsIURI> subDomainURI; + rv = NS_MutateURI(uri).SetHost(subDomain).Finalize(subDomainURI); + if (NS_FAILED(rv) || !subDomainURI) { + return NS_OK; + } + // Copy the attributes over + mozilla::OriginAttributes attrs = OriginAttributesRef(); + + if (!StaticPrefs::permissions_isolateBy_userContext()) { + // Disable userContext for permissions. + attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID); + } + RefPtr<nsIPrincipal> principal = + mozilla::BasePrincipal::CreateContentPrincipal(subDomainURI, attrs); + + if (!principal) { + return NS_OK; + } + principal.forget(aNextSubDomainPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetStorageOriginKey(nsACString& aOriginKey) { + aOriginKey.Truncate(); + + nsCOMPtr<nsIURI> uri; + nsresult rv = GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED); + + // The special handling of the file scheme should be consistent with + // GetLocalStorageQuotaKey. + + nsAutoCString domainOrigin; + rv = uri->GetAsciiHost(domainOrigin); + NS_ENSURE_SUCCESS(rv, rv); + + if (domainOrigin.IsEmpty()) { + // For the file:/// protocol use the exact directory as domain. + if (uri->SchemeIs("file")) { + nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = url->GetDirectory(domainOrigin); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // Append reversed domain + nsAutoCString reverseDomain; + rv = dom::StorageUtils::CreateReversedDomain(domainOrigin, reverseDomain); + NS_ENSURE_SUCCESS(rv, rv); + + aOriginKey.Append(reverseDomain); + + // Append scheme + nsAutoCString scheme; + rv = uri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + + aOriginKey.Append(':'); + aOriginKey.Append(scheme); + + // Append port if any + int32_t port = NS_GetRealPort(uri); + if (port != -1) { + aOriginKey.Append(nsPrintfCString(":%d", port)); + } + + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsScriptAllowedByPolicy(bool* aIsScriptAllowedByPolicy) { + AssertIsOnMainThread(); + *aIsScriptAllowedByPolicy = false; + nsCOMPtr<nsIURI> prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + if (!ssm) { + return NS_ERROR_UNEXPECTED; + } + return ssm->PolicyAllowsScript(prinURI, aIsScriptAllowedByPolicy); +} + +bool SiteIdentifier::Equals(const SiteIdentifier& aOther) const { + MOZ_ASSERT(IsInitialized()); + MOZ_ASSERT(aOther.IsInitialized()); + return mPrincipal->FastEquals(aOther.mPrincipal); +} + +NS_IMETHODIMP +BasePrincipal::CreateReferrerInfo(mozilla::dom::ReferrerPolicy aReferrerPolicy, + nsIReferrerInfo** _retval) { + nsCOMPtr<nsIURI> prinURI; + RefPtr<dom::ReferrerInfo> info; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + info = new dom::ReferrerInfo(nullptr); + info.forget(_retval); + return NS_OK; + } + info = new dom::ReferrerInfo(prinURI, aReferrerPolicy); + info.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetPrecursorPrincipal(nsIPrincipal** aPrecursor) { + *aPrecursor = nullptr; + return NS_OK; +} + +NS_IMPL_ADDREF(BasePrincipal::Deserializer) +NS_IMPL_RELEASE(BasePrincipal::Deserializer) + +NS_INTERFACE_MAP_BEGIN(BasePrincipal::Deserializer) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsISerializable) + if (mPrincipal) { + return mPrincipal->QueryInterface(aIID, aInstancePtr); + } else +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +BasePrincipal::Deserializer::Write(nsIObjectOutputStream* aStream) { + // Read is used still for legacy principals + MOZ_RELEASE_ASSERT(false, "Old style serialization is removed"); + return NS_OK; +} + +} // namespace mozilla diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h new file mode 100644 index 0000000000..3b845cb7b4 --- /dev/null +++ b/caps/BasePrincipal.h @@ -0,0 +1,441 @@ +/* -*- 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 mozilla_BasePrincipal_h +#define mozilla_BasePrincipal_h + +#include <stdint.h> +#include "ErrorList.h" +#include "js/TypeDecls.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/OriginAttributes.h" +#include "mozilla/RefPtr.h" +#include "nsAtom.h" +#include "nsIObjectOutputStream.h" +#include "nsIPrincipal.h" +#include "nsJSPrincipals.h" +#include "nsStringFwd.h" +#include "nscore.h" + +class ExpandedPrincipal; +class mozIDOMWindow; +class nsIChannel; +class nsIReferrerInfo; +class nsISupports; +class nsIURI; +namespace Json { +class Value; +} + +namespace mozilla { + +namespace dom { +enum class ReferrerPolicy : uint8_t; +} + +namespace extensions { +class WebExtensionPolicy; +class WebExtensionPolicyCore; +} // namespace extensions + +class BasePrincipal; + +// Content principals (and content 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 + * implementations. + * + * We should merge nsJSPrincipals into this class at some point. + */ +class BasePrincipal : public nsJSPrincipals { + public: + // Warning: this enum impacts Principal serialization into JSON format. + // Only update if you know exactly what you are doing + enum PrincipalKind { + eNullPrincipal = 0, + eContentPrincipal, + eExpandedPrincipal, + eSystemPrincipal, + eKindMax = eSystemPrincipal + }; + + template <typename T> + bool Is() const { + return mKind == T::Kind(); + } + + template <typename T> + T* As() { + MOZ_ASSERT(Is<T>()); + return static_cast<T*>(this); + } + + enum DocumentDomainConsideration { + DontConsiderDocumentDomain, + ConsiderDocumentDomain + }; + bool Subsumes(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration); + + NS_IMETHOD GetOrigin(nsACString& aOrigin) final; + NS_IMETHOD GetAsciiOrigin(nsACString& aOrigin) override; + NS_IMETHOD GetOriginNoSuffix(nsACString& aOrigin) final; + NS_IMETHOD Equals(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD EqualsConsideringDomain(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD EqualsURI(nsIURI* aOtherURI, bool* _retval) override; + NS_IMETHOD EqualsForPermission(nsIPrincipal* other, bool aExactHost, + bool* _retval) final; + NS_IMETHOD Subsumes(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD SubsumesConsideringDomain(nsIPrincipal* other, + bool* _retval) final; + NS_IMETHOD SubsumesConsideringDomainIgnoringFPD(nsIPrincipal* other, + bool* _retval) final; + NS_IMETHOD CheckMayLoad(nsIURI* uri, bool allowIfInheritsPrincipal) final; + NS_IMETHOD CheckMayLoadWithReporting(nsIURI* uri, + bool allowIfInheritsPrincipal, + uint64_t innerWindowID) final; + NS_IMETHOD GetAddonPolicy(extensions::WebExtensionPolicy** aResult) final; + NS_IMETHOD GetContentScriptAddonPolicy( + extensions::WebExtensionPolicy** aResult) final; + NS_IMETHOD GetIsNullPrincipal(bool* aResult) override; + NS_IMETHOD GetIsContentPrincipal(bool* aResult) override; + NS_IMETHOD GetIsExpandedPrincipal(bool* aResult) override; + NS_IMETHOD GetIsSystemPrincipal(bool* aResult) override; + NS_IMETHOD GetScheme(nsACString& aScheme) override; + NS_IMETHOD SchemeIs(const char* aScheme, bool* aResult) override; + NS_IMETHOD IsURIInPrefList(const char* aPref, bool* aResult) override; + NS_IMETHOD IsURIInList(const nsACString& aList, bool* aResult) override; + NS_IMETHOD IsL10nAllowed(nsIURI* aURI, bool* aResult) override; + NS_IMETHOD GetAboutModuleFlags(uint32_t* flags) override; + NS_IMETHOD GetIsAddonOrExpandedAddonPrincipal(bool* aResult) override; + NS_IMETHOD GetOriginAttributes(JSContext* aCx, + JS::MutableHandle<JS::Value> aVal) final; + NS_IMETHOD GetAsciiSpec(nsACString& aSpec) override; + NS_IMETHOD GetSpec(nsACString& aSpec) override; + NS_IMETHOD GetExposablePrePath(nsACString& aResult) override; + NS_IMETHOD GetExposableSpec(nsACString& aSpec) override; + NS_IMETHOD GetHostPort(nsACString& aRes) override; + NS_IMETHOD GetHost(nsACString& aRes) override; + NS_IMETHOD GetPrePath(nsACString& aResult) override; + NS_IMETHOD GetFilePath(nsACString& aResult) override; + NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final; + NS_IMETHOD GetIsIpAddress(bool* aIsIpAddress) override; + NS_IMETHOD GetIsLocalIpAddress(bool* aIsIpAddress) override; + NS_IMETHOD GetIsOnion(bool* aIsOnion) override; + NS_IMETHOD GetIsInIsolatedMozBrowserElement( + bool* aIsInIsolatedMozBrowserElement) final; + NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final; + NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final; + NS_IMETHOD GetSiteOrigin(nsACString& aSiteOrigin) final; + NS_IMETHOD GetSiteOriginNoSuffix(nsACString& aSiteOrigin) override; + NS_IMETHOD IsThirdPartyURI(nsIURI* uri, bool* aRes) override; + NS_IMETHOD IsThirdPartyPrincipal(nsIPrincipal* uri, bool* aRes) override; + NS_IMETHOD IsThirdPartyChannel(nsIChannel* aChannel, bool* aRes) override; + NS_IMETHOD GetIsOriginPotentiallyTrustworthy(bool* aResult) override; + NS_IMETHOD GetIsLoopbackHost(bool* aResult) override; + NS_IMETHOD IsSameOrigin(nsIURI* aURI, bool* aRes) override; + NS_IMETHOD GetPrefLightCacheKey(nsIURI* aURI, bool aWithCredentials, + const OriginAttributes& aOriginAttributes, + nsACString& _retval) override; + NS_IMETHOD HasFirstpartyStorageAccess(mozIDOMWindow* aCheckWindow, + uint32_t* aRejectedReason, + bool* aOutAllowed) override; + NS_IMETHOD GetAsciiHost(nsACString& aAsciiHost) override; + NS_IMETHOD GetLocalStorageQuotaKey(nsACString& aRes) override; + NS_IMETHOD AllowsRelaxStrictFileOriginPolicy(nsIURI* aURI, + bool* aRes) override; + NS_IMETHOD CreateReferrerInfo(mozilla::dom::ReferrerPolicy aReferrerPolicy, + nsIReferrerInfo** _retval) override; + NS_IMETHOD GetIsScriptAllowedByPolicy( + bool* aIsScriptAllowedByPolicy) override; + NS_IMETHOD GetStorageOriginKey(nsACString& aOriginKey) override; + + NS_IMETHOD GetNextSubDomainPrincipal( + nsIPrincipal** aNextSubDomainPrincipal) override; + + NS_IMETHOD GetPrecursorPrincipal(nsIPrincipal** aPrecursor) override; + + nsresult ToJSON(nsACString& aJSON); + static already_AddRefed<BasePrincipal> FromJSON(const nsACString& aJSON); + // Method populates a passed Json::Value with serializable fields + // which represent all of the fields to deserialize the principal + virtual nsresult PopulateJSONObject(Json::Value& aObject); + + virtual bool AddonHasPermission(const nsAtom* aPerm); + + virtual bool IsContentPrincipal() const { return false; }; + + static BasePrincipal* Cast(nsIPrincipal* aPrin) { + return static_cast<BasePrincipal*>(aPrin); + } + + static BasePrincipal& Cast(nsIPrincipal& aPrin) { + return *static_cast<BasePrincipal*>(&aPrin); + } + + static const BasePrincipal* Cast(const nsIPrincipal* aPrin) { + return static_cast<const BasePrincipal*>(aPrin); + } + + static const BasePrincipal& Cast(const nsIPrincipal& aPrin) { + return *static_cast<const BasePrincipal*>(&aPrin); + } + + static already_AddRefed<BasePrincipal> CreateContentPrincipal( + const nsACString& aOrigin); + + // This method may not create a content principal in case it's not possible to + // generate a correct origin from the passed URI. If this happens, a + // NullPrincipal is returned. + // + // If `aInitialDomain` is specified, and a ContentPrincipal is set, it will + // initially have its domain set to the given value, without re-computing js + // wrappers. Unlike `SetDomain()` this is safe to do off-main-thread. + + static already_AddRefed<BasePrincipal> CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs, + nsIURI* aInitialDomain = nullptr); + + const OriginAttributes& OriginAttributesRef() final { + return mOriginAttributes; + } + extensions::WebExtensionPolicy* AddonPolicy(); + RefPtr<extensions::WebExtensionPolicyCore> AddonPolicyCore(); + uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; } + uint32_t PrivateBrowsingId() const { + return mOriginAttributes.mPrivateBrowsingId; + } + bool IsInIsolatedMozBrowserElement() const { + return mOriginAttributes.mInIsolatedMozBrowser; + } + + PrincipalKind Kind() const { return mKind; } + + already_AddRefed<BasePrincipal> CloneForcingOriginAttributes( + const OriginAttributes& aOriginAttributes); + + // If this is an add-on content script principal, returns its AddonPolicy. + // Otherwise returns null. + extensions::WebExtensionPolicy* ContentScriptAddonPolicy(); + RefPtr<extensions::WebExtensionPolicyCore> ContentScriptAddonPolicyCore(); + + // Helper to check whether this principal is associated with an addon that + // allows unprivileged code to load aURI. aExplicit == true will prevent + // use of all_urls permission, requiring the domain in its permissions. + bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false); + + // Call these to avoid the cost of virtual dispatch. + inline bool FastEquals(nsIPrincipal* aOther); + inline bool FastEqualsConsideringDomain(nsIPrincipal* aOther); + inline bool FastSubsumes(nsIPrincipal* aOther); + inline bool FastSubsumesConsideringDomain(nsIPrincipal* aOther); + inline bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther); + inline bool FastSubsumesConsideringDomainIgnoringFPD(nsIPrincipal* aOther); + + // Fast way to check whether we have a system principal. + inline bool IsSystemPrincipal() const; + + // Returns the principal to inherit when a caller with this principal loads + // the given URI. + // + // For most principal types, this returns the principal itself. For expanded + // principals, it returns the first sub-principal which subsumes the given URI + // (or, if no URI is given, the last allowlist principal). + nsIPrincipal* PrincipalToInherit(nsIURI* aRequestedURI = nullptr); + + /* Returns true if this principal's CSP should override a document's CSP for + * loads that it triggers. Currently true for expanded principals which + * subsume the document principal, and add-on content principals regardless + * of whether they subsume the document principal. + */ + bool OverridesCSP(nsIPrincipal* aDocumentPrincipal); + + uint32_t GetOriginNoSuffixHash() const { return mOriginNoSuffix->hash(); } + uint32_t GetOriginSuffixHash() const { return mOriginSuffix->hash(); } + + virtual nsresult GetSiteIdentifier(SiteIdentifier& aSite) = 0; + + protected: + BasePrincipal(PrincipalKind aKind, const nsACString& aOriginNoSuffix, + const OriginAttributes& aOriginAttributes); + BasePrincipal(BasePrincipal* aOther, + const OriginAttributes& aOriginAttributes); + + virtual ~BasePrincipal(); + + // Note that this does not check OriginAttributes. Callers that depend on + // those must call Subsumes instead. + virtual bool SubsumesInternal(nsIPrincipal* aOther, + DocumentDomainConsideration aConsider) = 0; + + // Internal, side-effect-free check to determine whether the concrete + // principal would allow the load ignoring any common behavior implemented in + // BasePrincipal::CheckMayLoad. + // + // Safe to call from any thread, unlike CheckMayLoad. + virtual bool MayLoadInternal(nsIURI* aURI) = 0; + friend class ::ExpandedPrincipal; + + // Helper for implementing CheckMayLoad and CheckMayLoadWithReporting. + nsresult CheckMayLoadHelper(nsIURI* aURI, bool aAllowIfInheritsPrincipal, + bool aReport, uint64_t aInnerWindowID); + + void SetHasExplicitDomain() { mHasExplicitDomain = true; } + bool GetHasExplicitDomain() { return mHasExplicitDomain; } + + // KeyValT holds a principal subtype-specific key value and the associated + // parsed value after JSON parsing. + template <typename SerializedKey> + struct KeyValT { + static_assert(sizeof(SerializedKey) == 1, + "SerializedKey should be a uint8_t"); + SerializedKey key; + bool valueWasSerialized; + nsCString value; + }; + + // Common base class for all Deserializer implementations in concrete + // subclasses. Subclasses will initialize `mPrincipal` in `Read`, and then + // calls to `QueryInterface` will QI on the target object. + class Deserializer : public nsISerializable { + public: + NS_DECL_ISUPPORTS + NS_IMETHOD Write(nsIObjectOutputStream* aStream) override; + + protected: + virtual ~Deserializer() = default; + RefPtr<BasePrincipal> mPrincipal; + }; + + private: + static already_AddRefed<BasePrincipal> CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs, + const nsACString& aOriginNoSuffix, nsIURI* aInitialDomain); + + bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration); + + const RefPtr<nsAtom> mOriginNoSuffix; + const RefPtr<nsAtom> mOriginSuffix; + + const OriginAttributes mOriginAttributes; + const PrincipalKind mKind; + std::atomic<bool> mHasExplicitDomain; +}; + +inline bool BasePrincipal::FastEquals(nsIPrincipal* aOther) { + MOZ_ASSERT(aOther); + + auto other = Cast(aOther); + if (Kind() != other->Kind()) { + // Principals of different kinds can't be equal. + return false; + } + + // Two principals are considered to be equal if their origins are the same. + // If the two principals are content principals, their origin attributes + // (aka the origin suffix) must also match. + if (Kind() == eSystemPrincipal) { + return this == other; + } + + if (Kind() == eContentPrincipal || Kind() == eNullPrincipal) { + return mOriginNoSuffix == other->mOriginNoSuffix && + mOriginSuffix == other->mOriginSuffix; + } + + MOZ_ASSERT(Kind() == eExpandedPrincipal); + return mOriginNoSuffix == other->mOriginNoSuffix; +} + +inline bool BasePrincipal::FastEqualsConsideringDomain(nsIPrincipal* aOther) { + MOZ_ASSERT(aOther); + + // If neither of the principals have document.domain set, we use the fast path + // in Equals(). Otherwise, we fall back to the slow path below. + auto other = Cast(aOther); + if (!mHasExplicitDomain && !other->mHasExplicitDomain) { + return FastEquals(aOther); + } + + return Subsumes(aOther, ConsiderDocumentDomain) && + other->Subsumes(this, ConsiderDocumentDomain); +} + +inline bool BasePrincipal::FastSubsumes(nsIPrincipal* aOther) { + MOZ_ASSERT(aOther); + + // If two principals are equal, then they both subsume each other. + if (FastEquals(aOther)) { + return true; + } + + // Otherwise, fall back to the slow path. + return Subsumes(aOther, DontConsiderDocumentDomain); +} + +inline bool BasePrincipal::FastSubsumesConsideringDomain(nsIPrincipal* aOther) { + MOZ_ASSERT(aOther); + + // If neither of the principals have document.domain set, we hand off to + // FastSubsumes() which has fast paths for some special cases. Otherwise, we + // fall back to the slow path below. + if (!mHasExplicitDomain && !Cast(aOther)->mHasExplicitDomain) { + return FastSubsumes(aOther); + } + + return Subsumes(aOther, ConsiderDocumentDomain); +} + +inline bool BasePrincipal::FastSubsumesIgnoringFPD(nsIPrincipal* aOther) { + return FastSubsumesIgnoringFPD(aOther, DontConsiderDocumentDomain); +} + +inline bool BasePrincipal::FastSubsumesConsideringDomainIgnoringFPD( + nsIPrincipal* aOther) { + return FastSubsumesIgnoringFPD(aOther, ConsiderDocumentDomain); +} + +inline bool BasePrincipal::IsSystemPrincipal() const { + return Kind() == eSystemPrincipal; +} + +} // namespace mozilla + +inline bool nsIPrincipal::IsSystemPrincipal() const { + return mozilla::BasePrincipal::Cast(this)->IsSystemPrincipal(); +} + +#endif /* mozilla_BasePrincipal_h */ diff --git a/caps/ContentPrincipal.cpp b/caps/ContentPrincipal.cpp new file mode 100644 index 0000000000..22097e2c8f --- /dev/null +++ b/caps/ContentPrincipal.cpp @@ -0,0 +1,710 @@ +/* -*- 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 "ContentPrincipal.h" + +#include "mozIThirdPartyUtil.h" +#include "nsContentUtils.h" +#include "nscore.h" +#include "nsScriptSecurityManager.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "pratom.h" +#include "nsIURI.h" +#include "nsIURL.h" +#include "nsIStandardURL.h" +#include "nsIURIWithSpecialOrigin.h" +#include "nsIURIMutator.h" +#include "nsJSPrincipals.h" +#include "nsIEffectiveTLDService.h" +#include "nsIClassInfoImpl.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIProtocolHandler.h" +#include "nsError.h" +#include "nsIContentSecurityPolicy.h" +#include "nsNetCID.h" +#include "js/RealmIterators.h" +#include "js/Wrapper.h" + +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ExtensionPolicyService.h" +#include "mozilla/Preferences.h" +#include "mozilla/HashFunctions.h" + +#include "nsSerializationHelper.h" +#include "json/json.h" + +using namespace mozilla; + +NS_IMPL_CLASSINFO(ContentPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY, + NS_PRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(ContentPrincipal, nsIPrincipal) +NS_IMPL_CI_INTERFACE_GETTER(ContentPrincipal, nsIPrincipal) + +ContentPrincipal::ContentPrincipal(nsIURI* aURI, + const OriginAttributes& aOriginAttributes, + const nsACString& aOriginNoSuffix, + nsIURI* aInitialDomain) + : BasePrincipal(eContentPrincipal, aOriginNoSuffix, aOriginAttributes), + mURI(aURI), + mDomain(aInitialDomain) { + if (mDomain) { + // We're just creating the principal, so no need to re-compute wrappers. + SetHasExplicitDomain(); + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // Assert that the URI we get here isn't any of the schemes that we know we + // should not get here. These schemes always either inherit their principal + // or fall back to a null principal. These are schemes which return + // URI_INHERITS_SECURITY_CONTEXT from their protocol handler's + // GetProtocolFlags function. + bool hasFlag = false; + MOZ_DIAGNOSTIC_ASSERT( + NS_SUCCEEDED(NS_URIChainHasFlags( + aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &hasFlag)) && + !hasFlag); +#endif +} + +ContentPrincipal::ContentPrincipal(ContentPrincipal* aOther, + const OriginAttributes& aOriginAttributes) + : BasePrincipal(aOther, aOriginAttributes), + mURI(aOther->mURI), + mDomain(aOther->mDomain), + mAddon(aOther->mAddon) {} + +ContentPrincipal::~ContentPrincipal() = default; + +nsresult ContentPrincipal::GetScriptLocation(nsACString& aStr) { + return mURI->GetSpec(aStr); +} + +/* static */ +nsresult ContentPrincipal::GenerateOriginNoSuffixFromURI( + nsIURI* aURI, nsACString& aOriginNoSuffix) { + if (!aURI) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIURI> origin = NS_GetInnermostURI(aURI); + if (!origin) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(!NS_IsAboutBlank(origin), + "The inner URI for about:blank must be moz-safe-about:blank"); + + // Handle non-strict file:// uris. + if (!nsScriptSecurityManager::GetStrictFileOriginPolicy() && + NS_URIIsLocalFile(origin)) { + // If strict file origin policy is not in effect, all local files are + // considered to be same-origin, so return a known dummy origin here. + aOriginNoSuffix.AssignLiteral("file://UNIVERSAL_FILE_URI_ORIGIN"); + return NS_OK; + } + + nsresult rv; +// NB: This is only compiled for Thunderbird/Suite. +#if IS_ORIGIN_IS_FULL_SPEC_DEFINED + bool fullSpec = false; + rv = NS_URIChainHasFlags(origin, nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, + &fullSpec); + NS_ENSURE_SUCCESS(rv, rv); + if (fullSpec) { + return origin->GetAsciiSpec(aOriginNoSuffix); + } +#endif + + // We want the invariant that prinA.origin == prinB.origin i.f.f. + // prinA.equals(prinB). However, this requires that we impose certain + // constraints on the behavior and origin semantics of principals, and in + // particular, forbid creating origin strings for principals whose equality + // constraints are not expressible as strings (i.e. object equality). + // Moreover, we want to forbid URIs containing the magic "^" we use as a + // separating character for origin attributes. + // + // These constraints can generally be achieved by restricting .origin to + // nsIStandardURL-based URIs, but there are a few other URI schemes that we + // need to handle. + if (origin->SchemeIs("about") || + (origin->SchemeIs("moz-safe-about") && + // We generally consider two about:foo origins to be same-origin, but + // about:blank is special since it can be generated from different + // sources. We check for moz-safe-about:blank since origin is an + // innermost URI. + !StringBeginsWith(origin->GetSpecOrDefault(), + "moz-safe-about:blank"_ns))) { + rv = origin->GetAsciiSpec(aOriginNoSuffix); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t pos = aOriginNoSuffix.FindChar('?'); + int32_t hashPos = aOriginNoSuffix.FindChar('#'); + + if (hashPos != kNotFound && (pos == kNotFound || hashPos < pos)) { + pos = hashPos; + } + + if (pos != kNotFound) { + aOriginNoSuffix.Truncate(pos); + } + + // These URIs could technically contain a '^', but they never should. + if (NS_WARN_IF(aOriginNoSuffix.FindChar('^', 0) != -1)) { + aOriginNoSuffix.Truncate(); + return NS_ERROR_FAILURE; + } + return NS_OK; + } + + // This URL can be a blobURL. In this case, we should use the 'parent' + // principal instead. + nsCOMPtr<nsIPrincipal> blobPrincipal; + if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal( + origin, getter_AddRefs(blobPrincipal))) { + MOZ_ASSERT(blobPrincipal); + return blobPrincipal->GetOriginNoSuffix(aOriginNoSuffix); + } + + // If we reached this branch, we can only create an origin if we have a + // nsIStandardURL. So, we query to a nsIStandardURL, and fail if we aren't + // an instance of an nsIStandardURL nsIStandardURLs have the good property + // of escaping the '^' character in their specs, which means that we can be + // sure that the caret character (which is reserved for delimiting the end + // of the spec, and the beginning of the origin attributes) is not present + // in the origin string + nsCOMPtr<nsIStandardURL> standardURL = do_QueryInterface(origin); + if (!standardURL) { + return NS_ERROR_FAILURE; + } + + // See whether we have a useful hostPort. If we do, use that. + nsAutoCString hostPort; + if (!origin->SchemeIs("chrome")) { + rv = origin->GetAsciiHostPort(hostPort); + NS_ENSURE_SUCCESS(rv, rv); + } + if (!hostPort.IsEmpty()) { + rv = origin->GetScheme(aOriginNoSuffix); + NS_ENSURE_SUCCESS(rv, rv); + aOriginNoSuffix.AppendLiteral("://"); + aOriginNoSuffix.Append(hostPort); + return NS_OK; + } + + rv = aURI->GetAsciiSpec(aOriginNoSuffix); + NS_ENSURE_SUCCESS(rv, rv); + + // The origin, when taken from the spec, should not contain the ref part of + // the URL. + + int32_t pos = aOriginNoSuffix.FindChar('?'); + int32_t hashPos = aOriginNoSuffix.FindChar('#'); + + if (hashPos != kNotFound && (pos == kNotFound || hashPos < pos)) { + pos = hashPos; + } + + if (pos != kNotFound) { + aOriginNoSuffix.Truncate(pos); + } + + return NS_OK; +} + +bool ContentPrincipal::SubsumesInternal( + nsIPrincipal* aOther, + BasePrincipal::DocumentDomainConsideration aConsideration) { + MOZ_ASSERT(aOther); + + // For ContentPrincipal, Subsumes is equivalent to Equals. + if (aOther == this) { + return true; + } + + // If either the subject or the object has changed its principal by + // explicitly setting document.domain then the other must also have + // done so in order to be considered the same origin. This prevents + // DNS spoofing based on document.domain (154930) + if (aConsideration == ConsiderDocumentDomain) { + // Get .domain on each principal. + nsCOMPtr<nsIURI> thisDomain, otherDomain; + GetDomain(getter_AddRefs(thisDomain)); + aOther->GetDomain(getter_AddRefs(otherDomain)); + + // If either has .domain set, we have equality i.f.f. the domains match. + // Otherwise, we fall through to the non-document-domain-considering case. + if (thisDomain || otherDomain) { + bool isMatch = + nsScriptSecurityManager::SecurityCompareURIs(thisDomain, otherDomain); +#ifdef DEBUG + if (isMatch) { + nsAutoCString thisSiteOrigin, otherSiteOrigin; + MOZ_ALWAYS_SUCCEEDS(GetSiteOrigin(thisSiteOrigin)); + MOZ_ALWAYS_SUCCEEDS(aOther->GetSiteOrigin(otherSiteOrigin)); + MOZ_ASSERT( + thisSiteOrigin == otherSiteOrigin, + "SubsumesConsideringDomain passed with mismatched siteOrigin!"); + } +#endif + return isMatch; + } + } + + // Do a fast check (including origin attributes) or a slow uri comparison. + return FastEquals(aOther) || aOther->IsSameOrigin(mURI); +} + +NS_IMETHODIMP +ContentPrincipal::GetURI(nsIURI** aURI) { + *aURI = do_AddRef(mURI).take(); + return NS_OK; +} + +bool ContentPrincipal::MayLoadInternal(nsIURI* aURI) { + MOZ_ASSERT(aURI); + +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) + nsCOMPtr<nsIURIWithSpecialOrigin> uriWithSpecialOrigin = + do_QueryInterface(aURI); + if (uriWithSpecialOrigin) { + nsCOMPtr<nsIURI> origin; + nsresult rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + MOZ_ASSERT(origin); + OriginAttributes attrs; + RefPtr<BasePrincipal> principal = + BasePrincipal::CreateContentPrincipal(origin, attrs); + return nsIPrincipal::Subsumes(principal); + } +#endif + + nsCOMPtr<nsIPrincipal> blobPrincipal; + if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal( + aURI, getter_AddRefs(blobPrincipal))) { + MOZ_ASSERT(blobPrincipal); + return nsIPrincipal::Subsumes(blobPrincipal); + } + + // If this principal is associated with an addon, check whether that addon + // has been given permission to load from this domain. + if (AddonAllowsLoad(aURI)) { + return true; + } + + if (nsScriptSecurityManager::SecurityCompareURIs(mURI, aURI)) { + return true; + } + + // If strict file origin policy is in effect, local files will always fail + // SecurityCompareURIs unless they are identical. Explicitly check file origin + // policy, in that case. + if (nsScriptSecurityManager::GetStrictFileOriginPolicy() && + NS_URIIsLocalFile(aURI) && NS_RelaxStrictFileOriginPolicy(aURI, mURI)) { + return true; + } + + return false; +} + +uint32_t ContentPrincipal::GetHashValue() { + MOZ_ASSERT(mURI, "Need a principal URI"); + + nsCOMPtr<nsIURI> uri; + GetDomain(getter_AddRefs(uri)); + if (!uri) { + GetURI(getter_AddRefs(uri)); + }; + return NS_SecurityHashURI(uri); +} + +NS_IMETHODIMP +ContentPrincipal::GetDomain(nsIURI** aDomain) { + if (!GetHasExplicitDomain()) { + *aDomain = nullptr; + return NS_OK; + } + + mozilla::MutexAutoLock lock(mMutex); + NS_ADDREF(*aDomain = mDomain); + return NS_OK; +} + +NS_IMETHODIMP +ContentPrincipal::SetDomain(nsIURI* aDomain) { + AssertIsOnMainThread(); + MOZ_ASSERT(aDomain); + + { + mozilla::MutexAutoLock lock(mMutex); + mDomain = aDomain; + SetHasExplicitDomain(); + } + + // Set the changed-document-domain flag on compartments containing realms + // using this principal. + auto cb = [](JSContext*, void*, JS::Realm* aRealm, + const JS::AutoRequireNoGC& nogc) { + JS::Compartment* comp = JS::GetCompartmentForRealm(aRealm); + xpc::SetCompartmentChangedDocumentDomain(comp); + }; + JSPrincipals* principals = + nsJSPrincipals::get(static_cast<nsIPrincipal*>(this)); + + dom::AutoJSAPI jsapi; + jsapi.Init(); + JS::IterateRealmsWithPrincipals(jsapi.cx(), principals, nullptr, cb); + + return NS_OK; +} + +static nsresult GetSpecialBaseDomain(const nsCOMPtr<nsIURI>& aURI, + bool* aHandled, nsACString& aBaseDomain) { + *aHandled = false; + + // Special handling for a file URI. + if (NS_URIIsLocalFile(aURI)) { + // If strict file origin policy is not in effect, all local files are + // considered to be same-origin, so return a known dummy domain here. + if (!nsScriptSecurityManager::GetStrictFileOriginPolicy()) { + *aHandled = true; + aBaseDomain.AssignLiteral("UNIVERSAL_FILE_URI_ORIGIN"); + return NS_OK; + } + + // Otherwise, we return the file path. + nsCOMPtr<nsIURL> url = do_QueryInterface(aURI); + + if (url) { + *aHandled = true; + return url->GetFilePath(aBaseDomain); + } + } + + bool hasNoRelativeFlag; + nsresult rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_NORELATIVE, + &hasNoRelativeFlag); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // In case of FTP we want to get base domain via TLD service even if FTP + // protocol handler is disabled and the scheme is handled by external protocol + // handler which returns URI_NORELATIVE flag. + if (hasNoRelativeFlag && !aURI->SchemeIs("ftp")) { + *aHandled = true; + return aURI->GetSpec(aBaseDomain); + } + + if (aURI->SchemeIs("indexeddb")) { + *aHandled = true; + return aURI->GetSpec(aBaseDomain); + } + + return NS_OK; +} + +NS_IMETHODIMP +ContentPrincipal::GetBaseDomain(nsACString& aBaseDomain) { + // Handle some special URIs first. + bool handled; + nsresult rv = GetSpecialBaseDomain(mURI, &handled, aBaseDomain); + NS_ENSURE_SUCCESS(rv, rv); + + if (handled) { + return NS_OK; + } + + // For everything else, we ask the TLD service via the ThirdPartyUtil. + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID); + if (!thirdPartyUtil) { + return NS_ERROR_FAILURE; + } + + return thirdPartyUtil->GetBaseDomain(mURI, aBaseDomain); +} + +NS_IMETHODIMP +ContentPrincipal::GetSiteOriginNoSuffix(nsACString& aSiteOrigin) { + nsresult rv = GetOriginNoSuffix(aSiteOrigin); + NS_ENSURE_SUCCESS(rv, rv); + + // It is possible for two principals with the same origin to have different + // mURI values. In order to ensure that two principals with matching origins + // also have matching siteOrigins, we derive the siteOrigin entirely from the + // origin string and do not rely on mURI at all here. + nsCOMPtr<nsIURI> origin; + rv = NS_NewURI(getter_AddRefs(origin), aSiteOrigin); + if (NS_FAILED(rv)) { + // We got an error parsing the origin as a URI? siteOrigin == origin + // aSiteOrigin was already filled with `OriginNoSuffix` + return rv; + } + + // Handle some special URIs first. + nsAutoCString baseDomain; + bool handled; + rv = GetSpecialBaseDomain(origin, &handled, baseDomain); + NS_ENSURE_SUCCESS(rv, rv); + + if (handled) { + // This is a special URI ("file:", "about:", "view-source:", etc). Just + // return the origin. + return NS_OK; + } + + // For everything else, we ask the TLD service. Note that, unlike in + // GetBaseDomain, we don't use ThirdPartyUtil.getBaseDomain because if the + // host is an IP address that returns the raw address and we can't use it with + // SetHost below because SetHost expects '[' and ']' around IPv6 addresses. + // See bug 1491728. + nsCOMPtr<nsIEffectiveTLDService> tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + if (!tldService) { + return NS_ERROR_FAILURE; + } + + bool gotBaseDomain = false; + rv = tldService->GetBaseDomain(origin, 0, baseDomain); + if (NS_SUCCEEDED(rv)) { + gotBaseDomain = true; + } else { + // If this is an IP address or something like "localhost", we just continue + // with gotBaseDomain = false. + if (rv != NS_ERROR_HOST_IS_IP_ADDRESS && + rv != NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS && + rv != NS_ERROR_INVALID_ARG) { + return rv; + } + } + + // NOTE: Calling `SetHostPort` with a portless domain is insufficient to clear + // the port, so an extra `SetPort` call has to be made. + nsCOMPtr<nsIURI> siteUri; + NS_MutateURI mutator(origin); + mutator.SetUserPass(""_ns).SetPort(-1); + if (gotBaseDomain) { + mutator.SetHost(baseDomain); + } + rv = mutator.Finalize(siteUri); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create siteUri"); + NS_ENSURE_SUCCESS(rv, rv); + + aSiteOrigin.Truncate(); + rv = GenerateOriginNoSuffixFromURI(siteUri, aSiteOrigin); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create siteOriginNoSuffix"); + return rv; +} + +nsresult ContentPrincipal::GetSiteIdentifier(SiteIdentifier& aSite) { + nsCString siteOrigin; + nsresult rv = GetSiteOrigin(siteOrigin); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<BasePrincipal> principal = CreateContentPrincipal(siteOrigin); + if (!principal) { + NS_WARNING("could not instantiate content principal"); + return NS_ERROR_FAILURE; + } + + aSite.Init(principal); + return NS_OK; +} + +RefPtr<extensions::WebExtensionPolicyCore> ContentPrincipal::AddonPolicyCore() { + mozilla::MutexAutoLock lock(mMutex); + if (!mAddon.isSome()) { + NS_ENSURE_TRUE(mURI, nullptr); + + RefPtr<extensions::WebExtensionPolicyCore> core; + if (mURI->SchemeIs("moz-extension")) { + nsCString host; + NS_ENSURE_SUCCESS(mURI->GetHost(host), nullptr); + core = ExtensionPolicyService::GetCoreByHost(host); + } + + mAddon.emplace(core); + } + return *mAddon; +} + +NS_IMETHODIMP +ContentPrincipal::GetAddonId(nsAString& aAddonId) { + if (RefPtr<extensions::WebExtensionPolicyCore> policy = AddonPolicyCore()) { + policy->Id()->ToString(aAddonId); + } else { + aAddonId.Truncate(); + } + return NS_OK; +} + +NS_IMETHODIMP +ContentPrincipal::Deserializer::Read(nsIObjectInputStream* aStream) { + MOZ_ASSERT(!mPrincipal); + + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsIURI> principalURI; + nsresult rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports)); + if (NS_FAILED(rv)) { + return rv; + } + + principalURI = do_QueryInterface(supports); + // Enforce re-parsing about: URIs so that if they change, we continue to use + // their new principals correctly. + if (principalURI->SchemeIs("about")) { + nsAutoCString spec; + principalURI->GetSpec(spec); + NS_ENSURE_SUCCESS(NS_NewURI(getter_AddRefs(principalURI), spec), + NS_ERROR_FAILURE); + } + + nsCOMPtr<nsIURI> domain; + rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports)); + if (NS_FAILED(rv)) { + return rv; + } + + domain = do_QueryInterface(supports); + + nsAutoCString suffix; + rv = aStream->ReadCString(suffix); + NS_ENSURE_SUCCESS(rv, rv); + + OriginAttributes attrs; + bool ok = attrs.PopulateFromSuffix(suffix); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + // Since Bug 965637 we do not serialize the CSP within the + // Principal anymore. Nevertheless there might still be + // serialized Principals that do have a serialized CSP. + // For now, we just read the CSP here but do not actually + // consume it. Please note that we deliberately ignore + // the return value to avoid CSP deserialization problems. + // After Bug 1508939 we will have a new serialization for + // Principals which allows us to update the code here. + // Additionally, the format for serialized CSPs changed + // within Bug 965637 which also can cause failures within + // the CSP deserialization code. + Unused << NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports)); + + nsAutoCString originNoSuffix; + rv = GenerateOriginNoSuffixFromURI(principalURI, originNoSuffix); + NS_ENSURE_SUCCESS(rv, rv); + + mPrincipal = + new ContentPrincipal(principalURI, attrs, originNoSuffix, domain); + return NS_OK; +} + +nsresult ContentPrincipal::PopulateJSONObject(Json::Value& aObject) { + nsAutoCString principalURI; + nsresult rv = mURI->GetSpec(principalURI); + NS_ENSURE_SUCCESS(rv, rv); + + // We turn each int enum field into a JSON string key of the object + // aObject is the inner JSON object that has stringified enum keys + // An example aObject might be: + // + // eURI eSuffix + // | | + // {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"} + // | | | | + // ----------------------------- | + // | | | + // Key ---------------------- + // | + // Value + aObject[std::to_string(eURI)] = principalURI.get(); + + if (GetHasExplicitDomain()) { + nsAutoCString domainStr; + { + MutexAutoLock lock(mMutex); + rv = mDomain->GetSpec(domainStr); + NS_ENSURE_SUCCESS(rv, rv); + } + aObject[std::to_string(eDomain)] = domainStr.get(); + } + + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + if (suffix.Length() > 0) { + aObject[std::to_string(eSuffix)] = suffix.get(); + } + + return NS_OK; +} + +already_AddRefed<BasePrincipal> ContentPrincipal::FromProperties( + nsTArray<ContentPrincipal::KeyVal>& aFields) { + MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys"); + nsresult rv; + nsCOMPtr<nsIURI> principalURI; + nsCOMPtr<nsIURI> domain; + nsCOMPtr<nsIContentSecurityPolicy> csp; + OriginAttributes attrs; + + // The odd structure here is to make the code to not compile + // if all the switch enum cases haven't been codified + for (const auto& field : aFields) { + switch (field.key) { + case ContentPrincipal::eURI: + if (!field.valueWasSerialized) { + MOZ_ASSERT( + false, + "Content principals require a principal URI in serialized JSON"); + return nullptr; + } + rv = NS_NewURI(getter_AddRefs(principalURI), field.value.get()); + NS_ENSURE_SUCCESS(rv, nullptr); + + { + // Enforce re-parsing about: URIs so that if they change, we + // continue to use their new principals correctly. + if (principalURI->SchemeIs("about")) { + nsAutoCString spec; + principalURI->GetSpec(spec); + if (NS_FAILED(NS_NewURI(getter_AddRefs(principalURI), spec))) { + return nullptr; + } + } + } + break; + case ContentPrincipal::eDomain: + if (field.valueWasSerialized) { + rv = NS_NewURI(getter_AddRefs(domain), field.value.get()); + NS_ENSURE_SUCCESS(rv, nullptr); + } + break; + case ContentPrincipal::eSuffix: + if (field.valueWasSerialized) { + bool ok = attrs.PopulateFromSuffix(field.value); + if (!ok) { + return nullptr; + } + } + break; + } + } + nsAutoCString originNoSuffix; + rv = ContentPrincipal::GenerateOriginNoSuffixFromURI(principalURI, + originNoSuffix); + if (NS_FAILED(rv)) { + return nullptr; + } + + RefPtr<ContentPrincipal> principal = + new ContentPrincipal(principalURI, attrs, originNoSuffix, domain); + + return principal.forget(); +} diff --git a/caps/ContentPrincipal.h b/caps/ContentPrincipal.h new file mode 100644 index 0000000000..d2f9d7ed83 --- /dev/null +++ b/caps/ContentPrincipal.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 mozilla_ContentPrincipal_h +#define mozilla_ContentPrincipal_h + +#include "nsCOMPtr.h" +#include "nsJSPrincipals.h" +#include "nsTArray.h" +#include "nsNetUtil.h" +#include "nsScriptSecurityManager.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Mutex.h" +#include "mozilla/extensions/WebExtensionPolicy.h" + +namespace Json { +class Value; +} + +namespace mozilla { + +class ContentPrincipal final : public BasePrincipal { + public: + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + uint32_t GetHashValue() override; + NS_IMETHOD GetURI(nsIURI** aURI) override; + NS_IMETHOD GetDomain(nsIURI** aDomain) override; + NS_IMETHOD SetDomain(nsIURI* aDomain) override; + NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; + NS_IMETHOD GetAddonId(nsAString& aAddonId) override; + NS_IMETHOD GetSiteOriginNoSuffix(nsACString& aSiteOrigin) override; + bool IsContentPrincipal() const override { return true; } + + ContentPrincipal(nsIURI* aURI, const OriginAttributes& aOriginAttributes, + const nsACString& aOriginNoSuffix, nsIURI* aInitialDomain); + ContentPrincipal(ContentPrincipal* aOther, + const OriginAttributes& aOriginAttributes); + + static PrincipalKind Kind() { return eContentPrincipal; } + + virtual nsresult GetScriptLocation(nsACString& aStr) override; + + nsresult GetSiteIdentifier(SiteIdentifier& aSite) override; + + static nsresult GenerateOriginNoSuffixFromURI(nsIURI* aURI, + nsACString& aOrigin); + + RefPtr<extensions::WebExtensionPolicyCore> AddonPolicyCore(); + + virtual nsresult PopulateJSONObject(Json::Value& aObject) override; + // Serializable keys are the valid enum fields the serialization supports + enum SerializableKeys : uint8_t { + eURI = 0, + eDomain, + eSuffix, + eMax = eSuffix + }; + typedef mozilla::BasePrincipal::KeyValT<SerializableKeys> KeyVal; + + static already_AddRefed<BasePrincipal> FromProperties( + nsTArray<ContentPrincipal::KeyVal>& aFields); + + class Deserializer : public BasePrincipal::Deserializer { + public: + NS_IMETHOD Read(nsIObjectInputStream* aStream) override; + }; + + protected: + virtual ~ContentPrincipal(); + + bool SubsumesInternal(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration) override; + bool MayLoadInternal(nsIURI* aURI) override; + + private: + const nsCOMPtr<nsIURI> mURI; + mozilla::Mutex mMutex{"ContentPrincipal::mMutex"}; + nsCOMPtr<nsIURI> mDomain MOZ_GUARDED_BY(mMutex); + Maybe<RefPtr<extensions::WebExtensionPolicyCore>> mAddon + MOZ_GUARDED_BY(mMutex); +}; + +} // namespace mozilla + +#define NS_PRINCIPAL_CID \ + { \ + 0x653e0e4d, 0x3ee4, 0x45fa, { \ + 0xb2, 0x72, 0x97, 0xc2, 0x0b, 0xc0, 0x1e, 0xb8 \ + } \ + } + +#endif // mozilla_ContentPrincipal_h diff --git a/caps/ContentPrincipalInfoHashKey.h b/caps/ContentPrincipalInfoHashKey.h new file mode 100644 index 0000000000..ed1f79852f --- /dev/null +++ b/caps/ContentPrincipalInfoHashKey.h @@ -0,0 +1,56 @@ +/* -*- 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 CAPS_PRINCIPALHASHKEY_H_ +#define CAPS_PRINCIPALHASHKEY_H_ + +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "PLDHashTable.h" +#include "nsHashKeys.h" +#include "nsUnicharUtils.h" + +namespace mozilla { + +class ContentPrincipalInfoHashKey : public PLDHashEntryHdr { + public: + using KeyType = const ipc::ContentPrincipalInfo&; + using KeyTypePointer = const ipc::ContentPrincipalInfo*; + + explicit ContentPrincipalInfoHashKey(KeyTypePointer aKey) + : mPrincipalInfo(*aKey) { + MOZ_COUNT_CTOR(ContentPrincipalInfoHashKey); + } + ContentPrincipalInfoHashKey(ContentPrincipalInfoHashKey&& aOther) noexcept + : mPrincipalInfo(aOther.mPrincipalInfo) { + MOZ_COUNT_CTOR(ContentPrincipalInfoHashKey); + } + + MOZ_COUNTED_DTOR(ContentPrincipalInfoHashKey) + + KeyType GetKey() const { return mPrincipalInfo; } + + bool KeyEquals(KeyTypePointer aKey) const { + // Mocks BasePrincipal::FastEquals() + return mPrincipalInfo.originNoSuffix() == aKey->originNoSuffix() && + mPrincipalInfo.attrs() == aKey->attrs(); + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + nsAutoCString suffix; + aKey->attrs().CreateSuffix(suffix); + return HashGeneric(HashString(aKey->originNoSuffix()), HashString(suffix)); + } + + enum { ALLOW_MEMMOVE = true }; + + protected: + const ipc::ContentPrincipalInfo mPrincipalInfo; +}; + +} // namespace mozilla + +#endif // CAPS_PRINCIPALHASHKEY_H_ diff --git a/caps/DomainPolicy.cpp b/caps/DomainPolicy.cpp new file mode 100644 index 0000000000..65ad3d8df4 --- /dev/null +++ b/caps/DomainPolicy.cpp @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=4 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 "DomainPolicy.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/Unused.h" +#include "nsIURIMutator.h" +#include "nsScriptSecurityManager.h" + +namespace mozilla { + +using namespace ipc; +using namespace dom; + +NS_IMPL_ISUPPORTS(DomainPolicy, nsIDomainPolicy) + +static nsresult BroadcastDomainSetChange(DomainSetType aSetType, + DomainSetChangeType aChangeType, + nsIURI* aDomain = nullptr) { + MOZ_ASSERT(XRE_IsParentProcess(), + "DomainPolicy should only be exposed to the chrome process."); + + nsTArray<ContentParent*> parents; + ContentParent::GetAll(parents); + if (!parents.Length()) { + return NS_OK; + } + + for (uint32_t i = 0; i < parents.Length(); i++) { + Unused << parents[i]->SendDomainSetChanged(aSetType, aChangeType, aDomain); + } + return NS_OK; +} + +DomainPolicy::DomainPolicy() + : mBlocklist(new DomainSet(BLOCKLIST)), + mSuperBlocklist(new DomainSet(SUPER_BLOCKLIST)), + mAllowlist(new DomainSet(ALLOWLIST)), + mSuperAllowlist(new DomainSet(SUPER_ALLOWLIST)) { + if (XRE_IsParentProcess()) { + BroadcastDomainSetChange(NO_TYPE, ACTIVATE_POLICY); + } +} + +DomainPolicy::~DomainPolicy() { + // The SSM holds a strong ref to the DomainPolicy until Deactivate() is + // invoked, so we should never hit the destructor until that happens. + MOZ_ASSERT(!mBlocklist && !mSuperBlocklist && !mAllowlist && + !mSuperAllowlist); +} + +NS_IMETHODIMP +DomainPolicy::GetBlocklist(nsIDomainSet** aSet) { + nsCOMPtr<nsIDomainSet> set = mBlocklist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetSuperBlocklist(nsIDomainSet** aSet) { + nsCOMPtr<nsIDomainSet> set = mSuperBlocklist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetAllowlist(nsIDomainSet** aSet) { + nsCOMPtr<nsIDomainSet> set = mAllowlist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetSuperAllowlist(nsIDomainSet** aSet) { + nsCOMPtr<nsIDomainSet> set = mSuperAllowlist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::Deactivate() { + // Clear the hashtables first to free up memory, since script might + // hold the doomed sets alive indefinitely. + mBlocklist->Clear(); + mSuperBlocklist->Clear(); + mAllowlist->Clear(); + mSuperAllowlist->Clear(); + + // Null them out. + mBlocklist = nullptr; + mSuperBlocklist = nullptr; + mAllowlist = nullptr; + mSuperAllowlist = nullptr; + + // Inform the SSM. + nsScriptSecurityManager* ssm = + nsScriptSecurityManager::GetScriptSecurityManager(); + if (ssm) { + ssm->DeactivateDomainPolicy(); + } + if (XRE_IsParentProcess()) { + BroadcastDomainSetChange(NO_TYPE, DEACTIVATE_POLICY); + } + return NS_OK; +} + +void DomainPolicy::CloneDomainPolicy(DomainPolicyClone* aClone) { + aClone->active() = true; + mBlocklist->CloneSet(&aClone->blocklist()); + mSuperBlocklist->CloneSet(&aClone->superBlocklist()); + mAllowlist->CloneSet(&aClone->allowlist()); + mSuperAllowlist->CloneSet(&aClone->superAllowlist()); +} + +static void CopyURIs(const nsTArray<RefPtr<nsIURI>>& aDomains, + nsIDomainSet* aSet) { + for (uint32_t i = 0; i < aDomains.Length(); i++) { + if (NS_WARN_IF(!aDomains[i])) { + continue; + } + aSet->Add(aDomains[i]); + } +} + +void DomainPolicy::ApplyClone(const DomainPolicyClone* aClone) { + CopyURIs(aClone->blocklist(), mBlocklist); + CopyURIs(aClone->allowlist(), mAllowlist); + CopyURIs(aClone->superBlocklist(), mSuperBlocklist); + CopyURIs(aClone->superAllowlist(), mSuperAllowlist); +} + +static already_AddRefed<nsIURI> GetCanonicalClone(nsIURI* aURI) { + nsCOMPtr<nsIURI> clone; + nsresult rv = + NS_MutateURI(aURI).SetUserPass(""_ns).SetPathQueryRef(""_ns).Finalize( + clone); + NS_ENSURE_SUCCESS(rv, nullptr); + return clone.forget(); +} + +NS_IMPL_ISUPPORTS(DomainSet, nsIDomainSet) + +NS_IMETHODIMP +DomainSet::Add(nsIURI* aDomain) { + nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain); + NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); + mHashTable.Insert(clone); + if (XRE_IsParentProcess()) { + return BroadcastDomainSetChange(mType, ADD_DOMAIN, aDomain); + } + + return NS_OK; +} + +NS_IMETHODIMP +DomainSet::Remove(nsIURI* aDomain) { + nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain); + NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); + mHashTable.Remove(clone); + if (XRE_IsParentProcess()) { + return BroadcastDomainSetChange(mType, REMOVE_DOMAIN, aDomain); + } + + return NS_OK; +} + +NS_IMETHODIMP +DomainSet::Clear() { + mHashTable.Clear(); + if (XRE_IsParentProcess()) { + return BroadcastDomainSetChange(mType, CLEAR_DOMAINS); + } + + return NS_OK; +} + +NS_IMETHODIMP +DomainSet::Contains(nsIURI* aDomain, bool* aContains) { + *aContains = false; + nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain); + NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); + *aContains = mHashTable.Contains(clone); + return NS_OK; +} + +NS_IMETHODIMP +DomainSet::ContainsSuperDomain(nsIURI* aDomain, bool* aContains) { + *aContains = false; + nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain); + NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE); + nsAutoCString domain; + nsresult rv = clone->GetHost(domain); + NS_ENSURE_SUCCESS(rv, rv); + while (true) { + // Check the current domain. + if (mHashTable.Contains(clone)) { + *aContains = true; + return NS_OK; + } + + // Chop off everything before the first dot, or break if there are no + // dots left. + int32_t index = domain.Find("."); + if (index == kNotFound) break; + domain.Assign(Substring(domain, index + 1)); + rv = NS_MutateURI(clone).SetHost(domain).Finalize(clone); + NS_ENSURE_SUCCESS(rv, rv); + } + + // No match. + return NS_OK; +} + +void DomainSet::CloneSet(nsTArray<RefPtr<nsIURI>>* aDomains) { + AppendToArray(*aDomains, mHashTable); +} + +} /* namespace mozilla */ diff --git a/caps/DomainPolicy.h b/caps/DomainPolicy.h new file mode 100644 index 0000000000..24fe3b9395 --- /dev/null +++ b/caps/DomainPolicy.h @@ -0,0 +1,64 @@ +/* -*- 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 DomainPolicy_h__ +#define DomainPolicy_h__ + +#include "nsIDomainPolicy.h" +#include "nsTHashSet.h" +#include "nsURIHashKey.h" + +namespace mozilla { + +enum DomainSetChangeType { + ACTIVATE_POLICY, + DEACTIVATE_POLICY, + ADD_DOMAIN, + REMOVE_DOMAIN, + CLEAR_DOMAINS +}; + +enum DomainSetType { + NO_TYPE, + BLOCKLIST, + SUPER_BLOCKLIST, + ALLOWLIST, + SUPER_ALLOWLIST +}; + +class DomainSet final : public nsIDomainSet { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMAINSET + + explicit DomainSet(DomainSetType aType) : mType(aType) {} + + void CloneSet(nsTArray<RefPtr<nsIURI>>* aDomains); + + protected: + virtual ~DomainSet() {} + nsTHashSet<nsURIHashKey> mHashTable; + DomainSetType mType; +}; + +class DomainPolicy final : public nsIDomainPolicy { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMAINPOLICY + DomainPolicy(); + + private: + virtual ~DomainPolicy(); + + RefPtr<DomainSet> mBlocklist; + RefPtr<DomainSet> mSuperBlocklist; + RefPtr<DomainSet> mAllowlist; + RefPtr<DomainSet> mSuperAllowlist; +}; + +} /* namespace mozilla */ + +#endif /* DomainPolicy_h__ */ diff --git a/caps/ExpandedPrincipal.cpp b/caps/ExpandedPrincipal.cpp new file mode 100644 index 0000000000..8d8b35fe3b --- /dev/null +++ b/caps/ExpandedPrincipal.cpp @@ -0,0 +1,388 @@ +/* -*- 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 "ExpandedPrincipal.h" +#include "nsIClassInfoImpl.h" +#include "nsIObjectInputStream.h" +#include "nsReadableUtils.h" +#include "mozilla/Base64.h" +#include "mozilla/extensions/WebExtensionPolicy.h" +#include "json/json.h" + +using namespace mozilla; + +NS_IMPL_CLASSINFO(ExpandedPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY, + NS_EXPANDEDPRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(ExpandedPrincipal, nsIPrincipal, + nsIExpandedPrincipal) +NS_IMPL_CI_INTERFACE_GETTER(ExpandedPrincipal, nsIPrincipal, + nsIExpandedPrincipal) + +struct OriginComparator { + bool LessThan(nsIPrincipal* a, nsIPrincipal* b) const { + nsAutoCString originA; + DebugOnly<nsresult> rv = a->GetOrigin(originA); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + nsAutoCString originB; + rv = b->GetOrigin(originB); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return originA < originB; + } + + bool Equals(nsIPrincipal* a, nsIPrincipal* b) const { + nsAutoCString originA; + DebugOnly<nsresult> rv = a->GetOrigin(originA); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + nsAutoCString originB; + rv = b->GetOrigin(originB); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return a == b; + } +}; + +ExpandedPrincipal::ExpandedPrincipal( + nsTArray<nsCOMPtr<nsIPrincipal>>&& aPrincipals, + const nsACString& aOriginNoSuffix, const OriginAttributes& aAttrs) + : BasePrincipal(eExpandedPrincipal, aOriginNoSuffix, aAttrs), + mPrincipals(std::move(aPrincipals)) {} + +ExpandedPrincipal::~ExpandedPrincipal() = default; + +already_AddRefed<ExpandedPrincipal> ExpandedPrincipal::Create( + const nsTArray<nsCOMPtr<nsIPrincipal>>& aAllowList, + const OriginAttributes& aAttrs) { + // We force the principals to be sorted by origin so that ExpandedPrincipal + // origins can have a canonical form. + nsTArray<nsCOMPtr<nsIPrincipal>> principals; + OriginComparator c; + for (size_t i = 0; i < aAllowList.Length(); ++i) { + principals.InsertElementSorted(aAllowList[i], c); + } + + nsAutoCString origin; + origin.AssignLiteral("[Expanded Principal ["); + StringJoinAppend( + origin, ", "_ns, principals, + [](nsACString& dest, const nsCOMPtr<nsIPrincipal>& principal) { + nsAutoCString subOrigin; + DebugOnly<nsresult> rv = principal->GetOrigin(subOrigin); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + dest.Append(subOrigin); + }); + origin.AppendLiteral("]]"); + + RefPtr<ExpandedPrincipal> ep = + new ExpandedPrincipal(std::move(principals), origin, aAttrs); + return ep.forget(); +} + +NS_IMETHODIMP +ExpandedPrincipal::GetDomain(nsIURI** aDomain) { + *aDomain = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +ExpandedPrincipal::SetDomain(nsIURI* aDomain) { return NS_OK; } + +bool ExpandedPrincipal::SubsumesInternal( + nsIPrincipal* aOther, + BasePrincipal::DocumentDomainConsideration aConsideration) { + // If aOther is an ExpandedPrincipal too, we break it down into its component + // nsIPrincipals, and check subsumes on each one. + if (Cast(aOther)->Is<ExpandedPrincipal>()) { + auto* expanded = Cast(aOther)->As<ExpandedPrincipal>(); + + for (auto& other : expanded->AllowList()) { + // Use SubsumesInternal rather than Subsumes here, since OriginAttribute + // checks are only done between non-expanded sub-principals, and we don't + // need to incur the extra virtual call overhead. + if (!SubsumesInternal(other, aConsideration)) { + return false; + } + } + return true; + } + + // We're dealing with a regular principal. One of our principals must subsume + // it. + for (uint32_t i = 0; i < mPrincipals.Length(); ++i) { + if (Cast(mPrincipals[i])->Subsumes(aOther, aConsideration)) { + return true; + } + } + + return false; +} + +bool ExpandedPrincipal::MayLoadInternal(nsIURI* uri) { + for (uint32_t i = 0; i < mPrincipals.Length(); ++i) { + if (BasePrincipal::Cast(mPrincipals[i])->MayLoadInternal(uri)) { + return true; + } + } + + return false; +} + +uint32_t ExpandedPrincipal::GetHashValue() { + MOZ_CRASH("extended principal should never be used as key in a hash map"); +} + +NS_IMETHODIMP +ExpandedPrincipal::GetURI(nsIURI** aURI) { + *aURI = nullptr; + return NS_OK; +} + +const nsTArray<nsCOMPtr<nsIPrincipal>>& ExpandedPrincipal::AllowList() { + return mPrincipals; +} + +NS_IMETHODIMP +ExpandedPrincipal::GetBaseDomain(nsACString& aBaseDomain) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +ExpandedPrincipal::GetAddonId(nsAString& aAddonId) { + aAddonId.Truncate(); + return NS_OK; +}; + +bool ExpandedPrincipal::AddonHasPermission(const nsAtom* aPerm) { + for (size_t i = 0; i < mPrincipals.Length(); ++i) { + if (BasePrincipal::Cast(mPrincipals[i])->AddonHasPermission(aPerm)) { + return true; + } + } + return false; +} + +bool ExpandedPrincipal::AddonAllowsLoad(nsIURI* aURI, + bool aExplicit /* = false */) { + for (const auto& principal : mPrincipals) { + if (Cast(principal)->AddonAllowsLoad(aURI, aExplicit)) { + return true; + } + } + return false; +} + +void ExpandedPrincipal::SetCsp(nsIContentSecurityPolicy* aCSP) { + AssertIsOnMainThread(); + mCSP = new nsMainThreadPtrHolder<nsIContentSecurityPolicy>( + "ExpandedPrincipal::mCSP", aCSP); +} + +NS_IMETHODIMP +ExpandedPrincipal::GetCsp(nsIContentSecurityPolicy** aCsp) { + AssertIsOnMainThread(); + NS_IF_ADDREF(*aCsp = mCSP); + return NS_OK; +} + +nsIPrincipal* ExpandedPrincipal::PrincipalToInherit(nsIURI* aRequestedURI) { + if (aRequestedURI) { + // If a given sub-principal subsumes the given URI, use that principal for + // inheritance. In general, this only happens with certain CORS modes, loads + // with forced principal inheritance, and creation of XML documents from + // XMLHttpRequests or fetch requests. For URIs that normally inherit a + // principal (such as data: URIs), we fall back to the last principal in the + // allowlist. + for (const auto& principal : mPrincipals) { + if (Cast(principal)->MayLoadInternal(aRequestedURI)) { + return principal; + } + } + } + return mPrincipals.LastElement(); +} + +nsresult ExpandedPrincipal::GetScriptLocation(nsACString& aStr) { + aStr.AssignLiteral("[Expanded Principal ["); + for (size_t i = 0; i < mPrincipals.Length(); ++i) { + if (i != 0) { + aStr.AppendLiteral(", "); + } + + nsAutoCString spec; + nsresult rv = + nsJSPrincipals::get(mPrincipals.ElementAt(i))->GetScriptLocation(spec); + NS_ENSURE_SUCCESS(rv, rv); + + aStr.Append(spec); + } + aStr.AppendLiteral("]]"); + return NS_OK; +} + +////////////////////////////////////////// +// Methods implementing nsISerializable // +////////////////////////////////////////// + +// We've had way too many issues with unversioned serializations, so +// explicitly version this one. +static const uint32_t kSerializationVersion = 1; + +NS_IMETHODIMP +ExpandedPrincipal::Deserializer::Read(nsIObjectInputStream* aStream) { + uint32_t version; + nsresult rv = aStream->Read32(&version); + if (version != kSerializationVersion) { + MOZ_ASSERT(false, + "We really need to add handling of the old(?) version here"); + return NS_ERROR_UNEXPECTED; + } + + uint32_t count; + rv = aStream->Read32(&count); + if (NS_FAILED(rv)) { + return rv; + } + + nsTArray<nsCOMPtr<nsIPrincipal>> principals; + if (!principals.SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + OriginComparator c; + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr<nsISupports> read; + rv = aStream->ReadObject(true, getter_AddRefs(read)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(read); + if (!principal) { + return NS_ERROR_UNEXPECTED; + } + + // Play it safe and InsertElementSorted, in case the sort order + // changed for some bizarre reason. + principals.InsertElementSorted(std::move(principal), c); + } + + mPrincipal = ExpandedPrincipal::Create(principals, OriginAttributes()); + return NS_OK; +} + +nsresult ExpandedPrincipal::GetSiteIdentifier(SiteIdentifier& aSite) { + // Call GetSiteIdentifier on each of our principals and return a new + // ExpandedPrincipal. + + nsTArray<nsCOMPtr<nsIPrincipal>> allowlist; + for (const auto& principal : mPrincipals) { + SiteIdentifier site; + nsresult rv = Cast(principal)->GetSiteIdentifier(site); + NS_ENSURE_SUCCESS(rv, rv); + allowlist.AppendElement(site.GetPrincipal()); + } + + RefPtr<ExpandedPrincipal> expandedPrincipal = + ExpandedPrincipal::Create(allowlist, OriginAttributesRef()); + MOZ_ASSERT(expandedPrincipal, "ExpandedPrincipal::Create returned nullptr?"); + + aSite.Init(expandedPrincipal); + return NS_OK; +} + +nsresult ExpandedPrincipal::PopulateJSONObject(Json::Value& aObject) { + nsAutoCString principalList; + // First item through we have a blank separator and append the next result + nsAutoCString sep; + for (auto& principal : mPrincipals) { + nsAutoCString JSON; + BasePrincipal::Cast(principal)->ToJSON(JSON); + // This is blank for the first run through so the last in the list doesn't + // add a separator + principalList.Append(sep); + sep = ','; + // Values currently only copes with strings so encode into base64 to allow a + // CSV safely. + nsresult rv; + rv = Base64EncodeAppend(JSON, principalList); + NS_ENSURE_SUCCESS(rv, rv); + } + aObject[std::to_string(eSpecs)] = principalList.get(); + + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + if (suffix.Length() > 0) { + aObject[std::to_string(eSuffix)] = suffix.get(); + } + + return NS_OK; +} + +already_AddRefed<BasePrincipal> ExpandedPrincipal::FromProperties( + nsTArray<ExpandedPrincipal::KeyVal>& aFields) { + MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys"); + nsTArray<nsCOMPtr<nsIPrincipal>> allowList; + OriginAttributes attrs; + // The odd structure here is to make the code to not compile + // if all the switch enum cases haven't been codified + for (const auto& field : aFields) { + switch (field.key) { + case ExpandedPrincipal::eSpecs: + if (!field.valueWasSerialized) { + MOZ_ASSERT(false, + "Expanded principals require specs in serialized JSON"); + return nullptr; + } + for (const nsACString& each : field.value.Split(',')) { + nsAutoCString result; + nsresult rv; + rv = Base64Decode(each, result); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to decode"); + + NS_ENSURE_SUCCESS(rv, nullptr); + nsCOMPtr<nsIPrincipal> principal = BasePrincipal::FromJSON(result); + allowList.AppendElement(principal); + } + break; + case ExpandedPrincipal::eSuffix: + if (field.valueWasSerialized) { + bool ok = attrs.PopulateFromSuffix(field.value); + if (!ok) { + return nullptr; + } + } + break; + } + } + + if (allowList.Length() == 0) { + return nullptr; + } + + RefPtr<ExpandedPrincipal> expandedPrincipal = + ExpandedPrincipal::Create(allowList, attrs); + + return expandedPrincipal.forget(); +} + +NS_IMETHODIMP +ExpandedPrincipal::IsThirdPartyURI(nsIURI* aURI, bool* aRes) { + // ExpandedPrincipal for extension content scripts consist of two principals, + // the document's principal and the extension's principal. + // To make sure that the third-party check behaves like the web page on which + // the content script is running, ignore the extension's principal. + + for (const auto& principal : mPrincipals) { + if (!Cast(principal)->AddonPolicyCore()) { + return Cast(principal)->IsThirdPartyURI(aURI, aRes); + } + } + + if (mPrincipals.IsEmpty()) { + *aRes = true; + return NS_OK; + } + + return Cast(mPrincipals[0])->IsThirdPartyURI(aURI, aRes); +} diff --git a/caps/ExpandedPrincipal.h b/caps/ExpandedPrincipal.h new file mode 100644 index 0000000000..18f8ff8e4e --- /dev/null +++ b/caps/ExpandedPrincipal.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 ExpandedPrincipal_h +#define ExpandedPrincipal_h + +#include "nsCOMPtr.h" +#include "nsJSPrincipals.h" +#include "nsProxyRelease.h" +#include "nsTArray.h" +#include "nsNetUtil.h" +#include "mozilla/BasePrincipal.h" + +class nsIContentSecurityPolicy; + +namespace Json { +class Value; +} + +class ExpandedPrincipal : public nsIExpandedPrincipal, + public mozilla::BasePrincipal { + public: + static already_AddRefed<ExpandedPrincipal> Create( + const nsTArray<nsCOMPtr<nsIPrincipal>>& aAllowList, + const mozilla::OriginAttributes& aAttrs); + + static PrincipalKind Kind() { return eExpandedPrincipal; } + + NS_DECL_NSIEXPANDEDPRINCIPAL + + NS_IMETHOD_(MozExternalRefCountType) AddRef() override { + return nsJSPrincipals::AddRef(); + }; + NS_IMETHOD_(MozExternalRefCountType) Release() override { + return nsJSPrincipals::Release(); + }; + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + uint32_t GetHashValue() override; + NS_IMETHOD GetURI(nsIURI** aURI) override; + NS_IMETHOD GetDomain(nsIURI** aDomain) override; + NS_IMETHOD SetDomain(nsIURI* aDomain) override; + NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; + NS_IMETHOD GetAddonId(nsAString& aAddonId) override; + NS_IMETHOD IsThirdPartyURI(nsIURI* uri, bool* aRes) override; + virtual bool AddonHasPermission(const nsAtom* aPerm) override; + virtual nsresult GetScriptLocation(nsACString& aStr) override; + + bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false); + + void SetCsp(nsIContentSecurityPolicy* aCSP); + + // Returns the principal to inherit when this principal requests the given + // URL. See BasePrincipal::PrincipalToInherit. + nsIPrincipal* PrincipalToInherit(nsIURI* aRequestedURI = nullptr); + + nsresult GetSiteIdentifier(mozilla::SiteIdentifier& aSite) override; + + virtual nsresult PopulateJSONObject(Json::Value& aObject) override; + // Serializable keys are the valid enum fields the serialization supports + enum SerializableKeys : uint8_t { eSpecs = 0, eSuffix, eMax = eSuffix }; + typedef mozilla::BasePrincipal::KeyValT<SerializableKeys> KeyVal; + + static already_AddRefed<BasePrincipal> FromProperties( + nsTArray<ExpandedPrincipal::KeyVal>& aFields); + + class Deserializer : public BasePrincipal::Deserializer { + public: + NS_IMETHOD Read(nsIObjectInputStream* aStream) override; + }; + + protected: + explicit ExpandedPrincipal(nsTArray<nsCOMPtr<nsIPrincipal>>&& aPrincipals, + const nsACString& aOriginNoSuffix, + const mozilla::OriginAttributes& aAttrs); + + virtual ~ExpandedPrincipal(); + + bool SubsumesInternal(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration) override; + + bool MayLoadInternal(nsIURI* aURI) override; + + private: + const nsTArray<nsCOMPtr<nsIPrincipal>> mPrincipals; + nsMainThreadPtrHandle<nsIContentSecurityPolicy> mCSP + MOZ_GUARDED_BY(mozilla::sMainThreadCapability); +}; + +#define NS_EXPANDEDPRINCIPAL_CID \ + { \ + 0xe8ee88b0, 0x5571, 0x4086, { \ + 0xa4, 0x5b, 0x39, 0xa7, 0x16, 0x90, 0x6b, 0xdb \ + } \ + } + +#endif // ExpandedPrincipal_h diff --git a/caps/NullPrincipal.cpp b/caps/NullPrincipal.cpp new file mode 100644 index 0000000000..b6961f0727 --- /dev/null +++ b/caps/NullPrincipal.cpp @@ -0,0 +1,334 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 sts=2 ts=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/. */ + +/** + * This is the principal that has no rights and can't be accessed by + * anything other than itself and chrome; null principals are not + * same-origin with anything but themselves. + */ + +#include "mozilla/ArrayUtils.h" + +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/StaticPrefs_network.h" +#include "nsDocShell.h" +#include "NullPrincipal.h" +#include "DefaultURI.h" +#include "nsSimpleURI.h" +#include "nsIClassInfoImpl.h" +#include "nsNetCID.h" +#include "nsError.h" +#include "nsEscape.h" +#include "ContentPrincipal.h" +#include "nsScriptSecurityManager.h" +#include "pratom.h" +#include "nsIObjectInputStream.h" + +#include "json/json.h" + +using namespace mozilla; + +NS_IMPL_CLASSINFO(NullPrincipal, nullptr, nsIClassInfo::MAIN_THREAD_ONLY, + NS_NULLPRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(NullPrincipal, nsIPrincipal) +NS_IMPL_CI_INTERFACE_GETTER(NullPrincipal, nsIPrincipal) + +NullPrincipal::NullPrincipal(nsIURI* aURI, const nsACString& aOriginNoSuffix, + const OriginAttributes& aOriginAttributes) + : BasePrincipal(eNullPrincipal, aOriginNoSuffix, aOriginAttributes), + mURI(aURI) {} + +/* static */ +already_AddRefed<NullPrincipal> NullPrincipal::CreateWithInheritedAttributes( + nsIPrincipal* aInheritFrom) { + MOZ_ASSERT(aInheritFrom); + nsCOMPtr<nsIURI> uri = CreateURI(aInheritFrom); + return Create(Cast(aInheritFrom)->OriginAttributesRef(), uri); +} + +/* static */ +already_AddRefed<NullPrincipal> NullPrincipal::Create( + const OriginAttributes& aOriginAttributes, nsIURI* aNullPrincipalURI) { + nsCOMPtr<nsIURI> uri = aNullPrincipalURI; + if (!uri) { + uri = NullPrincipal::CreateURI(nullptr); + } + + MOZ_RELEASE_ASSERT(uri->SchemeIs(NS_NULLPRINCIPAL_SCHEME)); + + nsAutoCString originNoSuffix; + DebugOnly<nsresult> rv = uri->GetSpec(originNoSuffix); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + RefPtr<NullPrincipal> nullPrin = + new NullPrincipal(uri, originNoSuffix, aOriginAttributes); + return nullPrin.forget(); +} + +/* static */ +already_AddRefed<NullPrincipal> NullPrincipal::CreateWithoutOriginAttributes() { + return NullPrincipal::Create(OriginAttributes(), nullptr); +} + +void NullPrincipal::EscapePrecursorQuery(nsACString& aPrecursorQuery) { + // origins should not contain existing escape sequences, so set `esc_Forced` + // to force any `%` in the input to be escaped in addition to non-ascii, + // control characters and DEL. + nsCString modified; + if (NS_EscapeURLSpan(aPrecursorQuery, esc_Query | esc_Forced, modified)) { + aPrecursorQuery.Assign(std::move(modified)); + } +} + +void NullPrincipal::UnescapePrecursorQuery(nsACString& aPrecursorQuery) { + nsCString modified; + if (NS_UnescapeURL(aPrecursorQuery.BeginReading(), aPrecursorQuery.Length(), + /* aFlags */ 0, modified)) { + aPrecursorQuery.Assign(std::move(modified)); + } +} + +already_AddRefed<nsIURI> NullPrincipal::CreateURI( + nsIPrincipal* aPrecursor, const nsID* aNullPrincipalID) { + nsCOMPtr<nsIURIMutator> iMutator; + if (StaticPrefs::network_url_useDefaultURI()) { + iMutator = new mozilla::net::DefaultURI::Mutator(); + } else { + iMutator = new mozilla::net::nsSimpleURI::Mutator(); + } + + nsID uuid = aNullPrincipalID ? *aNullPrincipalID : nsID::GenerateUUID(); + + NS_MutateURI mutator(iMutator); + mutator.SetSpec(NS_NULLPRINCIPAL_SCHEME ":"_ns + + nsDependentCString(nsIDToCString(uuid).get())); + + // If there's a precursor URI, encode it in the null principal URI's query. + if (aPrecursor) { + nsAutoCString precursorOrigin; + switch (BasePrincipal::Cast(aPrecursor)->Kind()) { + case eNullPrincipal: { + // If the precursor null principal has a precursor, inherit it. + if (nsCOMPtr<nsIURI> nullPrecursorURI = aPrecursor->GetURI()) { + MOZ_ALWAYS_SUCCEEDS(nullPrecursorURI->GetQuery(precursorOrigin)); + } + break; + } + case eContentPrincipal: { + MOZ_ALWAYS_SUCCEEDS(aPrecursor->GetOriginNoSuffix(precursorOrigin)); +#ifdef DEBUG + nsAutoCString original(precursorOrigin); +#endif + EscapePrecursorQuery(precursorOrigin); +#ifdef DEBUG + nsAutoCString unescaped(precursorOrigin); + UnescapePrecursorQuery(unescaped); + MOZ_ASSERT(unescaped == original, + "cannot recover original precursor origin after escape"); +#endif + break; + } + + // For now, we won't track expanded or system principal precursors. We may + // want to track expanded principal precursors in the future, but it's + // unlikely we'll want to track system principal precursors. + case eExpandedPrincipal: + case eSystemPrincipal: + break; + } + if (!precursorOrigin.IsEmpty()) { + mutator.SetQuery(precursorOrigin); + } + } + + nsCOMPtr<nsIURI> uri; + MOZ_ALWAYS_SUCCEEDS(mutator.Finalize(getter_AddRefs(uri))); + return uri.forget(); +} + +nsresult NullPrincipal::GetScriptLocation(nsACString& aStr) { + return mURI->GetSpec(aStr); +} + +/** + * nsIPrincipal implementation + */ + +uint32_t NullPrincipal::GetHashValue() { return (NS_PTR_TO_INT32(this) >> 2); } + +NS_IMETHODIMP +NullPrincipal::GetURI(nsIURI** aURI) { + nsCOMPtr<nsIURI> uri = mURI; + uri.forget(aURI); + return NS_OK; +} +NS_IMETHODIMP +NullPrincipal::GetIsOriginPotentiallyTrustworthy(bool* aResult) { + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +NullPrincipal::GetDomain(nsIURI** aDomain) { + nsCOMPtr<nsIURI> uri = mURI; + uri.forget(aDomain); + return NS_OK; +} + +NS_IMETHODIMP +NullPrincipal::SetDomain(nsIURI* aDomain) { + // I think the right thing to do here is to just throw... Silently failing + // seems counterproductive. + return NS_ERROR_NOT_AVAILABLE; +} + +bool NullPrincipal::MayLoadInternal(nsIURI* aURI) { + // Also allow the load if we are the principal of the URI being checked. + nsCOMPtr<nsIPrincipal> blobPrincipal; + if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal( + aURI, getter_AddRefs(blobPrincipal))) { + MOZ_ASSERT(blobPrincipal); + return SubsumesInternal(blobPrincipal, + BasePrincipal::ConsiderDocumentDomain); + } + + return false; +} + +NS_IMETHODIMP +NullPrincipal::GetBaseDomain(nsACString& aBaseDomain) { + // For a null principal, we use our unique uuid as the base domain. + return mURI->GetPathQueryRef(aBaseDomain); +} + +NS_IMETHODIMP +NullPrincipal::GetAddonId(nsAString& aAddonId) { + aAddonId.Truncate(); + return NS_OK; +}; + +/** + * nsISerializable implementation + */ +NS_IMETHODIMP +NullPrincipal::Deserializer::Read(nsIObjectInputStream* aStream) { + nsAutoCString spec; + nsresult rv = aStream->ReadCString(spec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), spec); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString suffix; + rv = aStream->ReadCString(suffix); + NS_ENSURE_SUCCESS(rv, rv); + + OriginAttributes attrs; + bool ok = attrs.PopulateFromSuffix(suffix); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + mPrincipal = NullPrincipal::Create(attrs, uri); + NS_ENSURE_TRUE(mPrincipal, NS_ERROR_FAILURE); + + return NS_OK; +} + +nsresult NullPrincipal::PopulateJSONObject(Json::Value& aObject) { + nsAutoCString principalURI; + nsresult rv = mURI->GetSpec(principalURI); + NS_ENSURE_SUCCESS(rv, rv); + aObject[std::to_string(eSpec)] = principalURI.get(); + + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + if (suffix.Length() > 0) { + aObject[std::to_string(eSuffix)] = suffix.get(); + } + + return NS_OK; +} + +already_AddRefed<BasePrincipal> NullPrincipal::FromProperties( + nsTArray<NullPrincipal::KeyVal>& aFields) { + MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys"); + nsresult rv; + nsCOMPtr<nsIURI> uri; + OriginAttributes attrs; + + // The odd structure here is to make the code to not compile + // if all the switch enum cases haven't been codified + for (const auto& field : aFields) { + switch (field.key) { + case NullPrincipal::eSpec: + if (!field.valueWasSerialized) { + MOZ_ASSERT(false, + "Null principals require a spec URI in serialized JSON"); + return nullptr; + } + rv = NS_NewURI(getter_AddRefs(uri), field.value); + NS_ENSURE_SUCCESS(rv, nullptr); + break; + case NullPrincipal::eSuffix: + bool ok = attrs.PopulateFromSuffix(field.value); + if (!ok) { + return nullptr; + } + break; + } + } + + if (!uri) { + MOZ_ASSERT(false, "No URI deserialized"); + return nullptr; + } + + return NullPrincipal::Create(attrs, uri); +} + +NS_IMETHODIMP +NullPrincipal::GetPrecursorPrincipal(nsIPrincipal** aPrincipal) { + *aPrincipal = nullptr; + + nsAutoCString query; + if (NS_FAILED(mURI->GetQuery(query)) || query.IsEmpty()) { + return NS_OK; + } + UnescapePrecursorQuery(query); + + nsCOMPtr<nsIURI> precursorURI; + if (NS_FAILED(NS_NewURI(getter_AddRefs(precursorURI), query))) { + MOZ_ASSERT_UNREACHABLE( + "Failed to parse precursor from nullprincipal query"); + return NS_OK; + } + + // If our precursor is another null principal, re-construct it. This can + // happen if a null principal without a precursor causes another principal to + // be created. + if (precursorURI->SchemeIs(NS_NULLPRINCIPAL_SCHEME)) { +#ifdef DEBUG + nsAutoCString precursorQuery; + precursorURI->GetQuery(precursorQuery); + MOZ_ASSERT(precursorQuery.IsEmpty(), + "Null principal with nested precursors?"); +#endif + *aPrincipal = + NullPrincipal::Create(OriginAttributesRef(), precursorURI).take(); + return NS_OK; + } + + RefPtr<BasePrincipal> contentPrincipal = + BasePrincipal::CreateContentPrincipal(precursorURI, + OriginAttributesRef()); + // If `CreateContentPrincipal` failed, it will create a new NullPrincipal and + // return that instead. We only want to return real content principals here. + if (!contentPrincipal || !contentPrincipal->Is<ContentPrincipal>()) { + return NS_OK; + } + contentPrincipal.forget(aPrincipal); + return NS_OK; +} diff --git a/caps/NullPrincipal.h b/caps/NullPrincipal.h new file mode 100644 index 0000000000..cbce27c700 --- /dev/null +++ b/caps/NullPrincipal.h @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/** + * This is the principal that has no rights and can't be accessed by + * anything other than itself and chrome; null principals are not + * same-origin with anything but themselves. + */ + +#ifndef mozilla_NullPrincipal_h +#define mozilla_NullPrincipal_h + +#include "nsIPrincipal.h" +#include "nsJSPrincipals.h" +#include "nsCOMPtr.h" + +#include "mozilla/BasePrincipal.h" +#include "gtest/MozGtestFriend.h" + +class nsIDocShell; +class nsIURI; +namespace Json { +class Value; +} + +#define NS_NULLPRINCIPAL_CID \ + { \ + 0xbd066e5f, 0x146f, 0x4472, { \ + 0x83, 0x31, 0x7b, 0xfd, 0x05, 0xb1, 0xed, 0x90 \ + } \ + } + +#define NS_NULLPRINCIPAL_SCHEME "moz-nullprincipal" + +namespace mozilla { + +class NullPrincipal final : public BasePrincipal { + public: + static PrincipalKind Kind() { return eNullPrincipal; } + + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + uint32_t GetHashValue() override; + NS_IMETHOD GetURI(nsIURI** aURI) override; + NS_IMETHOD GetIsOriginPotentiallyTrustworthy(bool* aResult) override; + NS_IMETHOD GetDomain(nsIURI** aDomain) override; + NS_IMETHOD SetDomain(nsIURI* aDomain) override; + NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; + NS_IMETHOD GetAddonId(nsAString& aAddonId) override; + NS_IMETHOD GetPrecursorPrincipal(nsIPrincipal** aPrecursor) override; + + // Create a NullPrincipal, inheriting origin attributes from the given + // principal. + // If aInheritFrom is a content principal, or has a content principal + // precursor, it will be used as the precursor for this principal. + static already_AddRefed<NullPrincipal> CreateWithInheritedAttributes( + nsIPrincipal* aInheritFrom); + + // Create a new NullPrincipal with the specified OriginAttributes. + // + // If `aNullPrincipalURI` is specified, it must be a NS_NULLPRINCIPAL_SCHEME + // URI previously created using `NullPrincipal::CreateURI`, and will be used + // as the origin URI for this principal. + static already_AddRefed<NullPrincipal> Create( + const OriginAttributes& aOriginAttributes, + nsIURI* aNullPrincipalURI = nullptr); + + static already_AddRefed<NullPrincipal> CreateWithoutOriginAttributes(); + + // Generates a new unique `moz-nullprincipal:` URI. If `aPrecursor` is + // specified, it will be included in the generated URI as the null principal's + // precursor. + // + // The `aPrincipalID` attribute is used to force the creation of a + // deterministic NullPrincipal in situations where that is required. Avoid + // using this parameter unless absolutely necessary. + static already_AddRefed<nsIURI> CreateURI(nsIPrincipal* aPrecursor = nullptr, + const nsID* aPrincipalID = nullptr); + + virtual nsresult GetScriptLocation(nsACString& aStr) override; + + nsresult GetSiteIdentifier(SiteIdentifier& aSite) override { + aSite.Init(this); + return NS_OK; + } + + virtual nsresult PopulateJSONObject(Json::Value& aObject) override; + + // Serializable keys are the valid enum fields the serialization supports + enum SerializableKeys : uint8_t { eSpec = 0, eSuffix, eMax = eSuffix }; + typedef mozilla::BasePrincipal::KeyValT<SerializableKeys> KeyVal; + + static already_AddRefed<BasePrincipal> FromProperties( + nsTArray<NullPrincipal::KeyVal>& aFields); + + class Deserializer : public BasePrincipal::Deserializer { + public: + NS_IMETHOD Read(nsIObjectInputStream* aStream) override; + }; + + protected: + NullPrincipal(nsIURI* aURI, const nsACString& aOriginNoSuffix, + const OriginAttributes& aOriginAttributes); + + virtual ~NullPrincipal() = default; + + bool SubsumesInternal(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration) override { + MOZ_ASSERT(aOther); + return FastEquals(aOther); + } + + bool MayLoadInternal(nsIURI* aURI) override; + + const nsCOMPtr<nsIURI> mURI; + + private: + FRIEND_TEST(NullPrincipalPrecursor, EscapingRoundTrips); + + static void EscapePrecursorQuery(nsACString& aPrecursorQuery); + static void UnescapePrecursorQuery(nsACString& aPrecursorQuery); +}; + +} // namespace mozilla + +#endif // mozilla_NullPrincipal_h diff --git a/caps/OriginAttributes.cpp b/caps/OriginAttributes.cpp new file mode 100644 index 0000000000..b6746a54c4 --- /dev/null +++ b/caps/OriginAttributes.cpp @@ -0,0 +1,483 @@ +/* -*- 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/OriginAttributes.h" +#include "mozilla/Assertions.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "nsIEffectiveTLDService.h" +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsString.h" +#include "nsURLHelper.h" + +static const char kSourceChar = ':'; +static const char kSanitizedChar = '+'; + +namespace mozilla { + +static void MakeTopLevelInfo(const nsACString& aScheme, const nsACString& aHost, + int32_t aPort, bool aUseSite, + nsAString& aTopLevelInfo) { + if (!aUseSite) { + aTopLevelInfo.Assign(NS_ConvertUTF8toUTF16(aHost)); + return; + } + + // Note: If you change the serialization of the partition-key, please update + // StoragePrincipalHelper.cpp too. + + nsAutoCString site; + site.AssignLiteral("("); + site.Append(aScheme); + site.Append(","); + site.Append(aHost); + if (aPort != -1) { + site.Append(","); + site.AppendInt(aPort); + } + site.AppendLiteral(")"); + + aTopLevelInfo.Assign(NS_ConvertUTF8toUTF16(site)); +} + +static void MakeTopLevelInfo(const nsACString& aScheme, const nsACString& aHost, + bool aUseSite, nsAString& aTopLevelInfo) { + MakeTopLevelInfo(aScheme, aHost, -1, aUseSite, aTopLevelInfo); +} + +static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument, + nsIURI* aURI, bool aIsFirstPartyEnabled, + bool aForced, bool aUseSite, + nsString OriginAttributes::*aTarget, + OriginAttributes& aOriginAttributes) { + nsresult rv; + + if (!aURI) { + return; + } + + // If the prefs are off or this is not a top level load, bail out. + if ((!aIsFirstPartyEnabled || !aIsTopLevelDocument) && !aForced) { + return; + } + + nsAString& topLevelInfo = aOriginAttributes.*aTarget; + + nsAutoCString scheme; + rv = aURI->GetScheme(scheme); + NS_ENSURE_SUCCESS_VOID(rv); + + if (scheme.EqualsLiteral("about")) { + MakeTopLevelInfo(scheme, nsLiteralCString(ABOUT_URI_FIRST_PARTY_DOMAIN), + aUseSite, topLevelInfo); + return; + } + + // If a null principal URI was provided, extract the UUID portion of the URI + // to use for the first-party domain. + if (scheme.EqualsLiteral("moz-nullprincipal")) { + // Get the UUID portion of the URI, ignoring the precursor principal. + nsAutoCString filePath; + rv = aURI->GetFilePath(filePath); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + // Remove the `{}` characters from both ends. + filePath.Mid(filePath, 1, filePath.Length() - 2); + filePath.AppendLiteral(".mozilla"); + // Store the generated file path. + topLevelInfo = NS_ConvertUTF8toUTF16(filePath); + return; + } + + // Add-on principals should never get any first-party domain + // attributes in order to guarantee their storage integrity when switching + // FPI on and off. + if (scheme.EqualsLiteral("moz-extension")) { + return; + } + + nsCOMPtr<nsIPrincipal> blobPrincipal; + if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal( + aURI, getter_AddRefs(blobPrincipal))) { + MOZ_ASSERT(blobPrincipal); + topLevelInfo = blobPrincipal->OriginAttributesRef().*aTarget; + return; + } + + nsCOMPtr<nsIEffectiveTLDService> tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + MOZ_ASSERT(tldService); + NS_ENSURE_TRUE_VOID(tldService); + + nsAutoCString baseDomain; + rv = tldService->GetBaseDomain(aURI, 0, baseDomain); + if (NS_SUCCEEDED(rv)) { + MakeTopLevelInfo(scheme, baseDomain, aUseSite, topLevelInfo); + return; + } + + // Saving before rv is overwritten. + bool isIpAddress = (rv == NS_ERROR_HOST_IS_IP_ADDRESS); + bool isInsufficientDomainLevels = (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS); + + int32_t port; + rv = aURI->GetPort(&port); + NS_ENSURE_SUCCESS_VOID(rv); + + nsAutoCString host; + rv = aURI->GetHost(host); + NS_ENSURE_SUCCESS_VOID(rv); + + if (isIpAddress) { + // If the host is an IPv4/IPv6 address, we still accept it as a + // valid topLevelInfo. + nsAutoCString ipAddr; + + if (net_IsValidIPv6Addr(host)) { + // According to RFC2732, the host of an IPv6 address should be an + // IPv6reference. The GetHost() of nsIURI will only return the IPv6 + // address. So, we need to convert it back to IPv6reference here. + ipAddr.AssignLiteral("["); + ipAddr.Append(host); + ipAddr.AppendLiteral("]"); + } else { + ipAddr = host; + } + + MakeTopLevelInfo(scheme, ipAddr, port, aUseSite, topLevelInfo); + return; + } + + if (aUseSite) { + MakeTopLevelInfo(scheme, host, port, aUseSite, topLevelInfo); + return; + } + + if (isInsufficientDomainLevels) { + nsAutoCString publicSuffix; + rv = tldService->GetPublicSuffix(aURI, publicSuffix); + if (NS_SUCCEEDED(rv)) { + MakeTopLevelInfo(scheme, publicSuffix, port, aUseSite, topLevelInfo); + return; + } + } +} + +void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument, + nsIURI* aURI, bool aForced) { + PopulateTopLevelInfoFromURI( + aIsTopLevelDocument, aURI, IsFirstPartyEnabled(), aForced, + StaticPrefs::privacy_firstparty_isolate_use_site(), + &OriginAttributes::mFirstPartyDomain, *this); +} + +void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument, + const nsACString& aDomain) { + SetFirstPartyDomain(aIsTopLevelDocument, NS_ConvertUTF8toUTF16(aDomain)); +} + +void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument, + const nsAString& aDomain, + bool aForced) { + // If the pref is off or this is not a top level load, bail out. + if ((!IsFirstPartyEnabled() || !aIsTopLevelDocument) && !aForced) { + return; + } + + mFirstPartyDomain = aDomain; +} + +void OriginAttributes::SetPartitionKey(nsIURI* aURI) { + PopulateTopLevelInfoFromURI( + false /* aIsTopLevelDocument */, aURI, IsFirstPartyEnabled(), + true /* aForced */, StaticPrefs::privacy_dynamic_firstparty_use_site(), + &OriginAttributes::mPartitionKey, *this); +} + +void OriginAttributes::SetPartitionKey(const nsACString& aDomain) { + SetPartitionKey(NS_ConvertUTF8toUTF16(aDomain)); +} + +void OriginAttributes::SetPartitionKey(const nsAString& aDomain) { + mPartitionKey = aDomain; +} + +void OriginAttributes::CreateSuffix(nsACString& aStr) const { + URLParams params; + 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. + // + + if (mInIsolatedMozBrowser) { + params.Set(u"inBrowser"_ns, u"1"_ns); + } + + if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) { + value.Truncate(); + value.AppendInt(mUserContextId); + params.Set(u"userContextId"_ns, value); + } + + if (mPrivateBrowsingId) { + value.Truncate(); + value.AppendInt(mPrivateBrowsingId); + params.Set(u"privateBrowsingId"_ns, value); + } + + if (!mFirstPartyDomain.IsEmpty()) { + nsAutoString sanitizedFirstPartyDomain(mFirstPartyDomain); + sanitizedFirstPartyDomain.ReplaceChar(kSourceChar, kSanitizedChar); + + params.Set(u"firstPartyDomain"_ns, sanitizedFirstPartyDomain); + } + + if (!mGeckoViewSessionContextId.IsEmpty()) { + nsAutoString sanitizedGeckoViewUserContextId(mGeckoViewSessionContextId); + sanitizedGeckoViewUserContextId.ReplaceChar( + dom::quota::QuotaManager::kReplaceChars16, kSanitizedChar); + + params.Set(u"geckoViewUserContextId"_ns, sanitizedGeckoViewUserContextId); + } + + if (!mPartitionKey.IsEmpty()) { + nsAutoString sanitizedPartitionKey(mPartitionKey); + sanitizedPartitionKey.ReplaceChar(kSourceChar, kSanitizedChar); + + params.Set(u"partitionKey"_ns, sanitizedPartitionKey); + } + + aStr.Truncate(); + + params.Serialize(value, true); + 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 +} + +already_AddRefed<nsAtom> OriginAttributes::CreateSuffixAtom() const { + nsAutoCString suffix; + CreateSuffix(suffix); + return NS_Atomize(suffix); +} + +void OriginAttributes::CreateAnonymizedSuffix(nsACString& aStr) const { + OriginAttributes attrs = *this; + + if (!attrs.mFirstPartyDomain.IsEmpty()) { + attrs.mFirstPartyDomain.AssignLiteral("_anonymizedFirstPartyDomain_"); + } + + if (!attrs.mPartitionKey.IsEmpty()) { + attrs.mPartitionKey.AssignLiteral("_anonymizedPartitionKey_"); + } + + attrs.CreateSuffix(aStr); +} + +bool OriginAttributes::PopulateFromSuffix(const nsACString& aStr) { + if (aStr.IsEmpty()) { + return true; + } + + if (aStr[0] != '^') { + return false; + } + + // If a non-default mPrivateBrowsingId is passed and is not present in the + // suffix, then it will retain the id when it should be default according + // to the suffix. Set to default before iterating to fix this. + mPrivateBrowsingId = nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID; + + // Checking that we are in a pristine state + + MOZ_RELEASE_ASSERT(mUserContextId == 0); + MOZ_RELEASE_ASSERT(mPrivateBrowsingId == 0); + MOZ_RELEASE_ASSERT(mFirstPartyDomain.IsEmpty()); + MOZ_RELEASE_ASSERT(mGeckoViewSessionContextId.IsEmpty()); + MOZ_RELEASE_ASSERT(mPartitionKey.IsEmpty()); + + return URLParams::Parse( + Substring(aStr, 1, aStr.Length() - 1), + [this](const nsAString& aName, const nsAString& aValue) { + if (aName.EqualsLiteral("inBrowser")) { + if (!aValue.EqualsLiteral("1")) { + return false; + } + + mInIsolatedMozBrowser = true; + return true; + } + + if (aName.EqualsLiteral("addonId") || aName.EqualsLiteral("appId")) { + // No longer supported. Silently ignore so that legacy origin strings + // don't cause failures. + 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); + mUserContextId = static_cast<uint32_t>(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); + mPrivateBrowsingId = static_cast<uint32_t>(val); + + return true; + } + + if (aName.EqualsLiteral("firstPartyDomain")) { + nsAutoString firstPartyDomain(aValue); + firstPartyDomain.ReplaceChar(kSanitizedChar, kSourceChar); + mFirstPartyDomain.Assign(firstPartyDomain); + return true; + } + + if (aName.EqualsLiteral("geckoViewUserContextId")) { + mGeckoViewSessionContextId.Assign(aValue); + return true; + } + + if (aName.EqualsLiteral("partitionKey")) { + nsAutoString partitionKey(aValue); + partitionKey.ReplaceChar(kSanitizedChar, kSourceChar); + mPartitionKey.Assign(partitionKey); + return true; + } + + // No other attributes are supported. + return false; + }); +} + +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; +} + +/* static */ +bool OriginAttributes::ParsePartitionKey(const nsAString& aPartitionKey, + nsAString& outScheme, + nsAString& outBaseDomain, + int32_t& outPort) { + outScheme.Truncate(); + outBaseDomain.Truncate(); + outPort = -1; + + // Partition keys have the format "(<scheme>,<baseDomain>,[port])". The port + // is optional. For example: "(https,example.com,8443)" or + // "(http,example.org)". + // When privacy.dynamic_firstparty.use_site = false, the partitionKey contains + // only the host, e.g. "example.com". + // See MakeTopLevelInfo for the partitionKey serialization code. + + if (aPartitionKey.IsEmpty()) { + return true; + } + + // PartitionKey contains only the host. + if (!StaticPrefs::privacy_dynamic_firstparty_use_site()) { + outBaseDomain = aPartitionKey; + return true; + } + + // Smallest possible partitionKey is "(x,x)". Scheme and base domain are + // mandatory. + if (NS_WARN_IF(aPartitionKey.Length() < 5)) { + return false; + } + + if (NS_WARN_IF(aPartitionKey.First() != '(' || aPartitionKey.Last() != ')')) { + return false; + } + + // Remove outer brackets so we can string split. + nsAutoString str(Substring(aPartitionKey, 1, aPartitionKey.Length() - 2)); + + uint32_t fieldIndex = 0; + for (const nsAString& field : str.Split(',')) { + if (NS_WARN_IF(field.IsEmpty())) { + // There cannot be empty fields. + return false; + } + + if (fieldIndex == 0) { + outScheme.Assign(field); + } else if (fieldIndex == 1) { + outBaseDomain.Assign(field); + } else if (fieldIndex == 2) { + // Parse the port which is represented in the partitionKey string as a + // decimal (base 10) number. + long port = strtol(NS_ConvertUTF16toUTF8(field).get(), nullptr, 10); + // Invalid port. + if (NS_WARN_IF(port == 0)) { + return false; + } + outPort = static_cast<int32_t>(port); + } else { + NS_WARNING("Invalid partitionKey. Too many tokens"); + return false; + } + + fieldIndex++; + } + + // scheme and base domain are required. + return fieldIndex > 1; +} + +} // namespace mozilla diff --git a/caps/OriginAttributes.h b/caps/OriginAttributes.h new file mode 100644 index 0000000000..8e78a7cb24 --- /dev/null +++ b/caps/OriginAttributes.h @@ -0,0 +1,296 @@ +/* -*- 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 mozilla_OriginAttributes_h +#define mozilla_OriginAttributes_h + +#include "mozilla/dom/ChromeUtilsBinding.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "nsIScriptSecurityManager.h" + +namespace mozilla { + +class OriginAttributes : public dom::OriginAttributesDictionary { + public: + OriginAttributes() = default; + + explicit OriginAttributes(bool aInIsolatedMozBrowser) { + mInIsolatedMozBrowser = aInIsolatedMozBrowser; + } + + explicit OriginAttributes(const OriginAttributesDictionary& aOther) + : OriginAttributesDictionary(aOther) {} + + void SetFirstPartyDomain(const bool aIsTopLevelDocument, nsIURI* aURI, + bool aForced = false); + void SetFirstPartyDomain(const bool aIsTopLevelDocument, + const nsACString& aDomain); + void SetFirstPartyDomain(const bool aIsTopLevelDocument, + const nsAString& aDomain, bool aForced = false); + + void SetPartitionKey(nsIURI* aURI); + void SetPartitionKey(const nsACString& aDomain); + void SetPartitionKey(const nsAString& aDomain); + + enum { + STRIP_FIRST_PARTY_DOMAIN = 0x01, + STRIP_USER_CONTEXT_ID = 0x02, + STRIP_PRIVATE_BROWSING_ID = 0x04, + STRIP_PARITION_KEY = 0x08, + }; + + inline void StripAttributes(uint32_t aFlags) { + if (aFlags & STRIP_FIRST_PARTY_DOMAIN) { + mFirstPartyDomain.Truncate(); + } + + if (aFlags & STRIP_USER_CONTEXT_ID) { + mUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID; + } + + if (aFlags & STRIP_PRIVATE_BROWSING_ID) { + mPrivateBrowsingId = + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID; + } + + if (aFlags & STRIP_PARITION_KEY) { + mPartitionKey.Truncate(); + } + } + + bool operator==(const OriginAttributes& aOther) const { + return EqualsIgnoringFPD(aOther) && + mFirstPartyDomain == aOther.mFirstPartyDomain && + // FIXME(emilio, bug 1667440): Should this be part of + // EqualsIgnoringFPD instead? + mPartitionKey == aOther.mPartitionKey; + } + + bool operator!=(const OriginAttributes& aOther) const { + return !(*this == aOther); + } + + [[nodiscard]] bool EqualsIgnoringFPD(const OriginAttributes& aOther) const { + return mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser && + mUserContextId == aOther.mUserContextId && + mPrivateBrowsingId == aOther.mPrivateBrowsingId && + mGeckoViewSessionContextId == aOther.mGeckoViewSessionContextId; + } + + [[nodiscard]] bool EqualsIgnoringPartitionKey( + const OriginAttributes& aOther) const { + return EqualsIgnoringFPD(aOther) && + mFirstPartyDomain == aOther.mFirstPartyDomain; + } + + // Serializes/Deserializes non-default values into the suffix format, i.e. + // |^key1=value1&key2=value2|. If there are no non-default attributes, this + // returns an empty string. + void CreateSuffix(nsACString& aStr) const; + + // Like CreateSuffix, but returns an atom instead of producing a string. + already_AddRefed<nsAtom> CreateSuffixAtom() const; + + // Don't use this method for anything else than debugging! + void CreateAnonymizedSuffix(nsACString& aStr) const; + + [[nodiscard]] bool PopulateFromSuffix(const nsACString& aStr); + + // Populates the attributes from a string like + // |uri^key1=value1&key2=value2| and returns the uri without the suffix. + [[nodiscard]] bool PopulateFromOrigin(const nsACString& aOrigin, + nsACString& aOriginNoSuffix); + + // Helper function to match mIsPrivateBrowsing to existing private browsing + // flags. Once all other flags are removed, this can be removed too. + void SyncAttributesWithPrivateBrowsing(bool aInPrivateBrowsing); + + // check if "privacy.firstparty.isolate" is enabled. + static inline bool IsFirstPartyEnabled() { + return StaticPrefs::privacy_firstparty_isolate(); + } + + static inline bool UseSiteForFirstPartyDomain() { + if (IsFirstPartyEnabled()) { + return StaticPrefs::privacy_firstparty_isolate_use_site(); + } + return StaticPrefs::privacy_dynamic_firstparty_use_site(); + } + + // check if the access of window.opener across different FPDs is restricted. + // We only restrict the access of window.opener when first party isolation + // is enabled and "privacy.firstparty.isolate.restrict_opener_access" is on. + static inline bool IsRestrictOpenerAccessForFPI() { + // We always want to restrict window.opener if first party isolation is + // disabled. + return !StaticPrefs::privacy_firstparty_isolate() || + StaticPrefs::privacy_firstparty_isolate_restrict_opener_access(); + } + + // Check whether we block the postMessage across different FPDs when the + // targetOrigin is '*'. + [[nodiscard]] static inline bool IsBlockPostMessageForFPI() { + return StaticPrefs::privacy_firstparty_isolate() && + StaticPrefs::privacy_firstparty_isolate_block_post_message(); + } + + // returns true if the originAttributes suffix has mPrivateBrowsingId value + // different than 0. + static bool IsPrivateBrowsing(const nsACString& aOrigin); + + // Parse a partitionKey of the format "(<scheme>,<baseDomain>,[port])" into + // its components. + // Returns false if the partitionKey cannot be parsed because the format is + // invalid. + static bool ParsePartitionKey(const nsAString& aPartitionKey, + nsAString& outScheme, nsAString& outBaseDomain, + int32_t& outPort); +}; + +class OriginAttributesPattern : public dom::OriginAttributesPatternDictionary { + public: + // To convert a JSON string to an OriginAttributesPattern, do the following: + // + // OriginAttributesPattern pattern; + // if (!pattern.Init(aJSONString)) { + // ... // handle failure. + // } + OriginAttributesPattern() = default; + + explicit OriginAttributesPattern( + const OriginAttributesPatternDictionary& aOther) + : OriginAttributesPatternDictionary(aOther) {} + + // Performs a match of |aAttrs| against this pattern. + bool Matches(const OriginAttributes& aAttrs) const { + if (mInIsolatedMozBrowser.WasPassed() && + mInIsolatedMozBrowser.Value() != aAttrs.mInIsolatedMozBrowser) { + return false; + } + + if (mUserContextId.WasPassed() && + mUserContextId.Value() != aAttrs.mUserContextId) { + return false; + } + + if (mPrivateBrowsingId.WasPassed() && + mPrivateBrowsingId.Value() != aAttrs.mPrivateBrowsingId) { + return false; + } + + if (mFirstPartyDomain.WasPassed() && + mFirstPartyDomain.Value() != aAttrs.mFirstPartyDomain) { + return false; + } + + if (mGeckoViewSessionContextId.WasPassed() && + mGeckoViewSessionContextId.Value() != + aAttrs.mGeckoViewSessionContextId) { + return false; + } + + // If both mPartitionKey and mPartitionKeyPattern are passed, mPartitionKey + // takes precedence. + if (mPartitionKey.WasPassed()) { + if (mPartitionKey.Value() != aAttrs.mPartitionKey) { + return false; + } + } else if (mPartitionKeyPattern.WasPassed()) { + auto& pkPattern = mPartitionKeyPattern.Value(); + + if (pkPattern.mScheme.WasPassed() || pkPattern.mBaseDomain.WasPassed() || + pkPattern.mPort.WasPassed()) { + if (aAttrs.mPartitionKey.IsEmpty()) { + return false; + } + + nsString scheme; + nsString baseDomain; + int32_t port; + bool success = OriginAttributes::ParsePartitionKey( + aAttrs.mPartitionKey, scheme, baseDomain, port); + if (!success) { + return false; + } + + if (pkPattern.mScheme.WasPassed() && + pkPattern.mScheme.Value() != scheme) { + return false; + } + if (pkPattern.mBaseDomain.WasPassed() && + pkPattern.mBaseDomain.Value() != baseDomain) { + return false; + } + if (pkPattern.mPort.WasPassed() && pkPattern.mPort.Value() != port) { + return false; + } + } + } + + return true; + } + + bool Overlaps(const OriginAttributesPattern& aOther) const { + if (mInIsolatedMozBrowser.WasPassed() && + aOther.mInIsolatedMozBrowser.WasPassed() && + mInIsolatedMozBrowser.Value() != aOther.mInIsolatedMozBrowser.Value()) { + return false; + } + + if (mUserContextId.WasPassed() && aOther.mUserContextId.WasPassed() && + mUserContextId.Value() != aOther.mUserContextId.Value()) { + return false; + } + + if (mPrivateBrowsingId.WasPassed() && + aOther.mPrivateBrowsingId.WasPassed() && + mPrivateBrowsingId.Value() != aOther.mPrivateBrowsingId.Value()) { + return false; + } + + if (mFirstPartyDomain.WasPassed() && aOther.mFirstPartyDomain.WasPassed() && + mFirstPartyDomain.Value() != aOther.mFirstPartyDomain.Value()) { + return false; + } + + if (mGeckoViewSessionContextId.WasPassed() && + aOther.mGeckoViewSessionContextId.WasPassed() && + mGeckoViewSessionContextId.Value() != + aOther.mGeckoViewSessionContextId.Value()) { + return false; + } + + if (mPartitionKey.WasPassed() && aOther.mPartitionKey.WasPassed() && + mPartitionKey.Value() != aOther.mPartitionKey.Value()) { + return false; + } + + if (mPartitionKeyPattern.WasPassed() && + aOther.mPartitionKeyPattern.WasPassed()) { + auto& self = mPartitionKeyPattern.Value(); + auto& other = aOther.mPartitionKeyPattern.Value(); + + if (self.mScheme.WasPassed() && other.mScheme.WasPassed() && + self.mScheme.Value() != other.mScheme.Value()) { + return false; + } + if (self.mBaseDomain.WasPassed() && other.mBaseDomain.WasPassed() && + self.mBaseDomain.Value() != other.mBaseDomain.Value()) { + return false; + } + if (self.mPort.WasPassed() && other.mPort.WasPassed() && + self.mPort.Value() != other.mPort.Value()) { + return false; + } + } + + return true; + } +}; + +} // namespace mozilla + +#endif /* mozilla_OriginAttributes_h */ diff --git a/caps/PrincipalHashKey.h b/caps/PrincipalHashKey.h new file mode 100644 index 0000000000..1c245532a6 --- /dev/null +++ b/caps/PrincipalHashKey.h @@ -0,0 +1,58 @@ +/* -*- 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 mozilla_PrincipalHashKey_h +#define mozilla_PrincipalHashKey_h + +#include "BasePrincipal.h" +#include "PLDHashTable.h" +#include "mozilla/Unused.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" + +namespace mozilla { + +class PrincipalHashKey : public PLDHashEntryHdr { + public: + using KeyType = nsIPrincipal*; + using KeyTypePointer = const nsIPrincipal*; + + explicit PrincipalHashKey(const nsIPrincipal* aKey) + : mPrincipal(const_cast<nsIPrincipal*>(aKey)) { + MOZ_ASSERT(aKey); + MOZ_COUNT_CTOR(PrincipalHashKey); + } + PrincipalHashKey(PrincipalHashKey&& aKey) + : mPrincipal(std::move(aKey.mPrincipal)) { + MOZ_COUNT_CTOR(PrincipalHashKey); + } + + MOZ_COUNTED_DTOR(PrincipalHashKey) + + nsIPrincipal* GetKey() const { return mPrincipal; } + + bool KeyEquals(const nsIPrincipal* aKey) const { + return BasePrincipal::Cast(mPrincipal) + ->FastEquals(const_cast<nsIPrincipal*>(aKey)); + } + + static const nsIPrincipal* KeyToPointer(const nsIPrincipal* aKey) { + return aKey; + } + static PLDHashNumber HashKey(const nsIPrincipal* aKey) { + auto* bp = BasePrincipal::Cast(aKey); + return HashGeneric(bp->GetOriginNoSuffixHash(), bp->GetOriginSuffixHash()); + } + + enum { ALLOW_MEMMOVE = true }; + + protected: + nsCOMPtr<nsIPrincipal> mPrincipal; +}; + +} // namespace mozilla + +#endif diff --git a/caps/SystemPrincipal.cpp b/caps/SystemPrincipal.cpp new file mode 100644 index 0000000000..f92b5cc6b4 --- /dev/null +++ b/caps/SystemPrincipal.cpp @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* The privileged system principal. */ + +#include "nscore.h" +#include "SystemPrincipal.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsCOMPtr.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsString.h" +#include "nsIClassInfoImpl.h" +#include "pratom.h" + +using namespace mozilla; + +NS_IMPL_CLASSINFO(SystemPrincipal, nullptr, + nsIClassInfo::SINGLETON | nsIClassInfo::MAIN_THREAD_ONLY, + NS_SYSTEMPRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(SystemPrincipal, nsIPrincipal, nsISerializable) +NS_IMPL_CI_INTERFACE_GETTER(SystemPrincipal, nsIPrincipal, nsISerializable) + +static constexpr nsLiteralCString kSystemPrincipalSpec = + "[System Principal]"_ns; + +SystemPrincipal::SystemPrincipal() + : BasePrincipal(eSystemPrincipal, kSystemPrincipalSpec, + OriginAttributes()) {} + +static StaticMutex sSystemPrincipalMutex; +static StaticRefPtr<SystemPrincipal> sSystemPrincipal + MOZ_GUARDED_BY(sSystemPrincipalMutex); + +already_AddRefed<SystemPrincipal> SystemPrincipal::Get() { + StaticMutexAutoLock lock(sSystemPrincipalMutex); + return do_AddRef(sSystemPrincipal); +} + +already_AddRefed<SystemPrincipal> SystemPrincipal::Init() { + AssertIsOnMainThread(); + StaticMutexAutoLock lock(sSystemPrincipalMutex); + if (MOZ_UNLIKELY(sSystemPrincipal)) { + MOZ_ASSERT_UNREACHABLE("SystemPrincipal::Init() may only be called once"); + } else { + sSystemPrincipal = new SystemPrincipal(); + } + return do_AddRef(sSystemPrincipal); +} + +void SystemPrincipal::Shutdown() { + AssertIsOnMainThread(); + StaticMutexAutoLock lock(sSystemPrincipalMutex); + MOZ_ASSERT(sSystemPrincipal); + sSystemPrincipal = nullptr; +} + +nsresult SystemPrincipal::GetScriptLocation(nsACString& aStr) { + aStr.Assign(kSystemPrincipalSpec); + return NS_OK; +} + +/////////////////////////////////////// +// Methods implementing nsIPrincipal // +/////////////////////////////////////// + +uint32_t SystemPrincipal::GetHashValue() { return NS_PTR_TO_INT32(this); } + +NS_IMETHODIMP +SystemPrincipal::GetURI(nsIURI** aURI) { + *aURI = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +SystemPrincipal::GetIsOriginPotentiallyTrustworthy(bool* aResult) { + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP +SystemPrincipal::GetDomain(nsIURI** aDomain) { + *aDomain = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +SystemPrincipal::SetDomain(nsIURI* aDomain) { return NS_OK; } + +NS_IMETHODIMP +SystemPrincipal::GetBaseDomain(nsACString& aBaseDomain) { + // No base domain for chrome. + return NS_OK; +} + +NS_IMETHODIMP +SystemPrincipal::GetAddonId(nsAString& aAddonId) { + aAddonId.Truncate(); + return NS_OK; +}; + +////////////////////////////////////////// +// Methods implementing nsISerializable // +////////////////////////////////////////// + +NS_IMETHODIMP +SystemPrincipal::Read(nsIObjectInputStream* aStream) { + // no-op: CID is sufficient to identify the mSystemPrincipal singleton + return NS_OK; +} + +NS_IMETHODIMP +SystemPrincipal::Write(nsIObjectOutputStream* aStream) { + // Read is used still for legacy principals + MOZ_RELEASE_ASSERT(false, "Old style serialization is removed"); + return NS_OK; +} diff --git a/caps/SystemPrincipal.h b/caps/SystemPrincipal.h new file mode 100644 index 0000000000..7b89d7ddff --- /dev/null +++ b/caps/SystemPrincipal.h @@ -0,0 +1,83 @@ +/* -*- 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/. */ + +/* The privileged system principal. */ + +#ifndef mozilla_SystemPrincipal_h +#define mozilla_SystemPrincipal_h + +#include "nsIPrincipal.h" +#include "nsJSPrincipals.h" + +#include "mozilla/BasePrincipal.h" + +#define NS_SYSTEMPRINCIPAL_CID \ + { \ + 0x4a6212db, 0xaccb, 0x11d3, { \ + 0xb7, 0x65, 0x0, 0x60, 0xb0, 0xb6, 0xce, 0xcb \ + } \ + } +#define NS_SYSTEMPRINCIPAL_CONTRACTID "@mozilla.org/systemprincipal;1" + +class nsScriptSecurityManager; + +namespace Json { +class Value; +} + +namespace mozilla { + +class SystemPrincipal final : public BasePrincipal, public nsISerializable { + SystemPrincipal(); + + public: + static already_AddRefed<SystemPrincipal> Get(); + + static PrincipalKind Kind() { return eSystemPrincipal; } + + NS_IMETHOD_(MozExternalRefCountType) AddRef() override { + return nsJSPrincipals::AddRef(); + }; + NS_IMETHOD_(MozExternalRefCountType) Release() override { + return nsJSPrincipals::Release(); + }; + + NS_DECL_NSISERIALIZABLE + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + uint32_t GetHashValue() override; + NS_IMETHOD GetURI(nsIURI** aURI) override; + NS_IMETHOD GetDomain(nsIURI** aDomain) override; + NS_IMETHOD SetDomain(nsIURI* aDomain) override; + NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; + NS_IMETHOD GetAddonId(nsAString& aAddonId) override; + NS_IMETHOD GetIsOriginPotentiallyTrustworthy(bool* aResult) override; + + virtual nsresult GetScriptLocation(nsACString& aStr) override; + + nsresult GetSiteIdentifier(SiteIdentifier& aSite) override { + aSite.Init(this); + return NS_OK; + } + + protected: + friend class ::nsScriptSecurityManager; + + virtual ~SystemPrincipal() = default; + + static already_AddRefed<SystemPrincipal> Init(); + static void Shutdown(); + + bool SubsumesInternal(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration) override { + return true; + } + + bool MayLoadInternal(nsIURI* aURI) override { return true; } +}; + +} // namespace mozilla + +#endif // mozilla_SystemPrincipal_h diff --git a/caps/moz.build b/caps/moz.build new file mode 100644 index 0000000000..6d5d95f934 --- /dev/null +++ b/caps/moz.build @@ -0,0 +1,80 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +MOCHITEST_MANIFESTS += ["tests/mochitest/mochitest.ini"] +MOCHITEST_CHROME_MANIFESTS += ["tests/mochitest/chrome.ini"] +BROWSER_CHROME_MANIFESTS += ["tests/mochitest/browser.ini"] +XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.ini"] + +# Hack to make this file available as a resource:// URI. +TESTING_JS_MODULES += [ + "tests/mochitest/resource_test_file.html", +] + +XPIDL_SOURCES += [ + "nsIAddonPolicyService.idl", + "nsIDomainPolicy.idl", + "nsIPrincipal.idl", + "nsIScriptSecurityManager.idl", +] + +XPIDL_MODULE = "caps" + +EXPORTS += [ + "nsJSPrincipals.h", + "nsScriptSecurityManager.h", +] + +EXPORTS.mozilla = [ + "BasePrincipal.h", + "ContentPrincipal.h", + "ContentPrincipalInfoHashKey.h", + "ExpandedPrincipal.h", + "NullPrincipal.h", + "OriginAttributes.h", + "PrincipalHashKey.h", + "SystemPrincipal.h", +] + +SOURCES += [ + # Compile this separately since nsExceptionHandler.h conflicts + # with something from NullPrincipal.cpp. + "BasePrincipal.cpp", +] + +UNIFIED_SOURCES += [ + "ContentPrincipal.cpp", + "DomainPolicy.cpp", + "ExpandedPrincipal.cpp", + "nsJSPrincipals.cpp", + "nsScriptSecurityManager.cpp", + "NullPrincipal.cpp", + "OriginAttributes.cpp", + "SystemPrincipal.cpp", +] + +USE_LIBS += [ + "jsoncpp", +] + +LOCAL_INCLUDES += [ + "/docshell/base", + "/dom/base", + "/js/xpconnect/src", + "/netwerk/base", + "/netwerk/cookie", + "/toolkit/components/jsoncpp/include", +] + +if CONFIG["ENABLE_TESTS"]: + DIRS += ["tests/gtest"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +with Files("**"): + BUG_COMPONENT = ("Core", "Security: CAPS") diff --git a/caps/nsIAddonPolicyService.idl b/caps/nsIAddonPolicyService.idl new file mode 100644 index 0000000000..de218ff70c --- /dev/null +++ b/caps/nsIAddonPolicyService.idl @@ -0,0 +1,102 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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" +#include "nsIURI.idl" + +/** + * This interface allows the security manager to query custom per-addon security + * policy. + */ +[scriptable, uuid(8a034ef9-9d14-4c5d-8319-06c1ab574baa)] +interface nsIAddonPolicyService : nsISupports +{ + /** + * Returns the default content security policy which applies to extension + * documents which do not specify any custom policies. + */ + readonly attribute AString defaultCSP; + + /** + * Same as above, but used for extensions using manifest v3. + */ + readonly attribute AString defaultCSPV3; + + /** + * Returns the base content security policy which applies to all extension resources. + */ + AString getBaseCSP(in AString aAddonId); + + /** + * Returns the content security policy which applies to documents belonging + * to the extension with the given ID. This may be either a custom policy, + * if one was supplied, or the default policy if one was not. + */ + AString getExtensionPageCSP(in AString aAddonId); + + /** + * Returns the generated background page as a data-URI, if any. If the addon + * does not have an auto-generated background page, an empty string is + * returned. + */ + ACString getGeneratedBackgroundPageUrl(in ACString aAddonId); + + /** + * Returns true if the addon was granted the |aPerm| API permission. + */ + boolean addonHasPermission(in AString aAddonId, in AString aPerm); + + /** + * Returns true if unprivileged code associated with the given addon may load + * data from |aURI|. If |aExplicit| is true, the <all_urls> permission and + * permissive host globs are ignored when checking for a match. + */ + boolean addonMayLoadURI(in AString aAddonId, in nsIURI aURI, [optional] in boolean aExplicit); + + /** + * Returns the name of the WebExtension with the given ID, or the ID string + * if no matching add-on can be found. + */ + AString getExtensionName(in AString aAddonId); + + /** + * Returns true if a given extension:// URI is web-accessible and loadable by the source. + * This should be called if the protocol flags for the extension URI has URI_WEB_ACCESSIBLE. + */ + boolean sourceMayLoadExtensionURI(in nsIURI aSourceURI, in nsIURI aExtensionURI); + + /** + * Maps an extension URI to the ID of the addon it belongs to. + */ + AString extensionURIToAddonId(in nsIURI aURI); +}; + +/** + * This interface exposes functionality related to add-on content policy + * enforcement. + */ +[scriptable, uuid(7a4fe60b-9131-45f5-83f3-dc63b5d71a5d)] +interface nsIAddonContentPolicy : nsISupports +{ + /* options to pass to validateAddonCSP + * + * Manifest V2 uses CSP_ALLOW_ANY. + * In Manifest V3, extension_pages would use CSP_ALLOW_WASM + * and sandbox would use CSP_ALLOW_EVAL. + */ + const unsigned long CSP_ALLOW_ANY = 0xFFFF; + const unsigned long CSP_ALLOW_LOCALHOST = (1<<0); + const unsigned long CSP_ALLOW_EVAL = (1<<1); + const unsigned long CSP_ALLOW_REMOTE = (1<<2); + const unsigned long CSP_ALLOW_WASM = (1<<3); + + /** + * Checks a custom content security policy string, to ensure that it meets + * minimum security requirements. Returns null for valid policies, or a + * string describing the error for invalid policies. + */ + AString validateAddonCSP(in AString aPolicyString, in unsigned long aPermittedPolicy); +}; diff --git a/caps/nsIDomainPolicy.idl b/caps/nsIDomainPolicy.idl new file mode 100644 index 0000000000..74d7f3b656 --- /dev/null +++ b/caps/nsIDomainPolicy.idl @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 nsIURI; +interface nsIDomainSet; + +%{ C++ +namespace mozilla { +namespace dom { +class DomainPolicyClone; +} +} +%} + +[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone); +[ptr] native DomainPolicyCloneConstPtr(const mozilla::dom::DomainPolicyClone); + +/* + * When a domain policy is instantiated by invoking activateDomainPolicy() on + * nsIScriptSecurityManager, these domain sets are consulted when each new + * global is created (they have no effect on already-created globals). + * If javascript is globally enabled with |javascript.enabled|, the blocklists + * are consulted. If globally disabled, the allowlists are consulted. Lookups + * on blocklist and allowlist happen with contains(), and lookups on + * superBlocklist and superAllowlist happen with containsSuperDomain(). + * + * When deactivate() is invoked, the domain sets are emptied, and the + * nsIDomainPolicy ceases to have any effect on the system. + */ +[scriptable, builtinclass, uuid(82b24a20-6701-4d40-a0f9-f5dc7321b555)] +interface nsIDomainPolicy : nsISupports +{ + readonly attribute nsIDomainSet blocklist; + readonly attribute nsIDomainSet superBlocklist; + readonly attribute nsIDomainSet allowlist; + readonly attribute nsIDomainSet superAllowlist; + + void deactivate(); + + [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone); + [noscript, notxpcom] void applyClone(in DomainPolicyCloneConstPtr aClone); +}; + +[scriptable, builtinclass, uuid(665c981b-0a0f-4229-ac06-a826e02d4f69)] +interface nsIDomainSet : nsISupports +{ + /* + * Add a domain to the set. No-op if it already exists. + */ + void add(in nsIURI aDomain); + + /* + * Remove a domain from the set. No-op if it doesn't exist. + */ + void remove(in nsIURI aDomain); + + /* + * Remove all entries from the set. + */ + void clear(); + + /* + * Returns true if a given domain is in the set. + */ + bool contains(in nsIURI aDomain); + + /* + * Returns true if a given domain is a subdomain of one of the entries in + * the set. + */ + bool containsSuperDomain(in nsIURI aDomain); +}; diff --git a/caps/nsIPrincipal.idl b/caps/nsIPrincipal.idl new file mode 100644 index 0000000000..e7264aa5d8 --- /dev/null +++ b/caps/nsIPrincipal.idl @@ -0,0 +1,759 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* Defines the abstract interface for a principal. */ + +#include "nsIContentSecurityPolicy.idl" +#include "nsISerializable.idl" +#include "nsIAboutModule.idl" +#include "nsIReferrerInfo.idl" +interface nsIChannel; +#include "mozIDOMWindow.idl" + +%{C++ +struct JSPrincipals; +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsString.h" +#include "mozilla/DebugOnly.h" +namespace mozilla { +class OriginAttributes; +} + +/** + * Some methods have a fast path for the case when we're comparing a principal + * to itself. The situation may happen for example with about:blank documents. + */ + +#define DECL_FAST_INLINE_HELPER(method_) \ + inline bool method_(nsIPrincipal* aOther) \ + { \ + mozilla::DebugOnly<bool> val = false; \ + MOZ_ASSERT_IF(this == aOther, \ + NS_SUCCEEDED(method_(aOther, &val)) && val); \ + \ + bool retVal = false; \ + return \ + this == aOther || \ + (NS_SUCCEEDED(method_(aOther, &retVal)) && retVal); \ + } + +%} + +interface nsIURI; + +webidl WebExtensionPolicy; + +[ptr] native JSContext(JSContext); +[ptr] native JSPrincipals(JSPrincipals); +[ref] native PrincipalArray(const nsTArray<nsCOMPtr<nsIPrincipal>>); +[ref] native const_OriginAttributes(const mozilla::OriginAttributes); +native ReferrerPolicy(mozilla::dom::ReferrerPolicy); + +[scriptable, builtinclass, uuid(f75f502d-79fd-48be-a079-e5a7b8f80c8b)] +interface nsIPrincipal : nsISupports +{ + /** + * Returns whether the other principal is equivalent to this principal. + * Principals are considered equal if they are the same principal, or + * they have the same origin. + * + * May be called from any thread. + */ + boolean equals(in nsIPrincipal other); + + /** + * Returns whether the other principal is equivalent to this principal + * for permission purposes + * Matches {originAttributes ,equalsURIForPermission} + * + * May be called from any thread. + */ + + boolean equalsForPermission(in nsIPrincipal other, in bool aExactHost); + + /** + * Like equals, but takes document.domain changes into account. + * + * May be called from any thread, though document.domain may racily change + * during the comparison when called from off-main-thread. + */ + boolean equalsConsideringDomain(in nsIPrincipal other); + + %{C++ + DECL_FAST_INLINE_HELPER(Equals) + DECL_FAST_INLINE_HELPER(EqualsConsideringDomain) + %} + + /* + * Returns whether the Principals URI is equal to the other URI + * + * May be called from any thread. + */ + boolean equalsURI(in nsIURI aOtherURI); + + /** + * Returns a hash value for the principal. + * + * May be called from any thread. + */ + [notxpcom, nostdcall] readonly attribute unsigned long hashValue; + + /** + * The principal URI to which this principal pertains. This is + * generally the document URI. + * + * May be called from any thread. + */ + [infallible] readonly attribute nsIURI URI; + + /** + * The domain URI to which this principal pertains. + * This is null unless script successfully sets document.domain to our URI + * or a superdomain of our URI. + * Setting this has no effect on the URI. + * See https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin + * + * The getter may be called from any thread, but may only be set on the main thread. + */ + [noscript] attribute nsIURI domain; + + /** + * Returns whether the other principal is equal to or weaker than this + * principal. Principals are equal if they are the same object or they + * have the same origin. + * + * Thus a principal always subsumes itself. + * + * The system principal subsumes itself and all other principals. + * + * A null principal (corresponding to an unknown, hence assumed minimally + * privileged, security context) is not equal to any other principal + * (including other null principals), and therefore does not subsume + * anything but itself. + * + * May be called from any thread. + */ + boolean subsumes(in nsIPrincipal other); + + /** + * Same as the previous method, subsumes(), but takes document.domain into + * account. + * + * May be called from any thread, though document.domain may racily change + * during the comparison when called from off-main-thread. + */ + boolean subsumesConsideringDomain(in nsIPrincipal other); + + /** + * Same as the subsumesConsideringDomain(), but ignores the first party + * domain in its originAttributes. + * + * May be called from any thread, though document.domain may racily change + * during the comparison when called from off-main-thread. + */ + boolean subsumesConsideringDomainIgnoringFPD(in nsIPrincipal other); + + %{C++ + DECL_FAST_INLINE_HELPER(Subsumes) + DECL_FAST_INLINE_HELPER(SubsumesConsideringDomain) + DECL_FAST_INLINE_HELPER(SubsumesConsideringDomainIgnoringFPD) +#undef DECL_FAST_INLINE_HELPER + %} + + /** + * Checks whether this principal is allowed to load the network resource + * located at the given URI under the same-origin policy. This means that + * content principals are only allowed to load resources from the same + * domain, the system principal is allowed to load anything, and null + * principals can only load URIs where they are the principal. This is + * changed by the optional flag allowIfInheritsPrincipal (which defaults to + * false) which allows URIs that inherit their loader's principal. + * + * If the load is allowed this function does nothing. If the load is not + * allowed the function throws NS_ERROR_DOM_BAD_URI. + * + * NOTE: Other policies might override this, such as the Access-Control + * specification. + * NOTE: The 'domain' attribute has no effect on the behaviour of this + * function. + * NOTE: Main-Thread Only. + * + * + * @param uri The URI about to be loaded. + * @param allowIfInheritsPrincipal If true, the load is allowed if the + * loadee inherits the principal of the + * loader. + * @throws NS_ERROR_DOM_BAD_URI if the load is not allowed. + */ + void checkMayLoad(in nsIURI uri, + in boolean allowIfInheritsPrincipal); + + /** + * Like checkMayLoad, but if returning an error will also report that error + * to the console, using the provided window id. The window id may be 0 to + * report to just the browser console, not web consoles. + * + * NOTE: Main-Thread Only. + */ + void checkMayLoadWithReporting(in nsIURI uri, + in boolean allowIfInheritsPrincipal, + in unsigned long long innerWindowID); + + /** + * Checks if the provided URI is considered third-party to the + * URI of the principal. + * Returns true if the URI is third-party. + * + * May be called from any thread. + * + * @param uri - The URI to check + */ + boolean isThirdPartyURI(in nsIURI uri); + + /** + * Checks if the provided principal is considered third-party to the + * URI of the Principal. + * Returns true if the principal is third-party. + * + * May be called from any thread. + * + * @param principal - The principal to check + */ + boolean isThirdPartyPrincipal(in nsIPrincipal principal); + + /** + * Checks if the provided channel is considered third-party to the + * URI of the principal. + * Returns true if the channel is third-party. + * Returns false if the Principal is a System Principal + * + * NOTE: Main-Thread Only. + * + * @param channel - The Channel to check + */ + boolean isThirdPartyChannel(in nsIChannel channel); + + /** + * A dictionary of the non-default origin attributes associated with this + * nsIPrincipal. + * + * Attributes are tokens that are taken into account when determining whether + * two principals are same-origin - if any attributes differ, the principals + * are cross-origin, even if the scheme, host, and port are the same. + * Attributes should also be considered for all security and bucketing decisions, + * even those which make non-standard comparisons (like cookies, which ignore + * scheme, or quotas, which ignore subdomains). + * + * If you're looking for an easy-to-use canonical stringification of the origin + * attributes, see |originSuffix| below. + */ + [implicit_jscontext] + readonly attribute jsval originAttributes; + + // May be called from any thread. + [noscript, notxpcom, nostdcall, binaryname(OriginAttributesRef)] + const_OriginAttributes OriginAttributesRef(); + + /** + * A canonical representation of the origin for this principal. This + * consists of a base string (which, for content principals, is of the + * format scheme://host:port), concatenated with |originAttributes| (see + * below). + * + * We maintain the invariant that principalA.equals(principalB) if and only + * if principalA.origin == principalB.origin. + * + * May be called from any thread. + */ + readonly attribute ACString origin; + + /** + * Returns an ASCII compatible representation + * of the principals Origin + * + * May be called from any thread. + */ + [noscript] readonly attribute ACString asciiOrigin; + + /** + * Returns the "host:port" portion of the + * Principals URI, if any. + * + * May be called from any thread. + */ + readonly attribute ACString hostPort; + + /** + * Returns the "host:port" portion of the + * Principals URI, if any. + * + * May be called from any thread. + */ + readonly attribute ACString asciiHost; + + /** + * Returns the "host" portion of the + * Principals URI, if any. + * + * May be called from any thread. + */ + readonly attribute ACString host; + + /** + * Returns the prePath of the principals uri + * follows the format scheme: + * "scheme://username:password@hostname:portnumber/" + * + * May be called from any thread. + */ + readonly attribute ACString prePath; + + /** + * Returns the filePath of the principals uri. See nsIURI. + * + * May be called from any thread. + */ + readonly attribute ACString filePath; + + /** + * Returns the ASCII Spec from the Principals URI. + * Might return the empty string, e.g. for the case of + * a SystemPrincipal or an EpxandedPrincipal. + * + * May be called from any thread. + * + * WARNING: DO NOT USE FOR SECURITY CHECKS. + * just for logging purposes! + */ + readonly attribute ACString asciiSpec; + + /** + * Returns the Spec from the Principals URI. + * Might return the empty string, e.g. for the case of + * a SystemPrincipal or an EpxandedPrincipal. + * + * May be called from any thread. + * + * WARNING: Do not land new Code using, as this will be removed soon + */ + readonly attribute ACString spec; + + /** + * Returns the Pre Path of the Principals URI with + * user:pass stripped for privacy and spoof prevention + * + * May be called from any thread. + */ + readonly attribute ACString exposablePrePath; + + /** + * Returns the Spec of the Principals URI with + * user/pass/ref/query stripped for privacy and spoof prevention + * + * May be called from any thread. + */ + readonly attribute ACString exposableSpec; + + /** + * Return the scheme of the principals URI + * + * May be called from any thread. + */ + readonly attribute ACString scheme; + + /** + * Checks if the Principal's URI Scheme matches with the parameter + * + * May be called from any thread. + * + * @param scheme The scheme to be checked + */ + [infallible] + boolean schemeIs(in string scheme); + + /* + * Checks if the Principal's URI is contained in the given Pref + * + * NOTE: Main-Thread Only. + * + * @param pref The pref to be checked + */ + [infallible] + boolean isURIInPrefList(in string pref); + + /** + * Check if the Principal's URI is contained in the given list + * + * May be called from any thread. + * + * @param list The list to be checked + */ + [infallible] + boolean isURIInList(in ACString list); + + /** + * Uses NS_Security Compare to determine if the + * other URI is same-origin as the uri of the Principal + * + * May be called from any thread. + */ + [infallible] + boolean isSameOrigin(in nsIURI otherURI); + + /* + * Checks if the Principal is allowed to load the Provided file:// URI + * using NS_RelaxStrictFileOriginPolicy + * + * May be called from any thread. + */ + bool allowsRelaxStrictFileOriginPolicy(in nsIURI aURI); + + + /* + * Generates a Cache-Key for the Cors-Preflight Cache + * + * May be called from any thread. + */ + [noscript] + ACString getPrefLightCacheKey(in nsIURI aURI ,in bool aWithCredentials, + in const_OriginAttributes aOriginAttributes); + + + /* + * Checks if the Principals URI has first party storage access + * when loaded inside the provided 3rd party resource window. + * See also: ContentBlocking::ShouldAllowAccessFor + * + * NOTE: Main-Thread Only. + */ + bool hasFirstpartyStorageAccess(in mozIDOMWindow aWindow, out uint32_t rejectedReason); + + + /* + * Returns a Key for the LocalStorage Manager, used to + * check the Principals Origin Storage usage. + * + * May be called from any thread. + */ + readonly attribute ACString localStorageQuotaKey; + + /** + * Implementation of + * https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy + * + * The value returned by this method feeds into the the Secure Context + * algorithm that determins the value of Window.isSecureContext and + * WorkerGlobalScope.isSecureContext. + * + * This method returns false instead of throwing upon errors. + * + * NOTE: Main-Thread Only. + */ + [infallible] + readonly attribute boolean isOriginPotentiallyTrustworthy; + + /** + * NOTE: Main-Thread Only. + */ + [infallible] + readonly attribute boolean isLoopbackHost; + + /** + * Returns the Flags of the Principals + * associated AboutModule, in case there is one. + * + * NOTE: Main-Thread Only. + */ + uint32_t getAboutModuleFlags(); + + /** + * Returns the Key to access the Principals + * Origin Local/Session Storage + * + * May be called from any thread. + */ + readonly attribute ACString storageOriginKey; + + /** + * Creates and Returns a new ReferrerInfo with the + * Principals URI + * + * May be called from any thread. + */ + nsIReferrerInfo createReferrerInfo(in ReferrerPolicy aReferrerPolicy); + + /** + * The base part of |origin| without the concatenation with |originSuffix|. + * This doesn't have the important invariants described above with |origin|, + * and as such should only be used for legacy situations. + * + * May be called from any thread. + */ + readonly attribute ACString originNoSuffix; + + /** + * A string of the form ^key1=value1&key2=value2, where each pair represents + * an attribute with a non-default value. If all attributes have default + * values, this is the empty string. + * + * The value of .originSuffix is automatically serialized into .origin, so any + * consumers using that are automatically origin-attribute-aware. Consumers with + * special requirements must inspect and compare .originSuffix manually. + * + * May be called from any thread. + */ + readonly attribute AUTF8String originSuffix; + + /** + * A canonical representation of the site-origin for this principal. + * This string has the same format as |origin| (see above). Two principals + * with differing |siteOrigin| values will never compare equal, even when + * considering domain mutations. + * + * For most principals, |siteOrigin| matches |origin| precisely. Only + * principals which allow mutating |domain|, such as ContentPrincipal, + * override the default implementation in BasePrincipal. + * + * May be called from any thread. + */ + readonly attribute ACString siteOrigin; + + /** + * The base part of |siteOrigin| without the concatenation with + * |originSuffix|. + * + * May be called from any thread. + */ + readonly attribute ACString siteOriginNoSuffix; + + /** + * The base domain of the principal URI to which this principal pertains + * (generally the document URI), handling null principals and + * non-hierarchical schemes correctly. + * + * May be called from any thread. + */ + readonly attribute ACString baseDomain; + + /** + * Gets the ID of the add-on this principal belongs to. + * + * May be called from any thread. + */ + readonly attribute AString addonId; + + /** + * Gets the WebExtensionPolicy of the add-on this principal belongs to. + * + * NOTE: Main-Thread Only. + */ + readonly attribute WebExtensionPolicy addonPolicy; + readonly attribute WebExtensionPolicy contentScriptAddonPolicy; + + /** + * Gets the id of the user context this principal is inside. If this + * principal is inside the default userContext, this returns + * nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID. + * + * May be called from any thread. + */ + [infallible] readonly attribute unsigned long userContextId; + + /** + * Gets the id of the private browsing state of the context containing + * this principal. If the principal has a private browsing value of 0, it + * is not in private browsing. + * + * May be called from any thread. + */ + [infallible] readonly attribute unsigned long privateBrowsingId; + + /** + * Returns true iff the principal is inside an isolated mozbrowser element. + * <xul:browser> is not considered to be a mozbrowser element. + * <iframe mozbrowser noisolation> does not count as isolated since + * isolation is disabled. Isolation can only be disabled if the + * containing document is chrome. + * + * May be called from any thread. + */ + [infallible] readonly attribute boolean isInIsolatedMozBrowserElement; + + /** + * Returns true iff this is a null principal (corresponding to an + * unknown, hence assumed minimally privileged, security context). + * + * May be called from any thread. + */ + [infallible] readonly attribute boolean isNullPrincipal; + + /** + * Returns true iff this principal corresponds to a principal origin. + * + * May be called from any thread. + */ + [infallible] readonly attribute boolean isContentPrincipal; + + /** + * Returns true iff this is an expanded principal. + * + * May be called from any thread. + */ + [infallible] readonly attribute boolean isExpandedPrincipal; + + /** + * Returns true iff this is the system principal. C++ callers should use + * IsSystemPrincipal() instead of this scriptable accessor. + * + * May be called from any thread. + */ + readonly attribute boolean isSystemPrincipal; + + /** + * Faster and nicer version callable from C++. Callers must include + * BasePrincipal.h, where it's implemented. + * + * May be called from any thread. + */ + %{C++ + inline bool IsSystemPrincipal() const; + %} + + /** + * Returns true iff the principal is either an addon principal or + * an expanded principal, which contains at least one addon principal. + * + * May be called from any thread. + */ + [infallible] readonly attribute boolean isAddonOrExpandedAddonPrincipal; + + %{C++ + // MOZ_DBG support (threadsafe) + friend std::ostream& operator<<(std::ostream& aOut, const nsIPrincipal& aPrincipal) { + nsIPrincipal* principal = const_cast<nsIPrincipal*>(&aPrincipal); + nsAutoCString origin; + mozilla::DebugOnly<nsresult> rv = principal->GetOrigin(origin); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return aOut << "nsIPrincipal { " << origin << " }"; + } + %} + + /** + * Returns true if the URI is an Onion URI. + * + * May be called from any thread. + */ + [infallible] readonly attribute boolean isOnion; + + /** + * Returns true if the Domain Policy allows js execution + * for the Principal's URI + * + * NOTE: Main-Thread Only. + */ + readonly attribute boolean isScriptAllowedByPolicy; + + + /** + * Returns true if the Principal can acess l10n + * features for the Provided DocumentURI + * + * NOTE: Main-Thread Only. + */ + boolean isL10nAllowed(in nsIURI aDocumentURI); + + /** + * Returns a nsIPrincipal, with one less Subdomain Segment + * Returns `nullptr` if there are no more segments to remove. + * + * May be called from any thread. + */ + [infallible] readonly attribute nsIPrincipal nextSubDomainPrincipal; + + /** + * Returns if the principal is for an IP address. + * + * May be called from any thread. + */ + [infallible] readonly attribute boolean isIpAddress; + + /** + * Returns if the principal is for a local IP address. + * + * May be called from any thread. + */ + [infallible] readonly attribute boolean isLocalIpAddress; + + /** + * If this principal is a null principal, reconstruct the precursor + * principal which this null principal was derived from. This may be null, + * in which case this is not a null principal, there is no known precursor + * to this null principal, it was created by a privileged context, or there + * was a bugged origin in the precursor string. + * + * May be called from any thread. + * + * WARNING: Be careful when using this principal, as it is not part of the + * security properties of the null principal, and should NOT be used to + * grant a resource with a null principal access to resources from its + * precursor origin. This is only to be used for places where tracking how + * null principals were created is necessary. + */ + [infallible] readonly attribute nsIPrincipal precursorPrincipal; +}; + +/** + * If SystemPrincipal is too risky to use, but we want a principal to access + * more than one origin, ExpandedPrincipals letting us define an array of + * principals it subsumes. So script with an ExpandedPrincipals will gain + * same origin access when at least one of its principals it contains gained + * sameorigin acccess. An ExpandedPrincipal will be subsumed by the system + * principal, and by another ExpandedPrincipal that has all its principals. + * It is added for jetpack content-scripts to let them interact with the + * content and a well defined set of other domains, without the risk of + * leaking out a system principal to the content. See: Bug 734891 + */ +[uuid(f3e177Df-6a5e-489f-80a7-2dd1481471d8)] +interface nsIExpandedPrincipal : nsISupports +{ + /** + * An array of principals that the expanded principal subsumes. + * + * When an expanded principal is used as a triggering principal for a + * request that inherits a security context, one of its constitutent + * principals is inherited rather than the expanded principal itself. The + * last principal in the allowlist is the default principal to inherit. + * + * Note: this list is not reference counted, it is shared, so + * should not be changed and should only be used ephemerally. + * + * May be called from any thread. + */ + [noscript, notxpcom, nostdcall] + PrincipalArray AllowList(); + + + /** + * Bug 1548468: Move CSP off ExpandedPrincipal. + * + * A Content Security Policy associated with this principal. Use this function + * to query the associated CSP with this principal. + * + * NOTE: Main-Thread Only. + */ + readonly attribute nsIContentSecurityPolicy csp; + +%{ C++ + inline already_AddRefed<nsIContentSecurityPolicy> GetCsp() + { + nsCOMPtr<nsIContentSecurityPolicy> result; + mozilla::DebugOnly<nsresult> rv = GetCsp(getter_AddRefs(result)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return result.forget(); + } +%} + +}; diff --git a/caps/nsIScriptSecurityManager.idl b/caps/nsIScriptSecurityManager.idl new file mode 100644 index 0000000000..e0497db336 --- /dev/null +++ b/caps/nsIScriptSecurityManager.idl @@ -0,0 +1,335 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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" +#include "nsIPrincipal.idl" +interface nsIURI; +interface nsIChannel; +interface nsIClassInfo; +interface nsIDocShell; +interface nsIDomainPolicy; +interface nsILoadContext; + +%{ C++ +#include "jspubtd.h" + +namespace mozilla { +namespace dom { +class DomainPolicyClone; +} +} +%} + +[ptr] native JSContextPtr(JSContext); +[ptr] native JSObjectPtr(JSObject); +[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone); + +[scriptable, builtinclass, uuid(51daad87-3a0c-44cc-b620-7356801c9022)] +interface nsIScriptSecurityManager : nsISupports +{ + /** + * For each of these hooks returning NS_OK means 'let the action continue'. + * Returning an error code means 'veto the action'. XPConnect will return + * false to the js engine if the action is vetoed. The implementor of this + * interface is responsible for setting a JS exception into the JSContext + * if that is appropriate. + */ + [noscript] void canCreateWrapper(in JSContextPtr aJSContext, + in nsIIDRef aIID, + in nsISupports aObj, + in nsIClassInfo aClassInfo); + + [noscript] void canCreateInstance(in JSContextPtr aJSContext, + in nsCIDRef aCID); + + [noscript] void canGetService(in JSContextPtr aJSContext, + in nsCIDRef aCID); + + /** + * Check that the script currently running in context "cx" can load "uri". + * + * Will return error code NS_ERROR_DOM_BAD_URI if the load request + * should be denied. + * + * @param cx the JSContext of the script causing the load + * @param uri the URI that is being loaded + */ + [noscript] void checkLoadURIFromScript(in JSContextPtr cx, in nsIURI uri); + + /** + * Default CheckLoadURI permissions + */ + // Default permissions + const unsigned long STANDARD = 0; + + // Indicate that the load is a load of a new document that is not + // user-triggered. Here "user-triggered" could be broadly interpreted -- + // for example, scripted sets of window.location.href might be treated as + // "user-triggered" in some circumstances. A typical example of a load + // that is not user-triggered is a <meta> refresh load. If this flag is + // set, the load will be denied if the originating principal's URI has the + // nsIProtocolHandler::URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT flag set. + const unsigned long LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT = 1 << 0; + + // Allow the loading of chrome URLs by non-chrome URLs. Use with great + // care! This will actually allow the loading of any URI which has the + // nsIProtocolHandler::URI_IS_UI_RESOURCE protocol handler flag set. Ths + // probably means at least chrome: and resource:. + const unsigned long ALLOW_CHROME = 1 << 1; + + // Don't allow URLs which would inherit the caller's principal (such as + // javascript: or data:) to load. See + // nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT. + const unsigned long DISALLOW_INHERIT_PRINCIPAL = 1 << 2; + + // Alias for DISALLOW_INHERIT_PRINCIPAL for backwards compat with + // JS-implemented extensions. + const unsigned long DISALLOW_SCRIPT_OR_DATA = DISALLOW_INHERIT_PRINCIPAL; + + // Don't allow javascript: URLs to load + // WARNING: Support for this value was added in Mozilla 1.7.8 and + // Firefox 1.0.4. Use in prior versions WILL BE IGNORED. + // When using this, make sure that you actually want DISALLOW_SCRIPT, not + // DISALLOW_INHERIT_PRINCIPAL + const unsigned long DISALLOW_SCRIPT = 1 << 3; + + // Do not report errors if we just want to check if a principal can load + // a URI to not unnecessarily spam the error console. + const unsigned long DONT_REPORT_ERRORS = 1 << 4; + + /** + * Check that content with principal aPrincipal can load "uri". + * + * Will return error code NS_ERROR_DOM_BAD_URI if the load request + * should be denied. + * + * @param aPrincipal the principal identifying the actor causing the load + * @param uri the URI that is being loaded + * @param flags the permission set, see above + * @param innerWindowID the window ID for error reporting. If this is 0 + * (which happens automatically if it's not passed from JS), errors + * will only appear in the browser console, not window-associated + * consoles like the web console. + */ + [binaryname(CheckLoadURIWithPrincipal)] + void checkLoadURIWithPrincipalXPCOM(in nsIPrincipal aPrincipal, + in nsIURI uri, + in unsigned long flags, + [optional] in unsigned long long innerWindowID); + + /** + * Same as the above, but when called from JS, raises exceptions with more + * useful messages, including both the tested URI and the principal string. + */ + [implicit_jscontext, binaryname(CheckLoadURIWithPrincipalFromJS)] + void checkLoadURIWithPrincipal(in nsIPrincipal aPrincipal, + in nsIURI uri, + [optional] in unsigned long flags, + [optional] in unsigned long long innerWindowID); + + /** + * Similar to checkLoadURIWithPrincipal but there are two differences: + * + * 1) The URI is a string, not a URI object. + * 2) This function assumes that the URI may still be subject to fixup (and + * hence will check whether fixed-up versions of the URI are allowed to + * load as well); if any of the versions of this URI is not allowed, this + * function will return error code NS_ERROR_DOM_BAD_URI. + */ + [binaryname(CheckLoadURIStrWithPrincipal)] + void checkLoadURIStrWithPrincipalXPCOM(in nsIPrincipal aPrincipal, + in AUTF8String uri, + in unsigned long flags); + + /** + * Same as the above, but when called from JS, raises exceptions with more + * useful messages, including both the tested URI and the principal string. + */ + [implicit_jscontext, binaryname(CheckLoadURIStrWithPrincipalFromJS)] + void checkLoadURIStrWithPrincipal(in nsIPrincipal aPrincipal, + in AUTF8String uri, + [optional] in unsigned long flags); + + /** + * Returns true if the URI is from a domain that is allow-listed through + * prefs to be allowed to use file:// URIs. + * @param aUri the URI to be tested + */ + bool inFileURIAllowlist(in nsIURI aUri); + + ///////////////// Principals /////////////////////// + + /** + * Return the all-powerful system principal. + */ + nsIPrincipal getSystemPrincipal(); + + /** + * Returns a principal that has the OriginAttributes of the load context. + * @param loadContext to get the OriginAttributes from. + */ + nsIPrincipal getLoadContextContentPrincipal(in nsIURI uri, + in nsILoadContext loadContext); + + /** + * Returns a principal that has the OriginAttributes of the docshell. + * @param docShell to get the OriginAttributes from. + */ + nsIPrincipal getDocShellContentPrincipal(in nsIURI uri, + in nsIDocShell docShell); + + /** + * If this is a content principal, return a copy with different + * origin attributes. + */ + [implicit_jscontext] + nsIPrincipal principalWithOA(in nsIPrincipal principal, + in jsval originAttributes); + + /** + * Returns a principal whose origin is composed of |uri| and |originAttributes|. + * See nsIPrincipal.idl for a description of origin attributes, and + * ChromeUtils.webidl for a list of origin attributes and their defaults. + */ + [implicit_jscontext] + nsIPrincipal createContentPrincipal(in nsIURI uri, in jsval originAttributes); + + /** + * Returns a principal whose origin is the one we pass in. + * See nsIPrincipal.idl for a description of origin attributes, and + * ChromeUtils.webidl for a list of origin attributes and their defaults. + */ + nsIPrincipal createContentPrincipalFromOrigin(in ACString origin); + + /** + * Takes a principal and returns a string representation of it or a nullptr if it can't be serialized. + * Example output: `{"1": {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}}` + */ + ACString principalToJSON(in nsIPrincipal principal); + + /** + * Takes a string of the following format: + * `{"1": {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}}` + * and turns it into a principal or a nullptr on error. + */ + nsIPrincipal JSONToPrincipal(in ACString json); + + /** + * Returns a unique nonce principal with |originAttributes|. + * See nsIPrincipal.idl for a description of origin attributes, and + * ChromeUtils.webidl for a list of origin attributes and their defaults. + */ + [implicit_jscontext] + nsIPrincipal createNullPrincipal(in jsval originAttributes); + + /** + * Returns OK if aSourceURI and target have the same "origin" + * (scheme, host, and port). + * ReportError flag suppresses error reports for functions that + * don't need reporting. + * FromPrivateWindow indicates whether the error occurs in a private + * window or not. + */ + void checkSameOriginURI(in nsIURI aSourceURI, + in nsIURI aTargetURI, + in boolean reportError, + in boolean fromPrivateWindow); + + /** + * Get the principal for the given channel. This will typically be the + * channel owner if there is one, and the content principal for the + * channel's URI otherwise. aChannel must not be null. + */ + nsIPrincipal getChannelResultPrincipal(in nsIChannel aChannel); + + /** + * Get the storage principal for the given channel. This is basically the + * same of getChannelResultPrincipal() execept for trackers, where we + * return a principal with a different OriginAttributes. + */ + nsIPrincipal getChannelResultStoragePrincipal(in nsIChannel aChannel); + + /** + * This method returns 2 principals from a nsIChannel: + * - aPrincipal is the regular principal. + * - aPartitionedPrincipal is aPrincipal plus an isolation key in its + * originAttributes. + * See more in StoragePrincipalHelper.h + */ + void getChannelResultPrincipals(in nsIChannel aChannel, + out nsIPrincipal aPrincipal, + out nsIPrincipal aPartitionedPrincipal); + + /** + * Temporary API until bug 1220687 is fixed. + * + * Returns the same value as getChannelResultPrincipal, but ignoring + * sandboxing. Specifically, if sandboxing would have prevented the + * channel's triggering principal from being returned by + * getChannelResultPrincipal, the triggering principal will be returned + * by this method. + * + * Note that this method only ignores sandboxing of the channel in + * question, it does not ignore sandboxing of any channels further up a + * document chain. The triggering principal itself may still be the null + * principal due to sandboxing further up a document chain. In that regard + * the ignoring of sandboxing is limited. + */ + [noscript, nostdcall] + nsIPrincipal getChannelResultPrincipalIfNotSandboxed(in nsIChannel aChannel); + + /** + * Get the content principal for the channel's URI. + * aChannel must not be null. + */ + nsIPrincipal getChannelURIPrincipal(in nsIChannel aChannel); + + const unsigned long DEFAULT_USER_CONTEXT_ID = 0; + + const unsigned long DEFAULT_PRIVATE_BROWSING_ID = 0; + + /** + * Per-domain controls to enable and disable script. This system is designed + * to be used by at most one consumer, and enforces this with its semantics. + * + * Initially, domainPolicyActive is false. When activateDomainPolicy() is + * invoked, domainPolicyActive becomes true, and subsequent calls to + * activateDomainPolicy() will fail until deactivate() is invoked on the + * nsIDomainPolicy returned from activateDomainPolicy(). At this point, + * domainPolicyActive becomes false again, and a new consumer may acquire + * control of the system by invoking activateDomainPolicy(). + */ + nsIDomainPolicy activateDomainPolicy(); + readonly attribute boolean domainPolicyActive; + + /** + * Only the parent process can directly access domain policies, child + * processes only have a read-only mirror to the one in the parent. + * For child processes the mirror is updated via messages + * and ContentChild will hold the DomainPolicy by calling + * ActivateDomainPolicyInternal directly. New consumer to this + * function should not be addded. + */ + [noscript] nsIDomainPolicy activateDomainPolicyInternal(); + + /** + * This function is for internal use only. Every time a child process is spawned, we + * must clone any active domain policies in the parent to the new child. + */ + [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone); + + /** + * Query mechanism for the above policy. + * + * If domainPolicyEnabled is false, this simply returns the current value + * of javascript.enabled. Otherwise, it returns the same value, but taking + * the various blocklist/allowlist exceptions into account. + */ + bool policyAllowsScript(in nsIURI aDomain); +}; + +%{C++ +#define NS_SCRIPTSECURITYMANAGER_CONTRACTID "@mozilla.org/scriptsecuritymanager;1" +%} diff --git a/caps/nsJSPrincipals.cpp b/caps/nsJSPrincipals.cpp new file mode 100644 index 0000000000..cdaa0ce6bf --- /dev/null +++ b/caps/nsJSPrincipals.cpp @@ -0,0 +1,370 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsIPrincipal.h" +#include "xpcpublic.h" +#include "nsString.h" +#include "nsJSPrincipals.h" +#include "plstr.h" +#include "nsCOMPtr.h" +#include "nsStringBuffer.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::ipc; + +NS_IMETHODIMP_(MozExternalRefCountType) +nsJSPrincipals::AddRef() { + MOZ_ASSERT(int32_t(refcount) >= 0, "illegal refcnt"); + nsrefcnt count = ++refcount; + NS_LOG_ADDREF(this, count, "nsJSPrincipals", sizeof(*this)); + return count; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsJSPrincipals::Release() { + MOZ_ASSERT(0 != refcount, "dup release"); + nsrefcnt count = --refcount; + NS_LOG_RELEASE(this, count, "nsJSPrincipals"); + if (count == 0) { + delete this; + } + + return count; +} + +/* static */ +bool nsJSPrincipals::Subsume(JSPrincipals* jsprin, JSPrincipals* other) { + bool result; + nsresult rv = nsJSPrincipals::get(jsprin)->Subsumes( + nsJSPrincipals::get(other), &result); + return NS_SUCCEEDED(rv) && result; +} + +/* static */ +void nsJSPrincipals::Destroy(JSPrincipals* jsprin) { + // The JS runtime can call this method during the last GC when + // nsScriptSecurityManager is destroyed. So we must not assume here that + // the security manager still exists. + + nsJSPrincipals* nsjsprin = nsJSPrincipals::get(jsprin); + + // We need to destroy the nsIPrincipal. We'll do this by adding + // to the refcount and calling release + +#ifdef NS_BUILD_REFCNT_LOGGING + // The refcount logging considers AddRef-to-1 to indicate creation, + // so trick it into thinking it's otherwise, but balance the + // Release() we do below. + nsjsprin->refcount++; + nsjsprin->AddRef(); + nsjsprin->refcount--; +#else + nsjsprin->refcount++; +#endif + nsjsprin->Release(); +} + +#ifdef DEBUG + +// Defined here so one can do principals->dump() in the debugger +JS_PUBLIC_API void JSPrincipals::dump() { + if (debugToken == nsJSPrincipals::DEBUG_TOKEN) { + nsAutoCString str; + nsresult rv = static_cast<nsJSPrincipals*>(this)->GetScriptLocation(str); + fprintf(stderr, "nsIPrincipal (%p) = %s\n", static_cast<void*>(this), + NS_SUCCEEDED(rv) ? str.get() : "(unknown)"); + } else { + fprintf(stderr, + "!!! JSPrincipals (%p) is not nsJSPrincipals instance - bad token: " + "actual=0x%x expected=0x%x\n", + this, unsigned(debugToken), unsigned(nsJSPrincipals::DEBUG_TOKEN)); + } +} + +#endif + +/* static */ +bool nsJSPrincipals::ReadPrincipals(JSContext* aCx, + JSStructuredCloneReader* aReader, + JSPrincipals** aOutPrincipals) { + uint32_t tag; + uint32_t unused; + if (!JS_ReadUint32Pair(aReader, &tag, &unused)) { + return false; + } + + if (tag != SCTAG_DOM_NULL_PRINCIPAL && tag != SCTAG_DOM_SYSTEM_PRINCIPAL && + tag != SCTAG_DOM_CONTENT_PRINCIPAL && + tag != SCTAG_DOM_EXPANDED_PRINCIPAL) { + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return false; + } + + return ReadKnownPrincipalType(aCx, aReader, tag, aOutPrincipals); +} + +static bool ReadPrincipalInfo(JSStructuredCloneReader* aReader, + OriginAttributes& aAttrs, nsACString& aSpec, + nsACString& aOriginNoSuffix, + nsACString& aBaseDomain) { + uint32_t suffixLength, specLength; + if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) { + return false; + } + + nsAutoCString suffix; + if (!suffix.SetLength(suffixLength, fallible)) { + return false; + } + + if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) { + return false; + } + + if (!aAttrs.PopulateFromSuffix(suffix)) { + return false; + } + + if (!aSpec.SetLength(specLength, fallible)) { + return false; + } + + if (!JS_ReadBytes(aReader, aSpec.BeginWriting(), specLength)) { + return false; + } + + uint32_t originNoSuffixLength, dummy; + if (!JS_ReadUint32Pair(aReader, &originNoSuffixLength, &dummy)) { + return false; + } + + MOZ_ASSERT(dummy == 0); + if (dummy != 0) { + return false; + } + + if (!aOriginNoSuffix.SetLength(originNoSuffixLength, fallible)) { + return false; + } + + if (!JS_ReadBytes(aReader, aOriginNoSuffix.BeginWriting(), + originNoSuffixLength)) { + return false; + } + + uint32_t baseDomainIsVoid, baseDomainLength; + if (!JS_ReadUint32Pair(aReader, &baseDomainIsVoid, &baseDomainLength)) { + return false; + } + + if (baseDomainIsVoid != 0 && baseDomainIsVoid != 1) { + return false; + } + + if (baseDomainIsVoid) { + if (baseDomainLength != 0) { + return false; + } + + aBaseDomain.SetIsVoid(true); + return true; + } + + if (!aBaseDomain.SetLength(baseDomainLength, fallible)) { + return false; + } + + if (!JS_ReadBytes(aReader, aBaseDomain.BeginWriting(), baseDomainLength)) { + return false; + } + + return true; +} + +static bool ReadPrincipalInfo(JSStructuredCloneReader* aReader, uint32_t aTag, + PrincipalInfo& aInfo) { + if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) { + aInfo = SystemPrincipalInfo(); + } else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) { + OriginAttributes attrs; + nsAutoCString spec; + nsAutoCString originNoSuffix; + nsAutoCString baseDomain; + if (!::ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix, + baseDomain)) { + return false; + } + aInfo = NullPrincipalInfo(attrs, spec); + } else if (aTag == SCTAG_DOM_EXPANDED_PRINCIPAL) { + uint32_t length, unused; + if (!JS_ReadUint32Pair(aReader, &length, &unused)) { + return false; + } + + ExpandedPrincipalInfo expanded; + + for (uint32_t i = 0; i < length; i++) { + uint32_t tag; + if (!JS_ReadUint32Pair(aReader, &tag, &unused)) { + return false; + } + + PrincipalInfo sub; + if (!ReadPrincipalInfo(aReader, tag, sub)) { + return false; + } + expanded.allowlist().AppendElement(sub); + } + + aInfo = expanded; + } else if (aTag == SCTAG_DOM_CONTENT_PRINCIPAL) { + OriginAttributes attrs; + nsAutoCString spec; + nsAutoCString originNoSuffix; + nsAutoCString baseDomain; + if (!::ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix, + baseDomain)) { + return false; + } + +#ifdef FUZZING + if (originNoSuffix.IsEmpty()) { + return false; + } +#endif + + MOZ_DIAGNOSTIC_ASSERT(!originNoSuffix.IsEmpty()); + + // XXX: Do we care about mDomain for structured clone? + aInfo = ContentPrincipalInfo(attrs, originNoSuffix, spec, Nothing(), + baseDomain); + } else { +#ifdef FUZZING + return false; +#else + MOZ_CRASH("unexpected principal structured clone tag"); +#endif + } + + return true; +} + +/* static */ +bool nsJSPrincipals::ReadPrincipalInfo(JSStructuredCloneReader* aReader, + PrincipalInfo& aInfo) { + uint32_t tag, unused; + if (!JS_ReadUint32Pair(aReader, &tag, &unused)) { + return false; + } + return ::ReadPrincipalInfo(aReader, tag, aInfo); +} + +/* static */ +bool nsJSPrincipals::ReadKnownPrincipalType(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + JSPrincipals** aOutPrincipals) { + MOZ_ASSERT(aTag == SCTAG_DOM_NULL_PRINCIPAL || + aTag == SCTAG_DOM_SYSTEM_PRINCIPAL || + aTag == SCTAG_DOM_CONTENT_PRINCIPAL || + aTag == SCTAG_DOM_EXPANDED_PRINCIPAL); + + PrincipalInfo info; + if (!::ReadPrincipalInfo(aReader, aTag, info)) { + return false; + } + + auto principalOrErr = PrincipalInfoToPrincipal(info); + if (NS_WARN_IF(principalOrErr.isErr())) { + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return false; + } + + nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap(); + + *aOutPrincipals = get(principal.forget().take()); + return true; +} + +static bool WritePrincipalInfo(JSStructuredCloneWriter* aWriter, + const OriginAttributes& aAttrs, + const nsCString& aSpec, + const nsCString& aOriginNoSuffix, + const nsCString& aBaseDomain) { + nsAutoCString suffix; + aAttrs.CreateSuffix(suffix); + + if (!(JS_WriteUint32Pair(aWriter, suffix.Length(), aSpec.Length()) && + JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) && + JS_WriteBytes(aWriter, aSpec.get(), aSpec.Length()) && + JS_WriteUint32Pair(aWriter, aOriginNoSuffix.Length(), 0) && + JS_WriteBytes(aWriter, aOriginNoSuffix.get(), + aOriginNoSuffix.Length()))) { + return false; + } + + if (aBaseDomain.IsVoid()) { + return JS_WriteUint32Pair(aWriter, 1, 0); + } + + return JS_WriteUint32Pair(aWriter, 0, aBaseDomain.Length()) && + JS_WriteBytes(aWriter, aBaseDomain.get(), aBaseDomain.Length()); +} + +/* static */ +bool nsJSPrincipals::WritePrincipalInfo(JSStructuredCloneWriter* aWriter, + const PrincipalInfo& aInfo) { + if (aInfo.type() == PrincipalInfo::TNullPrincipalInfo) { + const NullPrincipalInfo& nullInfo = aInfo; + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0) && + ::WritePrincipalInfo(aWriter, nullInfo.attrs(), nullInfo.spec(), + ""_ns, ""_ns); + } + if (aInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0); + } + if (aInfo.type() == PrincipalInfo::TExpandedPrincipalInfo) { + const ExpandedPrincipalInfo& expanded = aInfo; + if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_EXPANDED_PRINCIPAL, 0) || + !JS_WriteUint32Pair(aWriter, expanded.allowlist().Length(), 0)) { + return false; + } + + for (uint32_t i = 0; i < expanded.allowlist().Length(); i++) { + if (!WritePrincipalInfo(aWriter, expanded.allowlist()[i])) { + return false; + } + } + return true; + } + + MOZ_ASSERT(aInfo.type() == PrincipalInfo::TContentPrincipalInfo); + const ContentPrincipalInfo& cInfo = aInfo; + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) && + ::WritePrincipalInfo(aWriter, cInfo.attrs(), cInfo.spec(), + cInfo.originNoSuffix(), cInfo.baseDomain()); +} + +bool nsJSPrincipals::write(JSContext* aCx, JSStructuredCloneWriter* aWriter) { + PrincipalInfo info; + if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(this, &info)))) { + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return false; + } + + return WritePrincipalInfo(aWriter, info); +} + +bool nsJSPrincipals::isSystemOrAddonPrincipal() { + JS::AutoSuppressGCAnalysis suppress; + return this->IsSystemPrincipal() || + this->GetIsAddonOrExpandedAddonPrincipal(); +} diff --git a/caps/nsJSPrincipals.h b/caps/nsJSPrincipals.h new file mode 100644 index 0000000000..54cf5f44c0 --- /dev/null +++ b/caps/nsJSPrincipals.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ +/* describes principals by their orginating uris*/ + +#ifndef nsJSPrincipals_h__ +#define nsJSPrincipals_h__ + +#include "js/Principals.h" +#include "nsIPrincipal.h" + +struct JSContext; +struct JSStructuredCloneReader; +struct JSStructuredCloneWriter; + +namespace mozilla { +namespace ipc { +class PrincipalInfo; +} // namespace ipc +} // namespace mozilla + +class nsJSPrincipals : public nsIPrincipal, public JSPrincipals { + public: + /* SpiderMonkey security callbacks. */ + static bool Subsume(JSPrincipals* jsprin, JSPrincipals* other); + static void Destroy(JSPrincipals* jsprin); + + /* JSReadPrincipalsOp for nsJSPrincipals */ + static bool ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader, + JSPrincipals** aOutPrincipals); + + static bool ReadKnownPrincipalType(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + JSPrincipals** aOutPrincipals); + + static bool ReadPrincipalInfo(JSStructuredCloneReader* aReader, + mozilla::ipc::PrincipalInfo& aInfo); + + /* For write() implementations of off-main-thread JSPrincipals. */ + static bool WritePrincipalInfo(JSStructuredCloneWriter* aWriter, + const mozilla::ipc::PrincipalInfo& aInfo); + + bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) final; + + bool isSystemOrAddonPrincipal() final; + + /* + * Get a weak reference to nsIPrincipal associated with the given JS + * principal, and vice-versa. + */ + static nsJSPrincipals* get(JSPrincipals* principals) { + nsJSPrincipals* self = static_cast<nsJSPrincipals*>(principals); + MOZ_ASSERT_IF(self, self->debugToken == DEBUG_TOKEN); + return self; + } + static nsJSPrincipals* get(nsIPrincipal* principal) { + nsJSPrincipals* self = static_cast<nsJSPrincipals*>(principal); + MOZ_ASSERT_IF(self, self->debugToken == DEBUG_TOKEN); + return self; + } + + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; + + nsJSPrincipals() { + refcount = 0; + setDebugToken(DEBUG_TOKEN); + } + + /** + * Return a string that can be used as JS script filename in error reports. + */ + virtual nsresult GetScriptLocation(nsACString& aStr) = 0; + static const uint32_t DEBUG_TOKEN = 0x0bf41760; + + protected: + virtual ~nsJSPrincipals() { setDebugToken(0); } +}; + +#endif /* nsJSPrincipals_h__ */ diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp new file mode 100644 index 0000000000..bf0899abcb --- /dev/null +++ b/caps/nsScriptSecurityManager.cpp @@ -0,0 +1,1849 @@ +/* -*- 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 "nsScriptSecurityManager.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/StaticPrefs_extensions.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozilla/StoragePrincipalHelper.h" + +#include "xpcpublic.h" +#include "XPCWrapper.h" +#include "nsILoadContext.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptContext.h" +#include "nsIScriptError.h" +#include "nsINestedURI.h" +#include "nspr.h" +#include "nsJSPrincipals.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ContentPrincipal.h" +#include "ExpandedPrincipal.h" +#include "SystemPrincipal.h" +#include "DomainPolicy.h" +#include "nsString.h" +#include "nsCRT.h" +#include "nsCRTGlue.h" +#include "nsContentSecurityUtils.h" +#include "nsDocShell.h" +#include "nsError.h" +#include "nsGlobalWindowInner.h" +#include "nsDOMCID.h" +#include "nsTextFormatter.h" +#include "nsIStringBundle.h" +#include "nsNetUtil.h" +#include "nsIEffectiveTLDService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIScriptGlobalObject.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIConsoleService.h" +#include "nsIOService.h" +#include "nsIContent.h" +#include "nsDOMJSUtils.h" +#include "nsAboutProtocolUtils.h" +#include "nsIClassInfo.h" +#include "nsIURIFixup.h" +#include "nsIURIMutator.h" +#include "nsIChromeRegistry.h" +#include "nsIResProtocolHandler.h" +#include "nsIContentSecurityPolicy.h" +#include "mozilla/Components.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/NullPrincipal.h" +#include <stdint.h> +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ExtensionPolicyService.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "nsContentUtils.h" +#include "nsJSUtils.h" +#include "nsILoadInfo.h" + +// This should be probably defined on some other place... but I couldn't find it +#define WEBAPPS_PERM_NAME "webapps-manage" + +using namespace mozilla; +using namespace mozilla::dom; + +nsIIOService* nsScriptSecurityManager::sIOService = nullptr; +std::atomic<bool> nsScriptSecurityManager::sStrictFileOriginPolicy = true; + +namespace { + +class BundleHelper { + public: + NS_INLINE_DECL_REFCOUNTING(BundleHelper) + + static nsIStringBundle* GetOrCreate() { + MOZ_ASSERT(!sShutdown); + + // Already shutting down. Nothing should require the use of the string + // bundle when shutting down. + if (sShutdown) { + return nullptr; + } + + if (!sSelf) { + sSelf = new BundleHelper(); + } + + return sSelf->GetOrCreateInternal(); + } + + static void Shutdown() { + sSelf = nullptr; + sShutdown = true; + } + + private: + ~BundleHelper() = default; + + nsIStringBundle* GetOrCreateInternal() { + if (!mBundle) { + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + if (NS_WARN_IF(!bundleService)) { + return nullptr; + } + + nsresult rv = bundleService->CreateBundle( + "chrome://global/locale/security/caps.properties", + getter_AddRefs(mBundle)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + } + + return mBundle; + } + + nsCOMPtr<nsIStringBundle> mBundle; + + static StaticRefPtr<BundleHelper> sSelf; + static bool sShutdown; +}; + +StaticRefPtr<BundleHelper> BundleHelper::sSelf; +bool BundleHelper::sShutdown = false; + +} // namespace + +/////////////////////////// +// Convenience Functions // +/////////////////////////// + +class nsAutoInPrincipalDomainOriginSetter { + public: + nsAutoInPrincipalDomainOriginSetter() { ++sInPrincipalDomainOrigin; } + ~nsAutoInPrincipalDomainOriginSetter() { --sInPrincipalDomainOrigin; } + static uint32_t sInPrincipalDomainOrigin; +}; +uint32_t nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin; + +static nsresult GetOriginFromURI(nsIURI* aURI, nsACString& aOrigin) { + if (!aURI) { + return NS_ERROR_NULL_POINTER; + } + if (nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin > 1) { + // Allow a single recursive call to GetPrincipalDomainOrigin, since that + // might be happening on a different principal from the first call. But + // after that, cut off the recursion; it just indicates that something + // we're doing in this method causes us to reenter a security check here. + return NS_ERROR_NOT_AVAILABLE; + } + + nsAutoInPrincipalDomainOriginSetter autoSetter; + + nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI); + NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED); + + nsAutoCString hostPort; + + nsresult rv = uri->GetHostPort(hostPort); + if (NS_SUCCEEDED(rv)) { + nsAutoCString scheme; + rv = uri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + aOrigin = scheme + "://"_ns + hostPort; + } else { + // Some URIs (e.g., nsSimpleURI) don't support host. Just + // get the full spec. + rv = uri->GetSpec(aOrigin); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +static nsresult GetPrincipalDomainOrigin(nsIPrincipal* aPrincipal, + nsACString& aOrigin) { + aOrigin.Truncate(); + nsCOMPtr<nsIURI> uri; + aPrincipal->GetDomain(getter_AddRefs(uri)); + nsresult rv = GetOriginFromURI(uri, aOrigin); + if (NS_SUCCEEDED(rv)) { + return rv; + } + // If there is no Domain fallback to the Principals Origin + return aPrincipal->GetOriginNoSuffix(aOrigin); +} + +inline void SetPendingExceptionASCII(JSContext* cx, const char* aMsg) { + JS_ReportErrorASCII(cx, "%s", aMsg); +} + +inline void SetPendingException(JSContext* cx, const char16_t* aMsg) { + NS_ConvertUTF16toUTF8 msg(aMsg); + JS_ReportErrorUTF8(cx, "%s", msg.get()); +} + +/* static */ +bool nsScriptSecurityManager::SecurityCompareURIs(nsIURI* aSourceURI, + nsIURI* aTargetURI) { + return NS_SecurityCompareURIs(aSourceURI, aTargetURI, + sStrictFileOriginPolicy); +} + +// SecurityHashURI is consistent with SecurityCompareURIs because +// NS_SecurityHashURI is consistent with NS_SecurityCompareURIs. See +// nsNetUtil.h. +uint32_t nsScriptSecurityManager::SecurityHashURI(nsIURI* aURI) { + return NS_SecurityHashURI(aURI); +} + +/* + * GetChannelResultPrincipal will return the principal that the resource + * returned by this channel will use. For example, if the resource is in + * a sandbox, it will return the nullprincipal. If the resource is forced + * to inherit principal, it will return the principal of its parent. If + * the load doesn't require sandboxing or inheriting, it will return the same + * principal as GetChannelURIPrincipal. Namely the principal of the URI + * that is being loaded. + */ +NS_IMETHODIMP +nsScriptSecurityManager::GetChannelResultPrincipal(nsIChannel* aChannel, + nsIPrincipal** aPrincipal) { + return GetChannelResultPrincipal(aChannel, aPrincipal, + /*aIgnoreSandboxing*/ false); +} + +nsresult nsScriptSecurityManager::GetChannelResultPrincipalIfNotSandboxed( + nsIChannel* aChannel, nsIPrincipal** aPrincipal) { + return GetChannelResultPrincipal(aChannel, aPrincipal, + /*aIgnoreSandboxing*/ true); +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetChannelResultStoragePrincipal( + nsIChannel* aChannel, nsIPrincipal** aPrincipal) { + nsCOMPtr<nsIPrincipal> principal; + nsresult rv = GetChannelResultPrincipal(aChannel, getter_AddRefs(principal), + /*aIgnoreSandboxing*/ false); + if (NS_WARN_IF(NS_FAILED(rv) || !principal)) { + return rv; + } + + if (!(principal->GetIsContentPrincipal())) { + // If for some reason we don't have a content principal here, just reuse our + // principal for the storage principal too, since attempting to create a + // storage principal would fail anyway. + principal.forget(aPrincipal); + return NS_OK; + } + + return StoragePrincipalHelper::Create( + aChannel, principal, /* aForceIsolation */ false, aPrincipal); +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetChannelResultPrincipals( + nsIChannel* aChannel, nsIPrincipal** aPrincipal, + nsIPrincipal** aPartitionedPrincipal) { + nsresult rv = GetChannelResultPrincipal(aChannel, aPrincipal, + /*aIgnoreSandboxing*/ false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!(*aPrincipal)->GetIsContentPrincipal()) { + // If for some reason we don't have a content principal here, just reuse our + // principal for the storage principal too, since attempting to create a + // storage principal would fail anyway. + nsCOMPtr<nsIPrincipal> copy = *aPrincipal; + copy.forget(aPartitionedPrincipal); + return NS_OK; + } + + return StoragePrincipalHelper::Create( + aChannel, *aPrincipal, /* aForceIsolation */ true, aPartitionedPrincipal); +} + +nsresult nsScriptSecurityManager::GetChannelResultPrincipal( + nsIChannel* aChannel, nsIPrincipal** aPrincipal, bool aIgnoreSandboxing) { + MOZ_ASSERT(aChannel, "Must have channel!"); + + // Check whether we have an nsILoadInfo that says what we should do. + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + if (loadInfo->GetForceInheritPrincipalOverruleOwner()) { + nsCOMPtr<nsIPrincipal> principalToInherit = + loadInfo->FindPrincipalToInherit(aChannel); + principalToInherit.forget(aPrincipal); + return NS_OK; + } + + nsCOMPtr<nsISupports> owner; + aChannel->GetOwner(getter_AddRefs(owner)); + if (owner) { + CallQueryInterface(owner, aPrincipal); + if (*aPrincipal) { + return NS_OK; + } + } + + if (!aIgnoreSandboxing && loadInfo->GetLoadingSandboxed()) { + // Determine the unsandboxed result principal to use as this null + // principal's precursor. Ignore errors here, as the precursor isn't + // required. + nsCOMPtr<nsIPrincipal> precursor; + GetChannelResultPrincipal(aChannel, getter_AddRefs(precursor), + /*aIgnoreSandboxing*/ true); + + // Construct a deterministic null principal URI from the precursor and the + // loadinfo's nullPrincipalID. + nsCOMPtr<nsIURI> nullPrincipalURI = NullPrincipal::CreateURI( + precursor, &loadInfo->GetSandboxedNullPrincipalID()); + + // Use the URI to construct the sandboxed result principal. + OriginAttributes attrs; + loadInfo->GetOriginAttributes(&attrs); + nsCOMPtr<nsIPrincipal> sandboxedPrincipal = + NullPrincipal::Create(attrs, nullPrincipalURI); + sandboxedPrincipal.forget(aPrincipal); + return NS_OK; + } + + bool forceInherit = loadInfo->GetForceInheritPrincipal(); + if (aIgnoreSandboxing && !forceInherit) { + // Check if SEC_FORCE_INHERIT_PRINCIPAL was dropped because of + // sandboxing: + if (loadInfo->GetLoadingSandboxed() && + loadInfo->GetForceInheritPrincipalDropped()) { + forceInherit = true; + } + } + if (forceInherit) { + nsCOMPtr<nsIPrincipal> principalToInherit = + loadInfo->FindPrincipalToInherit(aChannel); + principalToInherit.forget(aPrincipal); + return NS_OK; + } + + auto securityMode = loadInfo->GetSecurityMode(); + // The data: inheritance flags should only apply to the initial load, + // not to loads that it might have redirected to. + if (loadInfo->RedirectChain().IsEmpty() && + (securityMode == + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT || + securityMode == + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT || + securityMode == nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT)) { + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> principalToInherit = + loadInfo->FindPrincipalToInherit(aChannel); + bool inheritForAboutBlank = loadInfo->GetAboutBlankInherits(); + + if (nsContentUtils::ChannelShouldInheritPrincipal( + principalToInherit, uri, inheritForAboutBlank, false)) { + principalToInherit.forget(aPrincipal); + return NS_OK; + } + } + return GetChannelURIPrincipal(aChannel, aPrincipal); +} + +/* The principal of the URI that this channel is loading. This is never + * affected by things like sandboxed loads, or loads where we forcefully + * inherit the principal. Think of this as the principal of the server + * which this channel is loading from. Most callers should use + * GetChannelResultPrincipal instead of GetChannelURIPrincipal. Only + * call GetChannelURIPrincipal if you are sure that you want the + * principal that matches the uri, even in cases when the load is + * sandboxed or when the load could be a blob or data uri (i.e even when + * you encounter loads that may or may not be sandboxed and loads + * that may or may not inherit)." + */ +NS_IMETHODIMP +nsScriptSecurityManager::GetChannelURIPrincipal(nsIChannel* aChannel, + nsIPrincipal** aPrincipal) { + MOZ_ASSERT(aChannel, "Must have channel!"); + + // Get the principal from the URI. Make sure this does the same thing + // as Document::Reset and PrototypeDocumentContentSink::Init. + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + + // Inherit the origin attributes from loadInfo. + // If this is a top-level document load, the origin attributes of the + // loadInfo will be set from nsDocShell::DoURILoad. + // For subresource loading, the origin attributes of the loadInfo is from + // its loadingPrincipal. + OriginAttributes attrs = loadInfo->GetOriginAttributes(); + + // If the URI is supposed to inherit the security context of whoever loads it, + // we shouldn't make a content principal for it, so instead return a null + // principal. + bool inheritsPrincipal = false; + rv = NS_URIChainHasFlags(uri, + nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, + &inheritsPrincipal); + if (NS_FAILED(rv) || inheritsPrincipal) { + // Find a precursor principal to credit for the load. This won't impact + // security checks, but makes tracking the source of related loads easier. + nsCOMPtr<nsIPrincipal> precursorPrincipal = + loadInfo->FindPrincipalToInherit(aChannel); + nsCOMPtr<nsIURI> nullPrincipalURI = + NullPrincipal::CreateURI(precursorPrincipal); + *aPrincipal = NullPrincipal::Create(attrs, nullPrincipalURI).take(); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrincipal> prin = + BasePrincipal::CreateContentPrincipal(uri, attrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +///////////////////////////// +// nsScriptSecurityManager // +///////////////////////////// + +//////////////////////////////////// +// Methods implementing ISupports // +//////////////////////////////////// +NS_IMPL_ISUPPORTS(nsScriptSecurityManager, nsIScriptSecurityManager) + +/////////////////////////////////////////////////// +// Methods implementing nsIScriptSecurityManager // +/////////////////////////////////////////////////// + +///////////////// Security Checks ///////////////// + +bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction( + JSContext* cx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCode) { + MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); + + nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal(); + + // Check if Eval is allowed per firefox hardening policy + bool contextForbidsEval = + (subjectPrincipal->IsSystemPrincipal() || XRE_IsE10sParentProcess()); +#if defined(ANDROID) + contextForbidsEval = false; +#endif + + if (contextForbidsEval) { + nsAutoJSString scriptSample; + if (aKind == JS::RuntimeCode::JS && + NS_WARN_IF(!scriptSample.init(cx, aCode))) { + return false; + } + + if (!nsContentSecurityUtils::IsEvalAllowed( + cx, subjectPrincipal->IsSystemPrincipal(), scriptSample)) { + return false; + } + } + + // Get the window, if any, corresponding to the current global + nsCOMPtr<nsIContentSecurityPolicy> csp; + if (nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(cx)) { + csp = win->GetCsp(); + } + + if (!csp) { + // Get the CSP for addon sandboxes. If the principal is expanded and has a + // csp, we're probably in luck. + auto* basePrin = BasePrincipal::Cast(subjectPrincipal); + // ContentScriptAddonPolicy means it is also an expanded principal, thus + // this is in a sandbox used as a content script. + if (basePrin->ContentScriptAddonPolicy()) { + basePrin->As<ExpandedPrincipal>()->GetCsp(getter_AddRefs(csp)); + } + // don't do anything unless there's a CSP + if (!csp) { + return true; + } + } + + nsCOMPtr<nsICSPEventListener> cspEventListener; + if (!NS_IsMainThread()) { + WorkerPrivate* workerPrivate = + mozilla::dom::GetWorkerPrivateFromContext(cx); + if (workerPrivate) { + cspEventListener = workerPrivate->CSPEventListener(); + } + } + + bool evalOK = true; + bool reportViolation = false; + if (aKind == JS::RuntimeCode::JS) { + nsresult rv = csp->GetAllowsEval(&reportViolation, &evalOK); + if (NS_FAILED(rv)) { + NS_WARNING("CSP: failed to get allowsEval"); + return true; // fail open to not break sites. + } + } else { + if (NS_FAILED(csp->GetAllowsWasmEval(&reportViolation, &evalOK))) { + return false; + } + if (!evalOK) { + // Historically, CSP did not block WebAssembly in Firefox, and some + // add-ons use wasm and a stricter CSP. To avoid breaking them, ignore + // 'wasm-unsafe-eval' violations for MV2 extensions. + // TODO bug 1770909: remove this exception. + auto* addonPolicy = BasePrincipal::Cast(subjectPrincipal)->AddonPolicy(); + if (addonPolicy && addonPolicy->ManifestVersion() == 2) { + reportViolation = true; + evalOK = true; + } + } + } + + if (reportViolation) { + JS::AutoFilename scriptFilename; + nsAutoString fileName; + unsigned lineNum = 0; + unsigned columnNum = 0; + if (JS::DescribeScriptedCaller(cx, &scriptFilename, &lineNum, &columnNum)) { + if (const char* file = scriptFilename.get()) { + CopyUTF8toUTF16(nsDependentCString(file), fileName); + } + } else { + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + } + + nsAutoJSString scriptSample; + if (aKind == JS::RuntimeCode::JS && + NS_WARN_IF(!scriptSample.init(cx, aCode))) { + JS_ClearPendingException(cx); + return false; + } + uint16_t violationType = + aKind == JS::RuntimeCode::JS + ? nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL + : nsIContentSecurityPolicy::VIOLATION_TYPE_WASM_EVAL; + csp->LogViolationDetails(violationType, + nullptr, // triggering element + cspEventListener, fileName, scriptSample, lineNum, + columnNum, u""_ns, u""_ns); + } + + return evalOK; +} + +// static +bool nsScriptSecurityManager::JSPrincipalsSubsume(JSPrincipals* first, + JSPrincipals* second) { + return nsJSPrincipals::get(first)->Subsumes(nsJSPrincipals::get(second)); +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckSameOriginURI(nsIURI* aSourceURI, + nsIURI* aTargetURI, + bool reportError, + bool aFromPrivateWindow) { + // Please note that aFromPrivateWindow is only 100% accurate if + // reportError is true. + if (!SecurityCompareURIs(aSourceURI, aTargetURI)) { + if (reportError) { + ReportError("CheckSameOriginError", aSourceURI, aTargetURI, + aFromPrivateWindow); + } + return NS_ERROR_DOM_BAD_URI; + } + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckLoadURIFromScript(JSContext* cx, nsIURI* aURI) { + // Get principal of currently executing script. + MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); + nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(); + nsresult rv = CheckLoadURIWithPrincipal( + // Passing 0 for the window ID here is OK, because we will report a + // script-visible exception anyway. + principal, aURI, nsIScriptSecurityManager::STANDARD, 0); + if (NS_SUCCEEDED(rv)) { + // OK to load + return NS_OK; + } + + // Report error. + nsAutoCString spec; + if (NS_FAILED(aURI->GetAsciiSpec(spec))) return NS_ERROR_FAILURE; + nsAutoCString msg("Access to '"); + msg.Append(spec); + msg.AppendLiteral("' from script denied"); + SetPendingExceptionASCII(cx, msg.get()); + return NS_ERROR_DOM_BAD_URI; +} + +/** + * Helper method to handle cases where a flag passed to + * CheckLoadURIWithPrincipal means denying loading if the given URI has certain + * nsIProtocolHandler flags set. + * @return if success, access is allowed. Otherwise, deny access + */ +static nsresult DenyAccessIfURIHasFlags(nsIURI* aURI, uint32_t aURIFlags) { + MOZ_ASSERT(aURI, "Must have URI!"); + + bool uriHasFlags; + nsresult rv = NS_URIChainHasFlags(aURI, aURIFlags, &uriHasFlags); + NS_ENSURE_SUCCESS(rv, rv); + + if (uriHasFlags) { + return NS_ERROR_DOM_BAD_URI; + } + + return NS_OK; +} + +static bool EqualOrSubdomain(nsIURI* aProbeArg, nsIURI* aBase) { + nsresult rv; + nsCOMPtr<nsIURI> probe = aProbeArg; + + nsCOMPtr<nsIEffectiveTLDService> tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + NS_ENSURE_TRUE(tldService, false); + while (true) { + if (nsScriptSecurityManager::SecurityCompareURIs(probe, aBase)) { + return true; + } + + nsAutoCString host, newHost; + rv = probe->GetHost(host); + NS_ENSURE_SUCCESS(rv, false); + + rv = tldService->GetNextSubDomain(host, newHost); + if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + return false; + } + NS_ENSURE_SUCCESS(rv, false); + rv = NS_MutateURI(probe).SetHost(newHost).Finalize(probe); + NS_ENSURE_SUCCESS(rv, false); + } +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal, + nsIURI* aTargetURI, + uint32_t aFlags, + uint64_t aInnerWindowID) { + MOZ_ASSERT(aPrincipal, "CheckLoadURIWithPrincipal must have a principal"); + + // If someone passes a flag that we don't understand, we should + // fail, because they may need a security check that we don't + // provide. + NS_ENSURE_FALSE( + aFlags & + ~(nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT | + nsIScriptSecurityManager::ALLOW_CHROME | + nsIScriptSecurityManager::DISALLOW_SCRIPT | + nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL | + nsIScriptSecurityManager::DONT_REPORT_ERRORS), + NS_ERROR_UNEXPECTED); + NS_ENSURE_ARG_POINTER(aPrincipal); + NS_ENSURE_ARG_POINTER(aTargetURI); + + // If DISALLOW_INHERIT_PRINCIPAL is set, we prevent loading of URIs which + // would do such inheriting. That would be URIs that do not have their own + // security context. We do this even for the system principal. + if (aFlags & nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL) { + nsresult rv = DenyAccessIfURIHasFlags( + aTargetURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aPrincipal == mSystemPrincipal) { + // Allow access + return NS_OK; + } + + nsCOMPtr<nsIURI> sourceURI; + auto* basePrin = BasePrincipal::Cast(aPrincipal); + basePrin->GetURI(getter_AddRefs(sourceURI)); + if (!sourceURI) { + if (basePrin->Is<ExpandedPrincipal>()) { + // If the target addon is MV3 or the pref is on we require extension + // resources loaded from content to be listed in web_accessible_resources. + auto* targetPolicy = + ExtensionPolicyService::GetSingleton().GetByURL(aTargetURI); + bool contentAccessRequired = + targetPolicy && + (targetPolicy->ManifestVersion() > 2 || + StaticPrefs::extensions_content_web_accessible_enabled()); + auto expanded = basePrin->As<ExpandedPrincipal>(); + const auto& allowList = expanded->AllowList(); + // Only report errors when all principals fail. + // With expanded principals, which are used by extension content scripts, + // we check only against non-extension principals for access to extension + // resource to enforce making those resources explicitly web accessible. + uint32_t flags = aFlags | nsIScriptSecurityManager::DONT_REPORT_ERRORS; + for (size_t i = 0; i < allowList.Length() - 1; i++) { + if (contentAccessRequired && + BasePrincipal::Cast(allowList[i])->AddonPolicy()) { + continue; + } + nsresult rv = CheckLoadURIWithPrincipal(allowList[i], aTargetURI, flags, + aInnerWindowID); + if (NS_SUCCEEDED(rv)) { + // Allow access if it succeeded with one of the allowlisted principals + return NS_OK; + } + } + + if (contentAccessRequired && + BasePrincipal::Cast(allowList.LastElement())->AddonPolicy()) { + bool reportErrors = + !(aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS); + if (reportErrors) { + ReportError("CheckLoadURI", sourceURI, aTargetURI, + allowList.LastElement() + ->OriginAttributesRef() + .mPrivateBrowsingId > 0, + aInnerWindowID); + } + return NS_ERROR_DOM_BAD_URI; + } + // Report errors (if requested) for the last principal. + return CheckLoadURIWithPrincipal(allowList.LastElement(), aTargetURI, + aFlags, aInnerWindowID); + } + NS_ERROR( + "Non-system principals or expanded principal passed to " + "CheckLoadURIWithPrincipal " + "must have a URI!"); + return NS_ERROR_UNEXPECTED; + } + + // Automatic loads are not allowed from certain protocols. + if (aFlags & + nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT) { + nsresult rv = DenyAccessIfURIHasFlags( + sourceURI, + nsIProtocolHandler::URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT); + NS_ENSURE_SUCCESS(rv, rv); + } + + // If either URI is a nested URI, get the base URI + nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(sourceURI); + nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI); + + //-- get the target scheme + nsAutoCString targetScheme; + nsresult rv = targetBaseURI->GetScheme(targetScheme); + if (NS_FAILED(rv)) return rv; + + //-- Some callers do not allow loading javascript: + if ((aFlags & nsIScriptSecurityManager::DISALLOW_SCRIPT) && + targetScheme.EqualsLiteral("javascript")) { + return NS_ERROR_DOM_BAD_URI; + } + + // Check for uris that are only loadable by principals that subsume them + bool targetURIIsLoadableBySubsumers = false; + rv = NS_URIChainHasFlags(targetBaseURI, + nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS, + &targetURIIsLoadableBySubsumers); + NS_ENSURE_SUCCESS(rv, rv); + + if (targetURIIsLoadableBySubsumers) { + // check nothing else in the URI chain has flags that prevent + // access: + rv = CheckLoadURIFlags( + sourceURI, aTargetURI, sourceBaseURI, targetBaseURI, aFlags, + aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0, + aInnerWindowID); + NS_ENSURE_SUCCESS(rv, rv); + // Check the principal is allowed to load the target. + if (aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS) { + return aPrincipal->CheckMayLoad(targetBaseURI, false); + } + return aPrincipal->CheckMayLoadWithReporting(targetBaseURI, false, + aInnerWindowID); + } + + //-- get the source scheme + nsAutoCString sourceScheme; + rv = sourceBaseURI->GetScheme(sourceScheme); + if (NS_FAILED(rv)) return rv; + + if (sourceScheme.LowerCaseEqualsLiteral(NS_NULLPRINCIPAL_SCHEME)) { + // A null principal can target its own URI. + if (sourceURI == aTargetURI) { + return NS_OK; + } + } else if (sourceScheme.EqualsIgnoreCase("file") && + targetScheme.EqualsIgnoreCase("moz-icon")) { + // exception for file: linking to moz-icon://.ext?size=... + // Note that because targetScheme is the base (innermost) URI scheme, + // this does NOT allow file -> moz-icon:file:///... links. + // This is intentional. + return NS_OK; + } + + // Check for webextension + bool targetURIIsLoadableByExtensions = false; + rv = NS_URIChainHasFlags(aTargetURI, + nsIProtocolHandler::URI_LOADABLE_BY_EXTENSIONS, + &targetURIIsLoadableByExtensions); + NS_ENSURE_SUCCESS(rv, rv); + + if (targetURIIsLoadableByExtensions && + BasePrincipal::Cast(aPrincipal)->AddonPolicy()) { + return NS_OK; + } + + // If we get here, check all the schemes can link to each other, from the top + // down: + nsCOMPtr<nsIURI> currentURI = sourceURI; + nsCOMPtr<nsIURI> currentOtherURI = aTargetURI; + + bool denySameSchemeLinks = false; + rv = NS_URIChainHasFlags(aTargetURI, + nsIProtocolHandler::URI_SCHEME_NOT_SELF_LINKABLE, + &denySameSchemeLinks); + if (NS_FAILED(rv)) return rv; + + while (currentURI && currentOtherURI) { + nsAutoCString scheme, otherScheme; + currentURI->GetScheme(scheme); + currentOtherURI->GetScheme(otherScheme); + + bool schemesMatch = + scheme.Equals(otherScheme, nsCaseInsensitiveCStringComparator); + bool isSamePage = false; + bool isExtensionMismatch = false; + // about: URIs are special snowflakes. + if (scheme.EqualsLiteral("about") && schemesMatch) { + nsAutoCString moduleName, otherModuleName; + // about: pages can always link to themselves: + isSamePage = + NS_SUCCEEDED(NS_GetAboutModuleName(currentURI, moduleName)) && + NS_SUCCEEDED( + NS_GetAboutModuleName(currentOtherURI, otherModuleName)) && + moduleName.Equals(otherModuleName); + if (!isSamePage) { + // We will have allowed the load earlier if the source page has + // system principal. So we know the source has a content + // principal, and it's trying to link to something else. + // Linkable about: pages are always reachable, even if we hit + // the CheckLoadURIFlags call below. + // We punch only 1 other hole: iff the source is unlinkable, + // we let them link to other pages explicitly marked SAFE + // for content. This avoids world-linkable about: pages linking + // to non-world-linkable about: pages. + nsCOMPtr<nsIAboutModule> module, otherModule; + bool knowBothModules = + NS_SUCCEEDED( + NS_GetAboutModule(currentURI, getter_AddRefs(module))) && + NS_SUCCEEDED(NS_GetAboutModule(currentOtherURI, + getter_AddRefs(otherModule))); + uint32_t aboutModuleFlags = 0; + uint32_t otherAboutModuleFlags = 0; + knowBothModules = + knowBothModules && + NS_SUCCEEDED(module->GetURIFlags(currentURI, &aboutModuleFlags)) && + NS_SUCCEEDED(otherModule->GetURIFlags(currentOtherURI, + &otherAboutModuleFlags)); + if (knowBothModules) { + isSamePage = !(aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) && + (otherAboutModuleFlags & + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT); + if (isSamePage && + otherAboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) { + // XXXgijs: this is a hack. The target will be nested + // (with innerURI of moz-safe-about:whatever), and + // the source isn't, so we won't pass if we finish + // the loop. We *should* pass, though, so return here. + // This hack can go away when bug 1228118 is fixed. + return NS_OK; + } + } + } + } else if (schemesMatch && scheme.EqualsLiteral("moz-extension")) { + // If it is not the same exension, we want to ensure we end up + // calling CheckLoadURIFlags + nsAutoCString host, otherHost; + currentURI->GetHost(host); + currentOtherURI->GetHost(otherHost); + isExtensionMismatch = !host.Equals(otherHost); + } else { + bool equalExceptRef = false; + rv = currentURI->EqualsExceptRef(currentOtherURI, &equalExceptRef); + isSamePage = NS_SUCCEEDED(rv) && equalExceptRef; + } + + // If schemes are not equal, or they're equal but the target URI + // is different from the source URI and doesn't always allow linking + // from the same scheme, or this is two different extensions, check + // if the URI flags of the current target URI allow the current + // source URI to link to it. + // The policy is specified by the protocol flags on both URIs. + if (!schemesMatch || (denySameSchemeLinks && !isSamePage) || + isExtensionMismatch) { + return CheckLoadURIFlags( + currentURI, currentOtherURI, sourceBaseURI, targetBaseURI, aFlags, + aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0, + aInnerWindowID); + } + // Otherwise... check if we can nest another level: + nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(currentURI); + nsCOMPtr<nsINestedURI> nestedOtherURI = do_QueryInterface(currentOtherURI); + + // If schemes match and neither URI is nested further, we're OK. + if (!nestedURI && !nestedOtherURI) { + return NS_OK; + } + // If one is nested and the other isn't, something is wrong. + if (!nestedURI != !nestedOtherURI) { + return NS_ERROR_DOM_BAD_URI; + } + // Otherwise, both should be nested and we'll go through the loop again. + nestedURI->GetInnerURI(getter_AddRefs(currentURI)); + nestedOtherURI->GetInnerURI(getter_AddRefs(currentOtherURI)); + } + + // We should never get here. We should always return from inside the loop. + return NS_ERROR_DOM_BAD_URI; +} + +/** + * Helper method to check whether the target URI and its innermost ("base") URI + * has protocol flags that should stop it from being loaded by the source URI + * (and/or the source URI's innermost ("base") URI), taking into account any + * nsIScriptSecurityManager flags originally passed to + * CheckLoadURIWithPrincipal and friends. + * + * @return if success, access is allowed. Otherwise, deny access + */ +nsresult nsScriptSecurityManager::CheckLoadURIFlags( + nsIURI* aSourceURI, nsIURI* aTargetURI, nsIURI* aSourceBaseURI, + nsIURI* aTargetBaseURI, uint32_t aFlags, bool aFromPrivateWindow, + uint64_t aInnerWindowID) { + // Note that the order of policy checks here is very important! + // We start from most restrictive and work our way down. + bool reportErrors = !(aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS); + const char* errorTag = "CheckLoadURIError"; + + nsAutoCString targetScheme; + nsresult rv = aTargetBaseURI->GetScheme(targetScheme); + if (NS_FAILED(rv)) return rv; + + // Check for system target URI. Regular (non web accessible) extension + // URIs will also have URI_DANGEROUS_TO_LOAD. + rv = DenyAccessIfURIHasFlags(aTargetURI, + nsIProtocolHandler::URI_DANGEROUS_TO_LOAD); + if (NS_FAILED(rv)) { + // Deny access, since the origin principal is not system + if (reportErrors) { + ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow, + aInnerWindowID); + } + return rv; + } + + // Used by ExtensionProtocolHandler to prevent loading extension resources + // in private contexts if the extension does not have permission. + if (aFromPrivateWindow) { + rv = DenyAccessIfURIHasFlags( + aTargetURI, nsIProtocolHandler::URI_DISALLOW_IN_PRIVATE_CONTEXT); + if (NS_FAILED(rv)) { + if (reportErrors) { + ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow, + aInnerWindowID); + } + return rv; + } + } + + // If MV3 Extension uris are web accessible they have + // WEBEXT_URI_WEB_ACCESSIBLE. + bool maybeWebAccessible = false; + NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::WEBEXT_URI_WEB_ACCESSIBLE, + &maybeWebAccessible); + NS_ENSURE_SUCCESS(rv, rv); + if (maybeWebAccessible) { + bool isWebAccessible = false; + rv = ExtensionPolicyService::GetSingleton().SourceMayLoadExtensionURI( + aSourceURI, aTargetURI, &isWebAccessible); + if (NS_SUCCEEDED(rv) && isWebAccessible) { + return NS_OK; + } + if (reportErrors) { + ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow, + aInnerWindowID); + } + return NS_ERROR_DOM_BAD_URI; + } + + // Check for chrome target URI + bool targetURIIsUIResource = false; + rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, + &targetURIIsUIResource); + NS_ENSURE_SUCCESS(rv, rv); + if (targetURIIsUIResource) { + // ALLOW_CHROME is a flag that we pass on all loads _except_ docshell + // loads (since docshell loads run the loaded content with its origin + // principal). We are effectively allowing resource:// and chrome:// + // URIs to load as long as they are content accessible and as long + // they're not loading it as a document. + if (aFlags & nsIScriptSecurityManager::ALLOW_CHROME) { + bool sourceIsUIResource = false; + rv = NS_URIChainHasFlags(aSourceBaseURI, + nsIProtocolHandler::URI_IS_UI_RESOURCE, + &sourceIsUIResource); + NS_ENSURE_SUCCESS(rv, rv); + if (sourceIsUIResource) { + // Special case for moz-icon URIs loaded by a local resources like + // e.g. chrome: or resource: + if (targetScheme.EqualsLiteral("moz-icon")) { + return NS_OK; + } + } + + if (targetScheme.EqualsLiteral("resource")) { + if (StaticPrefs::security_all_resource_uri_content_accessible()) { + return NS_OK; + } + + nsCOMPtr<nsIProtocolHandler> ph; + rv = sIOService->GetProtocolHandler("resource", getter_AddRefs(ph)); + NS_ENSURE_SUCCESS(rv, rv); + if (!ph) { + return NS_ERROR_DOM_BAD_URI; + } + + nsCOMPtr<nsIResProtocolHandler> rph = do_QueryInterface(ph); + if (!rph) { + return NS_ERROR_DOM_BAD_URI; + } + + bool accessAllowed = false; + rph->AllowContentToAccess(aTargetBaseURI, &accessAllowed); + if (accessAllowed) { + return NS_OK; + } + } else if (targetScheme.EqualsLiteral("chrome")) { + // Allow the load only if the chrome package is allowlisted. + nsCOMPtr<nsIXULChromeRegistry> reg( + do_GetService(NS_CHROMEREGISTRY_CONTRACTID)); + if (reg) { + bool accessAllowed = false; + reg->AllowContentToAccess(aTargetBaseURI, &accessAllowed); + if (accessAllowed) { + return NS_OK; + } + } + } else if (targetScheme.EqualsLiteral("moz-page-thumb") || + targetScheme.EqualsLiteral("page-icon")) { + if (XRE_IsParentProcess()) { + return NS_OK; + } + + auto& remoteType = dom::ContentChild::GetSingleton()->GetRemoteType(); + if (remoteType == PRIVILEGEDABOUT_REMOTE_TYPE) { + return NS_OK; + } + } + } + + if (reportErrors) { + ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow, + aInnerWindowID); + } + return NS_ERROR_DOM_BAD_URI; + } + + // Check for target URI pointing to a file + bool targetURIIsLocalFile = false; + rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_IS_LOCAL_FILE, + &targetURIIsLocalFile); + NS_ENSURE_SUCCESS(rv, rv); + if (targetURIIsLocalFile) { + // Allow domains that were allowlisted in the prefs. In 99.9% of cases, + // this array is empty. + bool isAllowlisted; + MOZ_ALWAYS_SUCCEEDS(InFileURIAllowlist(aSourceURI, &isAllowlisted)); + if (isAllowlisted) { + return NS_OK; + } + + // Allow chrome:// + if (aSourceBaseURI->SchemeIs("chrome")) { + return NS_OK; + } + + // Nothing else. + if (reportErrors) { + ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow, + aInnerWindowID); + } + return NS_ERROR_DOM_BAD_URI; + } + +#ifdef DEBUG + { + // Everyone is allowed to load this. The case URI_LOADABLE_BY_SUBSUMERS + // is handled by the caller which is just delegating to us as a helper. + bool hasSubsumersFlag = false; + NS_URIChainHasFlags(aTargetBaseURI, + nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS, + &hasSubsumersFlag); + bool hasLoadableByAnyone = false; + NS_URIChainHasFlags(aTargetBaseURI, + nsIProtocolHandler::URI_LOADABLE_BY_ANYONE, + &hasLoadableByAnyone); + MOZ_ASSERT(hasLoadableByAnyone || hasSubsumersFlag, + "why do we get here and do not have any of the two flags set?"); + } +#endif + + return NS_OK; +} + +nsresult nsScriptSecurityManager::ReportError(const char* aMessageTag, + const nsACString& aSourceSpec, + const nsACString& aTargetSpec, + bool aFromPrivateWindow, + uint64_t aInnerWindowID) { + if (aSourceSpec.IsEmpty() || aTargetSpec.IsEmpty()) { + return NS_OK; + } + + nsCOMPtr<nsIStringBundle> bundle = BundleHelper::GetOrCreate(); + if (NS_WARN_IF(!bundle)) { + return NS_OK; + } + + // Localize the error message + nsAutoString message; + AutoTArray<nsString, 2> formatStrings; + CopyASCIItoUTF16(aSourceSpec, *formatStrings.AppendElement()); + CopyASCIItoUTF16(aTargetSpec, *formatStrings.AppendElement()); + nsresult rv = + bundle->FormatStringFromName(aMessageTag, formatStrings, message); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIConsoleService> console( + do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + NS_ENSURE_TRUE(console, NS_ERROR_FAILURE); + nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + NS_ENSURE_TRUE(error, NS_ERROR_FAILURE); + + // using category of "SOP" so we can link to MDN + if (aInnerWindowID != 0) { + rv = error->InitWithWindowID( + message, u""_ns, u""_ns, 0, 0, nsIScriptError::errorFlag, "SOP"_ns, + aInnerWindowID, true /* From chrome context */); + } else { + rv = error->Init(message, u""_ns, u""_ns, 0, 0, nsIScriptError::errorFlag, + "SOP"_ns, aFromPrivateWindow, + true /* From chrome context */); + } + NS_ENSURE_SUCCESS(rv, rv); + console->LogMessage(error); + return NS_OK; +} + +nsresult nsScriptSecurityManager::ReportError(const char* aMessageTag, + nsIURI* aSource, nsIURI* aTarget, + bool aFromPrivateWindow, + uint64_t aInnerWindowID) { + NS_ENSURE_TRUE(aSource && aTarget, NS_ERROR_NULL_POINTER); + + // Get the source URL spec + nsAutoCString sourceSpec; + nsresult rv = aSource->GetAsciiSpec(sourceSpec); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the target URL spec + nsAutoCString targetSpec; + rv = aTarget->GetAsciiSpec(targetSpec); + NS_ENSURE_SUCCESS(rv, rv); + + return ReportError(aMessageTag, sourceSpec, targetSpec, aFromPrivateWindow, + aInnerWindowID); +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckLoadURIStrWithPrincipal( + nsIPrincipal* aPrincipal, const nsACString& aTargetURIStr, + uint32_t aFlags) { + nsresult rv; + nsCOMPtr<nsIURI> target; + rv = NS_NewURI(getter_AddRefs(target), aTargetURIStr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CheckLoadURIWithPrincipal(aPrincipal, target, aFlags, 0); + if (rv == NS_ERROR_DOM_BAD_URI) { + // Don't warn because NS_ERROR_DOM_BAD_URI is one of the expected + // return values. + return rv; + } + NS_ENSURE_SUCCESS(rv, rv); + + // Now start testing fixup -- since aTargetURIStr is a string, not + // an nsIURI, we may well end up fixing it up before loading. + // Note: This needs to stay in sync with the nsIURIFixup api. + nsCOMPtr<nsIURIFixup> fixup = components::URIFixup::Service(); + if (!fixup) { + return rv; + } + + // URIFixup's keyword and alternate flags can only fixup to http/https, so we + // can skip testing them. This simplifies our life because this code can be + // invoked from the content process where the search service would not be + // available. + uint32_t flags[] = {nsIURIFixup::FIXUP_FLAG_NONE, + nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS}; + for (uint32_t i = 0; i < ArrayLength(flags); ++i) { + uint32_t fixupFlags = flags[i]; + if (aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0) { + fixupFlags |= nsIURIFixup::FIXUP_FLAG_PRIVATE_CONTEXT; + } + nsCOMPtr<nsIURIFixupInfo> fixupInfo; + rv = fixup->GetFixupURIInfo(aTargetURIStr, fixupFlags, + getter_AddRefs(fixupInfo)); + NS_ENSURE_SUCCESS(rv, rv); + rv = fixupInfo->GetPreferredURI(getter_AddRefs(target)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CheckLoadURIWithPrincipal(aPrincipal, target, aFlags, 0); + if (rv == NS_ERROR_DOM_BAD_URI) { + // Don't warn because NS_ERROR_DOM_BAD_URI is one of the expected + // return values. + return rv; + } + NS_ENSURE_SUCCESS(rv, rv); + } + + return rv; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckLoadURIWithPrincipalFromJS( + nsIPrincipal* aPrincipal, nsIURI* aTargetURI, uint32_t aFlags, + uint64_t aInnerWindowID, JSContext* aCx) { + MOZ_ASSERT(aPrincipal, + "CheckLoadURIWithPrincipalFromJS must have a principal"); + NS_ENSURE_ARG_POINTER(aPrincipal); + NS_ENSURE_ARG_POINTER(aTargetURI); + + nsresult rv = + CheckLoadURIWithPrincipal(aPrincipal, aTargetURI, aFlags, aInnerWindowID); + if (NS_FAILED(rv)) { + nsAutoCString uriStr; + Unused << aTargetURI->GetSpec(uriStr); + + nsAutoCString message("Load of "); + message.Append(uriStr); + + nsAutoCString principalStr; + Unused << aPrincipal->GetSpec(principalStr); + if (!principalStr.IsEmpty()) { + message.AppendPrintf(" from %s", principalStr.get()); + } + + message.Append(" denied"); + + dom::Throw(aCx, rv, message); + } + + return rv; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CheckLoadURIStrWithPrincipalFromJS( + nsIPrincipal* aPrincipal, const nsACString& aTargetURIStr, uint32_t aFlags, + JSContext* aCx) { + nsCOMPtr<nsIURI> targetURI; + MOZ_TRY(NS_NewURI(getter_AddRefs(targetURI), aTargetURIStr)); + + return CheckLoadURIWithPrincipalFromJS(aPrincipal, targetURI, aFlags, 0, aCx); +} + +NS_IMETHODIMP +nsScriptSecurityManager::InFileURIAllowlist(nsIURI* aUri, bool* aResult) { + MOZ_ASSERT(aUri); + MOZ_ASSERT(aResult); + + *aResult = false; + for (nsIURI* uri : EnsureFileURIAllowlist()) { + if (EqualOrSubdomain(aUri, uri)) { + *aResult = true; + return NS_OK; + } + } + + return NS_OK; +} + +///////////////// Principals /////////////////////// + +NS_IMETHODIMP +nsScriptSecurityManager::GetSystemPrincipal(nsIPrincipal** result) { + NS_ADDREF(*result = mSystemPrincipal); + + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CreateContentPrincipal( + nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx, + nsIPrincipal** aPrincipal) { + OriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIPrincipal> prin = + BasePrincipal::CreateContentPrincipal(aURI, attrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CreateContentPrincipalFromOrigin( + const nsACString& aOrigin, nsIPrincipal** aPrincipal) { + if (StringBeginsWith(aOrigin, "["_ns)) { + return NS_ERROR_INVALID_ARG; + } + + if (StringBeginsWith(aOrigin, + nsLiteralCString(NS_NULLPRINCIPAL_SCHEME ":"))) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateContentPrincipal(aOrigin); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::PrincipalToJSON(nsIPrincipal* aPrincipal, + nsACString& aJSON) { + aJSON.Truncate(); + if (!aPrincipal) { + return NS_ERROR_FAILURE; + } + + BasePrincipal::Cast(aPrincipal)->ToJSON(aJSON); + + if (aJSON.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::JSONToPrincipal(const nsACString& aJSON, + nsIPrincipal** aPrincipal) { + if (aJSON.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrincipal> principal = BasePrincipal::FromJSON(aJSON); + + if (!principal) { + return NS_ERROR_FAILURE; + } + + principal.forget(aPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CreateNullPrincipal( + JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx, + nsIPrincipal** aPrincipal) { + OriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIPrincipal> prin = NullPrincipal::Create(attrs); + prin.forget(aPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetLoadContextContentPrincipal( + nsIURI* aURI, nsILoadContext* aLoadContext, nsIPrincipal** aPrincipal) { + NS_ENSURE_STATE(aLoadContext); + OriginAttributes docShellAttrs; + aLoadContext->GetOriginAttributes(docShellAttrs); + + nsCOMPtr<nsIPrincipal> prin = + BasePrincipal::CreateContentPrincipal(aURI, docShellAttrs); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetDocShellContentPrincipal( + nsIURI* aURI, nsIDocShell* aDocShell, nsIPrincipal** aPrincipal) { + nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateContentPrincipal( + aURI, nsDocShell::Cast(aDocShell)->GetOriginAttributes()); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::PrincipalWithOA( + nsIPrincipal* aPrincipal, JS::Handle<JS::Value> aOriginAttributes, + JSContext* aCx, nsIPrincipal** aReturnPrincipal) { + if (!aPrincipal) { + return NS_OK; + } + if (aPrincipal->GetIsContentPrincipal()) { + OriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + auto* contentPrincipal = static_cast<ContentPrincipal*>(aPrincipal); + RefPtr<ContentPrincipal> copy = + new ContentPrincipal(contentPrincipal, attrs); + NS_ENSURE_TRUE(copy, NS_ERROR_FAILURE); + copy.forget(aReturnPrincipal); + } else { + // We do this for null principals, system principals (both fine) + // ... and expanded principals, where we should probably do something + // cleverer, but I also don't think we care too much. + nsCOMPtr<nsIPrincipal> prin = aPrincipal; + prin.forget(aReturnPrincipal); + } + + return *aReturnPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CanCreateWrapper(JSContext* cx, const nsIID& aIID, + nsISupports* aObj, + nsIClassInfo* aClassInfo) { + // XXX Special case for Exception ? + + // We give remote-XUL allowlisted domains a free pass here. See bug 932906. + JS::Rooted<JS::Realm*> contextRealm(cx, JS::GetCurrentRealmOrNull(cx)); + MOZ_RELEASE_ASSERT(contextRealm); + if (!xpc::AllowContentXBLScope(contextRealm)) { + return NS_OK; + } + + if (nsContentUtils::IsCallerChrome()) { + return NS_OK; + } + + //-- Access denied, report an error + nsAutoCString originUTF8; + nsIPrincipal* subjectPrincipal = nsContentUtils::SubjectPrincipal(); + GetPrincipalDomainOrigin(subjectPrincipal, originUTF8); + NS_ConvertUTF8toUTF16 originUTF16(originUTF8); + nsAutoCString classInfoNameUTF8; + if (aClassInfo) { + aClassInfo->GetClassDescription(classInfoNameUTF8); + } + if (classInfoNameUTF8.IsEmpty()) { + classInfoNameUTF8.AssignLiteral("UnnamedClass"); + } + + nsCOMPtr<nsIStringBundle> bundle = BundleHelper::GetOrCreate(); + if (NS_WARN_IF(!bundle)) { + return NS_OK; + } + + NS_ConvertUTF8toUTF16 classInfoUTF16(classInfoNameUTF8); + nsresult rv; + nsAutoString errorMsg; + if (originUTF16.IsEmpty()) { + AutoTArray<nsString, 1> formatStrings = {classInfoUTF16}; + rv = bundle->FormatStringFromName("CreateWrapperDenied", formatStrings, + errorMsg); + } else { + AutoTArray<nsString, 2> formatStrings = {classInfoUTF16, originUTF16}; + rv = bundle->FormatStringFromName("CreateWrapperDeniedForOrigin", + formatStrings, errorMsg); + } + NS_ENSURE_SUCCESS(rv, rv); + + SetPendingException(cx, errorMsg.get()); + return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CanCreateInstance(JSContext* cx, const nsCID& aCID) { + if (nsContentUtils::IsCallerChrome()) { + return NS_OK; + } + + //-- Access denied, report an error + nsAutoCString errorMsg("Permission denied to create instance of class. CID="); + char cidStr[NSID_LENGTH]; + aCID.ToProvidedString(cidStr); + errorMsg.Append(cidStr); + SetPendingExceptionASCII(cx, errorMsg.get()); + return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; +} + +NS_IMETHODIMP +nsScriptSecurityManager::CanGetService(JSContext* cx, const nsCID& aCID) { + if (nsContentUtils::IsCallerChrome()) { + return NS_OK; + } + + //-- Access denied, report an error + nsAutoCString errorMsg("Permission denied to get service. CID="); + char cidStr[NSID_LENGTH]; + aCID.ToProvidedString(cidStr); + errorMsg.Append(cidStr); + SetPendingExceptionASCII(cx, errorMsg.get()); + return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; +} + +const char sJSEnabledPrefName[] = "javascript.enabled"; +const char sFileOriginPolicyPrefName[] = + "security.fileuri.strict_origin_policy"; + +static const char* kObservedPrefs[] = {sJSEnabledPrefName, + sFileOriginPolicyPrefName, + "capability.policy.", nullptr}; + +///////////////////////////////////////////// +// Constructor, Destructor, Initialization // +///////////////////////////////////////////// +nsScriptSecurityManager::nsScriptSecurityManager(void) + : mPrefInitialized(false), mIsJavaScriptEnabled(false) { + static_assert( + sizeof(intptr_t) == sizeof(void*), + "intptr_t and void* have different lengths on this platform. " + "This may cause a security failure with the SecurityLevel union."); +} + +nsresult nsScriptSecurityManager::Init() { + nsresult rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); + NS_ENSURE_SUCCESS(rv, rv); + + InitPrefs(); + + // Create our system principal singleton + mSystemPrincipal = SystemPrincipal::Init(); + + return NS_OK; +} + +void nsScriptSecurityManager::InitJSCallbacks(JSContext* aCx) { + //-- Register security check callback in the JS engine + // Currently this is used to control access to function.caller + + static const JSSecurityCallbacks securityCallbacks = { + ContentSecurityPolicyPermitsJSAction, + JSPrincipalsSubsume, + }; + + MOZ_ASSERT(!JS_GetSecurityCallbacks(aCx)); + JS_SetSecurityCallbacks(aCx, &securityCallbacks); + JS_InitDestroyPrincipalsCallback(aCx, nsJSPrincipals::Destroy); + + JS_SetTrustedPrincipals(aCx, BasePrincipal::Cast(mSystemPrincipal)); +} + +/* static */ +void nsScriptSecurityManager::ClearJSCallbacks(JSContext* aCx) { + JS_SetSecurityCallbacks(aCx, nullptr); + JS_SetTrustedPrincipals(aCx, nullptr); +} + +static StaticRefPtr<nsScriptSecurityManager> gScriptSecMan; + +nsScriptSecurityManager::~nsScriptSecurityManager(void) { + Preferences::UnregisterPrefixCallbacks( + nsScriptSecurityManager::ScriptSecurityPrefChanged, kObservedPrefs, this); + if (mDomainPolicy) { + mDomainPolicy->Deactivate(); + } + // ContentChild might hold a reference to the domain policy, + // and it might release it only after the security manager is + // gone. But we can still assert this for the main process. + MOZ_ASSERT_IF(XRE_IsParentProcess(), !mDomainPolicy); +} + +void nsScriptSecurityManager::Shutdown() { + NS_IF_RELEASE(sIOService); + BundleHelper::Shutdown(); + SystemPrincipal::Shutdown(); +} + +nsScriptSecurityManager* nsScriptSecurityManager::GetScriptSecurityManager() { + return gScriptSecMan; +} + +/* static */ +void nsScriptSecurityManager::InitStatics() { + RefPtr<nsScriptSecurityManager> ssManager = new nsScriptSecurityManager(); + nsresult rv = ssManager->Init(); + if (NS_FAILED(rv)) { + MOZ_CRASH("ssManager->Init() failed"); + } + + ClearOnShutdown(&gScriptSecMan); + gScriptSecMan = ssManager; +} + +// Currently this nsGenericFactory constructor is used only from FastLoad +// (XPCOM object deserialization) code, when "creating" the system principal +// singleton. +already_AddRefed<SystemPrincipal> +nsScriptSecurityManager::SystemPrincipalSingletonConstructor() { + if (gScriptSecMan) + return do_AddRef(gScriptSecMan->mSystemPrincipal) + .downcast<SystemPrincipal>(); + return nullptr; +} + +struct IsWhitespace { + static bool Test(char aChar) { return NS_IsAsciiWhitespace(aChar); }; +}; +struct IsWhitespaceOrComma { + static bool Test(char aChar) { + return aChar == ',' || NS_IsAsciiWhitespace(aChar); + }; +}; + +template <typename Predicate> +uint32_t SkipPast(const nsCString& str, uint32_t base) { + while (base < str.Length() && Predicate::Test(str[base])) { + ++base; + } + return base; +} + +template <typename Predicate> +uint32_t SkipUntil(const nsCString& str, uint32_t base) { + while (base < str.Length() && !Predicate::Test(str[base])) { + ++base; + } + return base; +} + +// static +void nsScriptSecurityManager::ScriptSecurityPrefChanged(const char* aPref, + void* aSelf) { + static_cast<nsScriptSecurityManager*>(aSelf)->ScriptSecurityPrefChanged( + aPref); +} + +inline void nsScriptSecurityManager::ScriptSecurityPrefChanged( + const char* aPref) { + MOZ_ASSERT(mPrefInitialized); + mIsJavaScriptEnabled = + Preferences::GetBool(sJSEnabledPrefName, mIsJavaScriptEnabled); + sStrictFileOriginPolicy = + Preferences::GetBool(sFileOriginPolicyPrefName, false); + mFileURIAllowlist.reset(); +} + +void nsScriptSecurityManager::AddSitesToFileURIAllowlist( + const nsCString& aSiteList) { + for (uint32_t base = SkipPast<IsWhitespace>(aSiteList, 0), bound = 0; + base < aSiteList.Length(); + base = SkipPast<IsWhitespace>(aSiteList, bound)) { + // Grab the current site. + bound = SkipUntil<IsWhitespace>(aSiteList, base); + nsAutoCString site(Substring(aSiteList, base, bound - base)); + + // Check if the URI is schemeless. If so, add both http and https. + nsAutoCString unused; + if (NS_FAILED(sIOService->ExtractScheme(site, unused))) { + AddSitesToFileURIAllowlist("http://"_ns + site); + AddSitesToFileURIAllowlist("https://"_ns + site); + continue; + } + + // Convert it to a URI and add it to our list. + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), site); + if (NS_SUCCEEDED(rv)) { + mFileURIAllowlist.ref().AppendElement(uri); + } else { + nsCOMPtr<nsIConsoleService> console( + do_GetService("@mozilla.org/consoleservice;1")); + if (console) { + nsAutoString msg = + u"Unable to to add site to file:// URI allowlist: "_ns + + NS_ConvertASCIItoUTF16(site); + console->LogStringMessage(msg.get()); + } + } + } +} + +nsresult nsScriptSecurityManager::InitPrefs() { + nsIPrefBranch* branch = Preferences::GetRootBranch(); + NS_ENSURE_TRUE(branch, NS_ERROR_FAILURE); + + mPrefInitialized = true; + + // Set the initial value of the "javascript.enabled" prefs + ScriptSecurityPrefChanged(); + + // set observer callbacks in case the value of the prefs change + Preferences::RegisterPrefixCallbacks( + nsScriptSecurityManager::ScriptSecurityPrefChanged, kObservedPrefs, this); + + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::GetDomainPolicyActive(bool* aRv) { + *aRv = !!mDomainPolicy; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptSecurityManager::ActivateDomainPolicy(nsIDomainPolicy** aRv) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + + return ActivateDomainPolicyInternal(aRv); +} + +NS_IMETHODIMP +nsScriptSecurityManager::ActivateDomainPolicyInternal(nsIDomainPolicy** aRv) { + // We only allow one domain policy at a time. The holder of the previous + // policy must explicitly deactivate it first. + if (mDomainPolicy) { + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + + mDomainPolicy = new DomainPolicy(); + nsCOMPtr<nsIDomainPolicy> ptr = mDomainPolicy; + ptr.forget(aRv); + return NS_OK; +} + +// Intentionally non-scriptable. Script must have a reference to the +// nsIDomainPolicy to deactivate it. +void nsScriptSecurityManager::DeactivateDomainPolicy() { + mDomainPolicy = nullptr; +} + +void nsScriptSecurityManager::CloneDomainPolicy(DomainPolicyClone* aClone) { + MOZ_ASSERT(aClone); + if (mDomainPolicy) { + mDomainPolicy->CloneDomainPolicy(aClone); + } else { + aClone->active() = false; + } +} + +NS_IMETHODIMP +nsScriptSecurityManager::PolicyAllowsScript(nsIURI* aURI, bool* aRv) { + nsresult rv; + + // Compute our rule. If we don't have any domain policy set up that might + // provide exceptions to this rule, we're done. + *aRv = mIsJavaScriptEnabled; + if (!mDomainPolicy) { + return NS_OK; + } + + // We have a domain policy. Grab the appropriate set of exceptions to the + // rule (either the blocklist or the allowlist, depending on whether script + // is enabled or disabled by default). + nsCOMPtr<nsIDomainSet> exceptions; + nsCOMPtr<nsIDomainSet> superExceptions; + if (*aRv) { + mDomainPolicy->GetBlocklist(getter_AddRefs(exceptions)); + mDomainPolicy->GetSuperBlocklist(getter_AddRefs(superExceptions)); + } else { + mDomainPolicy->GetAllowlist(getter_AddRefs(exceptions)); + mDomainPolicy->GetSuperAllowlist(getter_AddRefs(superExceptions)); + } + + bool contains; + rv = exceptions->Contains(aURI, &contains); + NS_ENSURE_SUCCESS(rv, rv); + if (contains) { + *aRv = !*aRv; + return NS_OK; + } + rv = superExceptions->ContainsSuperDomain(aURI, &contains); + NS_ENSURE_SUCCESS(rv, rv); + if (contains) { + *aRv = !*aRv; + } + + return NS_OK; +} + +const nsTArray<nsCOMPtr<nsIURI>>& +nsScriptSecurityManager::EnsureFileURIAllowlist() { + if (mFileURIAllowlist.isSome()) { + return mFileURIAllowlist.ref(); + } + + // + // Rebuild the set of principals for which we allow file:// URI loads. This + // implements a small subset of an old pref-based CAPS people that people + // have come to depend on. See bug 995943. + // + + mFileURIAllowlist.emplace(); + nsAutoCString policies; + mozilla::Preferences::GetCString("capability.policy.policynames", policies); + for (uint32_t base = SkipPast<IsWhitespaceOrComma>(policies, 0), bound = 0; + base < policies.Length(); + base = SkipPast<IsWhitespaceOrComma>(policies, bound)) { + // Grab the current policy name. + bound = SkipUntil<IsWhitespaceOrComma>(policies, base); + auto policyName = Substring(policies, base, bound - base); + + // Figure out if this policy allows loading file:// URIs. If not, we can + // skip it. + nsCString checkLoadURIPrefName = + "capability.policy."_ns + policyName + ".checkloaduri.enabled"_ns; + nsAutoString value; + nsresult rv = Preferences::GetString(checkLoadURIPrefName.get(), value); + if (NS_FAILED(rv) || !value.LowerCaseEqualsLiteral("allaccess")) { + continue; + } + + // Grab the list of domains associated with this policy. + nsCString domainPrefName = + "capability.policy."_ns + policyName + ".sites"_ns; + nsAutoCString siteList; + Preferences::GetCString(domainPrefName.get(), siteList); + AddSitesToFileURIAllowlist(siteList); + } + + return mFileURIAllowlist.ref(); +} diff --git a/caps/nsScriptSecurityManager.h b/caps/nsScriptSecurityManager.h new file mode 100644 index 0000000000..8cf1bdbabf --- /dev/null +++ b/caps/nsScriptSecurityManager.h @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=4 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 nsScriptSecurityManager_h__ +#define nsScriptSecurityManager_h__ + +#include "nsIScriptSecurityManager.h" + +#include "mozilla/Maybe.h" +#include "nsIPrincipal.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsStringFwd.h" +#include "plstr.h" +#include "js/TypeDecls.h" + +#include <stdint.h> + +class nsIIOService; +class nsIStringBundle; + +namespace mozilla { +class OriginAttributes; +class SystemPrincipal; +} // namespace mozilla + +namespace JS { +enum class RuntimeCode; +} // namespace JS + +///////////////////////////// +// nsScriptSecurityManager // +///////////////////////////// +#define NS_SCRIPTSECURITYMANAGER_CID \ + { \ + 0x7ee2a4c0, 0x4b93, 0x17d3, { \ + 0xba, 0x18, 0x00, 0x60, 0xb0, 0xf1, 0x99, 0xa2 \ + } \ + } + +class nsScriptSecurityManager final : public nsIScriptSecurityManager { + public: + static void Shutdown(); + + NS_DEFINE_STATIC_CID_ACCESSOR(NS_SCRIPTSECURITYMANAGER_CID) + + NS_DECL_ISUPPORTS + NS_DECL_NSISCRIPTSECURITYMANAGER + + static nsScriptSecurityManager* GetScriptSecurityManager(); + + // Invoked exactly once, by XPConnect. + static void InitStatics(); + + void InitJSCallbacks(JSContext* aCx); + + // This has to be static because it is called after gScriptSecMan is cleared. + static void ClearJSCallbacks(JSContext* aCx); + + static already_AddRefed<mozilla::SystemPrincipal> + SystemPrincipalSingletonConstructor(); + + /** + * Utility method for comparing two URIs. For security purposes, two URIs + * are equivalent if their schemes, hosts, and ports (if any) match. This + * method returns true if aSubjectURI and aObjectURI have the same origin, + * false otherwise. + */ + static bool SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI); + static uint32_t SecurityHashURI(nsIURI* aURI); + + static nsresult ReportError(const char* aMessageTag, nsIURI* aSource, + nsIURI* aTarget, bool aFromPrivateWindow, + uint64_t aInnerWindowID = 0); + static nsresult ReportError(const char* aMessageTag, + const nsACString& sourceSpec, + const nsACString& targetSpec, + bool aFromPrivateWindow, + uint64_t aInnerWindowID = 0); + + static uint32_t HashPrincipalByOrigin(nsIPrincipal* aPrincipal); + + static bool GetStrictFileOriginPolicy() { return sStrictFileOriginPolicy; } + + void DeactivateDomainPolicy(); + + private: + // GetScriptSecurityManager is the only call that can make one + nsScriptSecurityManager(); + virtual ~nsScriptSecurityManager(); + + // Decides, based on CSP, whether or not eval() and stuff can be executed. + static bool ContentSecurityPolicyPermitsJSAction(JSContext* cx, + JS::RuntimeCode kind, + JS::Handle<JSString*> aCode); + + static bool JSPrincipalsSubsume(JSPrincipals* first, JSPrincipals* second); + + nsresult Init(); + + nsresult InitPrefs(); + + static void ScriptSecurityPrefChanged(const char* aPref, void* aSelf); + void ScriptSecurityPrefChanged(const char* aPref = nullptr); + + inline void AddSitesToFileURIAllowlist(const nsCString& aSiteList); + + nsresult GetChannelResultPrincipal(nsIChannel* aChannel, + nsIPrincipal** aPrincipal, + bool aIgnoreSandboxing); + + nsresult CheckLoadURIFlags(nsIURI* aSourceURI, nsIURI* aTargetURI, + nsIURI* aSourceBaseURI, nsIURI* aTargetBaseURI, + uint32_t aFlags, bool aFromPrivateWindow, + uint64_t aInnerWindowID); + + // Returns the file URI allowlist, initializing it if it has not been + // initialized. + const nsTArray<nsCOMPtr<nsIURI>>& EnsureFileURIAllowlist(); + + nsCOMPtr<nsIPrincipal> mSystemPrincipal; + bool mPrefInitialized; + bool mIsJavaScriptEnabled; + + // List of URIs whose domains and sub-domains are allowlisted to allow + // access to file: URIs. Lazily initialized; isNothing() when not yet + // initialized. + mozilla::Maybe<nsTArray<nsCOMPtr<nsIURI>>> mFileURIAllowlist; + + // This machinery controls new-style domain policies. The old-style + // policy machinery will be removed soon. + nsCOMPtr<nsIDomainPolicy> mDomainPolicy; + + static std::atomic<bool> sStrictFileOriginPolicy; + + static nsIIOService* sIOService; + static nsIStringBundle* sStrBundle; +}; + +#endif // nsScriptSecurityManager_h__ diff --git a/caps/tests/gtest/TestBackgroundThreadPrincipal.cpp b/caps/tests/gtest/TestBackgroundThreadPrincipal.cpp new file mode 100644 index 0000000000..c45341a4c0 --- /dev/null +++ b/caps/tests/gtest/TestBackgroundThreadPrincipal.cpp @@ -0,0 +1,90 @@ +/* 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 "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ContentPrincipal.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "nsIEventTarget.h" +#include "nsIURIMutator.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +template <typename F> +void RunOnBackgroundThread(F&& aFunction) { + ASSERT_NS_SUCCEEDED(NS_DispatchBackgroundTask( + NS_NewRunnableFunction("RunOnBackgroundThread", + std::forward<F>(aFunction)), + NS_DISPATCH_SYNC)); +} + +TEST(BackgroundThreadPrincipal, CreateContent) +{ + RunOnBackgroundThread([] { + nsCOMPtr<nsIURI> contentURI; + ASSERT_NS_SUCCEEDED(NS_NewURI(getter_AddRefs(contentURI), + "http://subdomain.example.com:8000"_ns)); + RefPtr<BasePrincipal> contentPrincipal = + BasePrincipal::CreateContentPrincipal(contentURI, OriginAttributes()); + EXPECT_TRUE(contentPrincipal->Is<ContentPrincipal>()); + + nsCString origin; + ASSERT_NS_SUCCEEDED(contentPrincipal->GetOrigin(origin)); + EXPECT_EQ(origin, "http://subdomain.example.com:8000"_ns); + + nsCString siteOrigin; + ASSERT_NS_SUCCEEDED(contentPrincipal->GetSiteOrigin(siteOrigin)); + EXPECT_EQ(siteOrigin, "http://example.com"_ns); + }); +} + +TEST(BackgroundThreadPrincipal, CreateNull) +{ + RunOnBackgroundThread([] { + nsCOMPtr<nsIURI> contentURI; + ASSERT_NS_SUCCEEDED(NS_NewURI(getter_AddRefs(contentURI), + "data:text/plain,hello world"_ns)); + RefPtr<BasePrincipal> principal = + BasePrincipal::CreateContentPrincipal(contentURI, OriginAttributes()); + EXPECT_TRUE(principal->Is<NullPrincipal>()); + + nsCString origin; + ASSERT_NS_SUCCEEDED(principal->GetOrigin(origin)); + EXPECT_TRUE(StringBeginsWith(origin, "moz-nullprincipal:"_ns)); + }); +} + +TEST(BackgroundThreadPrincipal, PrincipalInfoConversions) +{ + RunOnBackgroundThread([] { + nsCOMPtr<nsIURI> contentURI; + ASSERT_NS_SUCCEEDED(NS_NewURI(getter_AddRefs(contentURI), + "http://subdomain.example.com:8000"_ns)); + RefPtr<BasePrincipal> contentPrincipal = + BasePrincipal::CreateContentPrincipal(contentURI, OriginAttributes()); + EXPECT_TRUE(contentPrincipal->Is<ContentPrincipal>()); + + ipc::PrincipalInfo info; + ASSERT_NS_SUCCEEDED(ipc::PrincipalToPrincipalInfo(contentPrincipal, &info)); + + EXPECT_TRUE(info.type() == ipc::PrincipalInfo::TContentPrincipalInfo); + EXPECT_EQ(info.get_ContentPrincipalInfo().spec(), + "http://subdomain.example.com:8000/"_ns); + EXPECT_EQ(info.get_ContentPrincipalInfo().baseDomain(), "example.com"_ns); + + auto result = PrincipalInfoToPrincipal(info); + ASSERT_TRUE(result.isOk()); + nsCOMPtr<nsIPrincipal> deserialized = result.unwrap(); + EXPECT_TRUE(deserialized->GetIsContentPrincipal()); + + EXPECT_TRUE(deserialized->Equals(contentPrincipal)); + }); +} + +} // namespace mozilla diff --git a/caps/tests/gtest/TestNullPrincipalPrecursor.cpp b/caps/tests/gtest/TestNullPrincipalPrecursor.cpp new file mode 100644 index 0000000000..96db5aeffd --- /dev/null +++ b/caps/tests/gtest/TestNullPrincipalPrecursor.cpp @@ -0,0 +1,56 @@ +/* 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 "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/NullPrincipal.h" +#include "nsIURIMutator.h" +#include "nsPrintfCString.h" + +namespace mozilla { + +TEST(NullPrincipalPrecursor, EscapingRoundTrips) +{ + nsTArray<nsCString> inputs; + + inputs.AppendElements(mozilla::Span(std::array{ + "mailbox:///dev/shm/tmp5wkt9ff_.mozrunner/Mail/Local%20Folders/secure-mail?number=5"_ns, + })); + + // Add a string for every ASCII byte both escaped and unescaped. + for (uint8_t c = 0; c < 128; ++c) { + inputs.AppendElement(nsPrintfCString("%02X: %c", c, (char)c)); + inputs.AppendElement(nsPrintfCString("%02X: %%%02X", c, c)); + } + + nsID dummyID{0xddf15eaf, + 0x3837, + 0x4678, + {0x80, 0x3b, 0x86, 0x86, 0xe8, 0x17, 0x66, 0x71}}; + nsCOMPtr<nsIURI> baseURI = NullPrincipal::CreateURI(nullptr, &dummyID); + ASSERT_TRUE(baseURI); + + for (auto& input : inputs) { + // First build an escaped version of the input string using + // `EscapePrecursorQuery`. + nsCString escaped(input); + NullPrincipal::EscapePrecursorQuery(escaped); + + // Make sure that this escaped URI round-trips through a `moz-nullprincipal` + // URI's query without any additional escapes. + nsCOMPtr<nsIURI> clone; + EXPECT_NS_SUCCEEDED( + NS_MutateURI(baseURI).SetQuery(escaped).Finalize(clone)); + nsCString query; + EXPECT_NS_SUCCEEDED(clone->GetQuery(query)); + EXPECT_EQ(escaped, query); + + // Try to unescape our escaped URI and make sure we recover the input + // string. + nsCString unescaped(escaped); + NullPrincipal::UnescapePrecursorQuery(unescaped); + EXPECT_EQ(input, unescaped); + } +} + +} // namespace mozilla diff --git a/caps/tests/gtest/TestOriginAttributes.cpp b/caps/tests/gtest/TestOriginAttributes.cpp new file mode 100644 index 0000000000..fa759f80d5 --- /dev/null +++ b/caps/tests/gtest/TestOriginAttributes.cpp @@ -0,0 +1,118 @@ +/* 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 "gtest/gtest.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/Preferences.h" +#include "nsNetUtil.h" + +using mozilla::OriginAttributes; +using mozilla::Preferences; + +#define FPI_PREF "privacy.firstparty.isolate" +#define SITE_PREF "privacy.firstparty.isolate.use_site" + +#define TEST_FPD(_spec, _expected) \ + TestFPD(nsLiteralString(_spec), nsLiteralString(_expected)) + +namespace mozilla { + +static void TestSuffix(const OriginAttributes& attrs) { + nsAutoCString suffix; + attrs.CreateSuffix(suffix); + + OriginAttributes attrsFromSuffix; + bool success = attrsFromSuffix.PopulateFromSuffix(suffix); + EXPECT_TRUE(success); + + EXPECT_EQ(attrs, attrsFromSuffix); +} + +static void TestFPD(const nsAString& spec, const nsAString& expected) { + OriginAttributes attrs; + nsCOMPtr<nsIURI> url; + ASSERT_EQ(NS_NewURI(getter_AddRefs(url), spec), NS_OK); + attrs.SetFirstPartyDomain(true, url); + EXPECT_TRUE(attrs.mFirstPartyDomain.Equals(expected)); + + TestSuffix(attrs); +} + +TEST(OriginAttributes, Suffix_default) +{ + OriginAttributes attrs; + TestSuffix(attrs); +} + +TEST(OriginAttributes, Suffix_inIsolatedMozBrowser) +{ + OriginAttributes attrs(true); + TestSuffix(attrs); +} + +TEST(OriginAttributes, FirstPartyDomain_default) +{ + bool oldFpiPref = Preferences::GetBool(FPI_PREF); + Preferences::SetBool(FPI_PREF, true); + bool oldSitePref = Preferences::GetBool(SITE_PREF); + Preferences::SetBool(SITE_PREF, false); + + TEST_FPD(u"http://www.example.com", u"example.com"); + TEST_FPD(u"http://www.example.com:80", u"example.com"); + TEST_FPD(u"http://www.example.com:8080", u"example.com"); + TEST_FPD(u"http://s3.amazonaws.com", u"s3.amazonaws.com"); + TEST_FPD(u"http://com", u"com"); + TEST_FPD(u"http://com.", u"com."); + TEST_FPD(u"http://com:8080", u"com"); + TEST_FPD(u"http://.com", u""); + TEST_FPD(u"http://..com", u""); + TEST_FPD(u"http://127.0.0.1", u"127.0.0.1"); + TEST_FPD(u"http://[::1]", u"[::1]"); + TEST_FPD(u"about:config", + u"about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla"); + TEST_FPD(u"moz-extension://f5b6ca10-5bd4-4ed6-9baf-820dc5152bc1", u""); + TEST_FPD(u"moz-nullprincipal:{9bebdabb-828a-4284-8b00-432a968c6e42}", + u"9bebdabb-828a-4284-8b00-432a968c6e42.mozilla"); + TEST_FPD( + u"moz-nullprincipal:{9bebdabb-828a-4284-8b00-432a968c6e42}" + u"?https://www.example.com", + u"9bebdabb-828a-4284-8b00-432a968c6e42.mozilla"); + + Preferences::SetBool(FPI_PREF, oldFpiPref); + Preferences::SetBool(SITE_PREF, oldSitePref); +} + +TEST(OriginAttributes, FirstPartyDomain_site) +{ + bool oldFpiPref = Preferences::GetBool(FPI_PREF); + Preferences::SetBool(FPI_PREF, true); + bool oldSitePref = Preferences::GetBool(SITE_PREF); + Preferences::SetBool(SITE_PREF, true); + + TEST_FPD(u"http://www.example.com", u"(http,example.com)"); + TEST_FPD(u"http://www.example.com:80", u"(http,example.com)"); + TEST_FPD(u"http://www.example.com:8080", u"(http,example.com)"); + TEST_FPD(u"http://s3.amazonaws.com", u"(http,s3.amazonaws.com)"); + TEST_FPD(u"http://com", u"(http,com)"); + TEST_FPD(u"http://com.", u"(http,com.)"); + TEST_FPD(u"http://com:8080", u"(http,com,8080)"); + TEST_FPD(u"http://.com", u"(http,.com)"); + TEST_FPD(u"http://..com", u"(http,..com)"); + TEST_FPD(u"http://127.0.0.1", u"(http,127.0.0.1)"); + TEST_FPD(u"http://[::1]", u"(http,[::1])"); + TEST_FPD(u"about:config", + u"(about,about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla)"); + TEST_FPD(u"moz-extension://f5b6ca10-5bd4-4ed6-9baf-820dc5152bc1", u""); + TEST_FPD(u"moz-nullprincipal:{9bebdabb-828a-4284-8b00-432a968c6e42}", + u"9bebdabb-828a-4284-8b00-432a968c6e42.mozilla"); + TEST_FPD( + u"moz-nullprincipal:{9bebdabb-828a-4284-8b00-432a968c6e42}" + u"?https://www.example.com", + u"9bebdabb-828a-4284-8b00-432a968c6e42.mozilla"); + + Preferences::SetBool(FPI_PREF, oldFpiPref); + Preferences::SetBool(SITE_PREF, oldSitePref); +} + +} // namespace mozilla diff --git a/caps/tests/gtest/TestPrincipalAttributes.cpp b/caps/tests/gtest/TestPrincipalAttributes.cpp new file mode 100644 index 0000000000..bc0bff90f6 --- /dev/null +++ b/caps/tests/gtest/TestPrincipalAttributes.cpp @@ -0,0 +1,39 @@ +/* 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 "gtest/gtest.h" +#include "mozilla/BasePrincipal.h" +#include "nsScriptSecurityManager.h" + +using namespace mozilla; + +class PrincipalAttributesParam { + public: + nsAutoCString spec; + bool expectIsIpAddress; +}; + +class PrincipalAttributesTest + : public ::testing::TestWithParam<PrincipalAttributesParam> {}; + +TEST_P(PrincipalAttributesTest, PrincipalAttributesTest) { + nsCOMPtr<nsIScriptSecurityManager> ssm = + nsScriptSecurityManager::GetScriptSecurityManager(); + + nsAutoCString spec(GetParam().spec); + nsCOMPtr<nsIPrincipal> principal; + nsresult rv = + ssm->CreateContentPrincipalFromOrigin(spec, getter_AddRefs(principal)); + ASSERT_EQ(rv, NS_OK); + + ASSERT_EQ(principal->GetIsIpAddress(), GetParam().expectIsIpAddress); +} + +static const PrincipalAttributesParam kAttributes[] = { + {nsAutoCString("https://mozilla.com"), false}, + {nsAutoCString("https://127.0.0.1"), true}, + {nsAutoCString("https://[::1]"), true}, +}; + +INSTANTIATE_TEST_SUITE_P(TestPrincipalAttributes, PrincipalAttributesTest, + ::testing::ValuesIn(kAttributes)); diff --git a/caps/tests/gtest/TestPrincipalSerialization.cpp b/caps/tests/gtest/TestPrincipalSerialization.cpp new file mode 100644 index 0000000000..e3abbdeebf --- /dev/null +++ b/caps/tests/gtest/TestPrincipalSerialization.cpp @@ -0,0 +1,215 @@ +/* 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 "gtest/gtest.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ContentPrincipal.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/SystemPrincipal.h" +#include "mozilla/ExpandedPrincipal.h" + +using mozilla::BasePrincipal; +using mozilla::ContentPrincipal; +using mozilla::NullPrincipal; +using mozilla::SystemPrincipal; + +// None of these tests work in debug due to assert guards +#ifndef MOZ_DEBUG + +// calling toJson() twice with the same string arg +// (ensure that we truncate correctly where needed) +TEST(PrincipalSerialization, ReusedJSONArgument) +{ + nsCOMPtr<nsIScriptSecurityManager> ssm = + nsScriptSecurityManager::GetScriptSecurityManager(); + + nsAutoCString spec("https://mozilla.com"); + nsCOMPtr<nsIPrincipal> principal; + nsresult rv = + ssm->CreateContentPrincipalFromOrigin(spec, getter_AddRefs(principal)); + ASSERT_EQ(rv, NS_OK); + + nsAutoCString JSON; + rv = BasePrincipal::Cast(principal)->ToJSON(JSON); + ASSERT_EQ(rv, NS_OK); + ASSERT_TRUE(JSON.EqualsLiteral("{\"1\":{\"0\":\"https://mozilla.com/\"}}")); + + nsAutoCString spec2("https://example.com"); + nsCOMPtr<nsIPrincipal> principal2; + rv = ssm->CreateContentPrincipalFromOrigin(spec2, getter_AddRefs(principal2)); + ASSERT_EQ(rv, NS_OK); + + // Reuse JSON without truncation to check the code is doing this + rv = BasePrincipal::Cast(principal2)->ToJSON(JSON); + ASSERT_EQ(rv, NS_OK); + ASSERT_TRUE(JSON.EqualsLiteral("{\"1\":{\"0\":\"https://example.com/\"}}")); +} + +// Assure that calling FromProperties() with an empty array list always returns +// a nullptr The exception here is SystemPrincipal which doesn't have fields but +// it also doesn't implement FromProperties These are overly cautious checks +// that we don't try to create a principal in reality FromProperties is only +// called with a populated array. +TEST(PrincipalSerialization, FromPropertiesEmpty) +{ + nsTArray<ContentPrincipal::KeyVal> resContent; + nsCOMPtr<nsIPrincipal> contentPrincipal = + ContentPrincipal::FromProperties(resContent); + ASSERT_EQ(nullptr, contentPrincipal); + + nsTArray<ExpandedPrincipal::KeyVal> resExpanded; + nsCOMPtr<nsIPrincipal> expandedPrincipal = + ExpandedPrincipal::FromProperties(resExpanded); + ASSERT_EQ(nullptr, expandedPrincipal); + + nsTArray<NullPrincipal::KeyVal> resNull; + nsCOMPtr<nsIPrincipal> nullprincipal = NullPrincipal::FromProperties(resNull); + ASSERT_EQ(nullptr, nullprincipal); +} + +// Double check that if we have two valid principals in a serialized JSON that +// nullptr is returned +TEST(PrincipalSerialization, TwoKeys) +{ + // Sanity check that this returns a system principal + nsCOMPtr<nsIPrincipal> systemPrincipal = + BasePrincipal::FromJSON("{\"3\":{}}"_ns); + ASSERT_EQ(BasePrincipal::Cast(systemPrincipal)->Kind(), + BasePrincipal::eSystemPrincipal); + + // Sanity check that this returns a content principal + nsCOMPtr<nsIPrincipal> contentPrincipal = + BasePrincipal::FromJSON("{\"1\":{\"0\":\"https://mozilla.com\"}}"_ns); + ASSERT_EQ(BasePrincipal::Cast(contentPrincipal)->Kind(), + BasePrincipal::eContentPrincipal); + + // Check both combined don't return a principal + nsCOMPtr<nsIPrincipal> combinedPrincipal = BasePrincipal::FromJSON( + "{\"1\":{\"0\":\"https://mozilla.com\"},\"3\":{}}"_ns); + ASSERT_EQ(nullptr, combinedPrincipal); +} + +#endif // ifndef MOZ_DEBUG + +TEST(PrincipalSerialization, ExpandedPrincipal) +{ + // Check basic Expandedprincipal works without OA + nsCOMPtr<nsIScriptSecurityManager> ssm = + nsScriptSecurityManager::GetScriptSecurityManager(); + + uint32_t length = 2; + nsTArray<nsCOMPtr<nsIPrincipal> > allowedDomains(length); + allowedDomains.SetLength(length); + + nsAutoCString spec("https://mozilla.com"); + nsCOMPtr<nsIPrincipal> principal; + nsresult rv = + ssm->CreateContentPrincipalFromOrigin(spec, getter_AddRefs(principal)); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(BasePrincipal::Cast(principal)->Kind(), + BasePrincipal::eContentPrincipal); + allowedDomains[0] = principal; + + nsAutoCString spec2("https://mozilla.org"); + nsCOMPtr<nsIPrincipal> principal2; + rv = ssm->CreateContentPrincipalFromOrigin(spec2, getter_AddRefs(principal2)); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(BasePrincipal::Cast(principal2)->Kind(), + BasePrincipal::eContentPrincipal); + allowedDomains[1] = principal2; + + OriginAttributes attrs; + RefPtr<ExpandedPrincipal> result = + ExpandedPrincipal::Create(allowedDomains, attrs); + ASSERT_EQ(BasePrincipal::Cast(result)->Kind(), + BasePrincipal::eExpandedPrincipal); + + nsAutoCString JSON; + rv = BasePrincipal::Cast(result)->ToJSON(JSON); + ASSERT_EQ(rv, NS_OK); + ASSERT_STREQ( + JSON.get(), + "{\"2\":{\"0\":\"eyIxIjp7IjAiOiJodHRwczovL21vemlsbGEuY29tLyJ9fQ==," + "eyIxIjp7IjAiOiJodHRwczovL21vemlsbGEub3JnLyJ9fQ==\"}}"); + + nsCOMPtr<nsIPrincipal> returnedPrincipal = BasePrincipal::FromJSON(JSON); + auto outPrincipal = BasePrincipal::Cast(returnedPrincipal); + ASSERT_EQ(outPrincipal->Kind(), BasePrincipal::eExpandedPrincipal); + + ASSERT_TRUE(outPrincipal->FastSubsumesIgnoringFPD(principal)); + ASSERT_TRUE(outPrincipal->FastSubsumesIgnoringFPD(principal2)); + + nsAutoCString specDev("https://mozilla.dev"); + nsCOMPtr<nsIPrincipal> principalDev; + rv = ssm->CreateContentPrincipalFromOrigin(specDev, + getter_AddRefs(principalDev)); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(BasePrincipal::Cast(principalDev)->Kind(), + BasePrincipal::eContentPrincipal); + + ASSERT_FALSE(outPrincipal->FastSubsumesIgnoringFPD(principalDev)); +} + +TEST(PrincipalSerialization, ExpandedPrincipalOA) +{ + // Check Expandedprincipal works with top level OA + nsCOMPtr<nsIScriptSecurityManager> ssm = + nsScriptSecurityManager::GetScriptSecurityManager(); + + uint32_t length = 2; + nsTArray<nsCOMPtr<nsIPrincipal> > allowedDomains(length); + allowedDomains.SetLength(length); + + nsAutoCString spec("https://mozilla.com"); + nsCOMPtr<nsIPrincipal> principal; + nsresult rv = + ssm->CreateContentPrincipalFromOrigin(spec, getter_AddRefs(principal)); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(BasePrincipal::Cast(principal)->Kind(), + BasePrincipal::eContentPrincipal); + allowedDomains[0] = principal; + + nsAutoCString spec2("https://mozilla.org"); + nsCOMPtr<nsIPrincipal> principal2; + rv = ssm->CreateContentPrincipalFromOrigin(spec2, getter_AddRefs(principal2)); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(BasePrincipal::Cast(principal2)->Kind(), + BasePrincipal::eContentPrincipal); + allowedDomains[1] = principal2; + + OriginAttributes attrs; + nsAutoCString suffix("^userContextId=1"); + bool ok = attrs.PopulateFromSuffix(suffix); + ASSERT_TRUE(ok); + + RefPtr<ExpandedPrincipal> result = + ExpandedPrincipal::Create(allowedDomains, attrs); + ASSERT_EQ(BasePrincipal::Cast(result)->Kind(), + BasePrincipal::eExpandedPrincipal); + + nsAutoCString JSON; + rv = BasePrincipal::Cast(result)->ToJSON(JSON); + ASSERT_EQ(rv, NS_OK); + ASSERT_STREQ( + JSON.get(), + "{\"2\":{\"0\":\"eyIxIjp7IjAiOiJodHRwczovL21vemlsbGEuY29tLyJ9fQ==," + "eyIxIjp7IjAiOiJodHRwczovL21vemlsbGEub3JnLyJ9fQ==\",\"1\":\"^" + "userContextId=1\"}}"); + + nsCOMPtr<nsIPrincipal> returnedPrincipal = BasePrincipal::FromJSON(JSON); + auto outPrincipal = BasePrincipal::Cast(returnedPrincipal); + ASSERT_EQ(outPrincipal->Kind(), BasePrincipal::eExpandedPrincipal); + + ASSERT_TRUE(outPrincipal->FastSubsumesIgnoringFPD(principal)); + ASSERT_TRUE(outPrincipal->FastSubsumesIgnoringFPD(principal2)); + + nsAutoCString specDev("https://mozilla.dev"); + nsCOMPtr<nsIPrincipal> principalDev; + rv = ssm->CreateContentPrincipalFromOrigin(specDev, + getter_AddRefs(principalDev)); + ASSERT_EQ(rv, NS_OK); + ASSERT_EQ(BasePrincipal::Cast(principalDev)->Kind(), + BasePrincipal::eContentPrincipal); + + ASSERT_FALSE(outPrincipal->FastSubsumesIgnoringFPD(principalDev)); +} diff --git a/caps/tests/gtest/TestRedirectChainURITruncation.cpp b/caps/tests/gtest/TestRedirectChainURITruncation.cpp new file mode 100644 index 0000000000..34c633499c --- /dev/null +++ b/caps/tests/gtest/TestRedirectChainURITruncation.cpp @@ -0,0 +1,231 @@ +/* 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 "gtest/gtest.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ContentPrincipal.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/SystemPrincipal.h" +#include "mozilla/ExpandedPrincipal.h" +#include "nsContentUtils.h" +#include "mozilla/LoadInfo.h" + +namespace mozilla { + +void checkPrincipalTruncation(nsIPrincipal* aPrincipal, + const nsACString& aExpectedSpec = ""_ns, + const nsTArray<nsCString>& aExpectedSpecs = {}) { + nsCOMPtr<nsIPrincipal> truncatedPrincipal = + net::CreateTruncatedPrincipal(aPrincipal); + ASSERT_TRUE(truncatedPrincipal); + + if (aPrincipal->IsSystemPrincipal()) { + ASSERT_TRUE(truncatedPrincipal->IsSystemPrincipal()); + return; + } + + if (aPrincipal->GetIsNullPrincipal()) { + nsCOMPtr<nsIPrincipal> precursorPrincipal = + aPrincipal->GetPrecursorPrincipal(); + + nsAutoCString principalSpecEnding("}"); + nsAutoCString expectedTestSpec(aExpectedSpec); + if (!aExpectedSpec.IsEmpty()) { + principalSpecEnding += "?"_ns; + expectedTestSpec += "/"_ns; + } + + if (precursorPrincipal) { + nsAutoCString precursorSpec; + precursorPrincipal->GetAsciiSpec(precursorSpec); + ASSERT_TRUE(precursorSpec.Equals(expectedTestSpec)); + } + + // NullPrincipals have UUIDs as part of their scheme i.e. + // moz-nullprincipal:{9bebdabb-828a-4284-8b00-432a968c6e42} + // To avoid having to know the UUID beforehand we check the principal's spec + // before and after the UUID + nsAutoCString principalSpec; + truncatedPrincipal->GetAsciiSpec(principalSpec); + ASSERT_TRUE(StringBeginsWith(principalSpec, "moz-nullprincipal:{"_ns)); + ASSERT_TRUE( + StringEndsWith(principalSpec, principalSpecEnding + aExpectedSpec)); + return; + } + + if (aPrincipal->GetIsExpandedPrincipal()) { + const nsTArray<nsCOMPtr<nsIPrincipal>>& truncatedAllowList = + BasePrincipal::Cast(truncatedPrincipal) + ->As<ExpandedPrincipal>() + ->AllowList(); + + for (size_t i = 0; i < aExpectedSpecs.Length(); ++i) { + nsAutoCString principalSpec; + truncatedAllowList[i]->GetAsciiSpec(principalSpec); + ASSERT_TRUE(principalSpec.Equals(aExpectedSpecs[i])); + } + return; + } + + if (aPrincipal->GetIsContentPrincipal()) { + nsAutoCString principalSpec; + truncatedPrincipal->GetAsciiSpec(principalSpec); + ASSERT_TRUE(principalSpec.Equals(aExpectedSpec)); + return; + } + + // Tests should not reach this point + ADD_FAILURE(); +} + +void checkPrincipalTruncation(nsIPrincipal* aPrincipal, + const nsTArray<nsCString>& aExpectedSpecs = {}) { + checkPrincipalTruncation(aPrincipal, ""_ns, aExpectedSpecs); +} + +TEST(RedirectChainURITruncation, ContentPrincipal) +{ + // ======================= HTTP Scheme ======================= + nsAutoCString httpSpec( + "http://root:toor@www.example.com:200/foo/bar/baz.html?qux#thud"); + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), httpSpec); + ASSERT_EQ(rv, NS_OK); + + nsCOMPtr<nsIPrincipal> principal; + OriginAttributes attrs; + principal = BasePrincipal::CreateContentPrincipal(uri, attrs); + ASSERT_TRUE(principal); + + checkPrincipalTruncation(principal, + "http://www.example.com:200/foo/bar/baz.html"_ns); + + // ======================= HTTPS Scheme ======================= + nsAutoCString httpsSpec( + "https://root:toor@www.example.com:200/foo/bar/baz.html?qux#thud"); + rv = NS_NewURI(getter_AddRefs(uri), httpsSpec); + ASSERT_EQ(rv, NS_OK); + + principal = BasePrincipal::CreateContentPrincipal(uri, attrs); + ASSERT_TRUE(principal); + + checkPrincipalTruncation(principal, + "https://www.example.com:200/foo/bar/baz.html"_ns); + + // ======================= View Source Scheme ======================= + nsAutoCString viewSourceSpec( + "view-source:https://root:toor@www.example.com:200/foo/bar/" + "baz.html?qux#thud"); + rv = NS_NewURI(getter_AddRefs(uri), viewSourceSpec); + ASSERT_EQ(rv, NS_OK); + + principal = BasePrincipal::CreateContentPrincipal(uri, attrs); + ASSERT_TRUE(principal); + + checkPrincipalTruncation( + principal, "view-source:https://www.example.com:200/foo/bar/baz.html"_ns); + + // ======================= About Scheme ======================= + nsAutoCString aboutSpec("about:config"); + rv = NS_NewURI(getter_AddRefs(uri), aboutSpec); + ASSERT_EQ(rv, NS_OK); + + principal = BasePrincipal::CreateContentPrincipal(uri, attrs); + ASSERT_TRUE(principal); + + checkPrincipalTruncation(principal, "about:config"_ns); + + // ======================= Resource Scheme ======================= + nsAutoCString resourceSpec("resource://testing/"); + rv = NS_NewURI(getter_AddRefs(uri), resourceSpec); + ASSERT_EQ(rv, NS_OK); + + principal = BasePrincipal::CreateContentPrincipal(uri, attrs); + ASSERT_TRUE(principal); + + checkPrincipalTruncation(principal, "resource://testing/"_ns); + + // ======================= Chrome Scheme ======================= + nsAutoCString chromeSpec("chrome://foo/content/bar.xul"); + rv = NS_NewURI(getter_AddRefs(uri), chromeSpec); + ASSERT_EQ(rv, NS_OK); + + principal = BasePrincipal::CreateContentPrincipal(uri, attrs); + ASSERT_TRUE(principal); + + checkPrincipalTruncation(principal, "chrome://foo/content/bar.xul"_ns); +} + +TEST(RedirectChainURITruncation, NullPrincipal) +{ + // ======================= NullPrincipal ======================= + nsCOMPtr<nsIPrincipal> principal = + NullPrincipal::CreateWithoutOriginAttributes(); + ASSERT_TRUE(principal); + + checkPrincipalTruncation(principal, ""_ns); + + // ======================= NullPrincipal & Precursor ======================= + nsAutoCString precursorSpec( + "https://root:toor@www.example.com:200/foo/bar/baz.html?qux#thud"); + + nsCOMPtr<nsIURI> precursorURI; + nsresult rv = NS_NewURI(getter_AddRefs(precursorURI), precursorSpec); + ASSERT_EQ(rv, NS_OK); + + OriginAttributes attrs; + nsCOMPtr<nsIPrincipal> precursorPrincipal = + BasePrincipal::CreateContentPrincipal(precursorURI, attrs); + principal = NullPrincipal::CreateWithInheritedAttributes(precursorPrincipal); + ASSERT_TRUE(principal); + + checkPrincipalTruncation(principal, "https://www.example.com:200"_ns); +} + +TEST(RedirectChainURITruncation, SystemPrincipal) +{ + nsCOMPtr<nsIPrincipal> principal = nsContentUtils::GetSystemPrincipal(); + ASSERT_TRUE(principal); + + checkPrincipalTruncation(principal, ""_ns); +} + +TEST(RedirectChainURITruncation, ExtendedPrincipal) +{ + // ======================= HTTP Scheme ======================= + nsAutoCString httpSpec( + "http://root:toor@www.example.com:200/foo/bar/baz.html?qux#thud"); + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), httpSpec); + ASSERT_EQ(rv, NS_OK); + + nsCOMPtr<nsIPrincipal> firstContentPrincipal; + OriginAttributes attrs; + firstContentPrincipal = BasePrincipal::CreateContentPrincipal(uri, attrs); + ASSERT_TRUE(firstContentPrincipal); + + // ======================= HTTPS Scheme ======================= + nsCOMPtr<nsIPrincipal> secondContentPrincipal; + nsAutoCString httpsSpec( + "https://root:toor@www.example.com:200/foo/bar/baz.html?qux#thud"); + rv = NS_NewURI(getter_AddRefs(uri), httpsSpec); + ASSERT_EQ(rv, NS_OK); + + secondContentPrincipal = BasePrincipal::CreateContentPrincipal(uri, attrs); + ASSERT_TRUE(secondContentPrincipal); + + // ======================= ExpandedPrincipal ======================= + const nsTArray<nsCString>& expectedSpecs = { + "http://www.example.com:200/foo/bar/baz.html"_ns, + "https://www.example.com:200/foo/bar/baz.html"_ns, + }; + nsTArray<nsCOMPtr<nsIPrincipal>> allowList = {firstContentPrincipal, + secondContentPrincipal}; + nsCOMPtr<nsIPrincipal> principal = + ExpandedPrincipal::Create(allowList, attrs); + ASSERT_TRUE(principal); + + checkPrincipalTruncation(principal, expectedSpecs); +} + +} // namespace mozilla diff --git a/caps/tests/gtest/moz.build b/caps/tests/gtest/moz.build new file mode 100644 index 0000000000..e65c3bce75 --- /dev/null +++ b/caps/tests/gtest/moz.build @@ -0,0 +1,20 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +UNIFIED_SOURCES += [ + "TestBackgroundThreadPrincipal.cpp", + "TestNullPrincipalPrecursor.cpp", + "TestOriginAttributes.cpp", + "TestPrincipalAttributes.cpp", + "TestPrincipalSerialization.cpp", + "TestRedirectChainURITruncation.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +REQUIRES_UNIFIED_BUILD = True diff --git a/caps/tests/mochitest/browser.ini b/caps/tests/mochitest/browser.ini new file mode 100644 index 0000000000..a1c76eb57b --- /dev/null +++ b/caps/tests/mochitest/browser.ini @@ -0,0 +1,2 @@ +[browser_checkloaduri.js] +[browser_aboutOrigin.js] diff --git a/caps/tests/mochitest/browser_aboutOrigin.js b/caps/tests/mochitest/browser_aboutOrigin.js new file mode 100644 index 0000000000..fc2e2d8f53 --- /dev/null +++ b/caps/tests/mochitest/browser_aboutOrigin.js @@ -0,0 +1,12 @@ +"use strict"; + +let tests = ["about:robots?foo", "about:robots#foo", "about:robots?foo#bar"]; +tests.forEach(async test => { + add_task(async () => { + await BrowserTestUtils.withNewTab(test, async browser => { + await SpecialPowers.spawn(browser, [], () => { + is(content.document.nodePrincipal.origin, "about:robots"); + }); + }); + }); +}); diff --git a/caps/tests/mochitest/browser_checkloaduri.js b/caps/tests/mochitest/browser_checkloaduri.js new file mode 100644 index 0000000000..724fdc6769 --- /dev/null +++ b/caps/tests/mochitest/browser_checkloaduri.js @@ -0,0 +1,383 @@ +"use strict"; + +let ssm = Services.scriptSecurityManager; +// This will show a directory listing, but we never actually load these so that's OK. +const kDummyPage = getRootDirectory(gTestPath); + +const kAboutPagesRegistered = Promise.all([ + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-chrome-privs", + kDummyPage, + Ci.nsIAboutModule.ALLOW_SCRIPT + ), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-chrome-privs2", + kDummyPage, + Ci.nsIAboutModule.ALLOW_SCRIPT + ), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-unknown-linkable", + kDummyPage, + Ci.nsIAboutModule.MAKE_LINKABLE | Ci.nsIAboutModule.ALLOW_SCRIPT + ), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-unknown-linkable2", + kDummyPage, + Ci.nsIAboutModule.MAKE_LINKABLE | Ci.nsIAboutModule.ALLOW_SCRIPT + ), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-unknown-unlinkable", + kDummyPage, + Ci.nsIAboutModule.ALLOW_SCRIPT + ), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-unknown-unlinkable2", + kDummyPage, + Ci.nsIAboutModule.ALLOW_SCRIPT + ), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-content-unlinkable", + kDummyPage, + Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | + Ci.nsIAboutModule.ALLOW_SCRIPT + ), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-content-unlinkable2", + kDummyPage, + Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | + Ci.nsIAboutModule.ALLOW_SCRIPT + ), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-content-linkable", + kDummyPage, + Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | + Ci.nsIAboutModule.MAKE_LINKABLE | + Ci.nsIAboutModule.ALLOW_SCRIPT + ), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-content-linkable2", + kDummyPage, + Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT | + Ci.nsIAboutModule.MAKE_LINKABLE | + Ci.nsIAboutModule.ALLOW_SCRIPT + ), +]); + +const URLs = new Map([ + [ + "http://www.example.com", + [ + // For each of these entries, the booleans represent whether the parent URI can: + // - load them + // - load them without principal inheritance + // - whether the URI can be created at all (some protocol handlers will + // refuse to create certain variants) + ["http://www.example2.com", true, true, true], + ["https://www.example2.com", true, true, true], + ["moz-icon:file:///foo/bar/baz.exe", false, false, true], + ["moz-icon://.exe", false, false, true], + ["chrome://foo/content/bar.xul", false, false, true], + ["view-source:http://www.example2.com", false, false, true], + ["view-source:https://www.example2.com", false, false, true], + ["data:text/html,Hi", true, false, true], + ["view-source:data:text/html,Hi", false, false, true], + ["javascript:alert('hi')", true, false, true], + ["moz://a", false, false, true], + ["about:test-chrome-privs", false, false, true], + ["about:test-unknown-unlinkable", false, false, true], + ["about:test-content-unlinkable", false, false, true], + ["about:test-content-linkable", true, true, true], + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable", false, false, true], + ], + ], + [ + "view-source:http://www.example.com", + [ + ["http://www.example2.com", true, true, true], + ["https://www.example2.com", true, true, true], + ["moz-icon:file:///foo/bar/baz.exe", false, false, true], + ["moz-icon://.exe", false, false, true], + ["chrome://foo/content/bar.xul", false, false, true], + ["view-source:http://www.example2.com", true, true, true], + ["view-source:https://www.example2.com", true, true, true], + ["data:text/html,Hi", true, false, true], + ["view-source:data:text/html,Hi", true, false, true], + ["javascript:alert('hi')", true, false, true], + ["moz://a", false, false, true], + ["about:test-chrome-privs", false, false, true], + ["about:test-unknown-unlinkable", false, false, true], + ["about:test-content-unlinkable", false, false, true], + ["about:test-content-linkable", true, true, true], + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable", false, false, true], + ], + ], + // about: related tests. + [ + "about:test-chrome-privs", + [ + ["about:test-chrome-privs", true, true, true], + ["about:test-chrome-privs2", true, true, true], + ["about:test-chrome-privs2?foo#bar", true, true, true], + ["about:test-chrome-privs2?foo", true, true, true], + ["about:test-chrome-privs2#bar", true, true, true], + + ["about:test-unknown-unlinkable", true, true, true], + + ["about:test-content-unlinkable", true, true, true], + ["about:test-content-unlinkable?foo", true, true, true], + ["about:test-content-unlinkable?foo#bar", true, true, true], + ["about:test-content-unlinkable#bar", true, true, true], + + ["about:test-content-linkable", true, true, true], + + ["about:test-unknown-linkable", true, true, true], + ["moz-icon:file:///foo/bar/baz.exe", true, true, true], + ["moz-icon://.exe", true, true, true], + ], + ], + [ + "about:test-unknown-unlinkable", + [ + ["about:test-chrome-privs", false, false, true], + + // Can link to ourselves: + ["about:test-unknown-unlinkable", true, true, true], + // Can't link to unlinkable content if we're not sure it's privileged: + ["about:test-unknown-unlinkable2", false, false, true], + + ["about:test-content-unlinkable", true, true, true], + ["about:test-content-unlinkable2", true, true, true], + ["about:test-content-unlinkable2?foo", true, true, true], + ["about:test-content-unlinkable2?foo#bar", true, true, true], + ["about:test-content-unlinkable2#bar", true, true, true], + + ["about:test-content-linkable", true, true, true], + + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable", false, false, true], + ], + ], + [ + "about:test-content-unlinkable", + [ + ["about:test-chrome-privs", false, false, true], + + // Can't link to unlinkable content if we're not sure it's privileged: + ["about:test-unknown-unlinkable", false, false, true], + + ["about:test-content-unlinkable", true, true, true], + ["about:test-content-unlinkable2", true, true, true], + ["about:test-content-unlinkable2?foo", true, true, true], + ["about:test-content-unlinkable2?foo#bar", true, true, true], + ["about:test-content-unlinkable2#bar", true, true, true], + + ["about:test-content-linkable", true, true, true], + ["about:test-unknown-linkable", false, false, true], + ], + ], + [ + "about:test-unknown-linkable", + [ + ["about:test-chrome-privs", false, false, true], + + // Linkable content can't link to unlinkable content. + ["about:test-unknown-unlinkable", false, false, true], + + ["about:test-content-unlinkable", false, false, true], + ["about:test-content-unlinkable2", false, false, true], + ["about:test-content-unlinkable2?foo", false, false, true], + ["about:test-content-unlinkable2?foo#bar", false, false, true], + ["about:test-content-unlinkable2#bar", false, false, true], + + // ... but it can link to other linkable content. + ["about:test-content-linkable", true, true, true], + + // Can link to ourselves: + ["about:test-unknown-linkable", true, true, true], + + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable2", false, false, true], + ], + ], + [ + "about:test-content-linkable", + [ + ["about:test-chrome-privs", false, false, true], + + // Linkable content can't link to unlinkable content. + ["about:test-unknown-unlinkable", false, false, true], + + ["about:test-content-unlinkable", false, false, true], + + // ... but it can link to itself and other linkable content. + ["about:test-content-linkable", true, true, true], + ["about:test-content-linkable2", true, true, true], + + // Because this page doesn't have SAFE_FOR_UNTRUSTED, the web can't link to it: + ["about:test-unknown-linkable", false, false, true], + ], + ], +]); + +function testURL( + source, + target, + canLoad, + canLoadWithoutInherit, + canCreate, + flags +) { + function getPrincipalDesc(principal) { + if (principal.spec != "") { + return principal.spec; + } + if (principal.isSystemPrincipal) { + return "system principal"; + } + if (principal.isNullPrincipal) { + return "null principal"; + } + return "unknown principal"; + } + let threw = false; + let targetURI; + try { + targetURI = Services.io.newURI(target); + } catch (ex) { + ok( + !canCreate, + "Shouldn't be passing URIs that we can't create. Failed to create: " + + target + ); + return; + } + ok( + canCreate, + "Created a URI for " + + target + + " which should " + + (canCreate ? "" : "not ") + + "be possible." + ); + try { + ssm.checkLoadURIWithPrincipal(source, targetURI, flags); + } catch (ex) { + info(ex.message); + threw = true; + } + let inheritDisallowed = flags & ssm.DISALLOW_INHERIT_PRINCIPAL; + let shouldThrow = inheritDisallowed ? !canLoadWithoutInherit : !canLoad; + ok( + threw == shouldThrow, + "Should " + + (shouldThrow ? "" : "not ") + + "throw an error when loading " + + target + + " from " + + getPrincipalDesc(source) + + (inheritDisallowed ? " without" : " with") + + " principal inheritance." + ); +} + +add_task(async function() { + // In this test we want to verify both http and https load + // restrictions, hence we explicitly switch off the https-first + // upgrading mechanism. + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first", false]], + }); + + await kAboutPagesRegistered; + let baseFlags = ssm.STANDARD | ssm.DONT_REPORT_ERRORS; + for (let [sourceString, targetsAndExpectations] of URLs) { + let source; + if (sourceString.startsWith("about:test-chrome-privs")) { + source = ssm.getSystemPrincipal(); + } else { + source = ssm.createContentPrincipal(Services.io.newURI(sourceString), {}); + } + for (let [ + target, + canLoad, + canLoadWithoutInherit, + canCreate, + ] of targetsAndExpectations) { + testURL( + source, + target, + canLoad, + canLoadWithoutInherit, + canCreate, + baseFlags + ); + testURL( + source, + target, + canLoad, + canLoadWithoutInherit, + canCreate, + baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL + ); + } + } + + // Now test blob URIs, which we need to do in-content. + await BrowserTestUtils.withNewTab("http://www.example.com/", async function( + browser + ) { + await SpecialPowers.spawn(browser, [testURL.toString()], async function( + testURLFn + ) { + // eslint-disable-next-line no-shadow , no-eval + let testURL = eval("(" + testURLFn + ")"); + // eslint-disable-next-line no-shadow + let ssm = Services.scriptSecurityManager; + // eslint-disable-next-line no-shadow + let baseFlags = ssm.STANDARD | ssm.DONT_REPORT_ERRORS; + // eslint-disable-next-line no-unused-vars + let b = new content.Blob(["I am a blob"]); + let contentBlobURI = content.URL.createObjectURL(b); + let contentPrincipal = content.document.nodePrincipal; + // Loading this blob URI from the content page should work: + testURL(contentPrincipal, contentBlobURI, true, true, true, baseFlags); + testURL( + contentPrincipal, + contentBlobURI, + true, + true, + true, + baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL + ); + + testURL( + contentPrincipal, + "view-source:" + contentBlobURI, + false, + false, + true, + baseFlags + ); + testURL( + contentPrincipal, + "view-source:" + contentBlobURI, + false, + false, + true, + baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL + ); + }); + }); +}); diff --git a/caps/tests/mochitest/chrome.ini b/caps/tests/mochitest/chrome.ini new file mode 100644 index 0000000000..776afa34e4 --- /dev/null +++ b/caps/tests/mochitest/chrome.ini @@ -0,0 +1,12 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + file_data.txt + file_disableScript.html + !/caps/tests/mochitest/file_data.txt + !/caps/tests/mochitest/file_disableScript.html + +[test_bug995943.xhtml] +skip-if = (verify && debug && (os == 'mac')) +[test_addonMayLoad.html] +[test_disableScript.xhtml] diff --git a/caps/tests/mochitest/file_bug1367586-followon.html b/caps/tests/mochitest/file_bug1367586-followon.html new file mode 100644 index 0000000000..3b648ce746 --- /dev/null +++ b/caps/tests/mochitest/file_bug1367586-followon.html @@ -0,0 +1 @@ +<body>Follow-on navigation content</body> diff --git a/caps/tests/mochitest/file_bug1367586-redirect.sjs b/caps/tests/mochitest/file_bug1367586-redirect.sjs new file mode 100644 index 0000000000..12a980155f --- /dev/null +++ b/caps/tests/mochitest/file_bug1367586-redirect.sjs @@ -0,0 +1,8 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 302, "Moved"); + aResponse.setHeader( + "Location", + "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-target.html" + ); + aResponse.write("To be redirected to target"); +} diff --git a/caps/tests/mochitest/file_bug1367586-target.html b/caps/tests/mochitest/file_bug1367586-target.html new file mode 100644 index 0000000000..e2a2fde20d --- /dev/null +++ b/caps/tests/mochitest/file_bug1367586-target.html @@ -0,0 +1,6 @@ +<head><script> +window.addEventListener("pageshow", function(event) { + parent.ok(!event.persisted, "Should not load from bfcache"); +}); +</script></head> +<body>Redirect target content</body> diff --git a/caps/tests/mochitest/file_data.txt b/caps/tests/mochitest/file_data.txt new file mode 100644 index 0000000000..26d7bd8488 --- /dev/null +++ b/caps/tests/mochitest/file_data.txt @@ -0,0 +1 @@ +server data fetched over XHR diff --git a/caps/tests/mochitest/file_disableScript.html b/caps/tests/mochitest/file_disableScript.html new file mode 100644 index 0000000000..f4888cd586 --- /dev/null +++ b/caps/tests/mochitest/file_disableScript.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> +<script> +var gFiredOnload = false; +var gFiredOnclick = false; +</script> +</head> +<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;"> +</body> +</html> diff --git a/caps/tests/mochitest/mochitest.ini b/caps/tests/mochitest/mochitest.ini new file mode 100644 index 0000000000..204f637898 --- /dev/null +++ b/caps/tests/mochitest/mochitest.ini @@ -0,0 +1,16 @@ +[DEFAULT] +support-files = + file_bug1367586-followon.html + file_bug1367586-redirect.sjs + file_bug1367586-target.html + file_data.txt + file_disableScript.html + !/js/xpconnect/tests/mochitest/file_empty.html + +[test_bug246699.html] +[test_bug292789.html] +skip-if = os == 'android' +[test_bug423375.html] +[test_bug470804.html] +[test_bug1367586.html] +[test_disallowInheritPrincipal.html] diff --git a/caps/tests/mochitest/resource_test_file.html b/caps/tests/mochitest/resource_test_file.html new file mode 100644 index 0000000000..8201bd70e0 --- /dev/null +++ b/caps/tests/mochitest/resource_test_file.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<html><head><title>resource test file</title></head><body></body></html> diff --git a/caps/tests/mochitest/test_addonMayLoad.html b/caps/tests/mochitest/test_addonMayLoad.html new file mode 100644 index 0000000000..88b702e889 --- /dev/null +++ b/caps/tests/mochitest/test_addonMayLoad.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1180921 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1180921</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1180921 **/ + let module = Cu.getGlobalForObject(Services); + let ssm = Services.scriptSecurityManager; + + function StubPolicy(id, subdomain) { + /* globals MatchPatternSet */ + return new module.WebExtensionPolicy({ + id, + mozExtensionHostname: id, + baseURL: `file:///{id}`, + + allowedOrigins: new MatchPatternSet([`*://${subdomain}.example.org/*`]), + localizeCallback(string) {}, + }); + } + + /* globals WebExtensionPolicy */ + let policyA = StubPolicy("addona", "test1"); + let policyB = StubPolicy("addonb", "test2"); + policyA.active = true; + policyB.active = true; + + SimpleTest.waitForExplicitFinish(); + SimpleTest.registerCleanupFunction(function() { + policyA.active = false; + policyB.active = false; + }); + + function tryLoad(sb, uri) { + let p = new Promise(function(resolve, reject) { + Cu.exportFunction(resolve, sb, { defineAs: "finish" }); + Cu.exportFunction(reject, sb, { defineAs: "error" }); + sb.eval("try { (function () { " + + " var xhr = new XMLHttpRequest();" + + " xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { finish(xhr.status == 200); } };" + + " xhr.open('GET', '" + uri + "', true);" + + " xhr.send();" + + "})() } catch (e) { error(e); }"); + }); + return p; + } + + let addonA = new Cu.Sandbox(ssm.createContentPrincipal(Services.io.newURI("moz-extension://addonA/"), {}), + {wantGlobalProperties: ["XMLHttpRequest"]}); + let addonB = new Cu.Sandbox(ssm.createContentPrincipal(Services.io.newURI("moz-extension://addonB/"), {}), + {wantGlobalProperties: ["XMLHttpRequest"]}); + + function uriForDomain(d) { return d + "/tests/caps/tests/mochitest/file_data.txt"; } + + tryLoad(addonA, uriForDomain("http://test4.example.org")) + .then(function(success) { + ok(!success, "cross-origin load should fail for addon A"); + return tryLoad(addonA, uriForDomain("http://test1.example.org")); + }).then(function(success) { + ok(success, "allowlisted cross-origin load of test1 should succeed for addon A"); + return tryLoad(addonB, uriForDomain("http://test1.example.org")); + }).then(function(success) { + ok(!success, "non-allowlisted cross-origin load of test1 should fail for addon B"); + return tryLoad(addonB, uriForDomain("http://test2.example.org")); + }).then(function(success) { + ok(success, "allowlisted cross-origin load of test2 should succeed for addon B"); + return tryLoad(addonA, uriForDomain("http://test2.example.org")); + }).then(function(success) { + ok(!success, "non-allowlisted cross-origin load of test2 should fail for addon A"); + SimpleTest.finish(); + }, function(e) { + ok(false, "Rejected promise chain: " + e); + SimpleTest.finish(); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1180921">Mozilla Bug 1180921</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_bug1367586.html b/caps/tests/mochitest/test_bug1367586.html new file mode 100644 index 0000000000..d95693c94d --- /dev/null +++ b/caps/tests/mochitest/test_bug1367586.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1367586 +--> +<head> + <title>Test for Bug 1367586</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe id="load-frame"></iframe> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var frm = document.getElementById("load-frame"); +var step = 0; + +window.addEventListener("load", () => { + frm.contentWindow.location = "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-redirect.sjs"; + frm.addEventListener("load", function() { + ++step; + SimpleTest.executeSoon((function(_step, _frm) { + switch (_step) { + case 1: + is(_frm.contentWindow.location.href, "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-target.html", + "Redirected to the expected target in step 1"); + _frm.contentWindow.location = "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-followon.html"; + break; + case 2: + is(_frm.contentWindow.location.href, "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-followon.html", + "Navigated to the expected URL in step 2"); + _frm.contentWindow.history.back(); + break; + case 3: + is(_frm.contentWindow.location.href, "http://mochi.test:8888/tests/caps/tests/mochitest/file_bug1367586-target.html", + "Seeing the correct URL when navigating back in step 3"); + SimpleTest.finish(); + break; + } + }).bind(window, step, frm)); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_bug246699.html b/caps/tests/mochitest/test_bug246699.html new file mode 100644 index 0000000000..13c92e3743 --- /dev/null +++ b/caps/tests/mochitest/test_bug246699.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=246699 +--> +<head> + <title>Test for Bug 246699</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=246699">Mozilla Bug 246699</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe id="load-frame"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** + ** Test for Bug 246699 + ** (should produce stack information for caps errors) + **/ +function isError(e) { + return e.constructor.name === "Error" || e.constructor.name === "TypeError"; +} + +function hasStack(e) { + return isError(e) && /inciteCaps/.test(e.stack); +} + +function inciteCaps(f) { + try { + f(); + return "operation succeeded"; + } catch (e) { + if (hasStack(e)) { + return "denied-stack"; + } + return "unexpected: " + e; + } +} + +function tryChromeLoad() { + window.frames[0].location = "chrome://global/content/mozilla.html"; +} + +function tryComponentsClasses() { + return SpecialPowers.unwrap(SpecialPowers.Cc)["@mozilla.org/dummy;1"]; +} + + +is(inciteCaps(tryChromeLoad), "denied-stack", + "should get stack for content-loading-chrome rejection"); +is(inciteCaps(tryComponentsClasses), "denied-stack", + "should get stack for SpecialPowers.Components.classes rejection"); +</script> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_bug292789.html b/caps/tests/mochitest/test_bug292789.html new file mode 100644 index 0000000000..d386719448 --- /dev/null +++ b/caps/tests/mochitest/test_bug292789.html @@ -0,0 +1,121 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=292789 +--> +<head> + <title>Test for Bug 292789</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=292789">Mozilla Bug 292789</a> +<p id="display"></p> +<div id="content" style="display: none"> + <script src="chrome://global/content/treeUtils.js"></script> + <script type="application/javascript" src="chrome://mozapps/content/update/history.js"></script> + <script id="resjs" type="application/javascript"></script> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 292789 + ** + ** Selectively allow access to allowlisted chrome packages + ** even for ALLOW_CHROME mechanisms (<script>, <img> etc) + **/ + +/* import-globals-from ../../../toolkit/content/treeUtils.js */ +/* import-globals-from ../../../toolkit/mozapps/update/content/history.js */ + +SimpleTest.waitForExplicitFinish(); + +let ChromeUtils = { + import() { return {}; }, +}; + +/** <script src=""> test **/ +function testScriptSrc(aCallback) { + is(typeof gTreeUtils.sort, "function", + "content can still load <script> from chrome://global"); + + /** Try to find an export from history.js. We will find it if it is + ** improperly not blocked, otherwise it will be "undefined". + **/ + is(typeof gUpdateHistory, "undefined", + "content should not be able to load <script> from chrome://mozapps"); + + /** make sure the last one didn't pass because someone + ** moved history.js + **/ + var resjs = document.getElementById("resjs"); + resjs.onload = scriptOnload; + resjs.src = "resource://gre/chrome/toolkit/content/mozapps/update/history.js"; + document.getElementById("content").appendChild(resjs); + + function scriptOnload() { + is(typeof gUpdateHistory.onLoad, "function", + "history.js has not moved unexpectedly"); + + // trigger the callback + if (aCallback) + aCallback(); + } +} + +/** <img src=""> tests **/ +var img_global = "chrome://global/skin/media/error.png"; +var img_mozapps = "chrome://mozapps/skin/extensions/extensionGeneric.svg"; +var res_mozapps = "resource://gre/chrome/toolkit/skin/classic/mozapps/extensions/extensionGeneric.svg"; + +var imgTests = [[img_global, "success"], + [img_mozapps, "fail"], + [res_mozapps, "success"]]; + +var curImgTest = 0; + +function runImgTest() { + var test = imgTests[curImgTest++]; + var callback = curImgTest == imgTests.length ? finishTest : runImgTest; + loadImage(test[0], test[1], callback); +} + +function finishTest() { + SimpleTest.finish(); +} + +function fail(event) { + is("fail", event.target.expected, + "content should not be allowed to load " + event.target.src); + if (event.target.callback) + event.target.callback(); +} + +function success(event) { + is("success", event.target.expected, + "content should be able to load " + event.target.src); + if (event.target.callback) + event.target.callback(); +} + +function loadImage(uri, expect, callback) { + var img = document.createElement("img"); + img.onerror = fail; + img.onload = success; + img.expected = expect; + img.callback = callback; + img.src = uri; + // document.getElementById("content").appendChild(img); +} + +// Start off the script src test, and have it start the img tests when complete. +// Temporarily allow content to access all resource:// URIs. +SpecialPowers.pushPrefEnv({ + set: [ + ["security.all_resource_uri_content_accessible", true], + ], +}, () => testScriptSrc(runImgTest)); +</script> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_bug423375.html b/caps/tests/mochitest/test_bug423375.html new file mode 100644 index 0000000000..3d73b0b874 --- /dev/null +++ b/caps/tests/mochitest/test_bug423375.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=423375 +--> +<head> + <title>Test for Bug 423375</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=423375">Mozilla Bug 423375</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe id="load-frame"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** + ** Test for Bug 423375 + ** (content shouldn't be able to load chrome: or resource:) + **/ +function tryLoad(url) { + try { + window.frames[0].location = url; + return "loaded"; + } catch (e) { + if (/Access.*denied/.test(String(e))) { + return "denied"; + } + return "unexpected: " + e; + } +} + +is(tryLoad("chrome://global/content/mozilla.html"), "denied", + "content should have been prevented from loading chrome: URL"); +is(tryLoad("resource://gre-resources/html.css"), "denied", + "content should have been prevented from loading resource: URL"); +</script> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_bug470804.html b/caps/tests/mochitest/test_bug470804.html new file mode 100644 index 0000000000..a2d6c7e002 --- /dev/null +++ b/caps/tests/mochitest/test_bug470804.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=470804 +--> +<head> + <title>Test for Bug 470804</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=470804">Mozilla Bug 470804</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 470804 + Passing a null targetURL to checkLoadURIWithPrincipal shouldn't crash + **/ + +const nsIScriptSecurityManager = SpecialPowers.Ci.nsIScriptSecurityManager; +var secMan = SpecialPowers.Services.scriptSecurityManager; +var principal = SpecialPowers.wrap(document).nodePrincipal; +isnot(principal, undefined, "Should have a principal"); +isnot(principal, null, "Should have a non-null principal"); +is(principal.isSystemPrincipal, false, + "Shouldn't have system principal here"); +try { + secMan.checkLoadURIWithPrincipal(principal, null, + nsIScriptSecurityManager.STANDARD); +} catch (e) { + // throwing is fine, it's just crashing that's bad +} +ok(true, "Survival: we should get here without crashing"); +</script> +</pre> +</body> +</html> diff --git a/caps/tests/mochitest/test_bug995943.xhtml b/caps/tests/mochitest/test_bug995943.xhtml new file mode 100644 index 0000000000..41e9d2c7b8 --- /dev/null +++ b/caps/tests/mochitest/test_bug995943.xhtml @@ -0,0 +1,111 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=995943 +--> +<window title="Mozilla Bug 995943" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=995943" + target="_blank">Mozilla Bug 995943</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + function debug(msg) { info(msg); } + + /** Test for CAPS file:// URI prefs. **/ + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestCompleteLog(); + if (navigator.userAgent.includes("Mac OS X 10.10")) + SimpleTest.expectAssertions(5, 11); // See bug 1067022, 1307988 + else if (Services.appinfo.OS == "WINNT") + SimpleTest.expectAssertions(0, 1); // See bug 1067022 + else + SimpleTest.expectAssertions(0, 9); // See bug 1305241, 1145314 + + var rootdir = Services.appinfo.OS == "WINNT" ? "file:///C:" : "file:///"; + + function checkLoadFileURI(domain, shouldLoad) { + debug("Invoking checkLoadFileURI with domain: " + domain + ", shouldLoad: " + shouldLoad); + return new Promise(function(resolve, reject) { + $('ifr').addEventListener('load', function l1() { + debug("Invoked l1 for " + domain); + $('ifr').removeEventListener('load', l1); + function l2() { + debug("Invoked l2 for " + domain); + $('ifr').removeEventListener('load', l2); + ok(shouldLoad, "Successfully loaded file:// URI for domain: " + domain); + resolve(); + } + $('ifr').addEventListener('load', l2); + try { + window[0].wrappedJSObject.location = rootdir; + debug("Successfully navigated for " + domain); + } catch (e) { + ok(!shouldLoad && /denied|insecure/.test(e), + "Prevented loading of file:// URI for domain: " + domain + " - " + e); + $('ifr').removeEventListener('load', l2); + resolve(); + } + }); + let targetURI = domain + '/tests/js/xpconnect/tests/mochitest/file_empty.html'; + debug("Navigating iframe to " + targetURI); + $('ifr').contentWindow.location = targetURI; + }); + } + + function pushPrefs(prefs) { + return SpecialPowers.pushPrefEnv({ set: prefs }); + } + + function popPrefs() { + return new Promise(function(resolve) { SpecialPowers.popPrefEnv(resolve); }); + } + + var gGoCount = 0; + function go() { + debug("Invoking go for window with id: " + window.windowGlobalChild.innerWindowId); + is(++gGoCount, 1, "Should only call go once!"); + checkLoadFileURI('http://example.com', false).then( + pushPrefs.bind(null, [['capability.policy.policynames', ' somepolicy '], + ['capability.policy.somepolicy.checkloaduri.enabled', 'AlLAcCeSs'], + ['capability.policy.somepolicy.sites', 'http://example.com']])) + .then(checkLoadFileURI.bind(null, 'http://example.com', true)) + .then(popPrefs) + .then(checkLoadFileURI.bind(null, 'http://example.com', false)) + .then( + pushPrefs.bind(null, [['capability.policy.policynames', ',somepolicy, someotherpolicy, '], + ['capability.policy.somepolicy.checkloaduri.enabled', 'allaccess'], + ['capability.policy.someotherpolicy.checkloaduri.enabled', 'nope'], + ['capability.policy.somepolicy.sites', ' http://example.org test1.example.com https://test2.example.com '], + ['capability.policy.someotherpolicy.sites', 'http://example.net ']])) + .then(checkLoadFileURI.bind(null, 'http://example.org', true)) + .then(checkLoadFileURI.bind(null, 'http://test2.example.com', false)) + .then(checkLoadFileURI.bind(null, 'https://test2.example.com', true)) + .then(checkLoadFileURI.bind(null, 'http://sub1.test2.example.com', false)) + .then(checkLoadFileURI.bind(null, 'https://sub1.test2.example.com', true)) + .then(checkLoadFileURI.bind(null, 'http://example.net', false)) + .then(checkLoadFileURI.bind(null, 'http://test1.example.com', true)) + .then(checkLoadFileURI.bind(null, 'https://test1.example.com', true)) + .then(checkLoadFileURI.bind(null, 'http://sub1.test1.example.com', true)) + .then(checkLoadFileURI.bind(null, 'https://sub1.test1.example.com', true)) + .then(pushPrefs.bind(null, [['capability.policy.someotherpolicy.checkloaduri.enabled', 'allAccess']])) + .then(checkLoadFileURI.bind(null, 'http://example.net', true)) + .then(popPrefs) + .then(popPrefs) + .then(checkLoadFileURI.bind(null, 'http://example.net', false)) + .then(SimpleTest.finish.bind(SimpleTest)); + + } + addLoadEvent(go); + + ]]> + </script> + <iframe id="ifr" type="content" /> +</window> diff --git a/caps/tests/mochitest/test_disableScript.xhtml b/caps/tests/mochitest/test_disableScript.xhtml new file mode 100644 index 0000000000..3008eda43d --- /dev/null +++ b/caps/tests/mochitest/test_disableScript.xhtml @@ -0,0 +1,330 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=840488 +--> +<window title="Mozilla Bug 840488" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=840488" + target="_blank">Mozilla Bug 840488</a> + </body> + + <iframe id="root" name="root" type="content"/> + <iframe id="chromeFrame" name="chromeFrame" type="content"/> + + <!-- test code goes here --> + <script type="application/javascript"> + /* eslint-disable mozilla/no-useless-parameters, no-redeclare, no-undef */ + <![CDATA[ + + /** Test for all the different ways that script can be disabled for a given global. **/ + + SimpleTest.waitForExplicitFinish(); + const ssm = Services.scriptSecurityManager; + function makeURI(uri) { return Services.io.newURI(uri); } + const path = "/tests/caps/tests/mochitest/file_disableScript.html"; + const uri = "http://www.example.com" + path; + var rootFrame = document.getElementById('root'); + var chromeFrame = document.getElementById('chromeFrame'); + navigateFrame(rootFrame, uri + "?name=rootframe").then(function() { + navigateFrame(chromeFrame, "file_disableScript.html").then(go); + }); + + function navigateFrame(ifr, src) { + return new Promise(resolve => { + function onload() { + ifr.removeEventListener('load', onload); + resolve(); + } + ifr.addEventListener('load', onload, false); + ifr.setAttribute('src', src); + }); + } + + function navigateBack(ifr) { + return new Promise(resolve => { + + // pageshow events don't fire on the iframe element, so we need to use the + // chrome event handler for the docshell. + var browser = ifr.contentWindow.docShell.chromeEventHandler; + function onpageshow(evt) { + info("Navigated back. Persisted: " + evt.persisted); + browser.removeEventListener('pageshow', onpageshow); + resolve(); + } + browser.addEventListener('pageshow', onpageshow, false); + ifr.contentWindow.history.back(); + }); + } + + function addFrame(parentWin, name, expectOnload) { + let ifr = parentWin.document.createElement('iframe'); + parentWin.document.body.appendChild(ifr); + ifr.setAttribute('name', name); + return new Promise(resolve => { + // We need to append 'name' to avoid running afoul of recursive frame detection. + let frameURI = uri + "?name=" + name; + navigateFrame(ifr, frameURI).then(function() { + is(String(ifr.contentWindow.location), frameURI, "Successful load"); + is(!!ifr.contentWindow.wrappedJSObject.gFiredOnload, expectOnload, + "onload should only fire when scripts are enabled"); + resolve(); + }); + }); + } + + function checkScriptEnabled(win, expectEnabled) { + win.wrappedJSObject.gFiredOnclick = false; + win.document.body.dispatchEvent(new win.Event('click')); + is(win.wrappedJSObject.gFiredOnclick, expectEnabled, "Checking script-enabled for " + win.name + " (" + win.location + ")"); + } + + function setScriptEnabled(win, enabled) { + win.browsingContext.allowJavascript = enabled; + } + + function testList(expectEnabled, win, list, idx) { + idx = idx || 0; + return new Promise(resolve => { + let target = list[idx] + path; + info("Testing scriptability for: " + target + ". expecting " + expectEnabled); + navigateFrame(win.frameElement, target).then(function() { + checkScriptEnabled(win, expectEnabled); + if (idx == list.length - 1) + resolve(); + else + testList(expectEnabled, win, list, idx + 1).then(function() { resolve(); }); + }); + }); + } + + function testDomainPolicy(defaultScriptability, exceptions, superExceptions, + exempt, notExempt, set, superSet, win) { + // Populate our sets. + for (var e of exceptions) + set.add(makeURI(e)); + for (var e of superExceptions) + superSet.add(makeURI(e)); + + return testList(defaultScriptability, win, notExempt).then(function() { + return testList(!defaultScriptability, win, exempt); + }); + } + + function setScriptEnabledForBrowser(enabled) { + var prefname = "javascript.enabled"; + Services.prefs.setBoolPref(prefname, enabled); + } + + function reloadFrame(frame) { + return new Promise(resolve => { + frame.addEventListener('load', function onload() { + resolve(); + frame.removeEventListener('load', onload); + }, false); + frame.contentWindow.location.reload(true); + }); + } + + function go() { + var rootWin = rootFrame.contentWindow; + var chromeWin = chromeFrame.contentWindow; + + // Test simple docshell enable/disable. + checkScriptEnabled(rootWin, true); + setScriptEnabled(rootWin, false); + checkScriptEnabled(rootWin, false); + setScriptEnabled(rootWin, true); + checkScriptEnabled(rootWin, true); + + // Privileged frames are immune to docshell flags. + ok(chromeWin.document.nodePrincipal.isSystemPrincipal, "Sanity check for System Principal"); + setScriptEnabled(chromeWin, false); + checkScriptEnabled(chromeWin, true); + setScriptEnabled(chromeWin, true); + + // Play around with the docshell tree and make sure everything works as + // we expect. + addFrame(rootWin, 'parent', true).then(function() { + checkScriptEnabled(rootWin[0], true); + return addFrame(rootWin[0], 'childA', true); + }).then(function() { + checkScriptEnabled(rootWin[0][0], true); + setScriptEnabled(rootWin[0], false); + checkScriptEnabled(rootWin, true); + checkScriptEnabled(rootWin[0], false); + checkScriptEnabled(rootWin[0][0], false); + return addFrame(rootWin[0], 'childB', false); + }).then(function() { + checkScriptEnabled(rootWin[0][1], false); + setScriptEnabled(rootWin[0][0], false); + setScriptEnabled(rootWin[0], true); + checkScriptEnabled(rootWin[0], true); + checkScriptEnabled(rootWin[0][0], false); + setScriptEnabled(rootWin[0][0], true); + + // Flags are inherited from the parent docshell at attach time. Note that + // the flag itself is inherited, regardless of whether or not scripts are + // currently allowed on the parent (which could depend on the parent's + // parent). Check that. + checkScriptEnabled(rootWin[0][1], false); + setScriptEnabled(rootWin[0], false); + setScriptEnabled(rootWin[0][1], true); + return addFrame(rootWin[0][1], 'grandchild', false); + }).then(function() { + checkScriptEnabled(rootWin[0], false); + checkScriptEnabled(rootWin[0][1], false); + checkScriptEnabled(rootWin[0][1][0], false); + setScriptEnabled(rootWin[0], true); + checkScriptEnabled(rootWin[0], true); + checkScriptEnabled(rootWin[0][1], true); + checkScriptEnabled(rootWin[0][1][0], true); + + // Try navigating two frames, then munging docshell scriptability, then + // pulling the frames out of the bfcache to make sure that flags are + // properly propagated to inactive inner windows. We do this both for an + // 'own' docshell, as well as for an ancestor docshell. + return navigateFrame(rootWin[0][0].frameElement, rootWin[0][0].location + '-navigated'); + }).then(function() { return navigateFrame(rootWin[0][1][0].frameElement, rootWin[0][1][0].location + '-navigated'); }) + .then(function() { + checkScriptEnabled(rootWin[0][0], true); + checkScriptEnabled(rootWin[0][1][0], true); + setScriptEnabled(rootWin[0][0], false); + setScriptEnabled(rootWin[0][1], false); + checkScriptEnabled(rootWin[0][0], false); + checkScriptEnabled(rootWin[0][1][0], false); + return navigateBack(rootWin[0][0].frameElement); + }).then(function() { return navigateBack(rootWin[0][1][0].frameElement); }) + .then(function() { + checkScriptEnabled(rootWin[0][0], false); + checkScriptEnabled(rootWin[0][1][0], false); + + // Disable JS via the global pref pref. This is only guaranteed to have an effect + // for subsequent loads. + setScriptEnabledForBrowser(false); + return reloadFrame(rootFrame); + }).then(function() { + checkScriptEnabled(rootWin, false); + checkScriptEnabled(chromeWin, true); + setScriptEnabledForBrowser(true); + return reloadFrame(rootFrame); + }).then(function() { + checkScriptEnabled(rootWin, true); + + // Play around with dynamically blocking script for a given global. + // This takes effect immediately. + Cu.blockScriptForGlobal(rootWin); + Cu.blockScriptForGlobal(rootWin); + Cu.unblockScriptForGlobal(rootWin); + checkScriptEnabled(rootWin, false); + Cu.unblockScriptForGlobal(rootWin); + checkScriptEnabled(rootWin, true); + Cu.blockScriptForGlobal(rootWin); + try { + Cu.blockScriptForGlobal(chromeWin); + ok(false, "Should have thrown"); + } catch (e) { + ok(/may not be disabled/.test(e), + "Shouldn't be able to programmatically block script for system globals"); + } + return reloadFrame(rootFrame); + }).then(function() { + checkScriptEnabled(rootWin, true); + + // Test system-wide domain policy. This only takes effect for subsequently- + // loaded globals. + + // Check the basic semantics of the sets. + is(ssm.domainPolicyActive, false, "not enabled"); + window.policy = ssm.activateDomainPolicy(); + ok(policy instanceof Ci.nsIDomainPolicy, "Got a policy"); + try { + ssm.activateDomainPolicy(); + ok(false, "Should have thrown"); + } catch (e) { + ok(true, "can't have two live domain policies"); + } + var sbRef = policy.superBlocklist; + isnot(sbRef, null, "superBlocklist non-null"); + ok(!sbRef.contains(makeURI('http://www.example.com'))); + sbRef.add(makeURI('http://www.example.com/foopy')); + ok(sbRef.contains(makeURI('http://www.example.com'))); + sbRef.remove(makeURI('http://www.example.com')); + ok(!sbRef.contains(makeURI('http://www.example.com'))); + sbRef.add(makeURI('http://www.example.com/foopy/this.that/')); + ok(sbRef.contains(makeURI('http://www.example.com/baz'))); + ok(!sbRef.contains(makeURI('https://www.example.com'))); + ok(!sbRef.contains(makeURI('https://www.example.com:88'))); + ok(!sbRef.contains(makeURI('http://foo.www.example.com'))); + ok(sbRef.containsSuperDomain(makeURI('http://foo.www.example.com'))); + ok(sbRef.containsSuperDomain(makeURI('http://foo.bar.www.example.com'))); + ok(!sbRef.containsSuperDomain(makeURI('http://foo.bar.www.exxample.com'))); + ok(!sbRef.containsSuperDomain(makeURI('http://example.com'))); + ok(!sbRef.containsSuperDomain(makeURI('http://com/this.that/'))); + ok(!sbRef.containsSuperDomain(makeURI('https://foo.www.example.com'))); + ok(sbRef.contains(makeURI('http://www.example.com'))); + policy.deactivate(); + is(ssm.domainPolicyActive, false, "back to inactive"); + ok(!sbRef.contains(makeURI('http://www.example.com')), + "Disabling domain policy clears the set"); + policy = ssm.activateDomainPolicy(); + ok(policy.superBlocklist); + isnot(sbRef, policy.superBlocklist, "Mint new sets each time!"); + policy.deactivate(); + is(policy.blocklist, null, "blocklist nulled out"); + policy = ssm.activateDomainPolicy(); + isnot(policy.blocklist, null, "non-null again"); + isnot(policy.blocklist, sbRef, "freshly minted"); + policy.deactivate(); + + // + // Now, create and apply a mock-policy. We check the same policy both as + // a blocklist and as a allowlist. + // + + window.testPolicy = { + // The policy. + exceptions: ['http://test1.example.com', 'http://example.com'], + superExceptions: ['http://test2.example.org', 'https://test1.example.com'], + + // The testcases. + exempt: ['http://test1.example.com', 'http://example.com', + 'http://test2.example.org', 'http://sub1.test2.example.org', + 'https://sub1.test1.example.com'], + + notExempt: ['http://test2.example.com', 'http://sub1.test1.example.com', + 'http://www.example.com', 'https://test2.example.com', + 'https://example.com', 'http://test1.example.org'], + }; + + policy = ssm.activateDomainPolicy(); + info("Testing Blocklist-style Domain Policy"); + return testDomainPolicy(true, testPolicy.exceptions, + testPolicy.superExceptions, testPolicy.exempt, + testPolicy.notExempt, policy.blocklist, + policy.superBlocklist, rootWin); + }).then(function() { + policy.deactivate(); + policy = ssm.activateDomainPolicy(); + info("Testing Allowlist-style Domain Policy"); + setScriptEnabledForBrowser(false); + return testDomainPolicy(false, testPolicy.exceptions, + testPolicy.superExceptions, testPolicy.exempt, + testPolicy.notExempt, policy.allowlist, + policy.superAllowlist, rootWin); + }).then(function() { + setScriptEnabledForBrowser(true); + policy.deactivate(); + + SimpleTest.finish(); + }); + } + + ]]> + </script> +</window> diff --git a/caps/tests/mochitest/test_disallowInheritPrincipal.html b/caps/tests/mochitest/test_disallowInheritPrincipal.html new file mode 100644 index 0000000000..8f5a0b9353 --- /dev/null +++ b/caps/tests/mochitest/test_disallowInheritPrincipal.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=732413 +--> +<head> + <title>Test for Bug 732413</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=732413">Mozilla Bug 732413</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 732413 + Passing DISALLOW_INHERIT_PRINCIPAL flag should be effective even if + aPrincipal is the system principal. + **/ + +const nsIScriptSecurityManager = SpecialPowers.Ci.nsIScriptSecurityManager; +var secMan = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"] + .getService(nsIScriptSecurityManager); +var sysPrincipal = secMan.getSystemPrincipal(); +isnot(sysPrincipal, undefined, "Should have a principal"); +isnot(sysPrincipal, null, "Should have a non-null principal"); +is(sysPrincipal.isSystemPrincipal, true, + "Should have system principal here"); + + +var inheritingURI = SpecialPowers.Services.io.newURI("javascript:1+1"); + +// First try a normal call to checkLoadURIWithPrincipal +try { + secMan.checkLoadURIWithPrincipal(sysPrincipal, inheritingURI, + nsIScriptSecurityManager.STANDARD); + ok(true, "checkLoadURI allowed the load"); +} catch (e) { + ok(false, "checkLoadURI failed unexpectedly: " + e); +} + +// Now call checkLoadURIWithPrincipal with DISALLOW_INHERIT_PRINCIPAL +try { + secMan.checkLoadURIWithPrincipal(sysPrincipal, inheritingURI, + nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL); + ok(false, "checkLoadURI allowed the load unexpectedly"); +} catch (e) { + ok(true, "checkLoadURI prevented load of principal-inheriting URI"); +} + +</script> +</pre> +</body> +</html> diff --git a/caps/tests/unit/test_ipv6_host_literal.js b/caps/tests/unit/test_ipv6_host_literal.js new file mode 100644 index 0000000000..cde7d82d7e --- /dev/null +++ b/caps/tests/unit/test_ipv6_host_literal.js @@ -0,0 +1,38 @@ +var ssm = Services.scriptSecurityManager; +function makeURI(uri) { + return Services.io.newURI(uri); +} + +function testIPV6Host(aHost, aExpected) { + var ipv6Host = ssm.createContentPrincipal(makeURI(aHost), {}); + Assert.equal(ipv6Host.origin, aExpected); +} + +function run_test() { + testIPV6Host("http://[::1]/", "http://[::1]"); + + testIPV6Host( + "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/", + "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]" + ); + + testIPV6Host( + "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/", + "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443" + ); + + testIPV6Host( + "http://[2001:db8:85a3::1319:8a2e:370:7348]/", + "http://[2001:db8:85a3:0:1319:8a2e:370:7348]" + ); + + testIPV6Host( + "http://[20D1:0000:3238:DFE1:63:0000:0000:FEFB]/", + "http://[20d1:0:3238:dfe1:63::fefb]" + ); + + testIPV6Host( + "http://[20D1:0:3238:DFE1:63::FEFB]/", + "http://[20d1:0:3238:dfe1:63::fefb]" + ); +} diff --git a/caps/tests/unit/test_oa_partitionKey_pattern.js b/caps/tests/unit/test_oa_partitionKey_pattern.js new file mode 100644 index 0000000000..5d0625018f --- /dev/null +++ b/caps/tests/unit/test_oa_partitionKey_pattern.js @@ -0,0 +1,159 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests origin attributes partitionKey pattern matching. + */ + +"use strict"; + +function testMatch(oa, pattern, shouldMatch = true) { + let msg = `Origin attributes should ${ + shouldMatch ? "match" : "not match" + } pattern.`; + msg += ` oa: ${JSON.stringify(oa)} - pattern: ${JSON.stringify(pattern)}`; + Assert.equal( + ChromeUtils.originAttributesMatchPattern(oa, pattern), + shouldMatch, + msg + ); +} + +function getPartitionKey(scheme, baseDomain, port) { + if (!scheme || !baseDomain) { + return ""; + } + return `(${scheme},${baseDomain}${port ? `,${port}` : ``})`; +} + +function getOAWithPartitionKey(scheme, baseDomain, port, oa = {}) { + oa.partitionKey = getPartitionKey(scheme, baseDomain, port); + return oa; +} + +/** + * Tests that an OriginAttributesPattern which is empty or only has an empty + * partitionKeyPattern matches any partitionKey. + */ +add_task(async function test_empty_pattern_matches_any() { + let list = [ + getOAWithPartitionKey("https", "example.com"), + getOAWithPartitionKey("http", "example.net", 8080), + getOAWithPartitionKey(), + ]; + + for (let oa of list) { + testMatch(oa, {}); + testMatch(oa, { partitionKeyPattern: {} }); + } +}); + +/** + * Tests that if a partitionKeyPattern is passed, but the partitionKey is + * invalid, the pattern match will always fail. + */ +add_task(async function test_invalid_pk() { + let list = [ + "()", + "(,,)", + "(https)", + "(https,,)", + "(example.com)", + "(http,example.com,invalid)", + "(http,example.com,8000,1000)", + ].map(partitionKey => ({ partitionKey })); + + for (let oa of list) { + testMatch(oa, {}); + testMatch(oa, { partitionKeyPattern: {} }); + testMatch( + oa, + { partitionKeyPattern: { baseDomain: "example.com" } }, + false + ); + testMatch(oa, { partitionKeyPattern: { scheme: "https" } }, false); + } +}); + +/** + * Tests that if a pattern sets "partitionKey" it takes precedence over "partitionKeyPattern". + */ +add_task(async function test_string_overwrites_pattern() { + let oa = getOAWithPartitionKey("https", "example.com", 8080, { + userContextId: 2, + }); + + testMatch(oa, { partitionKey: oa.partitionKey }); + testMatch(oa, { + partitionKey: oa.partitionKey, + partitionKeyPattern: { baseDomain: "example.com" }, + }); + testMatch(oa, { + partitionKey: oa.partitionKey, + partitionKeyPattern: { baseDomain: "example.net" }, + }); + testMatch( + oa, + { + partitionKey: getPartitionKey("https", "example.net"), + partitionKeyPattern: { scheme: "https", baseDomain: "example.com" }, + }, + false + ); +}); + +/** + * Tests that we can match parts of a partitionKey by setting + * partitionKeyPattern. + */ +add_task(async function test_pattern() { + let a = getOAWithPartitionKey("https", "example.com", 8080, { + userContextId: 2, + }); + let b = getOAWithPartitionKey("https", "example.com", undefined, { + privateBrowsingId: 1, + }); + + for (let oa of [a, b]) { + // Match + testMatch(oa, { partitionKeyPattern: { scheme: "https" } }); + testMatch(oa, { + partitionKeyPattern: { scheme: "https", baseDomain: "example.com" }, + }); + testMatch( + oa, + { + partitionKeyPattern: { + scheme: "https", + baseDomain: "example.com", + port: 8080, + }, + }, + oa == a + ); + testMatch(oa, { + partitionKeyPattern: { baseDomain: "example.com" }, + }); + testMatch( + oa, + { + partitionKeyPattern: { port: 8080 }, + }, + oa == a + ); + + // Mismatch + testMatch(oa, { partitionKeyPattern: { scheme: "http" } }, false); + testMatch( + oa, + { partitionKeyPattern: { baseDomain: "example.net" } }, + false + ); + testMatch(oa, { partitionKeyPattern: { port: 8443 } }, false); + testMatch( + oa, + { partitionKeyPattern: { scheme: "https", baseDomain: "example.net" } }, + false + ); + } +}); diff --git a/caps/tests/unit/test_origin.js b/caps/tests/unit/test_origin.js new file mode 100644 index 0000000000..e5efaabea1 --- /dev/null +++ b/caps/tests/unit/test_origin.js @@ -0,0 +1,323 @@ +var ssm = Services.scriptSecurityManager; +function makeURI(uri) { + return Services.io.newURI(uri); +} + +function checkThrows(f) { + var threw = false; + try { + f(); + } catch (e) { + threw = true; + } + Assert.ok(threw); +} + +function checkCrossOrigin(a, b) { + Assert.ok(!a.equals(b)); + Assert.ok(!a.equalsConsideringDomain(b)); + Assert.ok(!a.subsumes(b)); + Assert.ok(!a.subsumesConsideringDomain(b)); + Assert.ok(!b.subsumes(a)); + Assert.ok(!b.subsumesConsideringDomain(a)); +} + +function checkOriginAttributes(prin, attrs, suffix) { + attrs = attrs || {}; + Assert.equal( + prin.originAttributes.inIsolatedMozBrowser, + attrs.inIsolatedMozBrowser || false + ); + Assert.equal(prin.originSuffix, suffix || ""); + Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), suffix || ""); + Assert.ok( + ChromeUtils.originAttributesMatchPattern(prin.originAttributes, attrs) + ); + if (!prin.isNullPrincipal && !prin.origin.startsWith("[")) { + Assert.ok(ssm.createContentPrincipalFromOrigin(prin.origin).equals(prin)); + } else { + checkThrows(() => ssm.createContentPrincipalFromOrigin(prin.origin)); + } +} + +function checkSandboxOriginAttributes(arr, attrs, options) { + options = options || {}; + var sandbox = Cu.Sandbox(arr, options); + checkOriginAttributes( + Cu.getObjectPrincipal(sandbox), + attrs, + ChromeUtils.originAttributesToSuffix(attrs) + ); +} + +// utility function useful for debugging +// eslint-disable-next-line no-unused-vars +function printAttrs(name, attrs) { + info( + name + + " {\n" + + "\tuserContextId: " + + attrs.userContextId + + ",\n" + + "\tinIsolatedMozBrowser: " + + attrs.inIsolatedMozBrowser + + ",\n" + + "\tprivateBrowsingId: '" + + attrs.privateBrowsingId + + "',\n" + + "\tfirstPartyDomain: '" + + attrs.firstPartyDomain + + "'\n}" + ); +} + +function checkValues(attrs, values) { + values = values || {}; + // printAttrs("attrs", attrs); + // printAttrs("values", values); + Assert.equal(attrs.userContextId, values.userContextId || 0); + Assert.equal( + attrs.inIsolatedMozBrowser, + values.inIsolatedMozBrowser || false + ); + Assert.equal(attrs.privateBrowsingId, values.privateBrowsingId || ""); + Assert.equal(attrs.firstPartyDomain, values.firstPartyDomain || ""); +} + +function run_test() { + // Attributeless origins. + Assert.equal(ssm.getSystemPrincipal().origin, "[System Principal]"); + checkOriginAttributes(ssm.getSystemPrincipal()); + var exampleOrg = ssm.createContentPrincipal( + makeURI("http://example.org"), + {} + ); + Assert.equal(exampleOrg.origin, "http://example.org"); + checkOriginAttributes(exampleOrg); + var exampleCom = ssm.createContentPrincipal( + makeURI("https://www.example.com:123"), + {} + ); + Assert.equal(exampleCom.origin, "https://www.example.com:123"); + checkOriginAttributes(exampleCom); + var nullPrin = Cu.getObjectPrincipal(new Cu.Sandbox(null)); + Assert.ok( + /^moz-nullprincipal:\{([0-9]|[a-z]|\-){36}\}$/.test(nullPrin.origin) + ); + checkOriginAttributes(nullPrin); + var ipv6Prin = ssm.createContentPrincipal( + makeURI("https://[2001:db8::ff00:42:8329]:123"), + {} + ); + Assert.equal(ipv6Prin.origin, "https://[2001:db8::ff00:42:8329]:123"); + checkOriginAttributes(ipv6Prin); + var ipv6NPPrin = ssm.createContentPrincipal( + makeURI("https://[2001:db8::ff00:42:8329]"), + {} + ); + Assert.equal(ipv6NPPrin.origin, "https://[2001:db8::ff00:42:8329]"); + checkOriginAttributes(ipv6NPPrin); + var ep = Cu.getObjectPrincipal( + Cu.Sandbox([exampleCom, nullPrin, exampleOrg]) + ); + checkOriginAttributes(ep); + checkCrossOrigin(exampleCom, exampleOrg); + checkCrossOrigin(exampleOrg, nullPrin); + + // nsEP origins should be in lexical order. + Assert.equal( + ep.origin, + `[Expanded Principal [${exampleOrg.origin}, ${exampleCom.origin}, ${nullPrin.origin}]]` + ); + + // Make sure createContentPrincipal does what the rest of gecko does. + Assert.ok( + exampleOrg.equals( + Cu.getObjectPrincipal(new Cu.Sandbox("http://example.org")) + ) + ); + + // + // Test origin attributes. + // + + // Just browser. + var exampleOrg_browser = ssm.createContentPrincipal( + makeURI("http://example.org"), + { inIsolatedMozBrowser: true } + ); + var nullPrin_browser = ssm.createNullPrincipal({ + inIsolatedMozBrowser: true, + }); + checkOriginAttributes( + exampleOrg_browser, + { inIsolatedMozBrowser: true }, + "^inBrowser=1" + ); + checkOriginAttributes( + nullPrin_browser, + { inIsolatedMozBrowser: true }, + "^inBrowser=1" + ); + Assert.equal(exampleOrg_browser.origin, "http://example.org^inBrowser=1"); + + // First party Uri + var exampleOrg_firstPartyDomain = ssm.createContentPrincipal( + makeURI("http://example.org"), + { firstPartyDomain: "example.org" } + ); + checkOriginAttributes( + exampleOrg_firstPartyDomain, + { firstPartyDomain: "example.org" }, + "^firstPartyDomain=example.org" + ); + Assert.equal( + exampleOrg_firstPartyDomain.origin, + "http://example.org^firstPartyDomain=example.org" + ); + + // Just userContext. + var exampleOrg_userContext = ssm.createContentPrincipal( + makeURI("http://example.org"), + { userContextId: 42 } + ); + checkOriginAttributes( + exampleOrg_userContext, + { userContextId: 42 }, + "^userContextId=42" + ); + Assert.equal( + exampleOrg_userContext.origin, + "http://example.org^userContextId=42" + ); + + checkSandboxOriginAttributes(null, {}); + checkSandboxOriginAttributes("http://example.org", {}); + checkSandboxOriginAttributes( + "http://example.org", + {}, + { originAttributes: {} } + ); + checkSandboxOriginAttributes(["http://example.org"], {}); + checkSandboxOriginAttributes( + ["http://example.org"], + {}, + { originAttributes: {} } + ); + + // Check that all of the above are cross-origin. + checkCrossOrigin(exampleOrg_browser, nullPrin_browser); + checkCrossOrigin(exampleOrg_firstPartyDomain, exampleOrg); + checkCrossOrigin(exampleOrg_userContext, exampleOrg); + + // Check Principal kinds. + function checkKind(prin, kind) { + Assert.equal(prin.isNullPrincipal, kind == "nullPrincipal"); + Assert.equal(prin.isContentPrincipal, kind == "contentPrincipal"); + Assert.equal(prin.isExpandedPrincipal, kind == "expandedPrincipal"); + Assert.equal(prin.isSystemPrincipal, kind == "systemPrincipal"); + } + checkKind(ssm.createNullPrincipal({}), "nullPrincipal"); + checkKind( + ssm.createContentPrincipal(makeURI("http://www.example.com"), {}), + "contentPrincipal" + ); + checkKind( + Cu.getObjectPrincipal( + Cu.Sandbox([ + ssm.createContentPrincipal(makeURI("http://www.example.com"), {}), + ]) + ), + "expandedPrincipal" + ); + checkKind(ssm.getSystemPrincipal(), "systemPrincipal"); + + // + // Test Origin Attribute Manipulation + // + + // check that we can create an empty origin attributes dict with default + // members and values. + var emptyAttrs = ChromeUtils.fillNonDefaultOriginAttributes({}); + checkValues(emptyAttrs); + + var uri = "http://example.org"; + var tests = [ + ["", {}], + ["^userContextId=3", { userContextId: 3 }], + ["^inBrowser=1", { inIsolatedMozBrowser: true }], + ["^firstPartyDomain=example.org", { firstPartyDomain: "example.org" }], + ]; + + // check that we can create an origin attributes from an origin properly + tests.forEach(t => { + let attrs = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]); + checkValues(attrs, t[1]); + Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), t[0]); + }); + + // check that we can create an origin attributes from a dict properly + tests.forEach(t => { + let attrs = ChromeUtils.fillNonDefaultOriginAttributes(t[1]); + checkValues(attrs, t[1]); + Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), t[0]); + }); + + // each row in the dflt_tests array has these values: + // [0] - the suffix used to create an origin attribute from + // [1] - the expected result of creating an origin attributes from [0] + // [2] - the expected result after setting userContextId to the default + // [3] - the expected result of creating a suffix from [2] + var dflt_tests = [ + ["", {}, {}, ""], + ["^userContextId=3", { userContextId: 3 }, {}, ""], + ]; + + // check that we can set the userContextId to default properly + dflt_tests.forEach(t => { + let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]); + checkValues(orig, t[1]); + let mod = orig; + mod.userContextId = 0; + checkValues(mod, t[2]); + Assert.equal(ChromeUtils.originAttributesToSuffix(mod), t[3]); + }); + + // each row in the dflt2_tests array has these values: + // [0] - the suffix used to create an origin attribute from + // [1] - the expected result of creating an origin attributes from [0] + // [2] - the expected result after setting firstPartyUri to the default + // [3] - the expected result of creating a suffix from [2] + var dflt2_tests = [ + ["", {}, {}, ""], + ["^firstPartyDomain=foo.com", { firstPartyDomain: "foo.com" }, {}, ""], + ]; + + // check that we can set the userContextId to default properly + dflt2_tests.forEach(t => { + let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]); + checkValues(orig, t[1]); + let mod = orig; + mod.firstPartyDomain = ""; + checkValues(mod, t[2]); + Assert.equal(ChromeUtils.originAttributesToSuffix(mod), t[3]); + }); + + var fileURI = makeURI("file:///foo/bar").QueryInterface(Ci.nsIFileURL); + var fileTests = [ + [true, fileURI.spec], + [false, "file://UNIVERSAL_FILE_URI_ORIGIN"], + ]; + fileTests.forEach(t => { + Services.prefs.setBoolPref("security.fileuri.strict_origin_policy", t[0]); + var filePrin = ssm.createContentPrincipal(fileURI, {}); + Assert.equal(filePrin.origin, t[1]); + }); + Services.prefs.clearUserPref("security.fileuri.strict_origin_policy"); + + var aboutBlankURI = makeURI("about:blank"); + var aboutBlankPrin = ssm.createContentPrincipal(aboutBlankURI, {}); + Assert.ok( + /^moz-nullprincipal:\{([0-9]|[a-z]|\-){36}\}$/.test(aboutBlankPrin.origin) + ); +} diff --git a/caps/tests/unit/test_precursor_principal.js b/caps/tests/unit/test_precursor_principal.js new file mode 100644 index 0000000000..156d9775b4 --- /dev/null +++ b/caps/tests/unit/test_precursor_principal.js @@ -0,0 +1,259 @@ +"use strict"; +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); +const { ExtensionTestUtils } = ChromeUtils.import( + "resource://testing-common/ExtensionXPCShellUtils.jsm" +); + +XPCShellContentUtils.init(this); +ExtensionTestUtils.init(this); + +const server = XPCShellContentUtils.createHttpServer({ + hosts: ["example.com", "example.org"], +}); + +server.registerPathHandler("/static_frames", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(` + <iframe name="same_origin" sandbox="allow-scripts allow-same-origin" src="http://example.com/frame"></iframe> + <iframe name="same_origin_sandbox" sandbox="allow-scripts" src="http://example.com/frame"></iframe> + <iframe name="cross_origin" sandbox="allow-scripts allow-same-origin" src="http://example.org/frame"></iframe> + <iframe name="cross_origin_sandbox" sandbox="allow-scripts" src="http://example.org/frame"></iframe> + <iframe name="data_uri" sandbox="allow-scripts allow-same-origin" src="data:text/html,<h1>Data Subframe</h1>"></iframe> + <iframe name="data_uri_sandbox" sandbox="allow-scripts" src="data:text/html,<h1>Data Subframe</h1>"></iframe> + <iframe name="srcdoc" sandbox="allow-scripts allow-same-origin" srcdoc="<h1>Srcdoc Subframe</h1>"></iframe> + <iframe name="srcdoc_sandbox" sandbox="allow-scripts" srcdoc="<h1>Srcdoc Subframe</h1>"></iframe> + <iframe name="blank" sandbox="allow-scripts allow-same-origin"></iframe> + <iframe name="blank_sandbox" sandbox="allow-scripts"></iframe> + <iframe name="redirect_com" sandbox="allow-scripts allow-same-origin" src="/redirect?com"></iframe> + <iframe name="redirect_com_sandbox" sandbox="allow-scripts" src="/redirect?com"></iframe> + <iframe name="redirect_org" sandbox="allow-scripts allow-same-origin" src="/redirect?org"></iframe> + <iframe name="redirect_org_sandbox" sandbox="allow-scripts" src="/redirect?org"></iframe> + <iframe name="ext_redirect_com_com" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?com"></iframe> + <iframe name="ext_redirect_com_com_sandbox" sandbox="allow-scripts" src="/ext_redirect?com"></iframe> + <iframe name="ext_redirect_org_com" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?com"></iframe> + <iframe name="ext_redirect_org_com_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?com"></iframe> + <iframe name="ext_redirect_com_org" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?org"></iframe> + <iframe name="ext_redirect_com_org_sandbox" sandbox="allow-scripts" src="/ext_redirect?org"></iframe> + <iframe name="ext_redirect_org_org" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?org"></iframe> + <iframe name="ext_redirect_org_org_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?org"></iframe> + <iframe name="ext_redirect_com_data" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?data"></iframe> + <iframe name="ext_redirect_com_data_sandbox" sandbox="allow-scripts" src="/ext_redirect?data"></iframe> + <iframe name="ext_redirect_org_data" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?data"></iframe> + <iframe name="ext_redirect_org_data_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?data"></iframe> + + <!-- XXX(nika): These aren't static as they perform loads dynamically - perhaps consider testing them separately? --> + <iframe name="client_replace_org_blank" sandbox="allow-scripts allow-same-origin" src="http://example.org/client_replace?blank"></iframe> + <iframe name="client_replace_org_blank_sandbox" sandbox="allow-scripts" src="http://example.org/client_replace?blank"></iframe> + <iframe name="client_replace_org_data" sandbox="allow-scripts allow-same-origin" src="http://example.org/client_replace?data"></iframe> + <iframe name="client_replace_org_data_sandbox" sandbox="allow-scripts" src="http://example.org/client_replace?data"></iframe> + `); +}); + +server.registerPathHandler("/frame", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(`<h1>HTTP Subframe</h1>`); +}); + +server.registerPathHandler("/redirect", (request, response) => { + let redirect; + if (request.queryString == "com") { + redirect = "http://example.com/frame"; + } else if (request.queryString == "org") { + redirect = "http://example.org/frame"; + } else { + response.setStatusLine(request.httpVersion, 404, "Not found"); + return; + } + + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", redirect); +}); + +server.registerPathHandler("/client_replace", (request, response) => { + let redirect; + if (request.queryString == "blank") { + redirect = "about:blank"; + } else if (request.queryString == "data") { + redirect = "data:text/html,<h1>Data Subframe</h1>"; + } else { + response.setStatusLine(request.httpVersion, 404, "Not found"); + return; + } + + response.setHeader("Content-Type", "text/html"); + response.write(` + <script> + window.location.replace(${JSON.stringify(redirect)}); + </script> + `); +}); + +add_task(async function sandboxed_precursor() { + // Bug 1725345: Make XPCShellContentUtils.createHttpServer support https + Services.prefs.setBoolPref("dom.security.https_first", false); + + let extension = await ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["webRequest", "webRequestBlocking", "<all_urls>"], + }, + background() { + // eslint-disable-next-line no-undef + browser.webRequest.onBeforeRequest.addListener( + details => { + let url = new URL(details.url); + if (!url.pathname.includes("ext_redirect")) { + return {}; + } + + let redirectUrl; + if (url.search == "?com") { + redirectUrl = "http://example.com/frame"; + } else if (url.search == "?org") { + redirectUrl = "http://example.org/frame"; + } else if (url.search == "?data") { + redirectUrl = "data:text/html,<h1>Data Subframe</h1>"; + } + return { redirectUrl }; + }, + { urls: ["<all_urls>"] }, + ["blocking"] + ); + }, + }); + await extension.startup(); + + registerCleanupFunction(async function() { + await extension.unload(); + }); + + for (let userContextId of [undefined, 1]) { + let comURI = Services.io.newURI("http://example.com"); + let comPrin = Services.scriptSecurityManager.createContentPrincipal( + comURI, + { userContextId } + ); + let orgURI = Services.io.newURI("http://example.org"); + let orgPrin = Services.scriptSecurityManager.createContentPrincipal( + orgURI, + { userContextId } + ); + + let page = await XPCShellContentUtils.loadContentPage( + "http://example.com/static_frames", + { + remote: true, + remoteSubframes: true, + userContextId, + } + ); + let bc = page.browsingContext; + + ok( + bc.currentWindowGlobal.documentPrincipal.equals(comPrin), + "toplevel principal matches" + ); + + // XXX: This is sketchy as heck, but it's also the easiest way to wait for + // the `window.location.replace` loads to finish. + await TestUtils.waitForCondition( + () => + bc.children.every( + child => + !child.currentWindowGlobal.documentURI.spec.includes( + "/client_replace" + ) + ), + "wait for every client_replace global to be replaced" + ); + + let principals = {}; + for (let child of bc.children) { + notEqual(child.name, "", "child frames must have names"); + ok(!(child.name in principals), "duplicate child frame name"); + principals[child.name] = child.currentWindowGlobal.documentPrincipal; + } + + function principal_is(name, expected) { + let principal = principals[name]; + info(`${name} = ${principal.origin}`); + ok(principal.equals(expected), `${name} is correct`); + } + function precursor_is(name, precursor) { + let principal = principals[name]; + info(`${name} = ${principal.origin}`); + ok(principal.isNullPrincipal, `${name} is null`); + ok( + principal.precursorPrincipal.equals(precursor), + `${name} has the correct precursor` + ); + } + + // Basic loads should have the principals or precursor principals for the + // document being loaded. + principal_is("same_origin", comPrin); + precursor_is("same_origin_sandbox", comPrin); + + principal_is("cross_origin", orgPrin); + precursor_is("cross_origin_sandbox", orgPrin); + + // Loads of a data: URI should complete with a sandboxed principal based on + // the principal which tried to perform the load. + precursor_is("data_uri", comPrin); + precursor_is("data_uri_sandbox", comPrin); + + // Loads which inherit principals, such as srcdoc an about:blank loads, + // should also inherit sandboxed precursor principals. + principal_is("srcdoc", comPrin); + precursor_is("srcdoc_sandbox", comPrin); + + principal_is("blank", comPrin); + precursor_is("blank_sandbox", comPrin); + + // Redirects shouldn't interfere with the final principal, and it should be + // based only on the final URI. + principal_is("redirect_com", comPrin); + precursor_is("redirect_com_sandbox", comPrin); + + principal_is("redirect_org", orgPrin); + precursor_is("redirect_org_sandbox", orgPrin); + + // Extension redirects should act like normal redirects, and still resolve + // with the principal or sandboxed principal of the final URI. + principal_is("ext_redirect_com_com", comPrin); + precursor_is("ext_redirect_com_com_sandbox", comPrin); + + principal_is("ext_redirect_com_org", orgPrin); + precursor_is("ext_redirect_com_org_sandbox", orgPrin); + + principal_is("ext_redirect_org_com", comPrin); + precursor_is("ext_redirect_org_com_sandbox", comPrin); + + principal_is("ext_redirect_org_org", orgPrin); + precursor_is("ext_redirect_org_org_sandbox", orgPrin); + + // When an extension redirects to a data: URI, we use the last non-data: URI + // in the chain as the precursor principal. + // FIXME: This should perhaps use the extension's principal instead? + precursor_is("ext_redirect_com_data", comPrin); + precursor_is("ext_redirect_com_data_sandbox", comPrin); + + precursor_is("ext_redirect_org_data", orgPrin); + precursor_is("ext_redirect_org_data_sandbox", orgPrin); + + // Check that navigations triggred by script within the frames will have the + // correct behaviour when navigating to blank and data URIs. + principal_is("client_replace_org_blank", orgPrin); + precursor_is("client_replace_org_blank_sandbox", orgPrin); + + precursor_is("client_replace_org_data", orgPrin); + precursor_is("client_replace_org_data_sandbox", orgPrin); + + await page.close(); + } + Services.prefs.clearUserPref("dom.security.https_first"); +}); diff --git a/caps/tests/unit/test_site_origin.js b/caps/tests/unit/test_site_origin.js new file mode 100644 index 0000000000..bbde787f00 --- /dev/null +++ b/caps/tests/unit/test_site_origin.js @@ -0,0 +1,184 @@ +const scriptSecMan = Services.scriptSecurityManager; + +// SystemPrincipal checks +let systemPrincipal = scriptSecMan.getSystemPrincipal(); +Assert.ok(systemPrincipal.isSystemPrincipal); +Assert.equal(systemPrincipal.origin, "[System Principal]"); +Assert.equal(systemPrincipal.originNoSuffix, "[System Principal]"); +Assert.equal(systemPrincipal.siteOrigin, "[System Principal]"); +Assert.equal(systemPrincipal.siteOriginNoSuffix, "[System Principal]"); + +// ContentPrincipal checks +let uri1 = Services.io.newURI("http://example.com"); +let prinicpal1 = scriptSecMan.createContentPrincipal(uri1, { + userContextId: 11, +}); +Assert.ok(prinicpal1.isContentPrincipal); +Assert.equal(prinicpal1.origin, "http://example.com^userContextId=11"); +Assert.equal(prinicpal1.originNoSuffix, "http://example.com"); +Assert.equal(prinicpal1.siteOrigin, "http://example.com^userContextId=11"); +Assert.equal(prinicpal1.siteOriginNoSuffix, "http://example.com"); + +let uri2 = Services.io.newURI("http://test1.example.com"); +let prinicpal2 = scriptSecMan.createContentPrincipal(uri2, { + userContextId: 22, +}); +Assert.ok(prinicpal2.isContentPrincipal); +Assert.equal(prinicpal2.origin, "http://test1.example.com^userContextId=22"); +Assert.equal(prinicpal2.originNoSuffix, "http://test1.example.com"); +Assert.equal(prinicpal2.siteOrigin, "http://example.com^userContextId=22"); +Assert.equal(prinicpal2.siteOriginNoSuffix, "http://example.com"); + +let uri3 = Services.io.newURI("https://test1.test2.example.com:5555"); +let prinicpal3 = scriptSecMan.createContentPrincipal(uri3, { + userContextId: 5555, +}); +Assert.ok(prinicpal3.isContentPrincipal); +Assert.equal( + prinicpal3.origin, + "https://test1.test2.example.com:5555^userContextId=5555" +); +Assert.equal(prinicpal3.originNoSuffix, "https://test1.test2.example.com:5555"); +Assert.equal(prinicpal3.siteOrigin, "https://example.com^userContextId=5555"); +Assert.equal(prinicpal3.siteOriginNoSuffix, "https://example.com"); + +let uri4 = Services.io.newURI("https://.example.com:6666"); +let prinicpal4 = scriptSecMan.createContentPrincipal(uri4, { + userContextId: 6666, +}); +Assert.ok(prinicpal4.isContentPrincipal); +Assert.equal(prinicpal4.origin, "https://.example.com:6666^userContextId=6666"); +Assert.equal(prinicpal4.originNoSuffix, "https://.example.com:6666"); +Assert.equal(prinicpal4.siteOrigin, "https://.example.com^userContextId=6666"); +Assert.equal(prinicpal4.siteOriginNoSuffix, "https://.example.com"); + +let aboutURI = Services.io.newURI("about:preferences"); +let aboutPrincipal = scriptSecMan.createContentPrincipal(aboutURI, { + userContextId: 66, +}); +Assert.ok(aboutPrincipal.isContentPrincipal); +Assert.equal(aboutPrincipal.origin, "about:preferences^userContextId=66"); +Assert.equal(aboutPrincipal.originNoSuffix, "about:preferences"); +Assert.equal(aboutPrincipal.siteOrigin, "about:preferences^userContextId=66"); +Assert.equal(aboutPrincipal.siteOriginNoSuffix, "about:preferences"); + +let viewSourceURI = Services.io.newURI( + "view-source:https://test1.test2.example.com" +); +let viewSourcePrincipal = scriptSecMan.createContentPrincipal(viewSourceURI, { + userContextId: 101, +}); +Assert.ok(viewSourcePrincipal.isContentPrincipal); +Assert.ok(viewSourcePrincipal.schemeIs("view-source")); +Assert.equal( + viewSourcePrincipal.origin, + "https://test1.test2.example.com^userContextId=101" +); +Assert.equal( + viewSourcePrincipal.originNoSuffix, + "https://test1.test2.example.com" +); +Assert.equal( + viewSourcePrincipal.siteOrigin, + "https://example.com^userContextId=101" +); +Assert.equal(viewSourcePrincipal.siteOriginNoSuffix, "https://example.com"); + +let mozExtensionURI = Services.io.newURI( + "moz-extension://924f966d-c93b-4fbf-968a-16608461663c/meh.txt" +); +let mozExtensionPrincipal = scriptSecMan.createContentPrincipal( + mozExtensionURI, + { + userContextId: 102, + } +); +Assert.ok(mozExtensionPrincipal.isContentPrincipal); +Assert.ok(mozExtensionPrincipal.schemeIs("moz-extension")); +Assert.equal( + mozExtensionPrincipal.origin, + "moz-extension://924f966d-c93b-4fbf-968a-16608461663c^userContextId=102" +); +Assert.equal( + mozExtensionPrincipal.originNoSuffix, + "moz-extension://924f966d-c93b-4fbf-968a-16608461663c" +); +Assert.equal( + mozExtensionPrincipal.siteOrigin, + "moz-extension://924f966d-c93b-4fbf-968a-16608461663c^userContextId=102" +); +Assert.equal( + mozExtensionPrincipal.siteOriginNoSuffix, + "moz-extension://924f966d-c93b-4fbf-968a-16608461663c" +); + +let localhostURI = Services.io.newURI(" http://localhost:44203"); +let localhostPrincipal = scriptSecMan.createContentPrincipal(localhostURI, { + userContextId: 144, +}); +Assert.ok(localhostPrincipal.isContentPrincipal); +Assert.ok(localhostPrincipal.schemeIs("http")); +Assert.equal( + localhostPrincipal.origin, + "http://localhost:44203^userContextId=144" +); +Assert.equal(localhostPrincipal.originNoSuffix, "http://localhost:44203"); +Assert.equal( + localhostPrincipal.siteOrigin, + "http://localhost^userContextId=144" +); +Assert.equal(localhostPrincipal.siteOriginNoSuffix, "http://localhost"); + +// NullPrincipal checks +let nullPrincipal = scriptSecMan.createNullPrincipal({ userContextId: 33 }); +Assert.ok(nullPrincipal.isNullPrincipal); +Assert.ok(nullPrincipal.origin.includes("moz-nullprincipal")); +Assert.ok(nullPrincipal.origin.includes("^userContextId=33")); +Assert.ok(nullPrincipal.originNoSuffix.includes("moz-nullprincipal")); +Assert.ok(!nullPrincipal.originNoSuffix.includes("^userContextId=33")); +Assert.ok(nullPrincipal.siteOrigin.includes("moz-nullprincipal")); +Assert.ok(nullPrincipal.siteOrigin.includes("^userContextId=33")); +Assert.ok(nullPrincipal.siteOriginNoSuffix.includes("moz-nullprincipal")); +Assert.ok(!nullPrincipal.siteOriginNoSuffix.includes("^userContextId=33")); + +// ExpandedPrincipal checks +let expandedPrincipal = Cu.getObjectPrincipal(Cu.Sandbox([prinicpal2])); +Assert.ok(expandedPrincipal.isExpandedPrincipal); +Assert.equal( + expandedPrincipal.origin, + "[Expanded Principal [http://test1.example.com^userContextId=22]]^userContextId=22" +); +Assert.equal( + expandedPrincipal.originNoSuffix, + "[Expanded Principal [http://test1.example.com^userContextId=22]]" +); +Assert.equal( + expandedPrincipal.siteOrigin, + "[Expanded Principal [http://test1.example.com^userContextId=22]]^userContextId=22" +); +Assert.equal( + expandedPrincipal.siteOriginNoSuffix, + "[Expanded Principal [http://test1.example.com^userContextId=22]]" +); + +let ipv6URI = Services.io.newURI("https://[2001:db8::ff00:42:8329]:123"); +let ipv6Principal = scriptSecMan.createContentPrincipal(ipv6URI, { + userContextId: 6, +}); +Assert.ok(ipv6Principal.isContentPrincipal); +Assert.equal( + ipv6Principal.origin, + "https://[2001:db8::ff00:42:8329]:123^userContextId=6" +); +Assert.equal( + ipv6Principal.originNoSuffix, + "https://[2001:db8::ff00:42:8329]:123" +); +Assert.equal( + ipv6Principal.siteOrigin, + "https://[2001:db8::ff00:42:8329]^userContextId=6" +); +Assert.equal( + ipv6Principal.siteOriginNoSuffix, + "https://[2001:db8::ff00:42:8329]" +); diff --git a/caps/tests/unit/test_uri_escaping.js b/caps/tests/unit/test_uri_escaping.js new file mode 100644 index 0000000000..7feb581295 --- /dev/null +++ b/caps/tests/unit/test_uri_escaping.js @@ -0,0 +1,28 @@ +var ssm = Services.scriptSecurityManager; + +function makeURI(uri) { + return Services.io.newURI(uri); +} + +function createPrincipal(aURI) { + try { + var uri = makeURI(aURI); + var principal = ssm.createContentPrincipal(uri, {}); + return principal; + } catch (e) { + return null; + } +} + +function run_test() { + Assert.equal(createPrincipal("http://test^test/foo^bar#x^y"), null); + + Assert.equal(createPrincipal("http://test^test/foo\\bar"), null); + + Assert.equal(createPrincipal("http://test:2^3/foo\\bar"), null); + + Assert.equal( + createPrincipal("http://test/foo^bar").exposableSpec, + "http://test/foo%5Ebar" + ); +} diff --git a/caps/tests/unit/xpcshell.ini b/caps/tests/unit/xpcshell.ini new file mode 100644 index 0000000000..9ff2b22617 --- /dev/null +++ b/caps/tests/unit/xpcshell.ini @@ -0,0 +1,11 @@ +[DEFAULT] +head = + +[test_origin.js] +[test_uri_escaping.js] +[test_ipv6_host_literal.js] +[test_oa_partitionKey_pattern.js] +[test_site_origin.js] +[test_precursor_principal.js] +firefox-appdir = browser +skip-if = toolkit == 'android' |