brave / brave-browser

Brave browser for Android, iOS, Linux, macOS, Windows.
https://brave.com
Mozilla Public License 2.0
17.77k stars 2.32k forks source link

block fingerprinting makes Canvas 2d fillRect leave a pattern of off-colored pixels #41881

Open Zutatensuppe opened 2 days ago

Zutatensuppe commented 2 days ago

Description

I noticed that when brave shields on and "block fingerprinting" (for canvas) active, and fill a canvas with a solid color (eg. magenta), not all pixels of the canvas are colored exactly in that color. A pattern stays on the canvas, kind of like a finger print in a way, because the pattern is always the same for the same domain it seems.

For the example of magenta (255,0,255) it creates a couple of pixels that are 255,1,255.

This is a simple code that can be pasted into the console of the browser, to check if the pattern comes up. It creates a small canvas, fills it with magenta, and then makes all pixels that are not exactly magenta black, to better visualize the issue. Then the canvas is added to the dom and displayed bigger, so it is easily visible, that something is wrong.

(() => {
    const size = 64;
    const c = document.createElement('canvas');
    c.width= size;
    c.height = size;
    c.style.position = 'fixed';
    c.style.top= '0px';
    c.style.left = '0px';
    c.style.zIndex = 10000;
    c.style.width = '256px';
    c.style.height = '256px';
    const ctx = c.getContext('2d');
    ctx.fillStyle = 'magenta';
    ctx.fillRect(0,0,size,size);

    const imageData = ctx.getImageData(0, 0, size, size);
    const pixels = imageData.data;
    for (let i2 = 0; i2 < pixels.length; i2 += 4) {
    const color = pixels.slice(i2, i2 + 4);
    if (color[0] !== 255 || color[1] !== 0 || color[2] !== 255 || color[3] !== 255) {
        pixels[i2] = 0;
        pixels[i2 + 1] = 0;
        pixels[i2 + 2] = 0;
    }
    }
    ctx.putImageData(imageData, 0, 0);
    document.body.append(c)
})()

Without fingerprinting, or with chrome or firefox, the canvas is always completely magenta as expected.

Steps to reproduce

  1. Turn brave shields on, enable 'block fingerprinting', enable 'canvas'
  2. Go to a website (make sure shields on as defined in step 1. for that website too) and open the developer console
  3. Paste and run the above mentioned code snippet

You will see that you get a magenta canvas with black dots, instead of a fully magenta canvas. You will also see that the pattern on the canvas changes for the different domains (but not for individual urls on that domain). When you do it in incognito mode, you get a different pattern.

Actual result

The canvas is not a solid color.

result in brave when running the above snippet on https://gatherer.wizards.com/Pages/Card/Discussion.aspx?multiverseid=108811 eg: Image

Expected result

result in firefox (or brave without fingerprinting) when running the above snippet on https://gatherer.wizards.com/Pages/Card/Discussion.aspx?multiverseid=108811

Image

Reproduces how often

Easily reproduced

Brave version (brave://version info)

i verified this in these two versions:

Brave 1.70.117 Chromium: 129.0.6668.59 (Official Build) (64-bit) Revision 5d4da8332eb061f79bb47ec5b0bf595600fdcd55 OS Linux

Brave 1.71.114 Chromium: 130.0.6723.58 (Official Build) (64-bit) Revision 89c4031c685a4296315a6f421f46275f0b72dd55 OS Windows 10 Version 22H2 (Build 19045.5011)

Channel information

Reproducibility

Miscellaneous information

I wonder if this caveat could be at least mentioned somewhere, because a pattern is generated, which looks like a finger print, but the feature is actually there to prevent finger printing? I don't really get it, so I would appreciate an explanation. The activating of the block fingerprinting and sub option canvas created graphical glitches in my application, and it feels weird to tell users to turn off "block fingerprinting" for canvas on my page.

I can of course work around this by just using putImageData instead of fillRect.

And because this can be worked around, how does this prevent the fingerprinting in the first place?

rebron commented 2 days ago

cc: @arthuredelstein @ShivanKaul

ShivanKaul commented 1 day ago

I wonder if this caveat could be at least mentioned somewhere, because a pattern is generated, which looks like a finger print, but the feature is actually there to prevent finger printing

Brave randomizes the output of certain Web APIs to prevent fingerprintability, including Canvas-related ones. The output pattern you're seeing would vary across browser sessions (we're actually working to make this even more ephemeral) and the top-level site. You can read more about how Brave's anti-fingerprinting measures work and the philosophy behind them here.

Canvas-based fingerprinting is a fairly common attack vector, since it exposes details about the user's hardware. Other browsers also acknowledge canvas APIs as fingerprinting vectors. You'd see similarly warped canvas output on Firefox with resistFingerprinting turned on and Safari in Private Browsing mode on https://shivankaul.com/brave/test-canvas-farbling.html (I put up your sample JS). Brave, however, does it by default in order to maximize privacy for our users, but our goal is to do it in such a way that preserves website functionality as far as possible.

Can you tell us the application/website that is being broken by this protection? We can look into it.