Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store some additional tracker data in tracking_map #2839

Merged
merged 8 commits into from
Apr 27, 2022
2 changes: 1 addition & 1 deletion src/js/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -1261,7 +1261,7 @@ Badger.prototype = {

// The order of these keys is also the order in which they should be imported.
// It's important that snitch_map be imported before action_map (#1972)
["snitch_map", "action_map", "settings_map"].forEach(function (key) {
["snitch_map", "action_map", "settings_map", "tracking_map"].forEach(function (key) {
if (utils.hasOwn(data, key)) {
self.storage.getStore(key).merge(data[key]);
}
Expand Down
9 changes: 8 additions & 1 deletion src/js/heuristicblocking.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ HeuristicBlocker.prototype = {
const TRACKER_ENTROPY_THRESHOLD = 33,
MIN_STR_LEN = 8;

let self = this;

for (let p of searchParams) {
let key = p[0],
value = p[1];
Expand Down Expand Up @@ -261,7 +263,12 @@ HeuristicBlocker.prototype = {
log("Found high-entropy cookie share from", tab_base, "to", request_host,
":", entropy, "bits\n cookie:", cookie.name, '=', cookie.value,
"\n arg:", key, "=", value, "\n substring:", s);
this._recordPrevalence(request_host, request_base, tab_base);
self._recordPrevalence(request_host, request_base, tab_base);

// record pixel cookie sharing
badger.storage.recordTrackingDetails(
request_base, tab_base, 'pixelcookieshare');

return;
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/js/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ window.SLIDERS_DONE = false;
const TOOLTIP_CONF = {
maxWidth: 400
};
const USER_DATA_EXPORT_KEYS = ["action_map", "snitch_map", "settings_map"];
const USER_DATA_EXPORT_KEYS = ["action_map", "snitch_map", "settings_map", "tracking_map"];

let i18n = chrome.i18n;

Expand Down Expand Up @@ -310,22 +310,22 @@ function importTrackerList() {
* @param {String} storageMapsList data from JSON file that user provided
*/
function parseUserDataFile(storageMapsList) {
let lists;
let data;

try {
lists = JSON.parse(storageMapsList);
data = JSON.parse(storageMapsList);
} catch (e) {
return alert(i18n.getMessage("invalid_json"));
}

// validate by checking we have the same keys in the import as in the export
if (JSON.stringify(Object.keys(lists).sort()) != JSON.stringify(USER_DATA_EXPORT_KEYS.sort())) {
// validate keys ("action_map" and "snitch_map" are required)
if (!['action_map', 'snitch_map'].every(i => utils.hasOwn(data, i))) {
return alert(i18n.getMessage("invalid_json"));
}

chrome.runtime.sendMessage({
type: "mergeUserData",
data: lists
data
}, () => {
alert(i18n.getMessage("import_successful"));
location.reload();
Expand Down
68 changes: 62 additions & 6 deletions src/js/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,22 @@ BadgerPen.prototype = {
"cookieblock_list",
"dnt_hashes",
"settings_map",
"private_storage", // misc. utility settings, not for export

// misc. utility settings, not for export
"private_storage",

// logs what kind of tracking was observed:
// {
// <tracker_base>: {
// <site_base>: [
// <tracking_type>, // "canvas" or "pixelcookieshare"
// ...
// ],
// ...
// },
// ...
// }
"tracking_map",
],

getStore: function (key) {
Expand All @@ -156,7 +171,7 @@ BadgerPen.prototype = {
*/
clearTrackerData: function () {
let self = this;
['snitch_map', 'action_map'].forEach(key => {
['action_map', 'snitch_map', 'tracking_map'].forEach(key => {
self.getStore(key).updateObject({});
});
},
Expand Down Expand Up @@ -460,11 +475,17 @@ BadgerPen.prototype = {
dot_base = '.' + base_domain,
actionMap = self.getStore('action_map'),
actions = actionMap.getItemClones(),
snitchMap = self.getStore('snitch_map');
snitchMap = self.getStore('snitch_map'),
trackingMap = self.getStore('tracking_map');

if (snitchMap.getItem(base_domain)) {
log("Removing %s from snitch_map", base_domain);
badger.storage.getStore("snitch_map").deleteItem(base_domain);
snitchMap.deleteItem(base_domain);
}

if (trackingMap.getItem(base_domain)) {
log("Removing %s from tracking_map", base_domain);
trackingMap.deleteItem(base_domain);
}

for (let domain in actions) {
Expand All @@ -489,6 +510,22 @@ BadgerPen.prototype = {
return;
}
_syncStorage(self.getStore(store_name), true, callback);
},

/**
* Simplifies updating tracking_map.
*/
recordTrackingDetails: function (tracker_base, site_base, tracking_type) {
let self = this,
trackingDataStore = self.getStore('tracking_map'),
entry = trackingDataStore.getItem(tracker_base) || {};
if (!utils.hasOwn(entry, site_base)) {
entry[site_base] = [];
}
if (!entry[site_base].includes(tracking_type)) {
entry[site_base].push(tracking_type);
}
trackingDataStore.setItem(tracker_base, entry);
}
};

Expand Down Expand Up @@ -677,9 +714,28 @@ BadgerStorage.prototype = {
} else if (self.name == "snitch_map") {
for (let tracker_base in mapData) {
let siteBases = mapData[tracker_base];
for (let siteBase of siteBases) {
for (let site_base of siteBases) {
badger.heuristicBlocking.updateTrackerPrevalence(
tracker_base, tracker_base, siteBase);
tracker_base, tracker_base, site_base);
}
}

} else if (self.name == "tracking_map") {
let snitchMap = badger.storage.getStore('snitch_map');
for (let tracker_base in mapData) {
// merge only if we have a corresponding snitch_map entry
let snitchItem = snitchMap.getItem(tracker_base);
if (!snitchItem) {
continue;
}
for (let site_base in mapData[tracker_base]) {
if (!snitchItem.includes(site_base)) {
continue;
}
for (let tracking_type of mapData[tracker_base][site_base]) {
badger.storage.recordTrackingDetails(
tracker_base, site_base, tracking_type);
}
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/js/webrequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -621,15 +621,21 @@ function recordFingerprinting(tab_id, msg) {
scriptData.canvas.fingerprinting = true;
log(script_host, 'caught fingerprinting on', document_host);

let document_base = window.getBaseDomain(document_host);

// mark this as a strike
badger.heuristicBlocking.updateTrackerPrevalence(
script_host, script_base, window.getBaseDomain(document_host));
script_host, script_base, document_base);

// log for popup
let action = checkAction(tab_id, script_host);
if (action) {
badger.logThirdPartyOriginOnTab(tab_id, script_host, action);
}

// record canvas fingerprinting
badger.storage.recordTrackingDetails(
script_base, document_base, 'canvas');
}
}
// This is a canvas write
Expand Down
21 changes: 15 additions & 6 deletions tests/selenium/cookie_sharing_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class PixelTrackingTest(pbtest.PBSeleniumTest):
- tracking domain is caught by pixel tracking heuristic, snitch map entry is updated
"""

def get_snitch_map(self):
return self.get_badger_storage('snitch_map').get('cloudinary.com')
def get_snitch_map_for(self, domain):
return self.get_badger_storage('snitch_map').get(domain)

def setUp(self):
# enable local learning
Expand All @@ -21,25 +21,34 @@ def setUp(self):
self.find_el_by_css('#local-learning-checkbox').click()

def test_pixel_cookie_sharing(self):
SITE_DOMAIN = "efforg.github.io"
FIXTURE_URL = (
"https://efforg.github.io/privacybadger-test-fixtures/html/"
f"https://{SITE_DOMAIN}/privacybadger-test-fixtures/html/"
"pixel_cookie_sharing.html"
)
TRACKER_BASE_DOMAIN = "cloudinary.com"

# clear seed data to prevent any potential false positives
self.clear_tracker_data()

# load the test fixture without the URL parameter to to verify there is no tracking on the page by default
self.load_url(FIXTURE_URL)

# check to make sure the domain wasn't logged in snitch map
assert not self.get_snitch_map(), (
assert not self.get_snitch_map_for(TRACKER_BASE_DOMAIN), (
"Tracking detected but page expected to have no tracking at this point")

# load the same test fixture, but pass the URL parameter for it to perform pixel cookie sharing
self.load_url(FIXTURE_URL + "?trackMe=true")

# check to make sure this domain is caught and correctly recorded in snitch map
assert ["efforg.github.io"] == self.get_snitch_map(), (
"Pixel cookie sharing tracking failed to be detected")
assert self.get_snitch_map_for(TRACKER_BASE_DOMAIN) == [SITE_DOMAIN], (
"Failed to detect tracking")

# check that we detected pixel cookie sharing specifically
assert "pixelcookieshare" in self.get_badger_storage('tracking_map')\
.get(TRACKER_BASE_DOMAIN, {}).get(SITE_DOMAIN, []), (
"Failed to record pixel cookie sharing detection")


if __name__ == "__main__":
Expand Down
39 changes: 11 additions & 28 deletions tests/selenium/fingerprinting_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,6 @@
class FingerprintingTest(pbtest.PBSeleniumTest):
"""Tests to make sure fingerprinting detection works as expected."""

def detected_fingerprinting(self, domain):
return self.js("""let tracker_origin = window.getBaseDomain("{}");
let tabData = chrome.extension.getBackgroundPage().badger.tabData;
return (
Object.keys(tabData).some(tab_id => {{
let fpData = tabData[tab_id].fpData;
return fpData &&
fpData.hasOwnProperty(tracker_origin) &&
fpData[tracker_origin].canvas &&
fpData[tracker_origin].canvas.fingerprinting === true;
}})
);""".format(domain))

def get_fillText_source(self):
return self.js("""
const canvas = document.getElementById("writetome");
Expand All @@ -38,11 +25,13 @@ def setUp(self):

@pytest.mark.flaky(reruns=3, condition=pbtest.shim.browser_type == "firefox")
def test_canvas_fingerprinting_detection(self):
SITE_DOMAIN = "efforg.github.io"
FIXTURE_URL = (
"https://efforg.github.io/privacybadger-test-fixtures/html/"
f"https://{SITE_DOMAIN}/privacybadger-test-fixtures/html/"
"fingerprinting.html"
)
FINGERPRINTING_DOMAIN = "cdn.jsdelivr.net"
FP_DOMAIN = "cdn.jsdelivr.net"
FP_BASE_DOMAIN = "jsdelivr.net"

# clear pre-trained/seed tracker data
self.clear_tracker_data()
Expand All @@ -53,18 +42,13 @@ def test_canvas_fingerprinting_detection(self):
# open popup and check slider state
self.load_pb_ui(FIXTURE_URL)
sliders = self.get_tracker_state()
self.assertIn(
FINGERPRINTING_DOMAIN,
sliders['notYetBlocked'],
"Canvas fingerprinting domain should be reported in the popup"
)
assert FP_DOMAIN in sliders['notYetBlocked'], (
"Canvas fingerprinting domain should be reported in the popup")

# check that we detected canvas fingerprinting specifically
self.load_url(self.options_url)
self.assertTrue(
self.detected_fingerprinting(FINGERPRINTING_DOMAIN),
"Canvas fingerprinting resource was detected as a fingerprinter."
)
assert "canvas" in self.get_badger_storage('tracking_map')\
.get(FP_BASE_DOMAIN, {}).get(SITE_DOMAIN, []), (
"Failed to detect canvas fingerprinting script")

# Privacy Badger overrides a few functions on canvas contexts to check for fingerprinting.
# In previous versions, it would restore the native function after a single call. Unfortunately,
Expand All @@ -80,9 +64,8 @@ def test_canvas_polyfill_clobbering(self):
self.load_url(FIXTURE_URL)

# check that we did not restore the native function (should be hipdi polyfill)
self.assertNotIn("[native code]", self.get_fillText_source(),
"Canvas context fillText is not native version (polyfill has been retained)."
)
assert "[native code]" not in self.get_fillText_source(), (
"Canvas context fillText is not native version (polyfill has been retained)")


if __name__ == "__main__":
Expand Down