зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1475189 - Block storage access in tracking sub-resources when not in iframes - part 3 - image cache, r=ehsan
--HG-- rename : toolkit/components/antitracking/test/browser/script.sjs => toolkit/components/antitracking/test/browser/subResources.sjs
This commit is contained in:
Родитель
1892fdf469
Коммит
9fc78a12aa
|
@ -10,9 +10,11 @@
|
|||
#include "nsContentUtils.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "mozilla/AntiTrackingCommon.h"
|
||||
#include "mozilla/dom/BlobURLProtocolHandler.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
#include "mozilla/dom/ServiceWorkerManager.h"
|
||||
#include "mozilla/StaticPrefs.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsPrintfCString.h"
|
||||
|
||||
|
@ -128,26 +130,45 @@ ImageCacheKey::SchemeIs(const char* aScheme)
|
|||
/* static */ void*
|
||||
ImageCacheKey::GetSpecialCaseDocumentToken(nsIDocument* aDocument, nsIURI* aURI)
|
||||
{
|
||||
if (!aDocument) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// For controlled documents, we cast the pointer into a void* to avoid
|
||||
// dereferencing it (since we only use it for comparisons).
|
||||
void* pointer = nullptr;
|
||||
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
||||
if (aDocument && swm) {
|
||||
ErrorResult rv;
|
||||
if (aDocument->GetController().isSome()) {
|
||||
pointer = aDocument;
|
||||
}
|
||||
if (swm && aDocument->GetController().isSome()) {
|
||||
return aDocument;
|
||||
}
|
||||
|
||||
// If this document has been marked as tracker, let's use its address to make
|
||||
// a unique cache key.
|
||||
if (!pointer && aDocument &&
|
||||
nsContentUtils::StorageDisabledByAntiTracking(aDocument->GetInnerWindow(),
|
||||
nullptr, aURI)) {
|
||||
pointer = aDocument;
|
||||
// We want to have a unique image cache if the anti-tracking feature is
|
||||
// enabled for 3rd party resources.
|
||||
if (!StaticPrefs::privacy_restrict3rdpartystorage_enabled() ||
|
||||
!nsContentUtils::IsThirdPartyWindowOrChannel(aDocument->GetInnerWindow(),
|
||||
nullptr, aURI)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return pointer;
|
||||
// If the window is 3rd party resource, let's see if the first party storage
|
||||
// access is granted for this image.
|
||||
if (nsContentUtils::IsTrackingResourceWindow(aDocument->GetInnerWindow())) {
|
||||
return nsContentUtils::StorageDisabledByAntiTracking(aDocument->GetInnerWindow(),
|
||||
nullptr, aURI)
|
||||
? aDocument : nullptr;
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (!AntiTrackingCommon::MaybeIsFirstPartyStorageAccessGrantedFor(aDocument->GetInnerWindow(),
|
||||
aURI)) {
|
||||
return aDocument;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
|
|
|
@ -157,7 +157,7 @@ AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(nsPIDOMWindowInner* a3rd
|
|||
}
|
||||
|
||||
if (!nsContentUtils::IsThirdPartyWindowOrChannel(a3rdPartyTrackingWindow,
|
||||
nullptr, nullptr) ||
|
||||
nullptr, aURI) ||
|
||||
!nsContentUtils::IsTrackingResourceWindow(a3rdPartyTrackingWindow)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -257,3 +257,51 @@ AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(nsIHttpChannel* aChannel
|
|||
|
||||
return result == nsIPermissionManager::ALLOW_ACTION;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
AntiTrackingCommon::MaybeIsFirstPartyStorageAccessGrantedFor(nsPIDOMWindowInner* aFirstPartyWindow,
|
||||
nsIURI* aURI)
|
||||
{
|
||||
MOZ_ASSERT(aFirstPartyWindow);
|
||||
MOZ_ASSERT(!nsContentUtils::IsTrackingResourceWindow(aFirstPartyWindow));
|
||||
MOZ_ASSERT(aURI);
|
||||
|
||||
if (!StaticPrefs::privacy_restrict3rdpartystorage_enabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!nsContentUtils::IsThirdPartyWindowOrChannel(aFirstPartyWindow,
|
||||
nullptr, aURI)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> parentPrincipal =
|
||||
nsGlobalWindowInner::Cast(aFirstPartyWindow)->GetPrincipal();
|
||||
if (NS_WARN_IF(!parentPrincipal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoString origin;
|
||||
nsresult rv = nsContentUtils::GetUTFOrigin(aURI, origin);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_ConvertUTF16toUTF8 utf8Origin(origin);
|
||||
|
||||
nsAutoCString type;
|
||||
CreatePermissionKey(utf8Origin, utf8Origin, type);
|
||||
|
||||
nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
|
||||
if (NS_WARN_IF(!pm)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t result = 0;
|
||||
rv = pm->TestPermissionFromPrincipal(parentPrincipal, type.get(), &result);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return result == nsIPermissionManager::ALLOW_ACTION;
|
||||
}
|
||||
|
|
|
@ -21,10 +21,22 @@ class AntiTrackingCommon final
|
|||
public:
|
||||
// This method returns true if the URI has first party storage access when
|
||||
// loaded inside the passed 3rd party context tracking resource window.
|
||||
// If the window is first party context, please use
|
||||
// MaybeIsFirstPartyStorageAccessGrantedFor();
|
||||
static bool
|
||||
IsFirstPartyStorageAccessGrantedFor(nsPIDOMWindowInner* a3rdPartyTrackingWindow,
|
||||
nsIURI* aURI);
|
||||
|
||||
// Note: you should use IsFirstPartyStorageAccessGrantedFor() passing the
|
||||
// nsIHttpChannel! Use this method _only_ if the channel is not available.
|
||||
// For first party window, it's impossible to know if the aURI is a tracking
|
||||
// resource synchronously, so here we return the best guest: if we are sure
|
||||
// that the permission is granted for the origin of aURI, this method returns
|
||||
// true, otherwise false.
|
||||
static bool
|
||||
MaybeIsFirstPartyStorageAccessGrantedFor(nsPIDOMWindowInner* aFirstPartyWindow,
|
||||
nsIURI* aURI);
|
||||
|
||||
// This can be called only if the a3rdPartyTrackingChannel is _really_ a 3rd
|
||||
// party context and marked as tracking resource.
|
||||
// It returns true if the URI has access to the first party storage.
|
||||
|
|
|
@ -19,4 +19,4 @@ support-files = server.sjs
|
|||
[browser_imageCache.js]
|
||||
support-files = image.sjs
|
||||
[browser_subResources.js]
|
||||
support-files = script.sjs
|
||||
support-files = subResources.sjs
|
||||
|
|
|
@ -31,7 +31,12 @@ AntiTracking.runTest("Image cache - should load the image twice.",
|
|||
img.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/image.sjs",
|
||||
await new Promise(resolve => { img.onload = resolve; });
|
||||
ok(true, "Image 4 loaded");
|
||||
});
|
||||
},
|
||||
null, // cleanup function
|
||||
null, // no extra prefs
|
||||
false, // no window open test
|
||||
false // no user-interaction test
|
||||
);
|
||||
|
||||
// We still want to see just 2 requests.
|
||||
add_task(async _ => {
|
||||
|
|
|
@ -20,30 +20,51 @@ add_task(async function() {
|
|||
let browser = gBrowser.getBrowserForTab(tab);
|
||||
await BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
info("Loading a tracking scripts");
|
||||
info("Loading tracking scripts and tracking images");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
// Let's load the script twice here.
|
||||
{
|
||||
let src = content.document.createElement("script");
|
||||
let p = new content.Promise(resolve => { src.onload = resolve; });
|
||||
content.document.body.appendChild(src);
|
||||
src.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/script.sjs";
|
||||
src.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
|
||||
await p;
|
||||
}
|
||||
|
||||
{
|
||||
let src = content.document.createElement("script");
|
||||
let p = new content.Promise(resolve => { src.onload = resolve; });
|
||||
content.document.body.appendChild(src);
|
||||
src.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/script.sjs";
|
||||
src.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
|
||||
await p;
|
||||
}
|
||||
|
||||
// Let's load an image twice here.
|
||||
{
|
||||
let img = content.document.createElement("img");
|
||||
let p = new content.Promise(resolve => { img.onload = resolve; });
|
||||
content.document.body.appendChild(img);
|
||||
img.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
|
||||
await p;
|
||||
}
|
||||
{
|
||||
let img = content.document.createElement("img");
|
||||
let p = new content.Promise(resolve => { img.onload = resolve; });
|
||||
content.document.body.appendChild(img);
|
||||
img.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
|
||||
await p;
|
||||
}
|
||||
});
|
||||
|
||||
await fetch("https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/script.sjs?result")
|
||||
await fetch("https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image")
|
||||
.then(r => r.text())
|
||||
.then(text => {
|
||||
is(text, 0, "No cookies received.");
|
||||
is(text, 0, "Cookies received for images");
|
||||
});
|
||||
|
||||
await fetch("https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=script")
|
||||
.then(r => r.text())
|
||||
.then(text => {
|
||||
is(text, 0, "Cookies received for scripts");
|
||||
});
|
||||
|
||||
info("Creating a 3rd party content");
|
||||
|
@ -85,30 +106,51 @@ add_task(async function() {
|
|||
});
|
||||
});
|
||||
|
||||
info("Loading a tracking scripts again");
|
||||
info("Loading tracking scripts and tracking images again");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
// Let's load the script twice here.
|
||||
{
|
||||
let src = content.document.createElement("script");
|
||||
let p = new content.Promise(resolve => { src.onload = resolve; });
|
||||
content.document.body.appendChild(src);
|
||||
src.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/script.sjs";
|
||||
src.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
|
||||
await p;
|
||||
}
|
||||
|
||||
{
|
||||
let src = content.document.createElement("script");
|
||||
let p = new content.Promise(resolve => { src.onload = resolve; });
|
||||
content.document.body.appendChild(src);
|
||||
src.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/script.sjs";
|
||||
src.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=script";
|
||||
await p;
|
||||
}
|
||||
|
||||
// Let's load an image twice here.
|
||||
{
|
||||
let img = content.document.createElement("img");
|
||||
let p = new content.Promise(resolve => { img.onload = resolve; });
|
||||
content.document.body.appendChild(img);
|
||||
img.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
|
||||
await p;
|
||||
}
|
||||
{
|
||||
let img = content.document.createElement("img");
|
||||
let p = new content.Promise(resolve => { img.onload = resolve; });
|
||||
content.document.body.appendChild(img);
|
||||
img.src = "https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?what=image";
|
||||
await p;
|
||||
}
|
||||
});
|
||||
|
||||
await fetch("https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/script.sjs?result")
|
||||
await fetch("https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=image")
|
||||
.then(r => r.text())
|
||||
.then(text => {
|
||||
is(text, 1, "One cookie received received.");
|
||||
is(text, 1, "One cookie received for images.");
|
||||
});
|
||||
|
||||
await fetch("https://tracking.example.com/browser/toolkit/components/antitracking/test/browser/subResources.sjs?result&what=script")
|
||||
.then(r => r.text())
|
||||
.then(text => {
|
||||
is(text, 1, "One cookie received received for scripts.");
|
||||
});
|
||||
|
||||
info("Removing the tab");
|
||||
|
|
|
@ -12,7 +12,7 @@ const TEST_3RD_PARTY_PAGE_UI = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyUI.h
|
|||
let {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
|
||||
|
||||
this.AntiTracking = {
|
||||
runTest(name, callbackTracking, callbackNonTracking, cleanupFunction, extraPrefs) {
|
||||
runTest(name, callbackTracking, callbackNonTracking, cleanupFunction, extraPrefs, windowOpenTest = true, userInteractionTest = true) {
|
||||
// Here we want to test that a 3rd party context is simply blocked.
|
||||
this._createTask(name, true, callbackTracking, extraPrefs);
|
||||
this._createCleanupTask(cleanupFunction);
|
||||
|
@ -23,12 +23,16 @@ this.AntiTracking = {
|
|||
this._createCleanupTask(cleanupFunction);
|
||||
|
||||
// Permission granted when there is a window.open()
|
||||
this._createWindowOpenTask(name, callbackTracking, callbackNonTracking, extraPrefs);
|
||||
this._createCleanupTask(cleanupFunction);
|
||||
if (windowOpenTest) {
|
||||
this._createWindowOpenTask(name, callbackTracking, callbackNonTracking, extraPrefs);
|
||||
this._createCleanupTask(cleanupFunction);
|
||||
}
|
||||
|
||||
// Permission granted when there is user-interaction.
|
||||
this._createUserInteractionTask(name, callbackTracking, callbackNonTracking, extraPrefs);
|
||||
this._createCleanupTask(cleanupFunction);
|
||||
if (userInteractionTest) {
|
||||
this._createUserInteractionTask(name, callbackTracking, callbackNonTracking, extraPrefs);
|
||||
this._createCleanupTask(cleanupFunction);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ function handleRequest(aRequest, aResponse) {
|
|||
let hints = parseInt(getState("hints") || 0) + 1;
|
||||
setState("hints", hints.toString());
|
||||
|
||||
aResponse.setHeader("Cache-Control", "max-age=10000", false);
|
||||
aResponse.setHeader("Set-Cookie", "foopy=1");
|
||||
aResponse.setHeader("Content-Type", "image/png", false);
|
||||
aResponse.write(IMAGE);
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
function handleRequest(aRequest, aResponse) {
|
||||
aResponse.setStatusLine(aRequest.httpVersion, 200);
|
||||
|
||||
if (aRequest.queryString.includes("result")) {
|
||||
aResponse.write(getState("hints") || 0);
|
||||
setState("hints", "0");
|
||||
return;
|
||||
}
|
||||
|
||||
if (aRequest.hasHeader('Cookie')) {
|
||||
let hints = parseInt(getState("hints") || 0) + 1;
|
||||
setState("hints", hints.toString());
|
||||
}
|
||||
|
||||
aResponse.setHeader("Set-Cookie", "foopy=1");
|
||||
aResponse.setHeader("Content-Type", "text/javascript", false);
|
||||
aResponse.write("42;");
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// A 1x1 PNG image.
|
||||
// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
|
||||
const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
|
||||
"ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
|
||||
|
||||
function handleRequest(aRequest, aResponse) {
|
||||
aResponse.setStatusLine(aRequest.httpVersion, 200);
|
||||
|
||||
let key = aRequest.queryString.includes("what=script") ? "script" : "image";
|
||||
|
||||
if (aRequest.queryString.includes("result")) {
|
||||
aResponse.write(getState(key) || 0);
|
||||
setState(key, "0");
|
||||
return;
|
||||
}
|
||||
|
||||
if (aRequest.hasHeader('Cookie')) {
|
||||
let hints = parseInt(getState(key) || 0) + 1;
|
||||
setState(key, hints.toString());
|
||||
}
|
||||
|
||||
aResponse.setHeader("Set-Cookie", "foopy=1");
|
||||
|
||||
if (key == "script") {
|
||||
aResponse.setHeader("Content-Type", "text/javascript", false);
|
||||
aResponse.write("42;");
|
||||
} else {
|
||||
aResponse.setHeader("Content-Type", "image/png", false);
|
||||
aResponse.write(IMAGE);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче