Closed Bug 1621433 Opened 7 months ago Closed 4 months ago

In RFP mode, turn the all-white canvas into a fully random 'poison pill'

Categories

(Core :: DOM: Security, enhancement, P2)

enhancement

Tracking

()

RESOLVED FIXED
mozilla78
Tracking Status
firefox78 --- fixed

People

(Reporter: tjr, Assigned: tjr)

References

(Blocks 1 open bug)

Details

(Whiteboard: [fingerprinting][domsecurity-active])

Attachments

(2 files)

Brave has debuted this interesting idea where they randomize WebGL, AudioContext, and Canvas not as a comprehensive fingerprinting solution, but instead as a 'poison pill' against naive tracking scripts that don't realize they should be smarter. Brave slightly randomizes the result (making it imperceptibly different but with the overall correct return result) meaning that a script that incorporates this 'poison pill' into its fingerprint will never match the user again and the fingerprint is useless.

(Because they are returning the real-ish value, they need to use a per-execution per-origin seed to avoid repeat calls that average out the random value.)

The notion of a poison pill is interesting in the context of Tor Browser because while Tor Browser (which can be thought of as RFP+enhancements Firefox lacks) tries to present a uniform fingerprint, it cannot do so entirely. Consider browser viewport size. Letterboxing rounds this to some extent, but imagine three users: Alice, Bob and Carol where Alice and Bob have the same browser size and Carol doesn't. Alice and Bob may have fingerprint X and Carol fingerprint Y.

Using the same poison pill idea, Alice, Bob, and Carol would present random fingerprints to a naive tracker and the same X/Y fingerprints to a smart tracker. (That is to say, Tor Browser is strictly no worse off by using the poison pill, and would be better against naive trackers.)

Because we are proposing returning a fully random canvas value each time, we don't need to worry about the adversary averaging out the randomness, so we can avoid all that per-execution, per-origin seed.

A downside of this is presently if a website shows you the canvas data it read, it's a white square. Now it would be.... random. Maybe not even parsable as an image. We'll have to test that...

Slightly OT: randomizing is great tool, depending on the metric being measured: i.e where it makes sense (no need to add complexity if not needed)

