Skip to content

Commit

Permalink
Merge pull request #2839
Browse files Browse the repository at this point in the history
Record canvas fingerprinters in tracking_map.
  • Loading branch information
ghostwords committed Apr 27, 2022
2 parents de36101 + e3a70f4 commit c2c3776
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 49 deletions.
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

0 comments on commit c2c3776

Please sign in to comment.