Bug 1441336 - Use addon permissions for PerformanceTiming properties draft
authorTomislav Jovanovic <tomica@gmail.com>
Wed, 04 Apr 2018 16:54:26 +0200
changeset 780658 f5a218cdaa7f40f6a64f1235e15c8a5f088a97a5
parent 774711 8c71359d60e21055074ae8bc3dcb796d20f0cbaf
child 780659 0287128c8af8a1ce8f4a79fb03b6f28decf974f5
push id106067
push userbmo:tomica@gmail.com
push dateWed, 11 Apr 2018 18:55:08 +0000
bugs1441336
milestone61.0a1
Bug 1441336 - Use addon permissions for PerformanceTiming properties We need to side-step existing cross-origin checks in Performance Timing code when the caller is a web extension content script that otherwise has permission to access the cross-origin resource. MozReview-Commit-ID: 8IgtqZgPWgY
caps/BasePrincipal.cpp
caps/ExpandedPrincipal.cpp
caps/ExpandedPrincipal.h
dom/performance/PerformanceResourceTiming.cpp
dom/performance/PerformanceResourceTiming.h
dom/performance/PerformanceTiming.h
dom/webidl/PerformanceResourceTiming.webidl
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -459,16 +459,19 @@ BasePrincipal::CloneStrippingUserContext
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   return BasePrincipal::CreateCodebasePrincipal(uri, attrs);
 }
 
 bool
 BasePrincipal::AddonAllowsLoad(nsIURI* aURI, bool aExplicit /* = false */)
 {
+  if (Is<ExpandedPrincipal>()) {
+    return As<ExpandedPrincipal>()->AddonAllowsLoad(aURI, aExplicit);
+  }
   if (auto policy = AddonPolicy()) {
     return policy->CanAccessURI(aURI, aExplicit);
   }
   return false;
 }
 
 void
 BasePrincipal::FinishInit(const nsACString& aOriginNoSuffix,
--- a/caps/ExpandedPrincipal.cpp
+++ b/caps/ExpandedPrincipal.cpp
@@ -175,16 +175,27 @@ ExpandedPrincipal::AddonHasPermission(co
   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;
+}
+
 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
--- a/caps/ExpandedPrincipal.h
+++ b/caps/ExpandedPrincipal.h
@@ -32,16 +32,18 @@ public:
   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;
   virtual bool AddonHasPermission(const nsAtom* aPerm) override;
   virtual nsresult GetScriptLocation(nsACString &aStr) override;
 
+  bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false);
+
   // Returns the principal to inherit when this principal requests the given
   // URL. See BasePrincipal::PrincipalToInherit.
   nsIPrincipal* PrincipalToInherit(nsIURI* aRequestedURI = nullptr);
 
 protected:
   explicit ExpandedPrincipal(nsTArray<nsCOMPtr<nsIPrincipal>> &aWhiteList);
 
   virtual ~ExpandedPrincipal();
--- a/dom/performance/PerformanceResourceTiming.cpp
+++ b/dom/performance/PerformanceResourceTiming.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "PerformanceResourceTiming.h"
 #include "mozilla/dom/PerformanceResourceTimingBinding.h"
+#include "nsNetUtil.h"
 
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(PerformanceResourceTiming,
                                    PerformanceEntry,
                                    mPerformance)
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PerformanceResourceTiming,
@@ -26,16 +27,21 @@ NS_IMPL_RELEASE_INHERITED(PerformanceRes
 PerformanceResourceTiming::PerformanceResourceTiming(UniquePtr<PerformanceTimingData>&& aPerformanceTiming,
                                                      Performance* aPerformance,
                                                      const nsAString& aName)
   : PerformanceEntry(aPerformance->GetParentObject(), aName, NS_LITERAL_STRING("resource"))
   , mTimingData(Move(aPerformanceTiming))
   , mPerformance(aPerformance)
 {
   MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
+  if (NS_IsMainThread()) {
+    // Used to check if an addon content script has access to this timing.
+    // We don't need it in workers, and ignore mOriginalURI if null.
+    NS_NewURI(getter_AddRefs(mOriginalURI), aName);
+  }
 }
 
 PerformanceResourceTiming::~PerformanceResourceTiming()
 {
 }
 
 DOMHighResTimeStamp
 PerformanceResourceTiming::StartTime() const
@@ -75,8 +81,40 @@ size_t
 PerformanceResourceTiming::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   return PerformanceEntry::SizeOfExcludingThis(aMallocSizeOf) +
          mInitiatorType.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
          (mTimingData
             ? mTimingData->NextHopProtocol().SizeOfExcludingThisIfUnshared(aMallocSizeOf)
             : 0);
 }
