Bug 1369303 - Part 2: Marking the performance timing API always reports 0 and the access of resource timing and user timing becomes NOP when 'privacy.resistFingerprinting' is true. r?baku,arthuredelstein draft
authorTim Huang <tihuang@mozilla.com>
Thu, 15 Jun 2017 16:48:27 +0800
changeset 594646 efcc5c50953092b6d18c8f61076ad21b063ea2c5
parent 594645 cc647f7e392c737eddb373d304b54194d2baeb14
child 594647 b6cf413989f0b68fca20a2d804b3d55ef9760d40
child 595141 8538d9db3bfb7e4d9423310e547b056a87dfabdb
push id64101
push userbmo:tihuang@mozilla.com
push dateThu, 15 Jun 2017 08:48:47 +0000
reviewersbaku, arthuredelstein
bugs1369303
milestone56.0a1
Bug 1369303 - Part 2: Marking the performance timing API always reports 0 and the access of resource timing and user timing becomes NOP when 'privacy.resistFingerprinting' is true. r?baku,arthuredelstein This patch is going to neutralize the threat of fingerprinting of performance API by spoofing the value of performance timing into 0, making getEntries* functions always returns an empty list and making mark() and measure() into NOP methods. In addition, this patch changes nsContentUtils::ShouldResistFingerprinting() to allow it can be called in both main thread and worker threads. MozReview-Commit-ID: C8Jt7KEMe5e
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/performance/Performance.cpp
dom/performance/PerformanceTiming.cpp
dom/performance/PerformanceTiming.h
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -2312,27 +2312,37 @@ nsContentUtils::IsCallerChrome()
   // If the check failed, look for UniversalXPConnect on the cx compartment.
   return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext());
 }
 
 /* static */
 bool
 nsContentUtils::ShouldResistFingerprinting()
 {
-  return nsRFPService::IsResistFingerprintingEnabled();
+  if (NS_IsMainThread()) {
+    return nsRFPService::IsResistFingerprintingEnabled();
+  }
+
+  workers::WorkerPrivate* workerPrivate = workers::GetCurrentThreadWorkerPrivate();
+  if (NS_WARN_IF(!workerPrivate)) {
+    return false;
+  }
+  workerPrivate->AssertIsOnWorkerThread();
+
+  return workerPrivate->ResistFingerprintingEnabled();
 }
 
 bool
 nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell)
 {
   if (!aDocShell) {
     return false;
   }
   bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument());
-  return !isChrome && nsRFPService::IsResistFingerprintingEnabled();
+  return !isChrome && ShouldResistFingerprinting();
 }
 
 /* static */
 void
 nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(int32_t  aChromeWidth,
                                                                 int32_t  aChromeHeight,
                                                                 int32_t  aScreenWidth,
                                                                 int32_t  aScreenHeight,
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -265,16 +265,17 @@ public:
     return SubjectPrincipal();
   }
 
   static bool LookupBindingMember(JSContext* aCx, nsIContent *aContent,
                                   JS::Handle<jsid> aId,
                                   JS::MutableHandle<JS::PropertyDescriptor> aDesc);
 
   // Check whether we should avoid leaking distinguishing information to JS/CSS.
+  // This function can be called both in the main thread and worker threads.
   static bool ShouldResistFingerprinting();
   static bool ShouldResistFingerprinting(nsIDocShell* aDocShell);
 
   // A helper function to calculate the rounded window size for fingerprinting
   // resistance. The rounded size is based on the chrome UI size and available
   // screen size. If the inputWidth/Height is greater than the available content
   // size, this will report the available content size. Otherwise, it will
   // round the size to the nearest upper 200x100.
