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:
Andrea Marchesini 2018-07-13 12:02:19 +02:00
Родитель 1892fdf469
Коммит 9fc78a12aa
10 изменённых файлов: 197 добавлений и 52 удалений

Просмотреть файл

@ -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);
}
}