Not only would it render (pun intended) a lot of fingerprinting scripts useless until they became smarter, it nullifies those metrics when they do become smarter. I believe this is the only solution for the following (with very little breakage: see how CanvasBlocker handles these, except measureText which isn't implemented yet)

This is an interesting idea. I wonder if it would be sufficient to generate one random byte and fill the entire canvas with it. That would always result in a grayscale image (I think) and it would be faster.

(In reply to Mark Smith [:mcs] from comment #3)

This is an interesting idea. I wonder if it would be sufficient to generate one random byte and fill the entire canvas with it. That would always result in a grayscale image (I think) and it would be faster.

I don't think so. That would mean that every user has 256 fingerprints instead of 1. But we could make it a max of 16 random bytes (128 bits).....

Tom, since you uploaded a patch I assume you are actively working on this bug, if not, please feel free to move to the backlog.

Assignee: nobody → tom
Status: NEW → ASSIGNED
Priority: -- → P2
Whiteboard: [fingerprinting] → [fingerprinting][domsecurity-active]

Not needed. It is always better to produce deterministic result.

Because we are proposing returning a fully random canvas value each time, we don't need to worry about the adversary averaging out the randomness, so we can avoid all that per-execution, per-origin seed.

No, it would result in some (>1, given that ones having rfp are rare) bits of information for them and some bits of info for all other users.

It is always better to produce deterministic result.

I mean for 2d-canvas that are used it is completely possible to use a pure software renderer as was suggested by Shacham and Mowery (inventors of canvas fingerprinting) quite long ago in their paper.

In RFP mode, canvas image extraction leads to an all-white image, replace that
with a random (sample 32 bytes of randomness and fill the buffer with that)
'poison pill'. This helps defeat naive fingerprinters by producing a random
image on every try.

Updated browser_canvas_fingerprinting_resistance.js to test whether the canvas
data is equal to the placed data (instead of testing if the canvas data was
equal to the placeholder data.)

Testing: ./mach test permissions/ and ./mach test gfx/ seem to pass.

Updates and replaces D66308.

Attachment #9143735 - Attachment description: Bug 1621433 - In RFP mode, turn canvas image extraction into a fully-random 'poison pill' for fingerprinters r?tjr → Bug 1621433 - In RFP mode, turn canvas image extraction into a random 'poison pill' for fingerprinters r?tjr

(In reply to KOLANICH from comment #7)

It is always better to produce deterministic result.

I mean for 2d-canvas that are used it is completely possible to use a pure software renderer as was suggested by Shacham and Mowery (inventors of canvas fingerprinting) quite long ago in their paper.

+1, this is the full solution. I don't think fuzzing outputs is wholly protective, particularly for WebGL. Using software renderers here allows us to fully implement the web specs and retain functionality at merely reduced performance, without still allowing tricky-but-established approaches to side-channeling data exfil.

Pushed by tritter@mozilla.com:
https://hg.mozilla.org/integration/autoland/rev/8a48a3a488ab
In RFP mode, turn canvas image extraction into a random 'poison pill' for fingerprinters r=tjr,jrmuizel

Backed out for hazard failures on CanvasRenderingContext2D.cpp

backout: https://hg.mozilla.org/integration/autoland/rev/5b3b92ac7ed09187d6cbfee0418a8b4927c4c728

push: https://treeherder.mozilla.org/#/jobs?repo=autoland&revision=8a48a3a488ab9f48ae4feaba11f0e5dab4cf00b9&searchStr=linux%2Cx64%2Cdebug%2Chazard-linux64-haz%2Fdebug%2C%28h%29&selectedTaskRun=XQiNV6j2RQ6O1KKxcayaHQ-1

failure log: https://treeherder.mozilla.org/logviewer.html#/jobs?job_id=301369664&repo=autoland&lineNumber=76824

[task 2020-05-08T06:04:38.560Z] Found 1 hazards 198 unsafe references 0 missing
[task 2020-05-08T06:04:38.561Z] Running heapwrites to generate heapWriteHazards.txt
[task 2020-05-08T06:04:38.561Z] PATH="/builds/worker/fetches/sixgill/usr/bin:${PATH}" SOURCE='/builds/worker/checkouts/gecko' LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/builds/worker/workspace/obj-haz-shell/dist/bin" ANALYZED_OBJDIR='/builds/worker/workspace/obj-analyzed' XDB='/builds/worker/fetches/sixgill/usr/bin/xdb.so' /builds/worker/workspace/obj-haz-shell/dist/bin/js /builds/worker/checkouts/gecko/js/src/devtools/rootAnalysis/analyzeHeapWrites.js > heapWriteHazards.txt
[task 2020-05-08T06:05:13.489Z] + check_hazards /builds/worker/workspace/analysis
[task 2020-05-08T06:05:13.490Z] + set +e
[task 2020-05-08T06:05:13.490Z] ++ grep -c 'Function.*has unrooted.*live across GC call' /builds/worker/workspace/analysis/rootingHazards.txt
[task 2020-05-08T06:05:13.491Z] + NUM_HAZARDS=1
[task 2020-05-08T06:05:13.492Z] ++ grep -c '^Function.takes unsafe address of unrooted' /builds/worker/workspace/analysis/refs.txt
[task 2020-05-08T06:05:13.493Z] + NUM_UNSAFE=198
[task 2020-05-08T06:05:13.493Z] ++ grep -c '^Function.
has unnecessary root' /builds/worker/workspace/analysis/unnecessary.txt
[task 2020-05-08T06:05:13.495Z] + NUM_UNNECESSARY=1297
[task 2020-05-08T06:05:13.496Z] ++ grep -c '^Dropped CFG' /builds/worker/workspace/analysis/build_xgill.log
[task 2020-05-08T06:05:13.557Z] + NUM_DROPPED=0
[task 2020-05-08T06:05:13.557Z] ++ perl -lne 'print $1 if m!found (\d+)/\d+ allowed errors!' /builds/worker/workspace/analysis/heapWriteHazards.txt
[task 2020-05-08T06:05:13.558Z] + NUM_WRITE_HAZARDS=0
[task 2020-05-08T06:05:13.559Z] ++ grep -c '^Function.*expected hazard.*but none were found' /builds/worker/workspace/analysis/rootingHazards.txt
[task 2020-05-08T06:05:13.560Z] + NUM_MISSING=0
[task 2020-05-08T06:05:13.560Z] + set +x
[task 2020-05-08T06:05:13.560Z] TinderboxPrint: rooting hazards<br/>1
[task 2020-05-08T06:05:13.560Z] TinderboxPrint: (unsafe references to unrooted GC pointers)<br/>198
[task 2020-05-08T06:05:13.560Z] TinderboxPrint: (unnecessary roots)<br/>1297
[task 2020-05-08T06:05:13.560Z] TinderboxPrint: missing expected hazards<br/>0
[task 2020-05-08T06:05:13.560Z] TinderboxPrint: heap write hazards<br/>0
[task 2020-05-08T06:05:13.561Z] TEST-UNEXPECTED-FAIL | hazards | unrooted 'nogc' of type 'JS::AutoCheckCannotGC' live across GC call at dom/canvas/CanvasRenderingContext2D.cpp:5109
[task 2020-05-08T06:05:13.562Z] TEST-UNEXPECTED-FAIL | hazards | 1 rooting hazards detected

hazard:

Function '_ZN7mozilla3dom24CanvasRenderingContext2D17GetImageDataArrayEP9JSContextiijjR12nsIPrincipalPP8JSObject$uint32 mozilla::dom::CanvasRenderingContext2D::GetImageDataArray(JSContext*, int32, int32, uint32, uint32, nsIPrincipal*, JSObject**)' has unrooted 'nogc' of type 'JS::AutoCheckCannotGC' live across GC call '_ZN7mozilla3dom29GeneratePlaceholderCanvasDataEjPPh$void mozilla::dom::GeneratePlaceholderCanvasData(uint32, uint8**)' at dom/canvas/CanvasRenderingContext2D.cpp:5109

Flags: needinfo?(tom)
Pushed by csabou@mozilla.com:
https://hg.mozilla.org/integration/autoland/rev/ab2a75db3ebe
In RFP mode, turn canvas image extraction into a random 'poison pill' for fingerprinters r=tjr,jrmuizel
Status: ASSIGNED → RESOLVED
Closed: 4 months ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla78
Regressions: 1638211
Regressions: 1638255
Regressions: 1663586
You need to log in before you can comment on or make changes to this bug.