Skip to content

Commit

Permalink
Merge pull request #2658
Browse files Browse the repository at this point in the history
Fix clobbering of overridden canvas methods.
  • Loading branch information
ghostwords committed Aug 17, 2020
2 parents debdb48 + aef17af commit 2df3d90
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 5 deletions.
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

0 comments on commit 2df3d90

Please sign in to comment.