+
+bool
+PerformanceResourceTiming::TimingAllowedForCaller(Maybe<nsIPrincipal*>& aCaller) const
+{
+  if (!mTimingData) {
+    return false;
+  }
+
+  if (mTimingData->TimingAllowed()) {
+    return true;
+  }
+
+  // Check if the addon has permission to access the cross-origin resource.
+  return mOriginalURI && aCaller.isSome() &&
+      BasePrincipal::Cast(aCaller.value())->AddonAllowsLoad(mOriginalURI);
+}
+
+bool
+PerformanceResourceTiming::ReportRedirectForCaller(Maybe<nsIPrincipal*>& aCaller) const
+{
+  if (!mTimingData) {
+    return false;
+  }
+
+  if (mTimingData->ShouldReportCrossOriginRedirect()) {
+    return true;
+  }
+
+  // Only report cross-origin redirect if the addon has <all_urls> permission.
+  return aCaller.isSome() &&
+      BasePrincipal::Cast(aCaller.value())->AddonHasPermission(nsGkAtoms::all_urlsPermission);
+}
--- a/dom/performance/PerformanceResourceTiming.h
+++ b/dom/performance/PerformanceResourceTiming.h
@@ -65,111 +65,129 @@ public:
   }
 
   DOMHighResTimeStamp FetchStart() const {
     return mTimingData
         ? mTimingData->FetchStartHighRes(mPerformance)
         : 0;
   }
 
