From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- caps/BasePrincipal.cpp | 1613 +++++++++++++++++ caps/BasePrincipal.h | 487 +++++ caps/ContentPrincipal.cpp | 792 +++++++++ caps/ContentPrincipal.h | 96 + caps/ContentPrincipalJSONHandler.h | 94 + caps/DomainPolicy.cpp | 222 +++ caps/DomainPolicy.h | 64 + caps/ExpandedPrincipal.cpp | 495 ++++++ caps/ExpandedPrincipal.h | 101 ++ caps/ExpandedPrincipalJSONHandler.h | 130 ++ caps/NullPrincipal.cpp | 401 +++++ caps/NullPrincipal.h | 127 ++ caps/NullPrincipalJSONHandler.h | 91 + caps/OriginAttributes.cpp | 494 ++++++ caps/OriginAttributes.h | 289 +++ caps/OriginAttributesHashKey.h | 55 + caps/PrincipalHashKey.h | 58 + caps/PrincipalJSONHandler.h | 116 ++ caps/SharedJSONHandler.h | 114 ++ caps/SubsumedPrincipalJSONHandler.h | 96 + caps/SystemPrincipal.cpp | 118 ++ caps/SystemPrincipal.h | 79 + caps/moz.build | 80 + caps/nsIAddonPolicyService.idl | 102 ++ caps/nsIDomainPolicy.idl | 76 + caps/nsIPrincipal.idl | 774 ++++++++ caps/nsIScriptSecurityManager.idl | 339 ++++ caps/nsJSPrincipals.cpp | 369 ++++ caps/nsJSPrincipals.h | 82 + caps/nsScriptSecurityManager.cpp | 1853 ++++++++++++++++++++ caps/nsScriptSecurityManager.h | 142 ++ caps/tests/gtest/TestBackgroundThreadPrincipal.cpp | 94 + caps/tests/gtest/TestNullPrincipalPrecursor.cpp | 56 + caps/tests/gtest/TestOriginAttributes.cpp | 118 ++ caps/tests/gtest/TestPrincipalAttributes.cpp | 39 + caps/tests/gtest/TestPrincipalSerialization.cpp | 192 ++ .../tests/gtest/TestRedirectChainURITruncation.cpp | 231 +++ caps/tests/gtest/moz.build | 18 + caps/tests/mochitest/browser.toml | 5 + caps/tests/mochitest/browser_aboutOrigin.js | 12 + caps/tests/mochitest/browser_checkloaduri.js | 393 +++++ caps/tests/mochitest/chrome.toml | 15 + caps/tests/mochitest/file_bug1367586-followon.html | 1 + caps/tests/mochitest/file_bug1367586-redirect.sjs | 8 + caps/tests/mochitest/file_bug1367586-target.html | 6 + caps/tests/mochitest/file_data.txt | 1 + caps/tests/mochitest/file_disableScript.html | 11 + caps/tests/mochitest/mochitest.toml | 26 + caps/tests/mochitest/resource_test_file.html | 2 + caps/tests/mochitest/test_addonMayLoad.html | 95 + caps/tests/mochitest/test_bug1367586.html | 50 + caps/tests/mochitest/test_bug246699.html | 60 + caps/tests/mochitest/test_bug292789.html | 121 ++ caps/tests/mochitest/test_bug423375.html | 43 + caps/tests/mochitest/test_bug470804.html | 41 + caps/tests/mochitest/test_bug995943.xhtml | 111 ++ caps/tests/mochitest/test_disableScript.xhtml | 330 ++++ .../mochitest/test_disallowInheritPrincipal.html | 58 + caps/tests/unit/test_ipv6_host_literal.js | 38 + caps/tests/unit/test_oa_partitionKey_pattern.js | 159 ++ caps/tests/unit/test_origin.js | 323 ++++ caps/tests/unit/test_precursor_principal.js | 259 +++ caps/tests/unit/test_site_origin.js | 184 ++ caps/tests/unit/test_uri_escaping.js | 28 + caps/tests/unit/xpcshell.toml | 16 + 65 files changed, 13093 insertions(+) create mode 100644 caps/BasePrincipal.cpp create mode 100644 caps/BasePrincipal.h create mode 100644 caps/ContentPrincipal.cpp create mode 100644 caps/ContentPrincipal.h create mode 100644 caps/ContentPrincipalJSONHandler.h create mode 100644 caps/DomainPolicy.cpp create mode 100644 caps/DomainPolicy.h create mode 100644 caps/ExpandedPrincipal.cpp create mode 100644 caps/ExpandedPrincipal.h create mode 100644 caps/ExpandedPrincipalJSONHandler.h create mode 100644 caps/NullPrincipal.cpp create mode 100644 caps/NullPrincipal.h create mode 100644 caps/NullPrincipalJSONHandler.h create mode 100644 caps/OriginAttributes.cpp create mode 100644 caps/OriginAttributes.h create mode 100644 caps/OriginAttributesHashKey.h create mode 100644 caps/PrincipalHashKey.h create mode 100644 caps/PrincipalJSONHandler.h create mode 100644 caps/SharedJSONHandler.h create mode 100644 caps/SubsumedPrincipalJSONHandler.h create mode 100644 caps/SystemPrincipal.cpp create mode 100644 caps/SystemPrincipal.h create mode 100644 caps/moz.build create mode 100644 caps/nsIAddonPolicyService.idl create mode 100644 caps/nsIDomainPolicy.idl create mode 100644 caps/nsIPrincipal.idl create mode 100644 caps/nsIScriptSecurityManager.idl create mode 100644 caps/nsJSPrincipals.cpp create mode 100644 caps/nsJSPrincipals.h create mode 100644 caps/nsScriptSecurityManager.cpp create mode 100644 caps/nsScriptSecurityManager.h create mode 100644 caps/tests/gtest/TestBackgroundThreadPrincipal.cpp create mode 100644 caps/tests/gtest/TestNullPrincipalPrecursor.cpp create mode 100644 caps/tests/gtest/TestOriginAttributes.cpp create mode 100644 caps/tests/gtest/TestPrincipalAttributes.cpp create mode 100644 caps/tests/gtest/TestPrincipalSerialization.cpp create mode 100644 caps/tests/gtest/TestRedirectChainURITruncation.cpp create mode 100644 caps/tests/gtest/moz.build create mode 100644 caps/tests/mochitest/browser.toml create mode 100644 caps/tests/mochitest/browser_aboutOrigin.js create mode 100644 caps/tests/mochitest/browser_checkloaduri.js create mode 100644 caps/tests/mochitest/chrome.toml create mode 100644 caps/tests/mochitest/file_bug1367586-followon.html create mode 100644 caps/tests/mochitest/file_bug1367586-redirect.sjs create mode 100644 caps/tests/mochitest/file_bug1367586-target.html create mode 100644 caps/tests/mochitest/file_data.txt create mode 100644 caps/tests/mochitest/file_disableScript.html create mode 100644 caps/tests/mochitest/mochitest.toml create mode 100644 caps/tests/mochitest/resource_test_file.html create mode 100644 caps/tests/mochitest/test_addonMayLoad.html create mode 100644 caps/tests/mochitest/test_bug1367586.html create mode 100644 caps/tests/mochitest/test_bug246699.html create mode 100644 caps/tests/mochitest/test_bug292789.html create mode 100644 caps/tests/mochitest/test_bug423375.html create mode 100644 caps/tests/mochitest/test_bug470804.html create mode 100644 caps/tests/mochitest/test_bug995943.xhtml create mode 100644 caps/tests/mochitest/test_disableScript.xhtml create mode 100644 caps/tests/mochitest/test_disallowInheritPrincipal.html create mode 100644 caps/tests/unit/test_ipv6_host_literal.js create mode 100644 caps/tests/unit/test_oa_partitionKey_pattern.js create mode 100644 caps/tests/unit/test_origin.js create mode 100644 caps/tests/unit/test_precursor_principal.js create mode 100644 caps/tests/unit/test_site_origin.js create mode 100644 caps/tests/unit/test_uri_escaping.js create mode 100644 caps/tests/unit/xpcshell.toml (limited to 'caps') diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp new file mode 100644 index 0000000000..6cd94741b0 --- /dev/null +++ b/caps/BasePrincipal.cpp @@ -0,0 +1,1613 @@ +/* -*- 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 "mozilla/JSONStringWriteFuncs.h" +#include "mozilla/JSONWriter.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 "nsSerializationHelper.h" + +#include "js/JSON.h" +#include "ContentPrincipalJSONHandler.h" +#include "ExpandedPrincipalJSONHandler.h" +#include "NullPrincipalJSONHandler.h" +#include "PrincipalJSONHandler.h" +#include "SubsumedPrincipalJSONHandler.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::GetWebExposedOriginSerialization(nsACString& aOrigin) { + aOrigin.Truncate(); + nsCOMPtr prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_ERROR_NOT_AVAILABLE; + } + return nsContentUtils::GetWebExposedOriginSerialization(prinURI, aOrigin); +} + +NS_IMETHODIMP +BasePrincipal::GetHostPort(nsACString& aRes) { + aRes.Truncate(); + nsCOMPtr 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 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); +} + +template +bool ContainerPrincipalJSONHandler::ProcessInnerResult( + bool aResult) { + if (!aResult) { + NS_WARNING("Failed to parse inner object"); + mState = State::Error; + return false; + } + return true; +} + +template +bool ContainerPrincipalJSONHandler::startObject() { + if (mInnerHandler.isSome()) { + return CallOnInner([&](auto& aInner) { return aInner.startObject(); }); + } + + switch (mState) { + case State::Init: + mState = State::StartObject; + break; + case State::SystemPrincipal_Key: + mState = State::SystemPrincipal_StartObject; + break; + default: + NS_WARNING("Unexpected object value"); + mState = State::Error; + return false; + } + + return true; +} + +template +bool ContainerPrincipalJSONHandler::propertyName( + const JS::Latin1Char* name, size_t length) { + if (mInnerHandler.isSome()) { + return CallOnInner( + [&](auto& aInner) { return aInner.propertyName(name, length); }); + } + + switch (mState) { + case State::StartObject: { + if (length != 1) { + NS_WARNING( + nsPrintfCString("Unexpected property name length: %zu", length) + .get()); + mState = State::Error; + return false; + } + + char key = char(name[0]); + switch (key) { + case BasePrincipal::NullPrincipalKey: + mState = State::NullPrincipal_Inner; + mInnerHandler.emplace(VariantType()); + break; + case BasePrincipal::ContentPrincipalKey: + mState = State::ContentPrincipal_Inner; + mInnerHandler.emplace(VariantType()); + break; + case BasePrincipal::SystemPrincipalKey: + mState = State::SystemPrincipal_Key; + break; + default: + if constexpr (CanContainExpandedPrincipal) { + if (key == BasePrincipal::ExpandedPrincipalKey) { + mState = State::ExpandedPrincipal_Inner; + mInnerHandler.emplace( + VariantType()); + break; + } + } + NS_WARNING( + nsPrintfCString("Unexpected property name: '%c'", key).get()); + mState = State::Error; + return false; + } + break; + } + default: + NS_WARNING("Unexpected property name"); + mState = State::Error; + return false; + } + + return true; +} + +template +bool ContainerPrincipalJSONHandler::endObject() { + if (mInnerHandler.isSome()) { + return CallOnInner([&](auto& aInner) { + if (!aInner.endObject()) { + return false; + } + if (aInner.HasAccepted()) { + this->mPrincipal = aInner.mPrincipal.forget(); + MOZ_ASSERT(this->mPrincipal); + mInnerHandler.reset(); + } + return true; + }); + } + + switch (mState) { + case State::SystemPrincipal_StartObject: + mState = State::SystemPrincipal_EndObject; + break; + case State::SystemPrincipal_EndObject: + this->mPrincipal = + BasePrincipal::Cast(nsContentUtils::GetSystemPrincipal()); + mState = State::EndObject; + break; + case State::NullPrincipal_Inner: + mState = State::EndObject; + break; + case State::ContentPrincipal_Inner: + mState = State::EndObject; + break; + default: + if constexpr (CanContainExpandedPrincipal) { + if (mState == State::ExpandedPrincipal_Inner) { + mState = State::EndObject; + break; + } + } + NS_WARNING("Unexpected end of object"); + mState = State::Error; + return false; + } + + return true; +} + +template +bool ContainerPrincipalJSONHandler::startArray() { + if constexpr (CanContainExpandedPrincipal) { + if (mInnerHandler.isSome()) { + return CallOnInner([&](auto& aInner) { return aInner.startArray(); }); + } + } + + NS_WARNING("Unexpected array value"); + mState = State::Error; + return false; +} + +template +bool ContainerPrincipalJSONHandler::endArray() { + if constexpr (CanContainExpandedPrincipal) { + if (mInnerHandler.isSome()) { + return CallOnInner([&](auto& aInner) { return aInner.endArray(); }); + } + } + + NS_WARNING("Unexpected array value"); + mState = State::Error; + return false; +} + +template +bool ContainerPrincipalJSONHandler::stringValue( + const JS::Latin1Char* str, size_t length) { + if (mInnerHandler.isSome()) { + return CallOnInner( + [&](auto& aInner) { return aInner.stringValue(str, length); }); + } + + NS_WARNING("Unexpected string value"); + mState = State::Error; + return false; +} + +template class ContainerPrincipalJSONHandler; +template class ContainerPrincipalJSONHandler; + +// 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 +// +already_AddRefed BasePrincipal::FromJSON( + const nsACString& aJSON) { + PrincipalJSONHandler handler; + + if (!JS::ParseJSONWithHandler( + reinterpret_cast(aJSON.BeginReading()), + aJSON.Length(), &handler)) { + NS_WARNING( + nsPrintfCString("Unable to parse: %s", aJSON.BeginReading()).get()); + MOZ_ASSERT(false, + "Unable to parse string as JSON to deserialize as a principal"); + return nullptr; + } + + return handler.Get(); +} + +// Returns a JSON representation of the principal. +// Calling BasePrincipal::FromJSON will deserialize the JSON into +// the corresponding principal type. +nsresult BasePrincipal::ToJSON(nsACString& aJSON) { + MOZ_ASSERT(aJSON.IsEmpty(), "ToJSON only supports an empty result input"); + aJSON.Truncate(); + + // NOTE: JSONWriter emits raw UTF-8 code units for non-ASCII range. + JSONStringRefWriteFunc func(aJSON); + JSONWriter writer(func, JSONWriter::CollectionStyle::SingleLineStyle); + + nsresult rv = ToJSON(writer); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult BasePrincipal::ToJSON(JSONWriter& aWriter) { + static_assert(eKindMax < ArrayLength(JSONEnumKeyStrings)); + + aWriter.Start(JSONWriter::CollectionStyle::SingleLineStyle); + + nsresult rv = WriteJSONProperties(aWriter); + NS_ENSURE_SUCCESS(rv, rv); + + aWriter.End(); + + return NS_OK; +} + +nsresult BasePrincipal::WriteJSONProperties(JSONWriter& aWriter) { + aWriter.StartObjectProperty(JSONEnumKeyStrings[Kind()], + JSONWriter::CollectionStyle::SingleLineStyle); + + nsresult rv = WriteJSONInnerProperties(aWriter); + NS_ENSURE_SUCCESS(rv, rv); + + aWriter.EndObject(); + + return NS_OK; +} + +nsresult BasePrincipal::WriteJSONInnerProperties(JSONWriter& aWriter) { + 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + nsCOMPtr exposableURI = net::nsIOService::CreateExposableURI(prinURI); + return exposableURI->GetDisplayPrePath(aPrepath); +} + +NS_IMETHODIMP +BasePrincipal::GetExposableSpec(nsACString& aSpec) { + aSpec.Truncate(); + nsCOMPtr prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + nsCOMPtr 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 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 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 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 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 prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + nsCOMPtr 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 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 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 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 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::IsContentAccessibleAboutURI(bool* aResult) { + *aResult = false; + + nsCOMPtr prinURI; + nsresult rv = GetURI(getter_AddRefs(prinURI)); + if (NS_FAILED(rv) || !prinURI) { + return NS_OK; + } + + if (!prinURI->SchemeIs("about")) { + return NS_OK; + } + + *aResult = NS_IsContentAccessibleAboutURI(prinURI); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetIsOriginPotentiallyTrustworthy(bool* aResult) { + AssertIsOnMainThread(); + *aResult = false; + + nsCOMPtr 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. + if (NS_FAILED(rv)) { + return NS_OK; + } + + *aRes = nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(host); + return NS_OK; +} + +NS_IMETHODIMP +BasePrincipal::GetAboutModuleFlags(uint32_t* flags) { + AssertIsOnMainThread(); + *flags = 0; + nsCOMPtr 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 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 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 policy(AddonPolicy()); + policy.forget(aResult); + return NS_OK; +} + +nsresult BasePrincipal::GetContentScriptAddonPolicy( + extensions::WebExtensionPolicy** aResult) { + RefPtr policy(ContentScriptAddonPolicy()); + policy.forget(aResult); + return NS_OK; +} + +extensions::WebExtensionPolicy* BasePrincipal::AddonPolicy() { + AssertIsOnMainThread(); + RefPtr core = AddonPolicyCore(); + return core ? core->GetMainThreadPolicy() : nullptr; +} + +RefPtr BasePrincipal::AddonPolicyCore() { + if (Is()) { + return As()->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()) { + return As()->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::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::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 uriWithSpecialOrigin = + do_QueryInterface(aURI); + if (uriWithSpecialOrigin) { + nsCOMPtr origin; + rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + MOZ_ASSERT(origin); + OriginAttributes attrs; + RefPtr principal = + CreateContentPrincipal(origin, attrs, aInitialDomain); + return principal.forget(); + } +#endif + + nsCOMPtr 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 principal = Cast(blobPrincipal); + return principal.forget(); + } + + // Mint a content principal. + RefPtr principal = + new ContentPrincipal(aURI, aAttrs, aOriginNoSuffix, aInitialDomain); + return principal.forget(); +} + +already_AddRefed 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 uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + return BasePrincipal::CreateContentPrincipal(uri, attrs); +} + +already_AddRefed BasePrincipal::CloneForcingOriginAttributes( + const OriginAttributes& aOriginAttributes) { + if (NS_WARN_IF(!IsContentPrincipal())) { + return nullptr; + } + + nsAutoCString originNoSuffix; + nsresult rv = GetOriginNoSuffix(originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr uri; + MOZ_ALWAYS_SUCCEEDS(GetURI(getter_AddRefs(uri))); + + // XXX: This does not copy over the domain. Should it? + RefPtr copy = + new ContentPrincipal(uri, aOriginAttributes, originNoSuffix, nullptr); + return copy.forget(); +} + +extensions::WebExtensionPolicy* BasePrincipal::ContentScriptAddonPolicy() { + AssertIsOnMainThread(); + RefPtr core = + ContentScriptAddonPolicyCore(); + return core ? core->GetMainThreadPolicy() : nullptr; +} + +RefPtr +BasePrincipal::ContentScriptAddonPolicyCore() { + if (!Is()) { + return nullptr; + } + + auto* expanded = As(); + for (const auto& prin : expanded->AllowList()) { + if (RefPtr policy = + BasePrincipal::Cast(prin)->AddonPolicyCore()) { + return policy; + } + } + + return nullptr; +} + +bool BasePrincipal::AddonAllowsLoad(nsIURI* aURI, + bool aExplicit /* = false */) { + if (Is()) { + return As()->AddonAllowsLoad(aURI, aExplicit); + } + if (auto policy = AddonPolicyCore()) { + return policy->CanAccessURI(aURI, aExplicit); + } + return false; +} + +NS_IMETHODIMP +BasePrincipal::GetLocalStorageQuotaKey(nsACString& aKey) { + aKey.Truncate(); + + nsCOMPtr 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 url = do_QueryInterface(uri, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = url->GetDirectory(baseDomain); + NS_ENSURE_SUCCESS(rv, rv); + } else { + nsCOMPtr 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 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 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 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 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 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 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 prinURI; + RefPtr 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; +} + +/* static */ +void BasePrincipal::WriteJSONProperty(JSONWriter& aWriter, + const Span& aKey, + const nsCString& aValue) { + aWriter.StringProperty(aKey, aValue); +} + +} // namespace mozilla diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h new file mode 100644 index 0000000000..9304be7dee --- /dev/null +++ b/caps/BasePrincipal.h @@ -0,0 +1,487 @@ +/* -*- 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 +#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 mozilla { + +class JSONWriter; + +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 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 + }; + + static constexpr char NullPrincipalKey = '0'; + static_assert(eNullPrincipal == 0); + static constexpr char ContentPrincipalKey = '1'; + static_assert(eContentPrincipal == 1); + static constexpr char ExpandedPrincipalKey = '2'; + static_assert(eExpandedPrincipal == 2); + static constexpr char SystemPrincipalKey = '3'; + static_assert(eSystemPrincipal == 3); + + template + bool Is() const { + return mKind == T::Kind(); + } + + template + T* As() { + MOZ_ASSERT(Is()); + return static_cast(this); + } + + enum DocumentDomainConsideration { + DontConsiderDocumentDomain, + ConsiderDocumentDomain + }; + bool Subsumes(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration); + + NS_IMETHOD GetOrigin(nsACString& aOrigin) final; + NS_IMETHOD GetWebExposedOriginSerialization(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 IsContentAccessibleAboutURI(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 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); + nsresult ToJSON(JSONWriter& aWriter); + nsresult WriteJSONProperties(JSONWriter& aWriter); + + static already_AddRefed FromJSON(const nsACString& aJSON); + + // Method to write serializable fields which represent all of the fields to + // deserialize the principal. + virtual nsresult WriteJSONInnerProperties(JSONWriter& aWriter); + + virtual bool AddonHasPermission(const nsAtom* aPerm); + + virtual bool IsContentPrincipal() const { return false; }; + + static BasePrincipal* Cast(nsIPrincipal* aPrin) { + return static_cast(aPrin); + } + + static BasePrincipal& Cast(nsIPrincipal& aPrin) { + return *static_cast(&aPrin); + } + + static const BasePrincipal* Cast(const nsIPrincipal* aPrin) { + return static_cast(aPrin); + } + + static const BasePrincipal& Cast(const nsIPrincipal& aPrin) { + return *static_cast(&aPrin); + } + + static already_AddRefed 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 CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs, + nsIURI* aInitialDomain = nullptr); + + const OriginAttributes& OriginAttributesRef() final { + return mOriginAttributes; + } + extensions::WebExtensionPolicy* AddonPolicy(); + RefPtr 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 CloneForcingOriginAttributes( + const OriginAttributes& aOriginAttributes); + + // If this is an add-on content script principal, returns its AddonPolicy. + // Otherwise returns null. + extensions::WebExtensionPolicy* ContentScriptAddonPolicy(); + RefPtr 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 + 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 mPrincipal; + }; + + private: + static constexpr Span JSONEnumKeyStrings[4] = { + MakeStringSpan("0"), + MakeStringSpan("1"), + MakeStringSpan("2"), + MakeStringSpan("3"), + }; + + static void WriteJSONProperty(JSONWriter& aWriter, + const Span& aKey, + const nsCString& aValue); + + protected: + template + static inline constexpr const Span& JSONEnumKeyString() { + static_assert(EnumValue < ArrayLength(JSONEnumKeyStrings)); + return JSONEnumKeyStrings[EnumValue]; + } + template + static void WriteJSONProperty(JSONWriter& aWriter, const nsCString& aValue) { + WriteJSONProperty(aWriter, JSONEnumKeyString(), aValue); + } + + private: + static already_AddRefed CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs, + const nsACString& aOriginNoSuffix, nsIURI* aInitialDomain); + + bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration); + + const RefPtr mOriginNoSuffix; + const RefPtr mOriginSuffix; + + const OriginAttributes mOriginAttributes; + const PrincipalKind mKind; + std::atomic 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); + } + + // Principals of different kinds can't be equal. + if (Kind() != other->Kind()) { + return false; + } + + // Only ContentPrincipals should have mHasExplicitDomain set to true, so test + // that we haven't ended up here instead of FastEquals by mistake. + MOZ_ASSERT(IsContentPrincipal(), + "Only content principals can set mHasExplicitDomain"); + + 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..265d3ced2b --- /dev/null +++ b/caps/ContentPrincipal.cpp @@ -0,0 +1,792 @@ +/* -*- 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 "js/JSON.h" +#include "ContentPrincipalJSONHandler.h" + +using namespace mozilla; + +NS_IMPL_CLASSINFO(ContentPrincipal, nullptr, 0, 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 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 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 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 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 uriWithSpecialOrigin = + do_QueryInterface(aURI); + if (uriWithSpecialOrigin) { + nsCOMPtr origin; + nsresult rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + MOZ_ASSERT(origin); + OriginAttributes attrs; + RefPtr principal = + BasePrincipal::CreateContentPrincipal(origin, attrs); + return nsIPrincipal::Subsumes(principal); + } +#endif + + nsCOMPtr 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 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(this)); + + dom::AutoJSAPI jsapi; + jsapi.Init(); + JS::IterateRealmsWithPrincipals(jsapi.cx(), principals, nullptr, cb); + + return NS_OK; +} + +static nsresult GetSpecialBaseDomain(const nsCOMPtr& 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 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 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 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 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 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 principal = CreateContentPrincipal(siteOrigin); + if (!principal) { + NS_WARNING("could not instantiate content principal"); + return NS_ERROR_FAILURE; + } + + aSite.Init(principal); + return NS_OK; +} + +RefPtr ContentPrincipal::AddonPolicyCore() { + mozilla::MutexAutoLock lock(mMutex); + if (!mAddon.isSome()) { + NS_ENSURE_TRUE(mURI, nullptr); + + RefPtr 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 policy = AddonPolicyCore()) { + policy->Id()->ToString(aAddonId); + } else { + aAddonId.Truncate(); + } + return NS_OK; +} + +NS_IMETHODIMP +ContentPrincipal::Deserializer::Read(nsIObjectInputStream* aStream) { + MOZ_ASSERT(!mPrincipal); + + nsCOMPtr supports; + nsCOMPtr 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 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::WriteJSONInnerProperties(JSONWriter& aWriter) { + 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, aWriter + // is set up to be inside of the inner object that has stringified enum keys + // An example inner object might be: + // + // eURI eSuffix + // | | + // {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"} + // | | | | + // ----------------------------- | + // | | | + // Key ---------------------- + // | + // Value + WriteJSONProperty(aWriter, principalURI); + + if (GetHasExplicitDomain()) { + nsAutoCString domainStr; + { + MutexAutoLock lock(mMutex); + rv = mDomain->GetSpec(domainStr); + NS_ENSURE_SUCCESS(rv, rv); + } + WriteJSONProperty(aWriter, domainStr); + } + + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + if (suffix.Length() > 0) { + WriteJSONProperty(aWriter, suffix); + } + + return NS_OK; +} + +bool ContentPrincipalJSONHandler::startObject() { + switch (mState) { + case State::Init: + mState = State::StartObject; + break; + default: + NS_WARNING("Unexpected object value"); + mState = State::Error; + return false; + } + + return true; +} + +bool ContentPrincipalJSONHandler::propertyName(const JS::Latin1Char* name, + size_t length) { + switch (mState) { + case State::StartObject: + case State::AfterPropertyValue: { + if (length != 1) { + NS_WARNING( + nsPrintfCString("Unexpected property name length: %zu", length) + .get()); + mState = State::Error; + return false; + } + + char key = char(name[0]); + switch (key) { + case ContentPrincipal::URIKey: + mState = State::URIKey; + break; + case ContentPrincipal::DomainKey: + mState = State::DomainKey; + break; + case ContentPrincipal::SuffixKey: + mState = State::SuffixKey; + break; + default: + NS_WARNING( + nsPrintfCString("Unexpected property name: '%c'", key).get()); + mState = State::Error; + return false; + } + break; + } + default: + NS_WARNING("Unexpected property name"); + mState = State::Error; + return false; + } + + return true; +} + +bool ContentPrincipalJSONHandler::endObject() { + switch (mState) { + case State::AfterPropertyValue: { + MOZ_ASSERT(mPrincipalURI); + // NOTE: mDomain is optional. + + nsAutoCString originNoSuffix; + nsresult rv = ContentPrincipal::GenerateOriginNoSuffixFromURI( + mPrincipalURI, originNoSuffix); + if (NS_FAILED(rv)) { + mState = State::Error; + return false; + } + + mPrincipal = + new ContentPrincipal(mPrincipalURI, mAttrs, originNoSuffix, mDomain); + MOZ_ASSERT(mPrincipal); + + mState = State::EndObject; + break; + } + default: + NS_WARNING("Unexpected end of object"); + mState = State::Error; + return false; + } + + return true; +} + +bool ContentPrincipalJSONHandler::stringValue(const JS::Latin1Char* str, + size_t length) { + switch (mState) { + case State::URIKey: { + nsDependentCSubstring spec(reinterpret_cast(str), length); + + nsresult rv = NS_NewURI(getter_AddRefs(mPrincipalURI), spec); + if (NS_FAILED(rv)) { + mState = State::Error; + return false; + } + + { + // Enforce re-parsing about: URIs so that if they change, we + // continue to use their new principals correctly. + if (mPrincipalURI->SchemeIs("about")) { + nsAutoCString spec; + mPrincipalURI->GetSpec(spec); + rv = NS_NewURI(getter_AddRefs(mPrincipalURI), spec); + if (NS_FAILED(rv)) { + mState = State::Error; + return false; + } + } + } + + mState = State::AfterPropertyValue; + break; + } + case State::DomainKey: { + nsDependentCSubstring spec(reinterpret_cast(str), length); + + nsresult rv = NS_NewURI(getter_AddRefs(mDomain), spec); + if (NS_FAILED(rv)) { + mState = State::Error; + return false; + } + + mState = State::AfterPropertyValue; + break; + } + case State::SuffixKey: { + nsDependentCSubstring attrs(reinterpret_cast(str), length); + if (!mAttrs.PopulateFromSuffix(attrs)) { + mState = State::Error; + return false; + } + + mState = State::AfterPropertyValue; + break; + } + default: + NS_WARNING("Unexpected string value"); + mState = State::Error; + return false; + } + + return true; +} diff --git a/caps/ContentPrincipal.h b/caps/ContentPrincipal.h new file mode 100644 index 0000000000..c87aedce85 --- /dev/null +++ b/caps/ContentPrincipal.h @@ -0,0 +1,96 @@ +/* -*- 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 mozilla { + +class JSONWriter; + +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 AddonPolicyCore(); + + virtual nsresult WriteJSONInnerProperties(JSONWriter& aWriter) override; + + // Serializable keys are the valid enum fields the serialization supports + enum SerializableKeys : uint8_t { + eURI = 0, + eDomain, + eSuffix, + eMax = eSuffix + }; + + static constexpr char URIKey = '0'; + static_assert(eURI == 0); + static constexpr char DomainKey = '1'; + static_assert(eDomain == 1); + static constexpr char SuffixKey = '2'; + static_assert(eSuffix == 2); + + 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 mURI; + mozilla::Mutex mMutex{"ContentPrincipal::mMutex"}; + nsCOMPtr mDomain MOZ_GUARDED_BY(mMutex); + Maybe> 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/ContentPrincipalJSONHandler.h b/caps/ContentPrincipalJSONHandler.h new file mode 100644 index 0000000000..e401af3000 --- /dev/null +++ b/caps/ContentPrincipalJSONHandler.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_ContentPrincipalJSONHandler_h +#define mozilla_ContentPrincipalJSONHandler_h + +#include // size_t +#include // uint32_t + +#include "js/TypeDecls.h" // JS::Latin1Char + +#include "mozilla/AlreadyAddRefed.h" // already_AddRefed +#include "mozilla/RefPtr.h" // RefPtr + +#include "nsCOMPtr.h" // nsCOMPtr +#include "nsDebug.h" // NS_WARNING +#include "nsIURI.h" // nsIURI + +#include "ContentPrincipal.h" +#include "OriginAttributes.h" +#include "SharedJSONHandler.h" + +namespace mozilla { + +// JSON parse handler for an inner object for ContentPrincipal. +// Used by PrincipalJSONHandler or SubsumedPrincipalJSONHandler. +class ContentPrincipalJSONHandler : public PrincipalJSONHandlerShared { + enum class State { + Init, + + // After the inner object's '{'. + StartObject, + + // After the property key for eURI. + URIKey, + + // After the property key for eDomain. + DomainKey, + + // After the property key for eSuffix. + SuffixKey, + + // After the property value for eURI, eDomain, or eSuffix. + AfterPropertyValue, + + // After the inner object's '}'. + EndObject, + + Error, + }; + + public: + ContentPrincipalJSONHandler() = default; + virtual ~ContentPrincipalJSONHandler() = default; + + virtual bool startObject() override; + + using PrincipalJSONHandlerShared::propertyName; + virtual bool propertyName(const JS::Latin1Char* name, size_t length) override; + + virtual bool endObject() override; + + virtual bool startArray() override { + NS_WARNING("Unexpected array value"); + mState = State::Error; + return false; + } + virtual bool endArray() override { + NS_WARNING("Unexpected array value"); + mState = State::Error; + return false; + } + + using PrincipalJSONHandlerShared::stringValue; + virtual bool stringValue(const JS::Latin1Char* str, size_t length) override; + + bool HasAccepted() const { return mState == State::EndObject; } + + protected: + virtual void SetErrorState() override { mState = State::Error; } + + private: + State mState = State::Init; + + nsCOMPtr mPrincipalURI; + nsCOMPtr mDomain; + OriginAttributes mAttrs; +}; + +} // namespace mozilla + +#endif // mozilla_ContentPrincipalJSONHandler_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 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 set = mBlocklist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetSuperBlocklist(nsIDomainSet** aSet) { + nsCOMPtr set = mSuperBlocklist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetAllowlist(nsIDomainSet** aSet) { + nsCOMPtr set = mAllowlist.get(); + set.forget(aSet); + return NS_OK; +} + +NS_IMETHODIMP +DomainPolicy::GetSuperAllowlist(nsIDomainSet** aSet) { + nsCOMPtr 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>& 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 GetCanonicalClone(nsIURI* aURI) { + nsCOMPtr 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 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 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 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 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>* 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>* aDomains); + + protected: + virtual ~DomainSet() {} + nsTHashSet mHashTable; + DomainSetType mType; +}; + +class DomainPolicy final : public nsIDomainPolicy { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMAINPOLICY + DomainPolicy(); + + private: + virtual ~DomainPolicy(); + + RefPtr mBlocklist; + RefPtr mSuperBlocklist; + RefPtr mAllowlist; + RefPtr mSuperAllowlist; +}; + +} /* namespace mozilla */ + +#endif /* DomainPolicy_h__ */ diff --git a/caps/ExpandedPrincipal.cpp b/caps/ExpandedPrincipal.cpp new file mode 100644 index 0000000000..7d65fec614 --- /dev/null +++ b/caps/ExpandedPrincipal.cpp @@ -0,0 +1,495 @@ +/* -*- 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 "mozilla/JSONWriter.h" + +#include "js/JSON.h" +#include "ExpandedPrincipalJSONHandler.h" +#include "SubsumedPrincipalJSONHandler.h" + +using namespace mozilla; + +NS_IMPL_CLASSINFO(ExpandedPrincipal, nullptr, 0, NS_EXPANDEDPRINCIPAL_CID) +NS_IMPL_QUERY_INTERFACE_CI(ExpandedPrincipal, nsIPrincipal, + nsIExpandedPrincipal) +NS_IMPL_CI_INTERFACE_GETTER(ExpandedPrincipal, nsIPrincipal, + nsIExpandedPrincipal) + +ExpandedPrincipal::ExpandedPrincipal( + nsTArray>&& aPrincipals, + const nsACString& aOriginNoSuffix, const OriginAttributes& aAttrs) + : BasePrincipal(eExpandedPrincipal, aOriginNoSuffix, aAttrs), + mPrincipals(std::move(aPrincipals)) {} + +ExpandedPrincipal::~ExpandedPrincipal() = default; + +already_AddRefed ExpandedPrincipal::Create( + const nsTArray>& aAllowList, + const OriginAttributes& aAttrs) { + nsTArray> principals; + for (size_t i = 0; i < aAllowList.Length(); ++i) { + principals.AppendElement(aAllowList[i]); + } + + nsAutoCString origin; + origin.AssignLiteral("[Expanded Principal ["); + StringJoinAppend( + origin, ", "_ns, principals, + [](nsACString& dest, const nsCOMPtr& principal) { + nsAutoCString subOrigin; + DebugOnly rv = principal->GetOrigin(subOrigin); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + dest.Append(subOrigin); + }); + origin.AppendLiteral("]]"); + + RefPtr 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()) { + auto* expanded = Cast(aOther)->As(); + + 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>& 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( + "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> principals; + if (!principals.SetCapacity(count, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr read; + rv = aStream->ReadObject(true, getter_AddRefs(read)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr principal = do_QueryInterface(read); + if (!principal) { + return NS_ERROR_UNEXPECTED; + } + + principals.AppendElement(std::move(principal)); + } + + 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> 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::Create(allowlist, OriginAttributesRef()); + MOZ_ASSERT(expandedPrincipal, "ExpandedPrincipal::Create returned nullptr?"); + + aSite.Init(expandedPrincipal); + return NS_OK; +} + +nsresult ExpandedPrincipal::WriteJSONInnerProperties(JSONWriter& aWriter) { + aWriter.StartArrayProperty(JSONEnumKeyString(), + JSONWriter::CollectionStyle::SingleLineStyle); + + for (const auto& principal : mPrincipals) { + aWriter.StartObjectElement(JSONWriter::CollectionStyle::SingleLineStyle); + + nsresult rv = BasePrincipal::Cast(principal)->WriteJSONProperties(aWriter); + NS_ENSURE_SUCCESS(rv, rv); + + aWriter.EndObject(); + } + + aWriter.EndArray(); + + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + if (suffix.Length() > 0) { + WriteJSONProperty(aWriter, suffix); + } + + return NS_OK; +} + +bool ExpandedPrincipalJSONHandler::ProcessSubsumedResult(bool aResult) { + if (!aResult) { + NS_WARNING("Failed to parse subsumed principal"); + mState = State::Error; + return false; + } + return true; +} + +bool ExpandedPrincipalJSONHandler::startObject() { + if (mSubsumedHandler.isSome()) { + return ProcessSubsumedResult(mSubsumedHandler->startObject()); + } + + switch (mState) { + case State::Init: + mState = State::StartObject; + break; + case State::StartArray: + mState = State::SubsumedPrincipal; + [[fallthrough]]; + case State::SubsumedPrincipal: + mSubsumedHandler.emplace(); + + return ProcessSubsumedResult(mSubsumedHandler->startObject()); + default: + NS_WARNING("Unexpected object value"); + mState = State::Error; + return false; + } + + return true; +} + +bool ExpandedPrincipalJSONHandler::propertyName(const JS::Latin1Char* name, + size_t length) { + if (mSubsumedHandler.isSome()) { + return ProcessSubsumedResult(mSubsumedHandler->propertyName(name, length)); + } + + switch (mState) { + case State::StartObject: + case State::AfterPropertyValue: { + if (length != 1) { + NS_WARNING( + nsPrintfCString("Unexpected property name length: %zu", length) + .get()); + mState = State::Error; + return false; + } + + char key = char(name[0]); + switch (key) { + case ExpandedPrincipal::SpecsKey: + mState = State::SpecsKey; + break; + case ExpandedPrincipal::SuffixKey: + mState = State::SuffixKey; + break; + default: + NS_WARNING( + nsPrintfCString("Unexpected property name: '%c'", key).get()); + mState = State::Error; + return false; + } + break; + } + default: + NS_WARNING("Unexpected property name"); + mState = State::Error; + return false; + } + + return true; +} + +bool ExpandedPrincipalJSONHandler::endObject() { + if (mSubsumedHandler.isSome()) { + if (!ProcessSubsumedResult(mSubsumedHandler->endObject())) { + return false; + } + if (mSubsumedHandler->HasAccepted()) { + nsCOMPtr principal = mSubsumedHandler->mPrincipal.forget(); + mSubsumedHandler.reset(); + mAllowList.AppendElement(principal); + } + return true; + } + + switch (mState) { + case State::AfterPropertyValue: + mPrincipal = ExpandedPrincipal::Create(mAllowList, mAttrs); + MOZ_ASSERT(mPrincipal); + + mState = State::EndObject; + break; + default: + NS_WARNING("Unexpected end of object"); + mState = State::Error; + return false; + } + + return true; +} + +bool ExpandedPrincipalJSONHandler::startArray() { + switch (mState) { + case State::SpecsKey: + mState = State::StartArray; + break; + default: + NS_WARNING("Unexpected array value"); + mState = State::Error; + return false; + } + + return true; +} + +bool ExpandedPrincipalJSONHandler::endArray() { + switch (mState) { + case State::SubsumedPrincipal: { + mState = State::AfterPropertyValue; + break; + } + default: + NS_WARNING("Unexpected end of array"); + mState = State::Error; + return false; + } + + return true; +} + +bool ExpandedPrincipalJSONHandler::stringValue(const JS::Latin1Char* str, + size_t length) { + if (mSubsumedHandler.isSome()) { + return ProcessSubsumedResult(mSubsumedHandler->stringValue(str, length)); + } + + switch (mState) { + case State::SpecsKey: { + nsDependentCSubstring specs(reinterpret_cast(str), length); + + for (const nsACString& each : specs.Split(',')) { + nsAutoCString result; + nsresult rv = Base64Decode(each, result); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to decode"); + if (NS_FAILED(rv)) { + mState = State::Error; + return false; + } + + nsCOMPtr principal = BasePrincipal::FromJSON(result); + if (!principal) { + mState = State::Error; + return false; + } + mAllowList.AppendElement(principal); + } + + mState = State::AfterPropertyValue; + break; + } + case State::SuffixKey: { + nsDependentCSubstring attrs(reinterpret_cast(str), length); + if (!mAttrs.PopulateFromSuffix(attrs)) { + mState = State::Error; + return false; + } + + mState = State::AfterPropertyValue; + break; + } + default: + NS_WARNING("Unexpected string value"); + mState = State::Error; + return false; + } + + return true; +} + +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..0981f9df70 --- /dev/null +++ b/caps/ExpandedPrincipal.h @@ -0,0 +1,101 @@ +/* -*- 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 mozilla { +class JSONWriter; +} // namespace mozilla + +class ExpandedPrincipal : public nsIExpandedPrincipal, + public mozilla::BasePrincipal { + public: + static already_AddRefed Create( + const nsTArray>& 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 WriteJSONInnerProperties( + mozilla::JSONWriter& aWriter) override; + + // Serializable keys are the valid enum fields the serialization supports + enum SerializableKeys : uint8_t { eSpecs = 0, eSuffix, eMax = eSuffix }; + + static constexpr char SpecsKey = '0'; + static_assert(eSpecs == 0); + static constexpr char SuffixKey = '1'; + static_assert(eSuffix == 1); + + class Deserializer : public BasePrincipal::Deserializer { + public: + NS_IMETHOD Read(nsIObjectInputStream* aStream) override; + }; + + protected: + explicit ExpandedPrincipal(nsTArray>&& 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> mPrincipals; + nsMainThreadPtrHandle 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/ExpandedPrincipalJSONHandler.h b/caps/ExpandedPrincipalJSONHandler.h new file mode 100644 index 0000000000..0e037cf044 --- /dev/null +++ b/caps/ExpandedPrincipalJSONHandler.h @@ -0,0 +1,130 @@ +/* -*- 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_ExpandedPrincipalJSONHandler_h +#define mozilla_ExpandedPrincipalJSONHandler_h + +#include // size_t +#include // uint32_t + +#include "js/TypeDecls.h" // JS::Latin1Char + +#include "mozilla/AlreadyAddRefed.h" // already_AddRefed +#include "mozilla/Maybe.h" // Maybe +#include "mozilla/RefPtr.h" // RefPtr + +#include "nsCOMPtr.h" // nsCOMPtr +#include "nsDebug.h" // NS_WARNING +#include "nsIPrincipal.h" // nsIPrincipal +#include "nsTArray.h" // nsTArray + +#include "OriginAttributes.h" +#include "ExpandedPrincipal.h" +#include "SubsumedPrincipalJSONHandler.h" +#include "SharedJSONHandler.h" + +namespace mozilla { + +// JSON parse handler for an inner object for ExpandedPrincipal. +// +// # Legacy format +// +// inner object +// | +// ------------------------------------------------- +// | | +// {"2": {"0": "eyIxIjp7IjAiOiJodHRwczovL2EuY29tLyJ9fQ=="}} +// | | | +// | ---------- Value +// | | +// PrincipalKind | +// | +// SerializableKeys +// +// The value is a CSV list of Base64 encoded prinipcals. +// +// # New format +// +// inner object +// | +// ------------------------------------------- +// | | +// | Subsumed principals | +// | | | +// | ------------------------------------| +// | | || +// {"2": {"0": [{"1": {"0": https://mozilla.com"}}]}} +// | | | +// -------------- Value +// | +// PrincipalKind +// +// Used by PrincipalJSONHandler. +class ExpandedPrincipalJSONHandler : public PrincipalJSONHandlerShared { + enum class State { + Init, + + // After the inner object's '{'. + StartObject, + + // After the property key for eSpecs. + SpecsKey, + + // After the property key for eSuffix. + SuffixKey, + + // After the subsumed principals array's '['. + StartArray, + + // Subsumed principals array's item. + // Delegates to mSubsumedHandler until the subsumed object's '}'. + SubsumedPrincipal, + + // After the property value for eSpecs or eSuffix, + // including after the subsumed principals array's ']'. + AfterPropertyValue, + + // After the inner object's '}'. + EndObject, + + Error, + }; + + public: + ExpandedPrincipalJSONHandler() = default; + virtual ~ExpandedPrincipalJSONHandler() = default; + + virtual bool startObject() override; + + using PrincipalJSONHandlerShared::propertyName; + virtual bool propertyName(const JS::Latin1Char* name, size_t length) override; + + virtual bool endObject() override; + + virtual bool startArray() override; + virtual bool endArray() override; + + using PrincipalJSONHandlerShared::stringValue; + virtual bool stringValue(const JS::Latin1Char* str, size_t length) override; + + bool HasAccepted() const { return mState == State::EndObject; } + + protected: + virtual void SetErrorState() override { mState = State::Error; } + + private: + bool ProcessSubsumedResult(bool aResult); + + private: + State mState = State::Init; + + nsTArray> mAllowList; + OriginAttributes mAttrs; + Maybe mSubsumedHandler; +}; + +} // namespace mozilla + +#endif // mozilla_ExpandedPrincipalJSONHandler_h diff --git a/caps/NullPrincipal.cpp b/caps/NullPrincipal.cpp new file mode 100644 index 0000000000..1742413d10 --- /dev/null +++ b/caps/NullPrincipal.cpp @@ -0,0 +1,401 @@ +/* -*- 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 "js/JSON.h" +#include "NullPrincipalJSONHandler.h" + +using namespace mozilla; + +NS_IMPL_CLASSINFO(NullPrincipal, nullptr, 0, 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::CreateWithInheritedAttributes( + nsIPrincipal* aInheritFrom) { + MOZ_ASSERT(aInheritFrom); + nsCOMPtr uri = CreateURI(aInheritFrom); + return Create(Cast(aInheritFrom)->OriginAttributesRef(), uri); +} + +/* static */ +already_AddRefed NullPrincipal::Create( + const OriginAttributes& aOriginAttributes, nsIURI* aNullPrincipalURI) { + nsCOMPtr uri = aNullPrincipalURI; + if (!uri) { + uri = NullPrincipal::CreateURI(nullptr); + } + + MOZ_RELEASE_ASSERT(uri->SchemeIs(NS_NULLPRINCIPAL_SCHEME)); + + nsAutoCString originNoSuffix; + DebugOnly rv = uri->GetSpec(originNoSuffix); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + RefPtr nullPrin = + new NullPrincipal(uri, originNoSuffix, aOriginAttributes); + return nullPrin.forget(); +} + +/* static */ +already_AddRefed 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 NullPrincipal::CreateURI( + nsIPrincipal* aPrecursor, const nsID* aNullPrincipalID) { + nsCOMPtr 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 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 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 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 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 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 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::WriteJSONInnerProperties(JSONWriter& aWriter) { + nsAutoCString principalURI; + nsresult rv = mURI->GetSpec(principalURI); + NS_ENSURE_SUCCESS(rv, rv); + WriteJSONProperty(aWriter, principalURI); + + nsAutoCString suffix; + OriginAttributesRef().CreateSuffix(suffix); + if (suffix.Length() > 0) { + WriteJSONProperty(aWriter, suffix); + } + + return NS_OK; +} + +NS_IMETHODIMP +NullPrincipal::GetPrecursorPrincipal(nsIPrincipal** aPrincipal) { + *aPrincipal = nullptr; + + nsAutoCString query; + if (NS_FAILED(mURI->GetQuery(query)) || query.IsEmpty()) { + return NS_OK; + } + UnescapePrecursorQuery(query); + + nsCOMPtr 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 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()) { + return NS_OK; + } + contentPrincipal.forget(aPrincipal); + return NS_OK; +} + +bool NullPrincipalJSONHandler::startObject() { + switch (mState) { + case State::Init: + mState = State::StartObject; + break; + default: + NS_WARNING("Unexpected object value"); + mState = State::Error; + return false; + } + + return true; +} + +bool NullPrincipalJSONHandler::propertyName(const JS::Latin1Char* name, + size_t length) { + switch (mState) { + case State::StartObject: + case State::AfterPropertyValue: { + if (length != 1) { + NS_WARNING( + nsPrintfCString("Unexpected property name length: %zu", length) + .get()); + mState = State::Error; + return false; + } + + char key = char(name[0]); + switch (key) { + case NullPrincipal::SpecKey: + mState = State::SpecKey; + break; + case NullPrincipal::SuffixKey: + mState = State::SuffixKey; + break; + default: + NS_WARNING( + nsPrintfCString("Unexpected property name: '%c'", key).get()); + mState = State::Error; + return false; + } + break; + } + default: + NS_WARNING("Unexpected property name"); + mState = State::Error; + return false; + } + + return true; +} + +bool NullPrincipalJSONHandler::endObject() { + switch (mState) { + case State::AfterPropertyValue: + MOZ_ASSERT(mUri); + + mPrincipal = NullPrincipal::Create(mAttrs, mUri); + MOZ_ASSERT(mPrincipal); + + mState = State::EndObject; + break; + default: + NS_WARNING("Unexpected end of object"); + mState = State::Error; + return false; + } + + return true; +} + +bool NullPrincipalJSONHandler::stringValue(const JS::Latin1Char* str, + size_t length) { + switch (mState) { + case State::SpecKey: { + nsDependentCSubstring spec(reinterpret_cast(str), length); + nsresult rv = NS_NewURI(getter_AddRefs(mUri), spec); + if (NS_FAILED(rv)) { + mState = State::Error; + return false; + } + + mState = State::AfterPropertyValue; + break; + } + case State::SuffixKey: { + nsDependentCSubstring attrs(reinterpret_cast(str), length); + if (!mAttrs.PopulateFromSuffix(attrs)) { + mState = State::Error; + return false; + } + + mState = State::AfterPropertyValue; + break; + } + default: + NS_WARNING("Unexpected string value"); + mState = State::Error; + return false; + } + + return true; +} diff --git a/caps/NullPrincipal.h b/caps/NullPrincipal.h new file mode 100644 index 0000000000..170a4e141c --- /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; + +#define NS_NULLPRINCIPAL_CID \ + { \ + 0xbd066e5f, 0x146f, 0x4472, { \ + 0x83, 0x31, 0x7b, 0xfd, 0x05, 0xb1, 0xed, 0x90 \ + } \ + } + +#define NS_NULLPRINCIPAL_SCHEME "moz-nullprincipal" + +namespace mozilla { + +class JSONWriter; + +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 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 Create( + const OriginAttributes& aOriginAttributes, + nsIURI* aNullPrincipalURI = nullptr); + + static already_AddRefed 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 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 WriteJSONInnerProperties(JSONWriter& aWriter) override; + + // Serializable keys are the valid enum fields the serialization supports + enum SerializableKeys : uint8_t { eSpec = 0, eSuffix, eMax = eSuffix }; + + static constexpr char SpecKey = '0'; + static_assert(eSpec == 0); + static constexpr char SuffixKey = '1'; + static_assert(eSuffix == 1); + + 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 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/NullPrincipalJSONHandler.h b/caps/NullPrincipalJSONHandler.h new file mode 100644 index 0000000000..3c5f9e1631 --- /dev/null +++ b/caps/NullPrincipalJSONHandler.h @@ -0,0 +1,91 @@ +/* -*- 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_NullPrincipalJSONHandler_h +#define mozilla_NullPrincipalJSONHandler_h + +#include // size_t +#include // uint32_t + +#include "js/TypeDecls.h" // JS::Latin1Char + +#include "mozilla/AlreadyAddRefed.h" // already_AddRefed +#include "mozilla/Assertions.h" // MOZ_ASSERT_UNREACHABLE +#include "mozilla/RefPtr.h" // RefPtr + +#include "nsCOMPtr.h" // nsCOMPtr +#include "nsDebug.h" // NS_WARNING +#include "nsIURI.h" // nsIURI + +#include "NullPrincipal.h" +#include "OriginAttributes.h" +#include "SharedJSONHandler.h" + +namespace mozilla { + +// JSON parse handler for an inner object for NullPrincipal. +// Used by PrincipalJSONHandler or SubsumedPrincipalJSONHandler. +class NullPrincipalJSONHandler : public PrincipalJSONHandlerShared { + enum class State { + Init, + + // After the inner object's '{'. + StartObject, + + // After the property key for eSpec. + SpecKey, + + // After the property key for eSuffix. + SuffixKey, + + // After the property value for eSpec or eSuffix. + AfterPropertyValue, + + // After the inner object's '}'. + EndObject, + + Error, + }; + + public: + NullPrincipalJSONHandler() = default; + virtual ~NullPrincipalJSONHandler() = default; + + virtual bool startObject() override; + + using PrincipalJSONHandlerShared::propertyName; + virtual bool propertyName(const JS::Latin1Char* name, size_t length) override; + + virtual bool endObject() override; + + virtual bool startArray() override { + NS_WARNING("Unexpected array value"); + mState = State::Error; + return false; + } + virtual bool endArray() override { + NS_WARNING("Unexpected array value"); + mState = State::Error; + return false; + } + + using PrincipalJSONHandlerShared::stringValue; + virtual bool stringValue(const JS::Latin1Char* str, size_t length) override; + + bool HasAccepted() const { return mState == State::EndObject; } + + protected: + virtual void SetErrorState() override { mState = State::Error; } + + private: + State mState = State::Init; + + nsCOMPtr mUri; + OriginAttributes mAttrs; +}; + +} // namespace mozilla + +#endif // mozilla_NullPrincipalJSONHandler_h diff --git a/caps/OriginAttributes.cpp b/caps/OriginAttributes.cpp new file mode 100644 index 0000000000..734a230495 --- /dev/null +++ b/caps/OriginAttributes.cpp @@ -0,0 +1,494 @@ +/* -*- 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; + nsCOMPtr uri = aURI; + // The URI could be nested (for example view-source:http://example.com), in + // that case we want to get the innermost URI (http://example.com). + nsCOMPtr nestedURI; + do { + NS_ENSURE_SUCCESS_VOID(uri->GetScheme(scheme)); + nestedURI = do_QueryInterface(uri); + // We can't just use GetInnermostURI on the nested URI, since that would + // also unwrap some about: URIs to hidden moz-safe-about: URIs, which we do + // not want. Thus we loop through with GetInnerURI until the URI isn't + // nested anymore or we encounter a about: scheme. + } while (nestedURI && !scheme.EqualsLiteral("about") && + NS_SUCCEEDED(nestedURI->GetInnerURI(getter_AddRefs(uri)))); + + 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 = uri->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 blobPrincipal; + if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal( + uri, getter_AddRefs(blobPrincipal))) { + MOZ_ASSERT(blobPrincipal); + topLevelInfo = blobPrincipal->OriginAttributesRef().*aTarget; + return; + } + + nsCOMPtr tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + MOZ_ASSERT(tldService); + NS_ENSURE_TRUE_VOID(tldService); + + nsAutoCString baseDomain; + rv = tldService->GetBaseDomain(uri, 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 = uri->GetPort(&port); + NS_ENSURE_SUCCESS_VOID(rv); + + nsAutoCString host; + rv = uri->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(uri, 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 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(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(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 "(,,[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(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..02e52f0799 --- /dev/null +++ b/caps/OriginAttributes.h @@ -0,0 +1,289 @@ +/* -*- 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 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(); + } + + // 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 "(,,[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/OriginAttributesHashKey.h b/caps/OriginAttributesHashKey.h new file mode 100644 index 0000000000..ff70c393d6 --- /dev/null +++ b/caps/OriginAttributesHashKey.h @@ -0,0 +1,55 @@ +/* -*- 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_OriginAttributesHashKey_h +#define mozilla_OriginAttributesHashKey_h + +#include "OriginAttributes.h" +#include "PLDHashTable.h" +#include "nsHashKeys.h" + +namespace mozilla { + +class OriginAttributesHashKey : public PLDHashEntryHdr { + public: + using KeyType = OriginAttributes; + using KeyTypeRef = const KeyType&; + using KeyTypePointer = const KeyType*; + + explicit OriginAttributesHashKey(KeyTypePointer aKey) { + mOriginAttributes = *aKey; + MOZ_COUNT_CTOR(OriginAttributesHashKey); + } + OriginAttributesHashKey(OriginAttributesHashKey&& aKey) noexcept { + mOriginAttributes = std::move(aKey.mOriginAttributes); + MOZ_COUNT_CTOR(OriginAttributesHashKey); + } + + MOZ_COUNTED_DTOR(OriginAttributesHashKey) + + KeyTypeRef GetKey() const { return mOriginAttributes; } + + bool KeyEquals(KeyTypePointer aKey) const { + return mOriginAttributes == *aKey; + } + + static KeyTypePointer KeyToPointer(KeyTypeRef aKey) { return &aKey; } + + static PLDHashNumber HashKey(KeyTypePointer aKey) { + nsAutoCString suffix; + aKey->CreateSuffix(suffix); + return mozilla::HashString(suffix); + } + + enum { ALLOW_MEMMOVE = true }; + + protected: + OriginAttributes mOriginAttributes; +}; + +} // namespace mozilla + +#endif diff --git a/caps/PrincipalHashKey.h b/caps/PrincipalHashKey.h new file mode 100644 index 0000000000..b5167aa11b --- /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(aKey)) { + MOZ_ASSERT(aKey); + MOZ_COUNT_CTOR(PrincipalHashKey); + } + PrincipalHashKey(PrincipalHashKey&& aKey) noexcept + : 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(aKey)); + } + + static const nsIPrincipal* KeyToPointer(const nsIPrincipal* aKey) { + return aKey; + } + static PLDHashNumber HashKey(const nsIPrincipal* aKey) { + const auto* bp = BasePrincipal::Cast(aKey); + return HashGeneric(bp->GetOriginNoSuffixHash(), bp->GetOriginSuffixHash()); + } + + enum { ALLOW_MEMMOVE = true }; + + protected: + nsCOMPtr mPrincipal; +}; + +} // namespace mozilla + +#endif diff --git a/caps/PrincipalJSONHandler.h b/caps/PrincipalJSONHandler.h new file mode 100644 index 0000000000..d9ef7725b3 --- /dev/null +++ b/caps/PrincipalJSONHandler.h @@ -0,0 +1,116 @@ +/* -*- 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_PrincipalJSONHandler_h +#define mozilla_PrincipalJSONHandler_h + +#include // size_t +#include // uint32_t + +#include "js/JSON.h" // JS::JSONParseHandler +#include "js/TypeDecls.h" // JS::Latin1Char + +#include "mozilla/AlreadyAddRefed.h" // already_AddRefed +#include "mozilla/RefPtr.h" // RefPtr +#include "mozilla/Variant.h" // Variant + +#include "nsDebug.h" // NS_WARNING +#include "nsPrintfCString.h" // nsPrintfCString + +#include "BasePrincipal.h" +#include "ContentPrincipalJSONHandler.h" +#include "ExpandedPrincipalJSONHandler.h" +#include "NullPrincipalJSONHandler.h" +#include "SharedJSONHandler.h" + +namespace mozilla { + +class PrincipalJSONHandlerTypes { + public: + enum class State { + Init, + + // After top-level object's '{'. + StartObject, + + // After the PrincipalKind property key for SystemPrincipal. + SystemPrincipal_Key, + // After the SystemPrincipal's inner object's '{'. + SystemPrincipal_StartObject, + // After the SystemPrincipal's inner object's '}'. + SystemPrincipal_EndObject, + + // After the PrincipalKind property key for NullPrincipal, ContentPrincipal, + // or ExpandedPrincipal, and also the entire inner object. + // Delegates to mInnerHandler until the inner object's '}'. + NullPrincipal_Inner, + ContentPrincipal_Inner, + ExpandedPrincipal_Inner, + + // After top-level object's '}'. + EndObject, + + Error, + }; + + using InnerHandlerT = + Maybe>; + + static constexpr bool CanContainExpandedPrincipal = true; +}; + +// JSON parse handler for the top-level object for principal. +// +// inner object +// | +// --------------------------------------------------------- +// | | +// {"1": {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}} +// | +// | +// | +// PrincipalKind +// +// For inner object except for the system principal case, this delegates +// to NullPrincipalJSONHandler, ContentPrincipalJSONHandler, or +// ExpandedPrincipalJSONHandler. +// +//// Null principal: +// {"0":{"0":"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}"}} +// +// Content principal: +// {"1":{"0":"https://mozilla.com"}} -> {"0":"https://mozilla.com"} +// +// Expanded principal: +// {"2":{"0":","}} +// +// System principal: +// {"3":{}} +class PrincipalJSONHandler + : public ContainerPrincipalJSONHandler { + using State = PrincipalJSONHandlerTypes::State; + using InnerHandlerT = PrincipalJSONHandlerTypes::InnerHandlerT; + + public: + PrincipalJSONHandler() = default; + virtual ~PrincipalJSONHandler() = default; + + virtual void error(const char* msg, uint32_t line, uint32_t column) override { + NS_WARNING( + nsPrintfCString("JSON Error: %s at line %u column %u of the JSON data", + msg, line, column) + .get()); + } + + already_AddRefed Get() { return mPrincipal.forget(); } + + protected: + virtual void SetErrorState() override { mState = State::Error; } +}; + +} // namespace mozilla + +#endif // mozilla_PrincipalJSONHandler_h diff --git a/caps/SharedJSONHandler.h b/caps/SharedJSONHandler.h new file mode 100644 index 0000000000..baf05810fe --- /dev/null +++ b/caps/SharedJSONHandler.h @@ -0,0 +1,114 @@ +/* -*- 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_SharedJSONHandler_h +#define mozilla_SharedJSONHandler_h + +#include "js/JSON.h" // JS::JSONParseHandler + +#include "mozilla/RefPtr.h" // RefPtr + +#include "BasePrincipal.h" // BasePrincipal + +namespace mozilla { + +// Base class of all principal JSON parse handlers. +class PrincipalJSONHandlerShared : public JS::JSONParseHandler { + public: + // Common handlers for inner objects. + + // NOTE: propertyName and stringValue are partial overloads. + // Subclasses should put the following: + // * `using PrincipalJSONHandlerShared::propertyName` + // * `using PrincipalJSONHandlerShared::stringValue` + virtual bool propertyName(const char16_t* name, size_t length) override { + NS_WARNING("Principal JSON shouldn't use non-ASCII"); + SetErrorState(); + return false; + }; + + virtual bool stringValue(const char16_t* str, size_t length) override { + NS_WARNING("Principal JSON shouldn't use non-ASCII"); + SetErrorState(); + return true; + } + + virtual bool numberValue(double d) override { + NS_WARNING("Unexpected number value"); + SetErrorState(); + return false; + } + + virtual bool booleanValue(bool v) override { + NS_WARNING("Unexpected boolean value"); + SetErrorState(); + return false; + } + + virtual bool nullValue() override { + NS_WARNING("Unexpected null value"); + SetErrorState(); + return false; + } + + virtual void error(const char* msg, uint32_t line, uint32_t column) override { + // Unused. + } + + protected: + // Set to the error state for the above handlers. + virtual void SetErrorState() = 0; + + public: + RefPtr mPrincipal; +}; + +// Base class shared between PrincipalJSONHandler and +// SubsumedPrincipalJSONHandler. +// This implements the common code for them, absorbing the difference about +// whether it can contain ExpandedPrincipal or not. +template +class ContainerPrincipalJSONHandler : public PrincipalJSONHandlerShared { + using State = typename HandlerTypesT::State; + using InnerHandlerT = typename HandlerTypesT::InnerHandlerT; + static constexpr bool CanContainExpandedPrincipal = + HandlerTypesT::CanContainExpandedPrincipal; + + public: + // Common handlers. + + virtual bool startObject() override; + + using PrincipalJSONHandlerShared::propertyName; + virtual bool propertyName(const JS::Latin1Char* name, size_t length) override; + virtual bool endObject() override; + + virtual bool startArray() override; + + virtual bool endArray() override; + + using PrincipalJSONHandlerShared::stringValue; + virtual bool stringValue(const JS::Latin1Char* str, size_t length) override; + + private: + bool ProcessInnerResult(bool aResult); + + template + bool CallOnInner(Func&& aFunc) { + return mInnerHandler->match([&](auto& aInner) { + bool result = aFunc(aInner); + return ProcessInnerResult(result); + }); + } + + protected: + State mState = State::Init; + + InnerHandlerT mInnerHandler; +}; + +} // namespace mozilla + +#endif // mozilla_SharedJSONHandler_h diff --git a/caps/SubsumedPrincipalJSONHandler.h b/caps/SubsumedPrincipalJSONHandler.h new file mode 100644 index 0000000000..67311a516d --- /dev/null +++ b/caps/SubsumedPrincipalJSONHandler.h @@ -0,0 +1,96 @@ +/* -*- 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_SubsumedPrincipalJSONHandler_h +#define mozilla_SubsumedPrincipalJSONHandler_h + +#include // size_t +#include // uint32_t + +#include "js/TypeDecls.h" // JS::Latin1Char + +#include "mozilla/AlreadyAddRefed.h" // already_AddRefed +#include "mozilla/Variant.h" // Variant +#include "mozilla/RefPtr.h" // RefPtr + +#include "nsDebug.h" // NS_WARNING + +#include "BasePrincipal.h" +#include "ContentPrincipalJSONHandler.h" +#include "NullPrincipalJSONHandler.h" +#include "SharedJSONHandler.h" + +namespace mozilla { + +class SubsumedPrincipalJSONHandlerTypes { + public: + enum class State { + Init, + + // After the subsumed principal object's '{'. + StartObject, + + // After the PrincipalKind property key for SystemPrincipal. + SystemPrincipal_Key, + // After the SystemPrincipal's inner object's '{'. + SystemPrincipal_StartObject, + // After the SystemPrincipal's inner object's '}'. + SystemPrincipal_EndObject, + + // After the PrincipalKind property key for NullPrincipal or + // ContentPrincipal, and also the entire inner object. + // Delegates to mInnerHandler until the inner object's '}'. + // + // Unlike PrincipalJSONHandler, subsumed principal doesn't contain + // ExpandedPrincipal. + NullPrincipal_Inner, + ContentPrincipal_Inner, + + // After the subsumed principal object's '}'. + EndObject, + + Error, + }; + + using InnerHandlerT = + Maybe>; + + static constexpr bool CanContainExpandedPrincipal = false; +}; + +// JSON parse handler for subsumed principal object inside ExpandedPrincipal's +// new format. +// +// Subsumed principal object +// | +// ---------------------------------- +// | | +// {"2": {"0": [{"1": {"0": https://mozilla.com"}}]}} +// | | | +// | --------------------------- +// | | +// | inner object +// PrincipalKind +// +// For inner object except for the system principal case, this delegates +// to NullPrincipalJSONHandler or ContentPrincipalJSONHandler. +class SubsumedPrincipalJSONHandler + : public ContainerPrincipalJSONHandler { + using State = SubsumedPrincipalJSONHandlerTypes::State; + using InnerHandlerT = SubsumedPrincipalJSONHandlerTypes::InnerHandlerT; + + public: + SubsumedPrincipalJSONHandler() = default; + virtual ~SubsumedPrincipalJSONHandler() = default; + + bool HasAccepted() const { return mState == State::EndObject; } + + protected: + virtual void SetErrorState() override { mState = State::Error; } +}; + +} // namespace mozilla + +#endif // mozilla_SubsumedPrincipalJSONHandler_h diff --git a/caps/SystemPrincipal.cpp b/caps/SystemPrincipal.cpp new file mode 100644 index 0000000000..5c2ed63a9a --- /dev/null +++ b/caps/SystemPrincipal.cpp @@ -0,0 +1,118 @@ +/* -*- 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, + 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 sSystemPrincipal + MOZ_GUARDED_BY(sSystemPrincipalMutex); + +already_AddRefed SystemPrincipal::Get() { + StaticMutexAutoLock lock(sSystemPrincipalMutex); + return do_AddRef(sSystemPrincipal); +} + +already_AddRefed 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..25f917ad24 --- /dev/null +++ b/caps/SystemPrincipal.h @@ -0,0 +1,79 @@ +/* -*- 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 mozilla { + +class SystemPrincipal final : public BasePrincipal, public nsISerializable { + SystemPrincipal(); + + public: + static already_AddRefed 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 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..87adef8a7e --- /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.toml"] +MOCHITEST_CHROME_MANIFESTS += ["tests/mochitest/chrome.toml"] +BROWSER_CHROME_MANIFESTS += ["tests/mochitest/browser.toml"] +XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.toml"] + +# 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", + "ExpandedPrincipal.h", + "NullPrincipal.h", + "OriginAttributes.h", + "OriginAttributesHashKey.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 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..bf60f83b04 --- /dev/null +++ b/caps/nsIPrincipal.idl @@ -0,0 +1,774 @@ +/* -*- 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 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>); +[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 serialization of the principal's origin, as + * specified by the whatwg HTML specification. If the principal does not + * have a host, the origin will be "null". + * + * https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin + * + * Note that this is different from `origin`, does not contain + * gecko-specific metadata like origin attributes, and should not be used + * for permissions or security checks. + * + * May be called from any thread. + */ + [noscript] readonly attribute ACString webExposedOriginSerialization; + + /** + * 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); + + /** + * Check if the Principal's URI is a content-accessible about: page + * + * May be called from any thread. + */ + [infallible] + boolean isContentAccessibleAboutURI(); + + /** + * 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. + * is not considered to be a mozbrowser element. + * +
+
+
+ + diff --git a/caps/tests/mochitest/test_bug246699.html b/caps/tests/mochitest/test_bug246699.html new file mode 100644 index 0000000000..9ff25c110a --- /dev/null +++ b/caps/tests/mochitest/test_bug246699.html @@ -0,0 +1,60 @@ + + + + + Test for Bug 246699 + + + + +Mozilla Bug 246699 +

