Skip to content
GoogleGHSA-24cm-69m9-fpw3

Apple: WebKit Canvas Denoising

MediumPublished Dec 26, 2024

### Summary When browsing in Safari’s Private Mode, WebKit adds noise to canvas readback to prevent fingerprinting. Due to the way noise is clamped for blocks of identical pixels, this can be removed by scaling the canvas. ### Severity Moderate - A script performing canvas fingerprinting can remove the noise (added in Private Mode) and recover the original image, making canvas fingerprinting possible again. ### Proof of Concept 1. Use a canvas (`c1`) to draw the fingerprintable shapes. 2. Set `context.imageSmoothingEnabled = false` (https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled). This ensures that colors are kept identical when scaling. 3. Create a new canvas `c2` with `height = c1.height * 3` and `width = c1.width * 3`. 4. Set the scale of `c2` to be 3x3. This creates a larger canvas where each original pixel is now represented by a 3x3 block of identical pixels (smoothing is disabled). 5. Copy `c1` into `c2` using `context.drawImage`. This scales `c1` up. The `drawImage` operation does not introduce noise. 6. Use `context.getImageData` to read the pixel data from the scaled canvas. This triggers WebKit's noise algorithm. 7. Bec...

GitHub advisory

Affected versions

PackageAffectedFixed in
WebKit
Product
all versionsNo fix yet
Details and references

### Summary When browsing in Safari’s Private Mode, WebKit adds noise to canvas readback to prevent fingerprinting. Due to the way noise is clamped for blocks of identical pixels, this can be removed by scaling the canvas. ### Severity Moderate - A script performing canvas fingerprinting can remove the noise (added in Private Mode) and recover the original image, making canvas fingerprinting possible again. ### Proof of Concept 1. Use a canvas (`c1`) to draw the fingerprintable shapes. 2. Set `context.imageSmoothingEnabled = false` (https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled). This ensures that colors are kept identical when scaling. 3. Create a new canvas `c2` with `height = c1.height * 3` and `width = c1.width * 3`. 4. Set the scale of `c2` to be 3x3. This creates a larger canvas where each original pixel is now represented by a 3x3 block of identical pixels (smoothing is disabled). 5. Copy `c1` into `c2` using `context.drawImage`. This scales `c1` up. The `drawImage` operation does not introduce noise. 6. Use `context.getImageData` to read the pixel data from the scaled canvas. This triggers WebKit's noise algorithm. 7. Because the scaling process created blocks of identical pixels, the center pixel of each block will have eight neighbors with the same color. This forces the noise clamping range to a single color, resulting in no noise being applied to that pixel, which allows the original color to be recovered. ```Javascript function denoise(c1) { const scale = 3; const c2 = document.createElement('canvas'); c2.width = c1.width * scale; c2.height = c1.height * scale; const context = c2.getContext('2d'); context.imageSmoothingEnabled = false; context.scale(scale, scale); context.drawImage(c1, 0, 0); const scaled = context.getImageData(0, 0, c2.width, c2.height); const real = new ImageData(c1.width, c1.height); for (let y = 0; y < c1.height; y += 1) { for (let x = 0; x < c1.width; x += 1) { const realIdx = (y * c1.width + x) * 4; const scaledIdx = (y * scale * c2.width + x * scale + 1) * 4; for (let chan = 0; chan < 4; chan += 1) { real.data[realIdx + chan] = scaled.data[scaledIdx + chan]; } } } return real; } ``` ### Further Analysis WebKit applies noise to canvas elements to make fingerprinting more difficult. However, this noise can be bypassed by manipulating the noise clamping process, specifically by scaling the canvas. This allows the attacker to completely recover the original, non-noised pixel colors, bypassing the fingerprinting protection. WebKit’s algorithm applies a noise to each color channel of every pixel. The noise is derived from a hash of the origin and the original pixel color. To prevent the noise from being too visible, WebKit "clamps" the noise. This means that after the noise is applied, the new pixel color is compared to its 8 neighboring pixels. If the new color is outside the minimum and maximum color values of its neighbors, it's adjusted to fit within that range. The key vulnerability is that if all neighboring pixels have the same color, the clamping range will only include that single color. This makes the noise deterministic and predictable. ### Timeline **Date reported**: 09/25/2024 **Date fixed**: **Date disclosed**: 12/26/2024

Severity from
GitHub (reviewed advisory)

More Google advisories

All Google
Advisory
ENGAGE - Server Displaying Sensitive Information
LowJan 10, 2025
Integer Overflow in eBPF DEVMAP map_delete_elem Leads to Out-of-Bounds
High7.8Jan 9, 2025
Linux Kernel: Integer Overflow in eBPF XSK map_delete_elem Leads to Out-of-Bounds
High7.8Jan 9, 2025
Linux Kernel: Out of bounds Write in ksmbd_vfs_stream_write
Critical9.8Jan 9, 2025
Linux Kernel: Out of bounds Read in ksmbd_vfs_stream_read
Critical9.1Jan 9, 2025
MacPorts: Remote Code Execution
MediumDec 23, 2024

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.