@@ -2197,17 +2198,17 @@ public:
   /*
    * Returns true if the browser should attempt to prevent the given caller type
    * from collecting distinctive information about the browser that could
    * be used to "fingerprint" and track the user across websites.
    */
   static bool ResistFingerprinting(mozilla::dom::CallerType aCallerType)
   {
     return aCallerType != mozilla::dom::CallerType::System &&
-           mozilla::nsRFPService::IsResistFingerprintingEnabled();
+           ShouldResistFingerprinting();
   }
 
   /**
    * Returns true if the browser should show busy cursor when loading page.
    */
   static bool UseActivityCursor()
   {
     return sUseActivityCursor;
--- a/dom/performance/Performance.cpp
+++ b/dom/performance/Performance.cpp
@@ -166,25 +166,37 @@ JSObject*
 Performance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PerformanceBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 Performance::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval)
 {
+  // We return an empty list when 'privacy.resistFingerprinting' is on.
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    aRetval.Clear();
+    return;
+  }
+
   aRetval = mResourceEntries;
   aRetval.AppendElements(mUserEntries);
   aRetval.Sort(PerformanceEntryComparator());
 }
 
 void
 Performance::GetEntriesByType(const nsAString& aEntryType,
                               nsTArray<RefPtr<PerformanceEntry>>& aRetval)
 {
+  // We return an empty list when 'privacy.resistFingerprinting' is on.
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    aRetval.Clear();
+    return;
+  }
+
   if (aEntryType.EqualsLiteral("resource")) {
     aRetval = mResourceEntries;
     return;
   }
 
   aRetval.Clear();
 
   if (aEntryType.EqualsLiteral("mark") ||
@@ -199,16 +211,21 @@ Performance::GetEntriesByType(const nsAS
 
 void
 Performance::GetEntriesByName(const nsAString& aName,
                               const Optional<nsAString>& aEntryType,
                               nsTArray<RefPtr<PerformanceEntry>>& aRetval)
 {
   aRetval.Clear();
 
+  // We return an empty list when 'privacy.resistFingerprinting' is on.
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   for (PerformanceEntry* entry : mResourceEntries) {
     if (entry->GetName().Equals(aName) &&
         (!aEntryType.WasPassed() ||
          entry->GetEntryType().Equals(aEntryType.Value()))) {
       aRetval.AppendElement(entry);
     }
   }
 
@@ -256,16 +273,21 @@ Performance::RoundTime(double aTime) con
   return nsRFPService::ReduceTimePrecisionAsMSecs(
     floor(aTime / maxResolutionMs) * maxResolutionMs);
 }
 
 
 void
 Performance::Mark(const nsAString& aName, ErrorResult& aRv)
 {
+  // We add nothing when 'privacy.resistFingerprinting' is on.
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   // Don't add the entry if the buffer is full. XXX should be removed by bug 1159003.
   if (mUserEntries.Length() >= mResourceTimingBufferSize) {
     return;
   }
 
   if (IsPerformanceTimingAttribute(aName)) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return;
@@ -320,16 +342,21 @@ Performance::ResolveTimestampFromName(co
 }
 
 void
 Performance::Measure(const nsAString& aName,
                      const Optional<nsAString>& aStartMark,
                      const Optional<nsAString>& aEndMark,
                      ErrorResult& aRv)
 {
+  // We add nothing when 'privacy.resistFingerprinting' is on.
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   // Don't add the entry if the buffer is full. XXX should be removed by bug
   // 1159003.
   if (mUserEntries.Length() >= mResourceTimingBufferSize) {
     return;
   }
 
   DOMHighResTimeStamp startTime;
   DOMHighResTimeStamp endTime;
@@ -434,16 +461,22 @@ Performance::SetResourceTimingBufferSize
   mResourceTimingBufferSize = aMaxSize;
 }
 
 void
 Performance::InsertResourceEntry(PerformanceEntry* aEntry)
 {
   MOZ_ASSERT(aEntry);
   MOZ_ASSERT(mResourceEntries.Length() < mResourceTimingBufferSize);
+
+  // We won't add an entry when 'privacy.resistFingerprint' is true.
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   if (mResourceEntries.Length() >= mResourceTimingBufferSize) {
     return;
   }
 
   mResourceEntries.InsertElementSorted(aEntry,
                                        PerformanceEntryComparator());
   if (mResourceEntries.Length() == mResourceTimingBufferSize) {
     // call onresourcetimingbufferfull
--- a/dom/performance/PerformanceTiming.cpp
+++ b/dom/performance/PerformanceTiming.cpp
@@ -30,17 +30,18 @@ PerformanceTiming::PerformanceTiming(Per
     mRedirectCount(0),
     mTimingAllowed(true),
     mAllRedirectsSameOrigin(true),
     mInitialized(!!aChannel),
     mReportCrossOriginRedirect(true)
 {
   MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
 
-  if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     mZeroTime = 0;
   }
 
   // The aHttpChannel argument is null if this PerformanceTiming object is
   // being used for navigation timing (which is only relevant for documents).
   // It has a non-null value if this PerformanceTiming object is being used
   // for resource timing, which can include document loads, both toplevel and
   // in subframes, and resources linked from a document.
@@ -89,17 +90,18 @@ PerformanceTiming::InitializeTimingInfo(
 PerformanceTiming::~PerformanceTiming()
 {
 }
 
 DOMHighResTimeStamp
 PerformanceTiming::FetchStartHighRes()
 {
   if (!mFetchStart) {
-    if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return mZeroTime;
     }
     MOZ_ASSERT(!mAsyncOpen.IsNull(), "The fetch start time stamp should always be "
         "valid if the performance timing is enabled");
     mFetchStart = (!mAsyncOpen.IsNull())
         ? TimeStampToDOMHighRes(mAsyncOpen)
         : 0.0;
   }
@@ -146,29 +148,31 @@ bool
 PerformanceTiming::TimingAllowed() const
 {
   return mTimingAllowed;
 }
 
 uint16_t
 PerformanceTiming::GetRedirectCount() const
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return 0;
   }
   if (!mAllRedirectsSameOrigin) {
     return 0;
   }
   return mRedirectCount;
 }
 
 bool
 PerformanceTiming::ShouldReportCrossOriginRedirect() const
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return false;
   }
 
   // If the redirect count is 0, or if one of the cross-origin
   // redirects doesn't have the proper Timing-Allow-Origin header,
   // then RedirectStart and RedirectEnd will be set to zero
   return (mRedirectCount != 0) && mReportCrossOriginRedirect;
 }
@@ -181,17 +185,18 @@ PerformanceTiming::ShouldReportCrossOrig
  * It's up to the consumers of this method (PerformanceTiming::RedirectStart()
  * and PerformanceResourceTiming::RedirectStart() to make such verifications.
  *
  * @return a valid timing if the Performance Timing is enabled
  */
 DOMHighResTimeStamp
 PerformanceTiming::RedirectStartHighRes()
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return mZeroTime;
   }
   return TimeStampToDOMHighResOrFetchStart(mRedirectStart);
 }
 
 DOMTimeMilliSec
 PerformanceTiming::RedirectStart()
 {
@@ -214,17 +219,18 @@ PerformanceTiming::RedirectStart()
  * (PerformanceTiming::RedirectEnd() and
  * PerformanceResourceTiming::RedirectEnd() to make such verifications.
  *
  * @return a valid timing if the Performance Timing is enabled
  */
 DOMHighResTimeStamp
 PerformanceTiming::RedirectEndHighRes()
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return mZeroTime;
   }
   return TimeStampToDOMHighResOrFetchStart(mRedirectEnd);
 }
 
 DOMTimeMilliSec
 PerformanceTiming::RedirectEnd()
 {
@@ -237,97 +243,103 @@ PerformanceTiming::RedirectEnd()
     return static_cast<int64_t>(RedirectEndHighRes());
   }
   return 0;
 }
 
 DOMHighResTimeStamp
 PerformanceTiming::DomainLookupStartHighRes()
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return mZeroTime;
   }
   return TimeStampToDOMHighResOrFetchStart(mDomainLookupStart);
 }
 
 DOMTimeMilliSec
 PerformanceTiming::DomainLookupStart()
 {
   return static_cast<int64_t>(DomainLookupStartHighRes());
 }
 
 DOMHighResTimeStamp
 PerformanceTiming::DomainLookupEndHighRes()
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return mZeroTime;
   }
   // Bug 1155008 - nsHttpTransaction is racy. Return DomainLookupStart when null
   return mDomainLookupEnd.IsNull() ? DomainLookupStartHighRes()
                                    : TimeStampToDOMHighRes(mDomainLookupEnd);
 }
 
 DOMTimeMilliSec
 PerformanceTiming::DomainLookupEnd()
 {
   return static_cast<int64_t>(DomainLookupEndHighRes());
 }
 
 DOMHighResTimeStamp
 PerformanceTiming::ConnectStartHighRes()
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return mZeroTime;
   }
   return mConnectStart.IsNull() ? DomainLookupEndHighRes()
                                 : TimeStampToDOMHighRes(mConnectStart);
 }
 
 DOMTimeMilliSec
 PerformanceTiming::ConnectStart()
 {
   return static_cast<int64_t>(ConnectStartHighRes());
 }
 
 DOMHighResTimeStamp
 PerformanceTiming::ConnectEndHighRes()
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return mZeroTime;
   }
   // Bug 1155008 - nsHttpTransaction is racy. Return ConnectStart when null
   return mConnectEnd.IsNull() ? ConnectStartHighRes()
                               : TimeStampToDOMHighRes(mConnectEnd);
 }
 
 DOMTimeMilliSec
 PerformanceTiming::ConnectEnd()
 {
   return static_cast<int64_t>(ConnectEndHighRes());
 }
 
 DOMHighResTimeStamp
 PerformanceTiming::RequestStartHighRes()
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return mZeroTime;
   }
   return TimeStampToDOMHighResOrFetchStart(mRequestStart);
 }
 
 DOMTimeMilliSec
 PerformanceTiming::RequestStart()
 {
   return static_cast<int64_t>(RequestStartHighRes());
 }
 
 DOMHighResTimeStamp
 PerformanceTiming::ResponseStartHighRes()
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return mZeroTime;
   }
   if (mResponseStart.IsNull() ||
      (!mCacheReadStart.IsNull() && mCacheReadStart < mResponseStart)) {
     mResponseStart = mCacheReadStart;
   }
   return TimeStampToDOMHighResOrFetchStart(mResponseStart);
 }