+ +
+
+
+ + diff --git a/caps/tests/mochitest/test_bug292789.html b/caps/tests/mochitest/test_bug292789.html new file mode 100644 index 0000000000..cd177dfd5b --- /dev/null +++ b/caps/tests/mochitest/test_bug292789.html @@ -0,0 +1,121 @@ + + + + + Test for Bug 292789 + + + + +Mozilla Bug 292789 +

+ +
+
+
+ + diff --git a/caps/tests/mochitest/test_bug423375.html b/caps/tests/mochitest/test_bug423375.html new file mode 100644 index 0000000000..2ae1dfb923 --- /dev/null +++ b/caps/tests/mochitest/test_bug423375.html @@ -0,0 +1,43 @@ + + + + + Test for Bug 423375 + + + + +Mozilla Bug 423375 +

+ +
+
+
+ + diff --git a/caps/tests/mochitest/test_bug470804.html b/caps/tests/mochitest/test_bug470804.html new file mode 100644 index 0000000000..26da479af6 --- /dev/null +++ b/caps/tests/mochitest/test_bug470804.html @@ -0,0 +1,41 @@ + + + + + Test for Bug 470804 + + + + +Mozilla Bug 470804 +

+ +
+
+
+ + diff --git a/caps/tests/mochitest/test_bug995943.xhtml b/caps/tests/mochitest/test_bug995943.xhtml new file mode 100644 index 0000000000..0ba95f548d --- /dev/null +++ b/caps/tests/mochitest/test_bug995943.xhtml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `); +}); + +server.registerPathHandler("/frame", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(`

HTTP Subframe

`); +}); + +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,

Data Subframe

"; + } else { + response.setStatusLine(request.httpVersion, 404, "Not found"); + return; + } + + response.setHeader("Content-Type", "text/html"); + response.write(` + + `); +}); + +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", ""], + }, + 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,

Data Subframe

"; + } + return { redirectUrl }; + }, + { 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.toml b/caps/tests/unit/xpcshell.toml new file mode 100644 index 0000000000..a04efc4be5 --- /dev/null +++ b/caps/tests/unit/xpcshell.toml @@ -0,0 +1,16 @@ +[DEFAULT] +head = "" + +["test_ipv6_host_literal.js"] + +["test_oa_partitionKey_pattern.js"] + +["test_origin.js"] + +["test_precursor_principal.js"] +firefox-appdir = "browser" +skip-if = ["os == 'android'"] + +["test_site_origin.js"] + +["test_uri_escaping.js"] -- cgit v1.2.3