Bug 1548349 - Make sure the image cache for third-party tracking subresources is keyed to the top-level document's eTLD+1; r=baku,aosmond
authorEhsan Akhgari <ehsan@mozilla.com>
Thu, 02 May 2019 12:27:07 +0000
changeset 531127 c477a96a488995459baab0948a10cb7231d7cbe3
parent 531126 d9a454afe9bf4e6299021d96948f135ecbc827ca
child 531128 dd542aa67434d488b27603c1819bffc5b420e854
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, aosmond
bugs1548349
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1548349 - Make sure the image cache for third-party tracking subresources is keyed to the top-level document's eTLD+1; r=baku,aosmond Differential Revision: https://phabricator.services.mozilla.com/D29546
image/ImageCacheKey.cpp
image/ImageCacheKey.h
--- a/image/ImageCacheKey.cpp
+++ b/image/ImageCacheKey.cpp
@@ -8,20 +8,22 @@
 #include "mozilla/HashFunctions.h"
 #include "mozilla/Move.h"
 #include "mozilla/Unused.h"
 #include "nsContentUtils.h"
 #include "nsICookieService.h"
 #include "nsLayoutUtils.h"
 #include "nsString.h"
 #include "mozilla/AntiTrackingCommon.h"
+#include "mozilla/HashFunctions.h"
 #include "mozilla/dom/BlobURLProtocolHandler.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ServiceWorkerManager.h"
 #include "mozilla/dom/Document.h"
