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

Fix clobbering of overridden canvas methods #2658

Merged
merged 5 commits into from
Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion doc/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ the code below. This should take several minutes.
$ BROWSER=chrome pytest -v
```

macOS users may need to provide the full path to the browser application folder. For example, to run tests on macOS in Firefox:
macOS users may need to provide the full path to the browser application folder. For example, to run tests on macOS:
```bash
$ BROWSER=/Applications/Firefox.app/Contents/MacOS/firefox-bin pytest -v
# or
$ BROWSER=/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome pytest -v
```

For more information, see our Travis CI [setup](/scripts/setup_travis.sh) and
Expand Down
17 changes: 14 additions & 3 deletions src/js/contentscripts/fingerprinting.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,19 @@ function getFpPageScript() {
);

item.obj[item.propName] = (function (orig) {
// set to true after the first write, if the method is not
// restorable. Happens if another library also overwrites
// this method.
var skip_monitoring = false;

function wrapped() {
var args = arguments;

if (is_canvas_write) {
// to avoid false positives,
// bail if the text being written is too short
if (!args[0] || args[0].length < 5) {
// bail if the text being written is too short,
// of if we've already sent a monitoring payload
if (skip_monitoring || !args[0] || args[0].length < 5) {
return orig.apply(this, args);
}
}
Expand All @@ -237,7 +242,13 @@ function getFpPageScript() {
// optimization: one canvas write is enough,
// restore original write method
// to this CanvasRenderingContext2D object instance
this[item.propName] = orig;
// Careful! Only restorable if we haven't already been replaced
// by another lib, such as the hidpi polyfill
if (this[item.propName] === wrapped) {
this[item.propName] = orig;
} else {
skip_monitoring = true;
}
}

return orig.apply(this, args);
Expand Down
25 changes: 25 additions & 0 deletions tests/selenium/fingerprinting_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ def detected_tracking(self, domain, page_url):
map[tracker_origin].indexOf(site_origin) != -1
);""".format(domain, page_url))

def get_fillText_source(self):
return self.js("""
const canvas = document.getElementById("writetome");
const ctx = canvas.getContext("2d");
return ctx.fillText.toString();
""")

# TODO can fail because our content script runs too late: https://crbug.com/478183
@pbtest.repeat_if_failed(3)
def test_canvas_fingerprinting_detection(self):
Expand Down Expand Up @@ -69,6 +76,24 @@ def test_canvas_fingerprinting_detection(self):
"Canvas fingerprinting resource was detected as a fingerprinter."
)

# 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,
# this would wipe out polyfills that had also overridden the same functions, such as the very
# popular "hidpi-canvas-polyfill".
def test_canvas_polyfill_clobbering(self):
FIXTURE_URL = (
"https://efforg.github.io/privacybadger-test-fixtures/html/"
"fingerprinting_canvas_hidpi.html"
)

# visit the page
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)."
)


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion tests/selenium/pbtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def unix_which(command, silent=False):

def get_browser_type(string):
for t in BROWSER_TYPES:
if t in string:
if t in string.lower():
return t
raise ValueError("couldn't get browser type from %s" % string)

Expand Down