Skip to content

Commit e1344b7

Browse files
Add explainer for canvas noise on readbacks
1 parent cb3add2 commit e1344b7

File tree

1 file changed

+57
-142
lines changed

1 file changed

+57
-142
lines changed

README.md

Lines changed: 57 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,92 @@
1-
# Explainer for the TODO API
1+
# Explainer for adding noise to canvas readbacks
22

3-
**Instructions for the explainer author: Search for "todo" in this repository and update all the
4-
instances as appropriate. For the instances in `index.bs`, update the repository name, but you can
5-
leave the rest until you start the specification. Then delete the TODOs and this block of text.**
6-
7-
This proposal is an early design sketch by [TODO: team] to describe the problem below and solicit
8-
feedback on the proposed solution. It has not been approved to ship in Chrome.
9-
10-
TODO: Fill in the whole explainer template below using https://tag.w3.org/explainers/ as a
11-
reference. Look for [brackets].
12-
13-
## Proponents
14-
15-
- [Proponent team 1]
16-
- [Proponent team 2]
17-
- [etc.]
3+
This proposal is an early design sketch by Chrome to describe the problem below and solicit feedback on the proposed solution. It has not been approved to ship in Chrome.
184

195
## Participate
20-
- https://github.com/explainers-by-googlers/[your-repository-name]/issues
21-
- [Discussion forum]
22-
23-
## Table of Contents [if the explainer is longer than one printed page]
24-
25-
<!-- Update this table of contents by running `npx doctoc README.md` -->
26-
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
27-
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
28-
29-
- [Introduction](#introduction)
30-
- [Goals](#goals)
31-
- [Non-goals](#non-goals)
32-
- [User research](#user-research)
33-
- [Use cases](#use-cases)
34-
- [Use case 1](#use-case-1)
35-
- [Use case 2](#use-case-2)
36-
- [[Potential Solution]](#potential-solution)
37-
- [How this solution would solve the use cases](#how-this-solution-would-solve-the-use-cases)
38-
- [Use case 1](#use-case-1-1)
39-
- [Use case 2](#use-case-2-1)
40-
- [Detailed design discussion](#detailed-design-discussion)
41-
- [[Tricky design choice #1]](#tricky-design-choice-1)
42-
- [[Tricky design choice 2]](#tricky-design-choice-2)
43-
- [Considered alternatives](#considered-alternatives)
44-
- [[Alternative 1]](#alternative-1)
45-
- [[Alternative 2]](#alternative-2)
46-
- [Stakeholder Feedback / Opposition](#stakeholder-feedback--opposition)
47-
- [References & acknowledgements](#references--acknowledgements)
48-
49-
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
6+
- https://github.com/explainers-by-googlers/canvas-noise-on-readbacks/issues
507

518
## Introduction
529

53-
[The "executive summary" or "abstract".
54-
Explain in a few sentences what the goals of the project are,
55-
and a brief overview of how the solution works.
56-
This should be no more than 1-2 paragraphs.]
57-
58-
## Goals
59-
60-
[What is the **end-user need** which this project aims to address? Make this section short, and
61-
elaborate in the Use cases section.]
62-
63-
## Non-goals
10+
The canvas APIs allow websites to draw shapes and forms on a canvas and read back the rendered image. However, the browser's rendering process leaks details about the GPU's properties. The goal of adding noise to canvas readbacks is to prevent scripts from easily obtaining identifying information that can be used to re-identify a browser across contexts.
6411

65-
[If there are "adjacent" goals which may appear to be in scope but aren't,
66-
enumerate them here. This section may be fleshed out as your design progresses and you encounter necessary technical and other trade-offs.]
12+
## Goals:
6713

68-
## User research
14+
* Prevent identifying information from being easily extracted from GPU-rendered canvases.
15+
* Improve user's privacy by aligning the exposed information with other data partitioning practices in the Web Platform.
16+
* Minimize the impact, both in terms of performance and breakage, for applications that intensively use `<canvas>` elements by only intervening when necessary.
17+
* Ensure that the noise added to canvas readbacks is applied every time that the raw pixels of the canvas are exposed to the website.
6918

70-
[If any user research has been conducted to inform your design choices,
71-
discuss the process and findings. User research should be more common than it is.]
19+
## Non-goals:
7220

73-
## Use cases
21+
* This document does not propose any changes to the way that operations on the canvas are executed, and only discusses noise that is added upon readback.
22+
* Applications that never read back the contents of the canvas will not be intervened upon.
7423

75-
[Describe in detail what problems end-users are facing, which this project is trying to solve. A
76-
common mistake in this section is to take a web developer's or server operator's perspective, which
77-
makes reviewers worry that the proposal will violate [RFC 8890, The Internet is for End
78-
Users](https://www.rfc-editor.org/rfc/rfc8890).]
24+
## Overview
25+
Small changes to the RGBA pixel values of the canvas are made whenever the contents of a canvas are read out, and the GPU was used to render contents on that canvas. While CPU rendering can - at least in theory - be implemented by browser vendors in a deterministic way that doesn't leak much information about the user's device, GPU rendering will have slightly different behavior for many operations depending on drivers and the specifications of the device. As a result, GPU rendering is more likely to end up leaking information about the user’s device. Initially, noise might be added when one of the following functions is called:
7926

80-
### Use case 1
27+
* HTMLCanvasElement.toDataURL
28+
* HTMLCanvasElement.toBlob
29+
* OffscreenCanvas.convertToBlob
30+
* CanvasRenderingContext2D.getImageData
8131

82-
### Use case 2
32+
Additionally, noise might be added when the contents of the canvas data are transferred to another host (e.g. a MediaStream) that would allow reading out those contents.
8333

84-
<!-- In your initial explainer, you shouldn't be attached or appear attached to any of the potential
85-
solutions you describe below this. -->
34+
The noise that is added is fairly small, in the range of -3 to 3, making the noise visually unnoticeable in most cases. Furthermore, the noise that is added to a specific canvas is deterministic and depends on the contents of the canvas and a seed derived from a per-session random token and the context the canvas is included in. This context is based on the origin of the embedding document as well as that of the top-level document, and follows state partitioning properties.
8635

87-
## [Potential Solution]
36+
Adding noise to canvas readbacks is most helpful in browsing modes where the user explicitly or implicitly signaled they care most about their privacy, for instance in Incognito mode.
8837

89-
[For each related element of the proposed solution - be it an additional JS method, a new object, a new element, a new concept etc., create a section which briefly describes it.]
38+
To reduce any potential UX breakage caused by changing pixel values, we make use of heuristics to determine whether noise should be applied. These heuristics target canvas operations currently used to extract information and will likely change over time.
9039

91-
```js
92-
// Provide example code - not IDL - demonstrating the design of the feature.
40+
## Noising algorithm
9341

94-
// If this API can be used on its own to address a user need,
95-
// link it back to one of the scenarios in the goals section.
42+
At a high level, the algorithm for adding noise to a canvas readback is implemented according to the following pseudo-code:
9643

97-
// If you need to show how to get the feature set up
98-
// (initialized, or using permissions, etc.), include that too.
99-
```
100-
101-
[Where necessary, provide links to longer explanations of the relevant pre-existing concepts and API.
102-
If there is no suitable external documentation, you might like to provide supplementary information as an appendix in this document, and provide an internal link where appropriate.]
44+
```python
45+
# For each partition a different session-bound random value is assigned
46+
TOKENS = map(PARTITION -> TOKEN)
10347

104-
[If this is already specced, link to the relevant section of the spec.]
48+
for (x, y) in canvas:
49+
r, g, b, a = get_pixel_at(x, y)
50+
pixel_hash = hash_init(TOKENS[PARTITION])
10551

106-
[If spec work is in progress, link to the PR or draft of the spec.]
52+
# Each pixel is determined by their RGBA channels and their (x, y) coordinates
53+
r, g, b, a, x, y = pixel
10754

108-
[If you have more potential solutions in mind, add ## Potential Solution 2, 3, etc. sections.]
55+
# The RGBA values of the current pixel are used as a new seed for the rolling hash
56+
rolling_hash.update(r, g, b, a)
10957

110-
### How this solution would solve the use cases
58+
# Determine the (x,y)-offset for the other pixel that will influence the noise.
59+
# The location is determined based on the token, partition, and RGBA values of the
60+
# current pixel. These offsets are in the range of -10 to 10.
61+
otherX, otherY = determine_offset(pixel_hash)
11162

112-
[If there are a suite of interacting APIs, show how they work together to solve the use cases described.]
63+
# Get the RGBA values for the other pixel, and use them to update the rolling hash.
64+
otherR, otherG, otherB, otherA = get_pixel_at(otherX, otherY)
65+
rolling_hash.update(otherR, otherG, otherB, otherA)
11366

114-
#### Use case 1
67+
# Add noise deltas to the RGBA channels of the current pixel, clamped to [0, 255].
68+
dR, dG, dB, dA = get_noise_deltas(pixel_hash)
69+
noised_canvas[x][y] = [clamp(r + dR), clamp(g + dG), clamp(b + dB), clamp(a + dA)]
11570

116-
[Description of the end-user scenario]
117-
118-
```js
119-
// Sample code demonstrating how to use these APIs to address that scenario.
71+
return noised_canvas
12072
```
12173

122-
#### Use case 2
123-
124-
[etc.]
125-
126-
## Detailed design discussion
127-
128-
### [Tricky design choice #1]
129-
130-
[Talk through the tradeoffs in coming to the specific design point you want to make.]
131-
132-
```js
133-
// Illustrated with example code.
134-
```
135-
136-
[This may be an open question,
137-
in which case you should link to any active discussion threads.]
138-
139-
### [Tricky design choice 2]
140-
141-
[etc.]
142-
143-
## Considered alternatives
144-
145-
[This should include as many alternatives as you can,
146-
from high level architectural decisions down to alternative naming choices.]
147-
148-
### [Alternative 1]
149-
150-
[Describe an alternative which was considered,
151-
and why you decided against it.]
152-
153-
### [Alternative 2]
154-
155-
[etc.]
156-
157-
## Stakeholder Feedback / Opposition
158-
159-
[Implementors and other stakeholders may already have publicly stated positions on this work. If you can, list them here with links to evidence as appropriate.]
74+
For a single pixel, the noise that is added is determined by the following factors:
16075

161-
- [Implementor A] : Positive
162-
- [Stakeholder B] : No signals
163-
- [Implementor C] : Negative
76+
* The context the related canvas is included in. The same canvases within the same context will still get the exact same noise.
77+
* Its own RGBA values. These might be unknown if it depends on the GPU-specific rendering.
78+
* The RGBA values of a neighboring pixel. This preserves identical noise distribution for identical pixel regions. Furthermore, [scaling attacks](https://github.com/google/security-research/security/advisories/GHSA-24cm-69m9-fpw3) are rendered substantially more difficult and expensive due to the unpredictable nature of hashing and random neighbor selection.
16479

165-
[If appropriate, explain the reasons given by other implementors for their concerns.]
80+
An optimization is made to the algorithm to reduce the overhead on large surfaces with the same pixel value, which can be compressed well without noise but would compress badly when noise is added. If the pixel value in the original canvas is an exact match, the exact same noise is applied to that pixel. As a result, the large surface will still be of the same value and thus compress well.
16681

167-
## References & acknowledgements
82+
## FAQs
16883

169-
[Your design will change and be informed by many people; acknowledge them in an ongoing way! It helps build community and, as we only get by through the contributions of many, is only fair.]
84+
**Canvas noise on readback is breaking my application, what should I do?**
17085

171-
[Unless you have a specific reason not to, these should be in alphabetical order.]
86+
Do you specifically need the GPU to render the contents? If not, you can consider switching using CPU rendering: set `willReadFrequently: true` when getting the context (see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext#willreadfrequently)). Note that for canvases with frequent readbacks, or for canvases where only few operations are performed, it is typically faster to use the CPU for rendering.
87+
If it’s not possible to switch to CPU rendering, please [file a bug](https://issues.chromium.org/issues/new?component=1456351&title=%5BCanvas%20anti-FP%20breakage%5D&description=-%20Breaking%20Site:%20https:%2F%2Fwww.example.com%2F%0A-%20Platform:%20%0A-%20Version:%20%0A%0A-%20Screenshot%20%5Boptional%5D:%20%0A%0A-%20Observed%20behavior:%20%0A-%20Expected%20behavior:%20%0A&template=0) with Chromium; we are interested to understand your use-case.
17288

173-
Many thanks for valuable feedback and advice from:
89+
## Security and privacy considerations
90+
The goal of canvas noising is to reduce information available through the Canvas API that can be used to re-identify the user across sites.
17491

175-
- [Person 1]
176-
- [Person 2]
177-
- [etc.]
92+
The per-partition token should be considered site data, and thus should be removed whenever the user deletes data for a particular site. This prevents the token from being abused as a persistent identifier.

0 commit comments

Comments
 (0)