Open Bug 1545527 Opened 5 years ago Updated 10 months ago

Extend createObjectURL to support canvas

Categories

(Core :: DOM: Core & HTML, enhancement, P5)

enhancement

Tracking

()

UNCONFIRMED

People

(Reporter: ryan.hendrickson, Unassigned)

References

Details

(Whiteboard: [fingerprinting])

With bug 967895 in play, some JS libraries (example) that generate dynamic images using <canvas> and then use those images in HTML and CSS now require user consent before they'll work in FF for users with fingerprinting resistance enabled; and from the user's perspective, the consent request is possibly opaque (what is canvas data and why does this page care?) and over-broad (I do want the web page to use canvas data to affect other things on the page; I don't want it to have arbitrary access to that data for fingerprinting purposes).

I propose, as a way library authors can choose to work around the above problem, extending URL.createObjectURL to accept HTMLCanvasElements, such that URL.createObjectURL(canvas) is roughly equivalent to canvas.toBlob(blob => callback(URL.createObjectURL(blob))) (modulo sync/async differences), except that canvas data permission is not required, since object URLs are opaque. (As the equivalency suggests, the object URL should stand for a snapshot of the canvas, not a live view, the same way that toBlob and toDataURL take snapshots.)

(Adding a mozToObjectURL method to HTMLCanvasElement instead would also be acceptable, particularly if making createObjectURL(canvas) synchronous is somehow difficult or undesirable. I figured createObjectURL would be better as it's already intended to be overloaded, and doesn't add anything to any namespaces that might require a vendor prefix.)

Holding an object URL to a canvas should enable a page to reuse that canvas's image on the page, or offer the image as a download to the user, but not to upload the image to a server or inspect anything that derives from its contents—which should follow from the fact that you can't get the underlying blob from an object URL.

you can't get the underlying blob from an object URL.

Um, except that I forgot about fetch.

Obviously, with resistFingerprinting on, we'd need to prevent fetching canvas blob URLs, or treat it like a cross-origin request and return an ‘opaque filtered response’.

I might have proposed achieving this by giving the canvas's blob URL a new opaque origin instead of the page's origin, but I do think that the blob URL should be usable in further canvas painting without clearing the target canvas's origin-clean flag.

So I think it would be overall less special-casey to simply amend fetch to filter responses from canvas blob URLs when fingerprinting resistance is enabled. Either way, this would be a minor deviation from the fetch spec, as the relevant section gives no provision for filtering responses from blob: URLs, based on origin or otherwise.

Priority: -- → P3

This is an interesting proposal, but is probably best made at https://github.com/w3c/FileAPI/issues/new or some such. It's not entirely clear to me that with Spectre this would be a sufficient long term workaround however.

It's also quite an intrusive change to how blob: URLs generally work as they can only be fetched same-origin (not properly specified, see open issues).

Depends on: 967895
Priority: P3 → P5
Whiteboard: [fingerprinting]

My concern with starting at the spec is that as far as I know, Firefox is the only major browser with functionality to restrict pages' access to their own canvases, and without such a restriction there's no real need for this feature.

I don't understand what you're suggesting about Spectre. Are you saying that a website could use a Spectre-class exploit to read canvas data without going through the regular API, and that a blob URL pointing to that canvas would make that easier? I'm not a security expert but I don't understand why that would be.

Mostly that preventing canvas read access might not be sufficient, which would suggest needing a different solution altogether.

(In reply to Ryan Hendrickson [rhendric on GitLab, GitHub] from comment #3)

... without such a restriction there's no real need for this feature.

To be fair, I can think of a few use cases for this - downloading the contents of the canvas as a file is one as mentioned in comment #1 (right now toDataUrl is used and it's suboptimal in a few ways), another would be stashing the contents of the canvas as a blob that you can then use as the 'src' of a <img> element or as the css background-image of various elements. I know there's a weird CSS way to use canvases already but I can imagine having it be general and being able to swap a canvas in for an existing url-based flow could be pretty nice. If the blob represents PNG/JPEG data that means the browser can evict the actual backing texture from memory (and the canvas can be discarded), so the blob ends up being a much cheaper way to set aside images.

The introduction of a special case for fetch/xhr against blobs for this would be frustrating, though. It's hard to know how much existing code would potentially be broken by that (but it's at least an 'if it hurts when you do that, stop doing that' situation since this would be a new feature)

As far as the security exposure goes, if this is anything like toDataUrl and the blob is png/jpeg data (I think it would have to be, to make it downloadable?) then you're at least introducing a timing channel where you can measure the time it takes to generate the blob and use that to estimate the entropy of the image, and there are probably ways you could use that to extract information. If any of the existing APIs that expose rough memory usage data to pages happen to count memory used to store the blob, you could also measure the size of the post-encode blob that way.

(In reply to Katelyn Gadd (:kael) from comment #5)

To be fair, I can think of a few use cases for this

Good points. Okay, I can see shopping this at the W3C repo as a good idea then.

then you're at least introducing a timing channel where you can measure the time it takes to generate the blob

If this was a sufficiently large concern, I can imagine ways to mitigate it—the blob ID doesn't have to block on the blob data being encoded, so you could, say, make an unencoded memory copy of the canvas texture (an operation that probably only depends on canvas size), then return the object URL, then encode the copy asynchronously. You could even return the object URL immediately as long as you make subsequent draws to that canvas block until the copy is complete. Not sure this extra complexity is actually worth the practical risk of exposing identifying data from this particular vector, but it's something that could be considered.

Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.