/* 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/dom/RemoteType.h" #include "OriginAttributes.h" #include "ipc/IPCMessageUtilsSpecializations.h" #include "mozilla/BasePrincipal.h" #include "mozilla/NeverDestroyed.h" #include "nsIURI.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" #include "nsURLHelper.h" namespace mozilla::dom { // URLParams value used to indicate a boolean 'true' value. // The lack of the attribute is used to indicate 'false'. static constexpr nsLiteralCString kRemoteTypeAttrTrue = "1"_ns; /* static */ const RemoteType& RemoteType::NotRemote() { static NeverDestroyed sNotRemote(Kind::NotRemote); return *sNotRemote; } /* static */ RemoteType RemoteType::SharedWeb(const OriginAttributes& aAttrs) { RemoteType type(Kind::WebContent); type.mUserContextId = aAttrs.mUserContextId; type.mPrivateBrowsingId = aAttrs.mPrivateBrowsingId; type.mGeckoViewSessionContextId = aAttrs.mGeckoViewSessionContextId; MOZ_ASSERT(type.CheckValidity()); return type; } RemoteType::RemoteType() = default; RemoteType::~RemoteType() = default; RemoteType::RemoteType(RemoteType::Kind aKind) : mKind(aKind) { MOZ_RELEASE_ASSERT(CheckValidity()); } RemoteType::RemoteType(const RemoteType&) = default; RemoteType& RemoteType::operator=(const RemoteType&) = default; bool RemoteType::operator==(const RemoteType& aOther) const { bool isEqual = mKind == aOther.mKind && mOriginNoSuffix == aOther.mOriginNoSuffix && std::apply( [&, this](auto&&... aAttrs) -> bool { return ((this->*aAttrs.mMember == aOther.*aAttrs.mMember) && ...); }, kAttrs); // Sanity checks to catch if someone misses a flag in one of the methods. MOZ_ASSERT_IF(isEqual, Hash() == aOther.Hash()); MOZ_ASSERT_IF(isEqual, Stringify() == aOther.Stringify()); return isEqual; } // We can directly add integers & enums to the hash, but need to call HashString // before adding strings to the hash. template static auto PreHashAttr(const T& aValue) { if constexpr (std::is_integral_v || std::is_enum_v) { return aValue; } else { return HashString(aValue); } } HashNumber RemoteType::Hash() const { return std::apply( [&, this](auto&&... aAttrs) -> HashNumber { return HashGeneric(PreHashAttr(mKind), HashString(mOriginNoSuffix), PreHashAttr(this->*aAttrs.mMember)...); }, kAttrs); } // NOTE: This specifically parses the "kind" part of the remote type string, // rather than the value returned from `StringifyKind`. This is only relevant // for `NotRemote`, which uses "parent" for `StringifyKind`, but VoidCString() // for `Stringify`. // // FIXME: We should make this more aligned by changing the representation of // `NotRemote` to "parent" (bug 1508757) static RemoteType::Kind ParseKind(const nsACString& aKindStr) { if (aKindStr.IsVoid()) { return RemoteType::Kind::NotRemote; } if (aKindStr == "prealloc"_ns) { return RemoteType::Kind::Prealloc; } // NOTE: The 'webIsolated' and 'web' prefixes are interpreted the same, and // generated based on the presence of a URL in ToString() to maintain // a level of backwards compatibility with tests assuming the old format. if (aKindStr == "web"_ns || aKindStr == "webIsolated"_ns) { return RemoteType::Kind::WebContent; } if (aKindStr == "webCOOP+COEP") { return RemoteType::Kind::WebCoopCoep; } if (aKindStr == "webServiceWorker") { return RemoteType::Kind::WebServiceWorker; } if (aKindStr == "file"_ns) { return RemoteType::Kind::File; } if (aKindStr == "privilegedabout"_ns) { return RemoteType::Kind::PrivilegedAbout; } if (aKindStr == "privilegedmozilla"_ns) { return RemoteType::Kind::PrivilegedMozilla; } if (aKindStr == "extension"_ns) { return RemoteType::Kind::Extension; } if (aKindStr == "inference"_ns) { return RemoteType::Kind::Inference; } return RemoteType::Kind::Unknown; } static bool ParseAttr(const nsACString& aValue, bool& aMember) { aMember = true; return aValue == kRemoteTypeAttrTrue; } static bool ParseAttr(const nsACString& aValue, uint32_t& aMember) { nsresult rv = NS_OK; aMember = aValue.ToInteger(&rv); return NS_SUCCEEDED(rv); } static bool ParseAttr(const nsACString& aValue, nsString& aMember) { aMember = NS_ConvertUTF8toUTF16(aValue); return true; } /* static */ RemoteType RemoteType::ParseNoValidityCheck( const nsACString& aRemoteType) { RemoteType type; int32_t equalIdx = aRemoteType.FindChar('='); if (equalIdx == kNotFound) { type.mKind = ParseKind(aRemoteType); return type; } type.mKind = ParseKind(Substring(aRemoteType, 0, equalIdx)); nsDependentCSubstring site(aRemoteType, equalIdx + 1); int32_t caretIdx = site.RFindChar('^'); if (caretIdx != kNotFound) { bool ok = URLParams::Parse( Substring(site, caretIdx + 1), true, [&](const nsACString& aName, const nsACString& aValue) { return std::apply( [&](auto&&... aAttrs) { return ((aName == aAttrs.mName && ParseAttr(aValue, type.*aAttrs.mMember)) || ...); }, kAttrs); }); NS_ENSURE_TRUE(ok, RemoteType{}); type.mOriginNoSuffix = Substring(site, 0, caretIdx); } else { type.mOriginNoSuffix = site; } return type; } /* static */ RemoteType RemoteType::Parse(const nsACString& aRemoteType) { RemoteType type = ParseNoValidityCheck(aRemoteType); // Ensure the type parsed to a valid RemoteType, as we never want to expose an // non-Unknown but invalid RemoteType outside of this .cpp file. NS_ENSURE_TRUE(type.CheckValidity(), RemoteType{}); // Ensure the string representation of the remote type matches aRemoteType. // This is much stricter than e.g. principal or OriginAttributes parsing, but // we shouldn't be storing RemoteType instances in the profile, so it should // be OK to be strict here. NS_ENSURE_TRUE(type.Stringify() == aRemoteType, RemoteType{}); return type; } nsCString RemoteType::StringifyKind() const { MOZ_ASSERT(CheckValidity()); switch (mKind) { case RemoteType::Kind::NotRemote: // NOTE: Unlike other remote types, the full RemoteType string for // NotRemote is VoidCString(). We hope to change this in bug 1508757. return "parent"_ns; case RemoteType::Kind::Prealloc: return "prealloc"_ns; case RemoteType::Kind::WebContent: // NOTE: See the comment in ParseKind for why we do this. return HasOrigin() ? "webIsolated"_ns : "web"_ns; case RemoteType::Kind::WebCoopCoep: return "webCOOP+COEP"_ns; case RemoteType::Kind::WebServiceWorker: return "webServiceWorker"_ns; case RemoteType::Kind::File: return "file"_ns; case RemoteType::Kind::PrivilegedAbout: return "privilegedabout"_ns; case RemoteType::Kind::PrivilegedMozilla: return "privilegedmozilla"_ns; case RemoteType::Kind::Extension: return "extension"_ns; case RemoteType::Kind::Inference: return "inference"_ns; case RemoteType::Kind::Unknown: // This is not a valid remote type, but we return a string here to avoid // crashes in logs if an unknown remote type is printed. return ""_ns; default: MOZ_CRASH("Unsupported Kind in StringifyKind"); } } static void SetAttr(URLParams& aParam, const nsACString& aName, const bool& aMember) { if (aMember) { aParam.Set(aName, kRemoteTypeAttrTrue); } } static void SetAttr(URLParams& aParam, const nsACString& aName, const uint32_t& aMember) { if (aMember != 0) { aParam.Set(aName, nsPrintfCString("%" PRIu32, aMember)); } } static void SetAttr(URLParams& aParam, const nsACString& aName, const nsString& aMember) { if (!aMember.IsEmpty()) { aParam.Set(aName, NS_ConvertUTF16toUTF8(aMember)); } } nsAutoCString RemoteType::StringifyMeta() const { MOZ_ASSERT(CheckValidity()); nsAutoCString meta(mOriginNoSuffix); if (HasAttrs()) { URLParams params; std::apply( [&, this](auto&&... aAttrs) { (SetAttr(params, aAttrs.mName, this->*aAttrs.mMember), ...); }, kAttrs); nsAutoCString paramsStr; params.Serialize(paramsStr, /* encode */ true); meta += "^"_ns + paramsStr; } return meta; } nsCString RemoteType::Stringify() const { MOZ_ASSERT(CheckValidity()); // FIXME: We should change the representation of not-remote! (bug 1508757) if (IsNotRemote()) { return VoidCString(); } nsCString kindStr = StringifyKind(); if (HasMeta()) { kindStr += "="_ns + StringifyMeta(); } return kindStr; } static bool AttrIsSet(const bool& aValue) { return aValue; } static bool AttrIsSet(const uint32_t& aValue) { return aValue != 0; } static bool AttrIsSet(const nsString& aValue) { return !aValue.IsEmpty(); } bool RemoteType::HasAttrs() const { return std::apply( [this](auto&&... aArgs) { return (AttrIsSet(this->*aArgs.mMember) || ...); }, kAttrs); } bool RemoteType::CheckValidity() const { // If we don't have a WebContent kind, no extra fields can be used. if (!IsWeb() && HasMeta()) { NS_WARNING("Invalid RemoteType: Non-web type has metadata"); return false; } if (HasOrigin()) { nsCOMPtr uri; if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mOriginNoSuffix))) { NS_WARNING("Invalid RemoteType: Invalid OriginNoSuffix URI"); return false; } nsCOMPtr principal = BasePrincipal::CreateContentPrincipal(uri, GetOriginAttributes()); if (!principal) { NS_WARNING("Invalid RemoteType: Failed to create content principal"); return false; } // Currently we always isolate by site, never by origin, so a remote type // should only contain a site origin. nsAutoCString origin; nsAutoCString siteOrigin; MOZ_ALWAYS_SUCCEEDS(principal->GetOriginNoSuffix(origin)); MOZ_ALWAYS_SUCCEEDS(principal->GetSiteOriginNoSuffix(siteOrigin)); if (origin != mOriginNoSuffix || origin != siteOrigin) { NS_WARNING("Invalid RemoteType: Non-canonical OriginNoSuffix"); return false; } } else if (IsWebCoopCoep() || IsWebServiceWorker()) { // These remote types require a URI specified. NS_WARNING( "Invalid RemoteType: Web{CoopCoep/ServiceWorker} without site origin"); return false; } return true; } bool RemoteType::SupportsPrealloc() const { MOZ_ASSERT(IsKnown()); MOZ_ASSERT(mKind != Kind::Prealloc); // NOTE: Extension processes do not support prealloc due to bug 1638119 return mKind != Kind::NotRemote && mKind != Kind::File && mKind != Kind::PrivilegedAbout && mKind != Kind::Extension && !mDisableJit; } OriginAttributes RemoteType::GetOriginAttributes() const { OriginAttributes attrs; attrs.mUserContextId = mUserContextId; attrs.mPrivateBrowsingId = mPrivateBrowsingId; attrs.mGeckoViewSessionContextId = mGeckoViewSessionContextId; return attrs; } RemoteType RemoteType::WithDisableJit(bool aDisableJit) const { MOZ_RELEASE_ASSERT(IsWeb()); RemoteType copy(*this); copy.mDisableJit = aDisableJit; MOZ_ASSERT(copy.CheckValidity()); return copy; } RemoteType RemoteType::WithSiteOrigin(const nsACString& aSiteOriginNoSuffix, Kind aNewKind) const { MOZ_RELEASE_ASSERT(IsSharedWeb()); RemoteType copy(*this); copy.mKind = aNewKind; copy.mOriginNoSuffix = aSiteOriginNoSuffix; // NOTE: We release-assert validity, as an invalid aSiteOriginURI or Kind // could create an invalid RemoteType. MOZ_RELEASE_ASSERT(copy.CheckValidity()); MOZ_RELEASE_ASSERT(copy.IsIsolatedWeb()); return copy; } } // namespace mozilla::dom // NOTE: This could probably be made more efficient by removing the // serialization to a string, but this keeps the logic simpler for now. namespace IPC { void ParamTraits::Write(MessageWriter* aWriter, const paramType& aParam) { MOZ_ASSERT(aParam.IsKnown(), "Cannot send unknown RemoteType"); WriteParam(aWriter, aParam.Stringify()); } bool ParamTraits::Read(MessageReader* aReader, paramType* aResult) { nsCString s; if (!ReadParam(aReader, &s)) { return false; } *aResult = mozilla::dom::RemoteType::Parse(s); return aResult->IsKnown(); } } // namespace IPC