-  DOMHighResTimeStamp RedirectStart() const {
+  DOMHighResTimeStamp RedirectStart(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
     // We have to check if all the redirect URIs had the same origin (since
-    // there is no check in RedirectEndHighRes())
-    return mTimingData && mTimingData->ShouldReportCrossOriginRedirect()
+    // there is no check in RedirectStartHighRes())
+    return ReportRedirectForCaller(aSubjectPrincipal)
         ? mTimingData->RedirectStartHighRes(mPerformance)
         : 0;
   }
 
-  DOMHighResTimeStamp RedirectEnd() const {
+  DOMHighResTimeStamp RedirectEnd(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
     // We have to check if all the redirect URIs had the same origin (since
     // there is no check in RedirectEndHighRes())
-    return mTimingData && mTimingData->ShouldReportCrossOriginRedirect()
+    return ReportRedirectForCaller(aSubjectPrincipal)
         ? mTimingData->RedirectEndHighRes(mPerformance)
         : 0;
   }
 
-  DOMHighResTimeStamp DomainLookupStart() const {
-    return mTimingData && mTimingData->TimingAllowed()
+  DOMHighResTimeStamp DomainLookupStart(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+    return TimingAllowedForCaller(aSubjectPrincipal)
         ? mTimingData->DomainLookupStartHighRes(mPerformance)
         : 0;
   }
 
-  DOMHighResTimeStamp DomainLookupEnd() const {
-    return mTimingData && mTimingData->TimingAllowed()
+  DOMHighResTimeStamp DomainLookupEnd(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+    return TimingAllowedForCaller(aSubjectPrincipal)
         ? mTimingData->DomainLookupEndHighRes(mPerformance)
         : 0;
   }
 
-  DOMHighResTimeStamp ConnectStart() const {
-    return mTimingData && mTimingData->TimingAllowed()
+  DOMHighResTimeStamp ConnectStart(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+    return TimingAllowedForCaller(aSubjectPrincipal)
         ? mTimingData->ConnectStartHighRes(mPerformance)
         : 0;
   }
 
-  DOMHighResTimeStamp ConnectEnd() const {
-    return mTimingData && mTimingData->TimingAllowed()
+  DOMHighResTimeStamp ConnectEnd(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+    return TimingAllowedForCaller(aSubjectPrincipal)
         ? mTimingData->ConnectEndHighRes(mPerformance)
         : 0;
   }
 
-  DOMHighResTimeStamp RequestStart() const {
-    return mTimingData && mTimingData->TimingAllowed()
+  DOMHighResTimeStamp RequestStart(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+    return TimingAllowedForCaller(aSubjectPrincipal)
         ? mTimingData->RequestStartHighRes(mPerformance)
         : 0;
   }
 
-  DOMHighResTimeStamp ResponseStart() const {
-    return mTimingData && mTimingData->TimingAllowed()
+  DOMHighResTimeStamp ResponseStart(Maybe<nsIPrincipal*>& aSubjectPrincipal) const {
+    return TimingAllowedForCaller(aSubjectPrincipal)
         ? mTimingData->ResponseStartHighRes(mPerformance)
         : 0;
   }
 
   DOMHighResTimeStamp ResponseEnd() const {
     return mTimingData
         ? mTimingData->ResponseEndHighRes(mPerformance)
         : 0;
   }
 
-  DOMHighResTimeStamp SecureConnectionStart() const
+  DOMHighResTimeStamp SecureConnectionStart(Maybe<nsIPrincipal*>& aSubjectPrincipal) const
   {
-    return mTimingData && mTimingData->TimingAllowed()
-        ?  mTimingData->SecureConnectionStartHighRes(mPerformance)
+    return TimingAllowedForCaller(aSubjectPrincipal)
+        ? mTimingData->SecureConnectionStartHighRes(mPerformance)
         : 0;
   }
 
   virtual const PerformanceResourceTiming* ToResourceTiming() const override
   {
     return this;
   }
 
-  uint64_t TransferSize() const
+  uint64_t TransferSize(Maybe<nsIPrincipal*>& aSubjectPrincipal) const
   {
-    return mTimingData ? mTimingData->TransferSize() : 0;
+    return TimingAllowedForCaller(aSubjectPrincipal)
+        ? mTimingData->TransferSize()
+        : 0;
   }
 
-  uint64_t EncodedBodySize() const
+  uint64_t EncodedBodySize(Maybe<nsIPrincipal*>& aSubjectPrincipal) const
   {
-    return mTimingData ? mTimingData->EncodedBodySize() : 0;
+    return TimingAllowedForCaller(aSubjectPrincipal)
+        ? mTimingData->EncodedBodySize()
+        : 0;
   }
 
-  uint64_t DecodedBodySize() const
+  uint64_t DecodedBodySize(Maybe<nsIPrincipal*>& aSubjectPrincipal) const
   {
-    return mTimingData ? mTimingData->DecodedBodySize() : 0;
+    return TimingAllowedForCaller(aSubjectPrincipal)
+        ? mTimingData->DecodedBodySize()
+        : 0;
   }
 
   size_t
   SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~PerformanceResourceTiming();
 
   size_t
   SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
+  // Check if caller has access to cross-origin timings, either by the rules
+  // from the spec, or based on addon permissions.
+  bool
+  TimingAllowedForCaller(Maybe<nsIPrincipal*>& aCaller) const;
+
+  // Check if cross-origin redirects should be reported to the caller.
+  bool
+  ReportRedirectForCaller(Maybe<nsIPrincipal*>& aCaller) const;
+
   nsString mInitiatorType;
   UniquePtr<PerformanceTimingData> mTimingData;
   RefPtr<Performance> mPerformance;
+
+  // The same initial requested URI as the `name` attribute.
+  nsCOMPtr<nsIURI> mOriginalURI;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_PerformanceResourceTiming_h___ */
--- a/dom/performance/PerformanceTiming.h
+++ b/dom/performance/PerformanceTiming.h
@@ -49,27 +49,27 @@ public:
 
   const nsString& NextHopProtocol() const
   {
     return mNextHopProtocol;
   }
 
   uint64_t TransferSize() const
   {
-    return mTimingAllowed ? mTransferSize : 0;
+    return mTransferSize;
   }
 
   uint64_t EncodedBodySize() const
   {
-    return mTimingAllowed ? mEncodedBodySize : 0;
+    return mEncodedBodySize;
   }
 
   uint64_t DecodedBodySize() const
   {
-    return mTimingAllowed ? mDecodedBodySize : 0;
+    return mDecodedBodySize;
   }
 
   /**
    * @param   aStamp
    *          The TimeStamp recorded for a specific event. This TimeStamp can
    *          be null.
    * @return  the duration of an event with a given TimeStamp, relative to the
    *          navigationStart TimeStamp (the moment the user landed on the
--- a/dom/webidl/PerformanceResourceTiming.webidl
+++ b/dom/webidl/PerformanceResourceTiming.webidl
@@ -12,26 +12,42 @@
 
 [Exposed=(Window,Worker)]
 interface PerformanceResourceTiming : PerformanceEntry
 {
   readonly attribute DOMString initiatorType;
   readonly attribute DOMString nextHopProtocol;
 
   readonly attribute DOMHighResTimeStamp workerStart;
+
+  [NeedsSubjectPrincipal]
   readonly attribute DOMHighResTimeStamp redirectStart;
+  [NeedsSubjectPrincipal]
   readonly attribute DOMHighResTimeStamp redirectEnd;
+
   readonly attribute DOMHighResTimeStamp fetchStart;
+
+  [NeedsSubjectPrincipal]
   readonly attribute DOMHighResTimeStamp domainLookupStart;
+  [NeedsSubjectPrincipal]
   readonly attribute DOMHighResTimeStamp domainLookupEnd;
+  [NeedsSubjectPrincipal]
   readonly attribute DOMHighResTimeStamp connectStart;
+  [NeedsSubjectPrincipal]
   readonly attribute DOMHighResTimeStamp connectEnd;
+  [NeedsSubjectPrincipal]
   readonly attribute DOMHighResTimeStamp secureConnectionStart;
+  [NeedsSubjectPrincipal]
   readonly attribute DOMHighResTimeStamp requestStart;
+  [NeedsSubjectPrincipal]
   readonly attribute DOMHighResTimeStamp responseStart;
+
   readonly attribute DOMHighResTimeStamp responseEnd;
 
+  [NeedsSubjectPrincipal]
   readonly attribute unsigned long long transferSize;
+  [NeedsSubjectPrincipal]
   readonly attribute unsigned long long encodedBodySize;
+  [NeedsSubjectPrincipal]
   readonly attribute unsigned long long decodedBodySize;
 
   jsonifier;
 };