/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsIPrincipal.h" #include "xpcpublic.h" #include "nsString.h" #include "nsJSPrincipals.h" #include "nsCOMPtr.h" #include "nsStringBuffer.h" #include "mozilla/BasePrincipal.h" #include "mozilla/StaticPtr.h" #include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::ipc; NS_IMETHODIMP_(MozExternalRefCountType) nsJSPrincipals::AddRef() { MOZ_ASSERT(int32_t(refcount) >= 0, "illegal refcnt"); nsrefcnt count = ++refcount; NS_LOG_ADDREF(this, count, "nsJSPrincipals", sizeof(*this)); return count; } NS_IMETHODIMP_(MozExternalRefCountType) nsJSPrincipals::Release() { MOZ_ASSERT(0 != refcount, "dup release"); nsrefcnt count = --refcount; NS_LOG_RELEASE(this, count, "nsJSPrincipals"); if (count == 0) { delete this; } return count; } /* static */ bool nsJSPrincipals::Subsume(JSPrincipals* jsprin, JSPrincipals* other) { bool result; nsresult rv = nsJSPrincipals::get(jsprin)->Subsumes( nsJSPrincipals::get(other), &result); return NS_SUCCEEDED(rv) && result; } /* static */ void nsJSPrincipals::Destroy(JSPrincipals* jsprin) { // The JS runtime can call this method during the last GC when // nsScriptSecurityManager is destroyed. So we must not assume here that // the security manager still exists. nsJSPrincipals* nsjsprin = nsJSPrincipals::get(jsprin); // We need to destroy the nsIPrincipal. We'll do this by adding // to the refcount and calling release #ifdef NS_BUILD_REFCNT_LOGGING // The refcount logging considers AddRef-to-1 to indicate creation, // so trick it into thinking it's otherwise, but balance the // Release() we do below. nsjsprin->refcount++; nsjsprin->AddRef(); nsjsprin->refcount--; #else nsjsprin->refcount++; #endif nsjsprin->Release(); } #ifdef DEBUG // Defined here so one can do principals->dump() in the debugger JS_PUBLIC_API void JSPrincipals::dump() { if (debugToken == nsJSPrincipals::DEBUG_TOKEN) { nsAutoCString str; nsresult rv = static_cast(this)->GetScriptLocation(str); fprintf(stderr, "nsIPrincipal (%p) = %s\n", static_cast(this), NS_SUCCEEDED(rv) ? str.get() : "(unknown)"); } else { fprintf(stderr, "!!! JSPrincipals (%p) is not nsJSPrincipals instance - bad token: " "actual=0x%x expected=0x%x\n", this, unsigned(debugToken), unsigned(nsJSPrincipals::DEBUG_TOKEN)); } } #endif /* static */ bool nsJSPrincipals::ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader, JSPrincipals** aOutPrincipals) { uint32_t tag; uint32_t unused; if (!JS_ReadUint32Pair(aReader, &tag, &unused)) { return false; } if (tag != SCTAG_DOM_NULL_PRINCIPAL && tag != SCTAG_DOM_SYSTEM_PRINCIPAL && tag != SCTAG_DOM_CONTENT_PRINCIPAL && tag != SCTAG_DOM_EXPANDED_PRINCIPAL) { xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); return false; } return ReadKnownPrincipalType(aCx, aReader, tag, aOutPrincipals); } static bool ReadPrincipalInfo(JSStructuredCloneReader* aReader, OriginAttributes& aAttrs, nsACString& aSpec, nsACString& aOriginNoSuffix, nsACString& aBaseDomain) { uint32_t suffixLength, specLength; if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) { return false; } nsAutoCString suffix; if (!suffix.SetLength(suffixLength, fallible)) { return false; } if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) { return false; } if (!aAttrs.PopulateFromSuffix(suffix)) { return false; } if (!aSpec.SetLength(specLength, fallible)) { return false; } if (!JS_ReadBytes(aReader, aSpec.BeginWriting(), specLength)) { return false; } uint32_t originNoSuffixLength, dummy; if (!JS_ReadUint32Pair(aReader, &originNoSuffixLength, &dummy)) { return false; } MOZ_ASSERT(dummy == 0); if (dummy != 0) { return false; } if (!aOriginNoSuffix.SetLength(originNoSuffixLength, fallible)) { return false; } if (!JS_ReadBytes(aReader, aOriginNoSuffix.BeginWriting(), originNoSuffixLength)) { return false; } uint32_t baseDomainIsVoid, baseDomainLength; if (!JS_ReadUint32Pair(aReader, &baseDomainIsVoid, &baseDomainLength)) { return false; } if (baseDomainIsVoid != 0 && baseDomainIsVoid != 1) { return false; } if (baseDomainIsVoid) { if (baseDomainLength != 0) { return false; } aBaseDomain.SetIsVoid(true); return true; } if (!aBaseDomain.SetLength(baseDomainLength, fallible)) { return false; } if (!JS_ReadBytes(aReader, aBaseDomain.BeginWriting(), baseDomainLength)) { return false; } return true; } static bool ReadPrincipalInfo(JSStructuredCloneReader* aReader, uint32_t aTag, PrincipalInfo& aInfo) { if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) { aInfo = SystemPrincipalInfo(); } else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) { OriginAttributes attrs; nsAutoCString spec; nsAutoCString originNoSuffix; nsAutoCString baseDomain; if (!::ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix, baseDomain)) { return false; } aInfo = NullPrincipalInfo(attrs, spec); } else if (aTag == SCTAG_DOM_EXPANDED_PRINCIPAL) { uint32_t length, unused; if (!JS_ReadUint32Pair(aReader, &length, &unused)) { return false; } ExpandedPrincipalInfo expanded; for (uint32_t i = 0; i < length; i++) { uint32_t tag; if (!JS_ReadUint32Pair(aReader, &tag, &unused)) { return false; } PrincipalInfo sub; if (!ReadPrincipalInfo(aReader, tag, sub)) { return false; } expanded.allowlist().AppendElement(sub); } aInfo = expanded; } else if (aTag == SCTAG_DOM_CONTENT_PRINCIPAL) { OriginAttributes attrs; nsAutoCString spec; nsAutoCString originNoSuffix; nsAutoCString baseDomain; if (!::ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix, baseDomain)) { return false; } #ifdef FUZZING if (originNoSuffix.IsEmpty()) { return false; } #endif MOZ_DIAGNOSTIC_ASSERT(!originNoSuffix.IsEmpty()); // XXX: Do we care about mDomain for structured clone? aInfo = ContentPrincipalInfo(attrs, originNoSuffix, spec, Nothing(), baseDomain); } else { #ifdef FUZZING return false; #else MOZ_CRASH("unexpected principal structured clone tag"); #endif } return true; } /* static */ bool nsJSPrincipals::ReadPrincipalInfo(JSStructuredCloneReader* aReader, PrincipalInfo& aInfo) { uint32_t tag, unused; if (!JS_ReadUint32Pair(aReader, &tag, &unused)) { return false; } return ::ReadPrincipalInfo(aReader, tag, aInfo); } /* static */ bool nsJSPrincipals::ReadKnownPrincipalType(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, JSPrincipals** aOutPrincipals) { MOZ_ASSERT(aTag == SCTAG_DOM_NULL_PRINCIPAL || aTag == SCTAG_DOM_SYSTEM_PRINCIPAL || aTag == SCTAG_DOM_CONTENT_PRINCIPAL || aTag == SCTAG_DOM_EXPANDED_PRINCIPAL); PrincipalInfo info; if (!::ReadPrincipalInfo(aReader, aTag, info)) { return false; } auto principalOrErr = PrincipalInfoToPrincipal(info); if (NS_WARN_IF(principalOrErr.isErr())) { xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); return false; } nsCOMPtr principal = principalOrErr.unwrap(); *aOutPrincipals = get(principal.forget().take()); return true; } static bool WritePrincipalInfo(JSStructuredCloneWriter* aWriter, const OriginAttributes& aAttrs, const nsCString& aSpec, const nsCString& aOriginNoSuffix, const nsCString& aBaseDomain) { nsAutoCString suffix; aAttrs.CreateSuffix(suffix); if (!(JS_WriteUint32Pair(aWriter, suffix.Length(), aSpec.Length()) && JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) && JS_WriteBytes(aWriter, aSpec.get(), aSpec.Length()) && JS_WriteUint32Pair(aWriter, aOriginNoSuffix.Length(), 0) && JS_WriteBytes(aWriter, aOriginNoSuffix.get(), aOriginNoSuffix.Length()))) { return false; } if (aBaseDomain.IsVoid()) { return JS_WriteUint32Pair(aWriter, 1, 0); } return JS_WriteUint32Pair(aWriter, 0, aBaseDomain.Length()) && JS_WriteBytes(aWriter, aBaseDomain.get(), aBaseDomain.Length()); } /* static */ bool nsJSPrincipals::WritePrincipalInfo(JSStructuredCloneWriter* aWriter, const PrincipalInfo& aInfo) { if (aInfo.type() == PrincipalInfo::TNullPrincipalInfo) { const NullPrincipalInfo& nullInfo = aInfo; return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0) && ::WritePrincipalInfo(aWriter, nullInfo.attrs(), nullInfo.spec(), ""_ns, ""_ns); } if (aInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0); } if (aInfo.type() == PrincipalInfo::TExpandedPrincipalInfo) { const ExpandedPrincipalInfo& expanded = aInfo; if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_EXPANDED_PRINCIPAL, 0) || !JS_WriteUint32Pair(aWriter, expanded.allowlist().Length(), 0)) { return false; } for (uint32_t i = 0; i < expanded.allowlist().Length(); i++) { if (!WritePrincipalInfo(aWriter, expanded.allowlist()[i])) { return false; } } return true; } MOZ_ASSERT(aInfo.type() == PrincipalInfo::TContentPrincipalInfo); const ContentPrincipalInfo& cInfo = aInfo; return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) && ::WritePrincipalInfo(aWriter, cInfo.attrs(), cInfo.spec(), cInfo.originNoSuffix(), cInfo.baseDomain()); } bool nsJSPrincipals::write(JSContext* aCx, JSStructuredCloneWriter* aWriter) { PrincipalInfo info; if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(this, &info)))) { xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); return false; } return WritePrincipalInfo(aWriter, info); } bool nsJSPrincipals::isSystemOrAddonPrincipal() { JS::AutoSuppressGCAnalysis suppress; return this->IsSystemPrincipal() || this->GetIsAddonOrExpandedAddonPrincipal(); }