+#include "nsHashKeys.h"
 #include "nsPrintfCString.h"
 
 namespace mozilla {
 
 using namespace dom;
 
 namespace image {
 
@@ -38,48 +40,57 @@ static Maybe<uint64_t> BlobSerial(nsIURI
   return Nothing();
 }
 
 ImageCacheKey::ImageCacheKey(nsIURI* aURI, const OriginAttributes& aAttrs,
                              Document* aDocument)
     : mURI(aURI),
       mOriginAttributes(aAttrs),
       mControlledDocument(GetSpecialCaseDocumentToken(aDocument, aURI)),
+      mTopLevelBaseDomain(GetTopLevelBaseDomain(aDocument, aURI)),
       mIsChrome(false) {
   if (SchemeIs("blob")) {
     mBlobSerial = BlobSerial(mURI);
   } else if (SchemeIs("chrome")) {
     mIsChrome = true;
   }
 }
 
 ImageCacheKey::ImageCacheKey(const ImageCacheKey& aOther)
     : mURI(aOther.mURI),
       mBlobSerial(aOther.mBlobSerial),
       mBlobRef(aOther.mBlobRef),
       mOriginAttributes(aOther.mOriginAttributes),
       mControlledDocument(aOther.mControlledDocument),
+      mTopLevelBaseDomain(aOther.mTopLevelBaseDomain),
       mHash(aOther.mHash),
       mIsChrome(aOther.mIsChrome) {}
 
 ImageCacheKey::ImageCacheKey(ImageCacheKey&& aOther)
     : mURI(std::move(aOther.mURI)),
       mBlobSerial(std::move(aOther.mBlobSerial)),
       mBlobRef(std::move(aOther.mBlobRef)),
       mOriginAttributes(aOther.mOriginAttributes),
       mControlledDocument(aOther.mControlledDocument),
+      mTopLevelBaseDomain(aOther.mTopLevelBaseDomain),
       mHash(aOther.mHash),
       mIsChrome(aOther.mIsChrome) {}
 
 bool ImageCacheKey::operator==(const ImageCacheKey& aOther) const {
   // Don't share the image cache between a controlled document and anything
   // else.
   if (mControlledDocument != aOther.mControlledDocument) {
     return false;
   }
+  // Don't share the image cache between two top-level documents of different
+  // base domains.
+  if (!mTopLevelBaseDomain.Equals(aOther.mTopLevelBaseDomain,
+                                  nsCaseInsensitiveCStringComparator())) {
+    return false;
+  }
   // The origin attributes always have to match.
   if (mOriginAttributes != aOther.mOriginAttributes) {
     return false;
   }
   if (mBlobSerial || aOther.mBlobSerial) {
     if (mBlobSerial && mBlobRef.IsEmpty()) {
       EnsureBlobRef();
     }
@@ -122,17 +133,18 @@ void ImageCacheKey::EnsureHash() const {
     }
     hash = HashGeneric(*mBlobSerial, HashString(mBlobRef));
   } else {
     nsAutoCString spec;
     Unused << mURI->GetSpec(spec);
     hash = HashString(spec);
   }
 
-  hash = AddToHash(hash, HashString(suffix), HashString(ptr));
+  hash = AddToHash(hash, HashString(suffix), HashString(mTopLevelBaseDomain),
+                   HashString(ptr));
   mHash.emplace(hash);
 }
 
 bool ImageCacheKey::SchemeIs(const char* aScheme) {
   bool matches = false;
   return NS_SUCCEEDED(mURI->SchemeIs(aScheme, &matches)) && matches;
 }
 
@@ -148,33 +160,50 @@ void* ImageCacheKey::GetSpecialCaseDocum
 
   // For controlled documents, we cast the pointer into a void* to avoid
   // dereferencing it (since we only use it for comparisons).
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   if (swm && aDocument->GetController().isSome()) {
     return aDocument;
   }
 
+  return nullptr;
+}
+
+/* static */
+nsCString ImageCacheKey::GetTopLevelBaseDomain(Document* aDocument,
+                                               nsIURI* aURI) {
+  if (!aDocument || !aDocument->GetInnerWindow()) {
+    return EmptyCString();
+  }
+
   // If the window is 3rd party resource, let's see if first-party storage
   // access is granted for this image.
   if (nsContentUtils::IsThirdPartyTrackingResourceWindow(
           aDocument->GetInnerWindow())) {
     return nsContentUtils::StorageDisabledByAntiTracking(aDocument, aURI)
-               ? aDocument
-               : nullptr;
+               ? aDocument->GetBaseDomain()
+               : EmptyCString();
   }
 
   // Another scenario is if this image is a 3rd party resource loaded by a
   // first party context. In this case, we should check if the nsIChannel has
   // been marked as tracking resource, but we don't have the channel yet at
   // this point.  The best approach here is to be conservative: if we are sure
-  // that the permission is granted, let's return a nullptr. Otherwise, let's
-  // make a unique image cache.
+  // that the permission is granted, let's return 0. Otherwise, let's make a
+  // unique image cache per the top-level document eTLD+1.
   if (!AntiTrackingCommon::MaybeIsFirstPartyStorageAccessGrantedFor(
           aDocument->GetInnerWindow(), aURI)) {
-    return aDocument;
+    nsPIDOMWindowOuter* top = aDocument->GetInnerWindow()->GetScriptableTop();
+    nsPIDOMWindowInner* topInner = top->GetCurrentInnerWindow();
+    if (!topInner) {
+      return aDocument
+          ->GetBaseDomain();  // because we don't have anything better!
+    }
+    return topInner->GetExtantDoc() ? topInner->GetExtantDoc()->GetBaseDomain()
+                                    : EmptyCString();
   }
 
-  return nullptr;
+  return EmptyCString();
 }
 
 }  // namespace image
 }  // namespace mozilla
--- a/image/ImageCacheKey.h
+++ b/image/ImageCacheKey.h
@@ -56,29 +56,35 @@ class ImageCacheKey final {
 
   /// A token indicating which service worker controlled document this entry
   /// belongs to, if any.
   void* ControlledDocument() const { return mControlledDocument; }
 
  private:
   bool SchemeIs(const char* aScheme);
 
-  // For ServiceWorker and for anti-tracking we need to use the document as
+  // For ServiceWorker we need to use the document as
   // token for the key. All those exceptions are handled by this method.
   static void* GetSpecialCaseDocumentToken(dom::Document* aDocument,
                                            nsIURI* aURI);
 
+  // For anti-tracking we need to use the top-level document's base domain for
+  // the key. This is handled by this method.
+  static nsCString GetTopLevelBaseDomain(dom::Document* aDocument,
+                                         nsIURI* aURI);
+
   void EnsureHash() const;
   void EnsureBlobRef() const;
 
   nsCOMPtr<nsIURI> mURI;
   Maybe<uint64_t> mBlobSerial;
   mutable nsCString mBlobRef;
   OriginAttributes mOriginAttributes;
   void* mControlledDocument;
+  nsCString mTopLevelBaseDomain;
   mutable Maybe<PLDHashNumber> mHash;
   bool mIsChrome;
 };
 
 }  // namespace image
 }  // namespace mozilla
 
 #endif  // mozilla_image_src_ImageCacheKey_h