@@ -336,17 +348,18 @@ DOMTimeMilliSec
 PerformanceTiming::ResponseStart()
 {
   return static_cast<int64_t>(ResponseStartHighRes());
 }
 
 DOMHighResTimeStamp
 PerformanceTiming::ResponseEndHighRes()
 {
-  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) {
+  if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized() ||
+      nsContentUtils::ShouldResistFingerprinting()) {
     return mZeroTime;
   }
   if (mResponseEnd.IsNull() ||
      (!mCacheReadEnd.IsNull() && mCacheReadEnd < mResponseEnd)) {
     mResponseEnd = mCacheReadEnd;
   }
   // Bug 1155008 - nsHttpTransaction is racy. Return ResponseStart when null
   return mResponseEnd.IsNull() ? ResponseStartHighRes()
--- a/dom/performance/PerformanceTiming.h
+++ b/dom/performance/PerformanceTiming.h
@@ -111,33 +111,36 @@ public:
   }
 
   virtual JSObject* WrapObject(JSContext *cx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   // PerformanceNavigation WebIDL methods
   DOMTimeMilliSec NavigationStart() const
   {
-    if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetNavigationStart();
   }
 
   DOMTimeMilliSec UnloadEventStart()
   {
-    if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetUnloadEventStart();
   }
 
   DOMTimeMilliSec UnloadEventEnd()
   {
-    if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetUnloadEventEnd();
   }
 
   uint16_t GetRedirectCount() const;
 
   // Checks if the resource is either same origin as the page that started
@@ -175,65 +178,72 @@ public:
   DOMTimeMilliSec ConnectStart();
   DOMTimeMilliSec ConnectEnd();
   DOMTimeMilliSec RequestStart();
   DOMTimeMilliSec ResponseStart();
   DOMTimeMilliSec ResponseEnd();
 
   DOMTimeMilliSec DomLoading()
   {
-    if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetDomLoading();
   }
 
   DOMTimeMilliSec DomInteractive() const
   {
-    if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetDomInteractive();
   }
 
   DOMTimeMilliSec DomContentLoadedEventStart() const
   {
-    if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetDomContentLoadedEventStart();
   }
 
   DOMTimeMilliSec DomContentLoadedEventEnd() const
   {
-    if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetDomContentLoadedEventEnd();
   }
 
   DOMTimeMilliSec DomComplete() const
   {
-    if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetDomComplete();
   }
 
   DOMTimeMilliSec LoadEventStart() const
   {
-    if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetLoadEventStart();
   }
 
   DOMTimeMilliSec LoadEventEnd() const
   {
-    if (!nsContentUtils::IsPerformanceTimingEnabled()) {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetLoadEventEnd();
   }
 
 private:
   ~PerformanceTiming();