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

Canvas fingerprint changes in Blink on macOS #574

Closed
Finesse opened this issue Oct 23, 2020 · 5 comments
Closed

Canvas fingerprint changes in Blink on macOS #574

Finesse opened this issue Oct 23, 2020 · 5 comments
Assignees

Comments

@Finesse
Copy link
Member

Finesse commented Oct 23, 2020

I use Chrome 86 on macOS 10.15.6, my laptop has only 1 GPU. The canvas fingerprint produces one picture on some websites and another on other websites:

Picture 1 Picture 2
local other

The only difference is the right margin of the emoji. Both pictures are stable: one of them is produced on a website, and their distribution is almost equal.

Use the following browser console script to run get a canvas fingerprint in your browser:

function initFingerprintJS() {
  FingerprintJS.load()
    .then(fp => fp.get())
    .then(result => {
      console.log(FingerprintJS.murmurX64Hash128(result.components.canvas.value.data));
    });
}
var script = document.createElement('script'); script.onload = initFingerprintJS; script.src = 'https://cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@3.0.0/dist/fp.min.js'; document.head.appendChild(script);

The results are the same in regular and private mode, the zoom level is 100% in all the cases that I've checked.


  • The difference doesn't happen in Chrome 86 on Windows 10 on my local virtual machine and on Windows 7 and 10 on BrowserStack
  • The difference happens in Chrome 86–88 on macOS Catalina and Mojave on BrowserStack
  • The difference doesn't happen in Firefox 82 and Safari 14 on my local macOS 10.15.6
  • The difference doesn't happen in Chrome 83 on Android 8 (Galaxy S9) and 11 (Pixel 4) on BrowserStack
  • The difference happens in other Chromium browsers (Edge, Yandex) on my local macOS 10.15.6

So the issue affects only visitors that use a Chromium-based browser on macOS.


The problem doesn't affect the version 2, probably because it has no text after the emoji. Run the following code in a browser console to get an open v2 canvas fingerprint:

function initFingerprintJS() {
  Fingerprint2.get((components) => {
    const canvasComponent = components.find(component => component.key === 'canvas');
    console.log(Fingerprint2.x64hash128(canvasComponent.value[1].slice(10)));
  });
}
var script = document.createElement('script'); script.onload = initFingerprintJS; script.src = 'https://cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@2/dist/fingerprint2.min.js'; document.head.appendChild(script);

A fingerprint variant persists until you close the browser tab (even if no fingerprint was taken on the first page of the tab).


I've found the key difference that causes the image change: at least 1 character on the page must be rendered with the BlinkMacSystemFont font, the font can be set through the font-family CSS property (the closer the font is to the start of the list, the higher the chance to affect a character). The canvas is affected despite the BlinkMacSystemFont font not being used in the canvas image. The fact that the fingerprint variant persists until the tab is closed makes me think that this is a subtle bug of Chromium.


The right margin of the emoji is reduced regardless of the font that I set to context.font. The margin is reduced if the canvas text font size is less than 18pt, it doesn't depend on these browser settings: font size, page zoom, customize font / font size.


Possible solutions:

  • Ask library users to use the same font-family on every page of their websites or not use BlinkMacSystemFont at all (that isn't always possible). It won't help because the picture variant depends on the first page of the browser tab, not on the current page.
  • Change the canvas image such way that it isn't affected by this Chrome feature (put the emoji to the end or increate the text size). It will change the fingerprint of all visitors.
  • Make the canvas component draw only one of the pictures. I haven't found a way to do so. The picture variant is persistent so the library can affect it only if the agent runs on the first page of the browser tab.
  • Address the canvas emoji rendering issue to Google. There is no guarantee that they will fix it and that they will fix it soon.

We've decided to address the rendering issue to the Chromium team and wait because:

  • The issue affects a small number of users
  • A website must meet some special requirements to face this issue
@Finesse Finesse self-assigned this Oct 23, 2020
@Finesse
Copy link
Member Author

Finesse commented Oct 23, 2020

I've discovered that the Chromium bug affects not font sizes less than a specific values:

BlinkMacSystemFont off BlinkMacSystemFont on difference

According to the pictures, every 3rd pt font size value is stable starting from 18pt and every px value is stable starting from 24px. The common pattern may be: "the font size values that are ≥24px and integer in the px equivalent are stable". FYI 1pt = 4/3px.

The picture script
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<title>Canvas narrow emojis</title>
	<style>
		body {
			font-family: BlinkMacSystemFont;
		}
	</style>
</head>
<body>
	<p>This text is required to reproduce the issue</p>
	<canvas width="600" height="400"></canvas>
	<script>
		function start() {
			const canvas = document.querySelector('canvas');
			const context = canvas.getContext('2d');
			const text = 'Hello ✋😃🤚 world';
			let verticalPosition = 5;

			context.fillStyle = '#fff';
			context.fillRect(0, 0, canvas.width, canvas.height);
			context.fillStyle = '#000';

			for (let fontSize = 11; fontSize <= 24; fontSize += 1) {
				verticalPosition += fontSize * 1.6;
				const fontSizeWithUnit = `${fontSize}pt`;
				context.font = `${fontSizeWithUnit} sans-serif`; // The font face here doesn't affect the issue
				context.fillText(`${fontSizeWithUnit}: ${text}`, 2, verticalPosition);
			}

			verticalPosition = 0;

			for (let fontSize = 15; fontSize <= 27; fontSize += 1) {
				verticalPosition += fontSize * 1.5;
				const fontSizeWithUnit = `${fontSize}px`;
				context.font = `${fontSizeWithUnit} sans-serif`; // The font face here doesn't affect the issue
				context.fillText(`${fontSizeWithUnit}: ${text}`, 302, verticalPosition);
			}
		}

		setTimeout(start, 0);
	</script>
</body>
</html>

@Finesse
Copy link
Member Author

Finesse commented Oct 23, 2020

This difference was introduced on Chrome 83 (checked on macOS 10.15).

@Finesse Finesse changed the title Canvas fingerprint changes depending on the page CSS Canvas fingerprint changes in Chromium on macOS Nov 11, 2020
@Finesse Finesse changed the title Canvas fingerprint changes in Chromium on macOS Canvas fingerprint changes in Blink on macOS Nov 18, 2020
@spyjo
Copy link

spyjo commented Nov 19, 2020

I can confirm this bug is still present in Chrome 87.0.4280.67 on Mac OS 10.15.7.
I just had a case with a website made with Bootstrap, which include BlinkMacSystemFont by default.

@Finesse
Copy link
Member Author

Finesse commented Nov 30, 2020

TL;DR. The steps to reproduce are:

  1. Take the HTML code from Canvas fingerprint changes in Blink on macOS #574 (comment) and save it to a file. Upload the file to a web server (optionally).
  2. Open Chrome version 83 or newer on macOS, open a new tab and open the HTML file in it.
  3. Take the HTML code from Canvas fingerprint changes in Blink on macOS #574 (comment), remove the font-family: BlinkMacSystemFont; line and save it to another file. Upload the file to a web server (optionally).
  4. Open a new tab and open the other HTML file in it.

The images in the tabs will be different.

Finesse added a commit that referenced this issue Jan 13, 2021
- Leave no text to the right of the emoji;
- Use concrete fonts to exclude the effect of font preferences (where'll be a separate entropy source for them);
@Finesse
Copy link
Member Author

Finesse commented Jan 13, 2021

Fixed by d837040 by moving the emojis to the right of the text lines. The fix will be published in the next big release that combines all changes that breaks the fingerprint compatibility.

@Finesse Finesse closed this as completed Jan 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants