mapbox / mapbox-gl-js

Interactive, thoroughly customizable maps in the browser, powered by vector tiles and WebGL
https://docs.mapbox.com/mapbox-gl-js/
Other
11.17k stars 2.22k forks source link

Blocking fingerprinting in browser settings can cause icons not to render or map not to render #8377

Closed kbauhausness closed 5 years ago

kbauhausness commented 5 years ago

The browser Brave's "device recognition"/"fingerprinting" blocking blocks anything that attempts to read data out of a canvas to prevent it from being used for browser fingerprinting (see https://browserleaks.com/canvas). This defaults to "block 3rd-party fingerprinting", which can apply when a map is embedded in an external website.

More on Google/Chrome's future plans for anti-fingerprinting functionality here.

mapbox-gl-js version: 1.0.0, any

browser: able to reproduce in Brave, Chrome given anti-fingerprinting settings enabled

Steps to Trigger Behavior

In Brave:

  1. change the setting (under Settings > Shields) to "Block all fingerprinting",
  2. you can reproduce with this simple page:
    <!DOCTYPE html>
    <html>
    <body>
    <canvas id="canvas" width="100" height="100"></canvas>
    <div id="output"></div>
    <script type="text/javascript">
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        ctx.fillStyle = "#FF0000";
        ctx.fillRect(0, 0, 100, 100);
        var imageData = ctx.getImageData(0, 0, 100, 100);
        document.getElementById("output").innerHTML = "getImageData width: " + imageData.width + " height: " + imageData.height;
    </script>
    </html>
  3. You'll see that in a normal browser, the expected width and height of 100x100 are seen, but with that setting in Brave, it returns a 0x0 image.
  4. Similarly, setting Brave to "block all fingerprinting" and loading this mapbox map, the error RangeError('out of range destination coordinates for image copy') is thrown and no map is rendered: https://api.mapbox.com/styles/v1/mapbox/streets-v9.html?title=true&access_token=ACCESS_TOKEN#1.1/0/0 . Screen Shot 2019-06-21 at 11 59 31 AM

In Chrome:

  1. install the Chrome extension CanvasFingerprintBlock
  2. load the map https://api.mapbox.com/styles/v1/mapbox/streets-v9.html?title=true&access_token=ACCESS_TOKEN#1.1/0/0
  3. Notice that the map loads, but icons are missing and the following message is shown in the CanvasFingerprintBlock extension: Screen Shot 2019-06-21 at 12 02 30 PM Screen Shot 2019-06-21 at 12 01 53 PM

Link to Demonstration

https://jsbin.com/

Expected Behavior

The map is still rendered / icons are still rendered even when anti-fingerprinting settings are enabled.

Actual Behavior

In Brave with anti-fingerprinting, the map is not rendered. In Chrome with anti-fingerprinting, the icons from the spritesheet are not rendered.

peterqliu commented 5 years ago

Flagging the current getImageData implementation:

https://github.com/mapbox/mapbox-gl-js/blob/27ac83a63eecdec3c9f5597d87fddcf1f3bdd660/src/util/browser.js#L37-L47

May be worth exploring get-pixels's canvas-less, node implementation https://github.com/scijs/get-pixels/blob/master/node-pixels.js

1ec5 commented 5 years ago

Is TinySDF also affected? TinySDF is used for rendering ideographic characters using client-side fonts.

https://github.com/mapbox/tiny-sdf/blob/34a06b10e99555a156e4d147ab28fd11da4bd4e0/index.js#L40

ahk commented 5 years ago

Is TinySDF also affected?

It looks like it is. It's using the same function of canvas API.

ryanhamley commented 5 years ago

It definitely blocks local ideograph generation as well. I just tested it out to be sure.

Screen Shot 2019-06-24 at 4 37 37 PM
ryanhamley commented 5 years ago

It looks like the ImageData interface may be what we need to avoid this.

Creates an ImageData object from a given Uint8ClampedArray and the size of the image it contains. If no array is given, it creates an image of a black rectangle. Note that this is the most common way to create such an object in workers as createImageData() is not available there.

EDIT: Ooops, misread the description. This won't work.

cc @asheemmamoowala @mourner Is this what you had in mind?

mourner commented 5 years ago

@ryanhamley no, that's the whole point of TinySDF — we need to use getImageData to get the data drawn on the canvas with the Canvas text API. It won't work without it. createImageData is only useful for the reverse — generating an array of pixel data that you want drawn on a Canvas.

asheemmamoowala commented 5 years ago

According to the docs, createImageBitmap can be used to get the image data out of a <canvas> or HTMLCanvasElement as well. Shouldn't that be able to replace a canvasElem.getImageData() call?

ryanhamley commented 5 years ago

I believe after a bit of research that canvas fingerprinting is what's blocking sprites and glyphs. But there's also WebGL fingerprinting and blocking this causes the entire map to render as a white screen. That's why we see a difference between Chrome using the CanvasFingerprintBlock plugin and Brave. When you disable all fingerprinting in Brave, it blocks both Canvas and WebGL fingerprinting.

Under Technical Details, Brave says:

Because most browser fingerprinting defense requires disabling web features that are required for many sites to work properly, it is implemented as off-by-default for now (can be turned on in about:preferences globally, or on a per-site basis in the Bravery panel). We will consider turning it on-by-default when we have fingerprinting detection heuristics with a sufficiently-low false positive rate.

You can create an ImageBitmap from a canvas element, but the ImageBitmap instance only has height and width properties; it does not expose the image data the way that canvas.getImageData does (which returns an instance of ImageData).

mourner commented 5 years ago

@ryanhamley does this mean that there are no next actions for us here — we just wait for browsers to get better at not blocking essential features when turning on fingerprinting protection, and rely on users not turning it fully on until that time — right?

ryanhamley commented 5 years ago

That's what my research is pointing towards @mourner I want to do a bit more testing today before I close this though.

ryanhamley commented 5 years ago

I did some fairly extensive research on this issue and there's not really anything we can do about it. Fingerprinting blockers disable functionality that GL JS relies on and there are no alternative ways of making the library work. This is a won't fix issue.

andrewharvey commented 4 years ago

In light of the unable to fix here I've opened https://github.com/mapbox/mapbox-gl-supported/issues/25 in case mapbox-gl-supported can detect the potential issue and return false.