under resistFingerprinting, media query matches is always false
Categories
(Core :: CSS Parsing and Computation, defect)
Tracking
()
People
(Reporter: mike, Assigned: fkilic)
References
(Blocks 1 open bug)
Details
Attachments
(4 files)
User Agent: Mozilla/5.0 (X11; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0
Steps to reproduce:
actual version: Firefox Developer Edition 122.0b4
At the console, I ran
window.matchMedia('(resolution: ' + window.devicePixelRatio + 'dppx)')
both with privacy.resistFingerprinting on and off.
Actual results:
with privacy.resistFingerprinting off:
window.devicePixelRatio
<- 1.2
window.matchMedia('(resolution: ' + window.devicePixelRatio + 'dppx)')
<- MediaQueryList { media: "(resolution: 1.2dppx)", matches: true, onchange: null }
With it on:
window.devicePixelRatio
<- 1
window.matchMedia('(resolution: ' + window.devicePixelRatio + 'dppx)')
<- MediaQueryList { media: "(resolution: 1dppx)", matches: false, onchange: null }
Expected results:
The returned MediaQueryList field 'matches' should always be true, even if firefox lies about the resolution for privacy reasons.
Reporter | ||
Comment 1•10 months ago
|
||
This caused a panic in winit: https://github.com/rust-windowing/winit/issues/3345
Comment 2•10 months ago
|
||
The Bugbug bot thinks this bug should belong to the 'Core::Privacy: Anti-Tracking' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.
Comment 3•10 months ago
|
||
So nsGlobalWindowInner::GetDevicePixelRatio
returns 1.0
when resistFingerprinting is on, but Gecko_MediaFeatures_GetResolution
returns pc->DeviceContext()->GetFullZoom()
. Should those match or is the assumption in comment 0 wrong?
Comment 5•10 months ago
|
||
RFP protects all those by only returning the zoom level
Updated•9 months ago
|
Assignee | ||
Comment 6•19 days ago
|
||
Updated•19 days ago
|
Assignee | ||
Comment 7•19 days ago
|
||
Assignee | ||
Comment 8•19 days ago
|
||
Is the zoom level something we want to leak? It was being leaked already (you can brute-force matchMedia('(resolution: <some value here>dppx)')
to get user's zoom level), so I built the patches around that fact, but if we want to hide it, then we would have to do something else (changing even the current behaviour).
Comment 9•19 days ago
|
||
where anotated with [✓ RFP]
, these are all protected to only return the "zoom" level (except window.devicePixelRatio
which returns 2
). Without RFP, "zoom" is a calculation and a best guess at that - it's not something that can be looked up or relied on - for example system scaling will affect the measurements !== "zoom". RFP also ignores any site specific zoom levels, and always resets to 100% zoom on new tabs and new eTLD+1s etc. If a user zooms after that, big deal, the protected values are still protected (i.e everyone would be the same assuming the same zoom levels apply .. 100, 110 etc)
We haven't patched all possible methods, but we can certainly make it harder. Subpixel entropy (which is effectively what this is), manifests almost everywhere measurements are made, but we think we may have a plan for that - "a concept of a plan" - which involves jittering zoom :) early days
It was being leaked already (you can brute-force
matchMedia
('(resolution: <some value here>dppx)') to get user's zoom level)
Unless I am missing something, the matchMedia
lines and the first two values on the devicePixelRatio
line (so total of five values) in the image are already doing that: a binary search using a lamba and two properties: excuse my code
So AFAIK, there is no leak in matchMedia
so I built the patches around that fact
but are we leaking? see above. Also just because we can't (yet) cover all methods (the subpixel problem) doesn't mean the current protection isn't worth it. It's not a zero sum game.
I fail to see what multiplying the zoom level x 2
(our spoofed value) will achieve that leaving it at x 1
already does. The value 2 for dPR is a recent change. If you mean multiplying by the real dPR value then we lose our protection - and that's exactly what we're protecting against - leaking dpi/dPR
ni: piero, maybe he can explain it better than me
Comment 10•18 days ago
|
||
(In reply to Fatih Kilic from comment #8)
Is the zoom level something we want to leak? It was being leaked already (you can brute-force
matchMedia('(resolution: <some value here>dppx)')
to get user's zoom level), so I built the patches around that fact, but if we want to hide it, then we would have to do something else (changing even the current behaviour).
Yes, that is indeed possible. You won't get a direct value, but you can get close to one (e.g., with a bisection like Thorin said).
Notice that it will leak also subpixels (e.g., layout.css.devPixelsPerPx
, or system-wide scaling), because of some roundings that happen when converting between device pixels and app units.
E.g., for 150% zoom and layout.css.devPixelsPerPx
set to 1.2
you get something around 1.5151515 +- ε
with matchMedia(resolution > {value}dppx)
.
We don't care much about zoom, as it'll be isolated, reset, and there isn't a real way to prevent measuring it indirectly, if one really wanted to.
We'd like to do something about the subpixels, but it's a very hard problem.
Comment 11•18 days ago
|
||
Yes, that is indeed possible
Just to be clear - it's not leaking dpi or dPR (unless I am missing something). At 100% zoom everyone is identical. Outside of 100% zoom, subpixels may manifest (probably, can't confirm off the top of my head) but the measurements are still based on our "hardcoded" 100% zoom levels
Comment 12•18 days ago
|
||
(In reply to Thorin [:thorin] from comment #11)
Yes, that is indeed possible
Just to be clear - it's not leaking dpi or dPR (unless I am missing something). At 100% zoom everyone is identical. Outside of 100% zoom, subpixels may manifest (probably, can't confirm off the top of my head) but the measurements are still based on our "hardcoded" 100% zoom levels
To be more precise, with RFP the resolution
matches the "full zoom", which is computed for example when the app units per dev pixel change.
Since it's computed with a round, the full zoom matches the zoom value the user sees only for some zoom values (such as 100%) or when using neat scaling levels (such as 1, 1.5 or 2).
In most of the other cases, subpixels will affect this value, but you won't be able to get the exact scaling level (system scaling, devPixelsPerPx
, etc...) reliably from that.
Assignee | ||
Comment 13•18 days ago
|
||
but are we leaking? see above. Also just because we can't (yet) cover all methods (the subpixel problem) doesn't mean the current protection isn't worth it. It's not a zero sum game.
I mean it is not leak if you don't consider it a leak ahhaha. that was basically my question, do we care about whether user zoomed or not.
I fail to see what multiplying the zoom level x 2 (our spoofed value) will achieve that leaving it at x 1 already does
This will just match properties. For example, if devicePixeRatio is 2, then dpi should be 96*2. Zooming changes mAppUnitsPerDevPixel, which is then used everywhere to compute these values, including devicePixelRatio.
If you have a devicePixelRatio=2 device, go to TZP.html in non-RFP and note the values you got for those CSS properties you shared in the image, and then turn on RFP repeat the same steps, the only thing not changing would be devicePixelRatio, which doesn't make sense. the other properties listed there should be multiplied by the device pixel ratio, and in our case that's 2. So, this patch will only match those broken properties.
Since it's computed with a round, the full zoom matches the zoom value the user sees only for some zoom values (such as 100%) or when using neat scaling levels (such as 1, 1.5 or 2).
Is it really important to get super precise though? With no RFP, at 110% on a normally 2 devicePixelRatio device, I get 2.2222222222222223 for dPR and 2.222222328186035 for the estimate, at 120%, I get 2.4 for dPR and 2.4000000953674316 for the estimate. I think it is already pretty close.
So if this patch lands, we will expose the exact values being estimated here, but it is already pretty precise I think?
Other than that, just multiplying the CSS properties by 2 shouldn't really affect anything, at best it should make websites work better. Looking at the source code,
nsGlobalWindowInner::GetDevicePixelRatio
is only used for getting window.devicePixelRatioGecko_MediaFeatures_GetResolution
is only used byeval_resolution
and that is only used by resolution, -moz-device-pixel-ratio, and -webkit-{min|max}-device-pixel-ratio media queries.
Comment 14•18 days ago
|
||
So if this patch lands, we will expose the exact values being estimated here, but it is already pretty precise I think?
Yes, the patches don't add any additional information.
They look good to me.
Comment 15•18 days ago
|
||
OP's STR are a little confusing. Nine months ago we used a hardcoded 1
for dpr, and all the matchMedia results returned the equivalent. The problem only arose when the user zoomed off 100%, because all the other values would change, but window.devicePixelRatio didn't change. So the problem was one we didn't care about - when users are not at 100% zoom. It is not clear from OP that the user was zoomed. So the original issue was a non-issue IMO (because TB uses 100% zoom on all new eTLD+1's and new tabs).
But now we hardcode at 2
, the default matchMedia etc values don't match (at 100% zoom). So now we want to x 2
it all so they do match [1]. This is fine. If we're going to always multiple by 2
then this does not solve the issue of when the user zooms - which again, is not an issue TB cares about. Otherwise the real solution would be to multiple dpr (hardcoded at 2) by zoom and then use that to multiple matchMedia
Unless I'm missing something, this is not addressing the original issue, but does address one that has come up since (namely changing to hardcoded 2
)
[1] Make sure -webkit is rounded down to it's few results or you will look weird
Comment 16•18 days ago
|
||
I wonder if there's much actual value in spoofing the devicePixelRatio
at all here?
It's pretty-trivially sniffable using JS, based on how border-snapping works (which depends on and hence exposes the relationship between CSS pixels and border-pixels): https://drafts.csswg.org/css-values-4/#snap-a-length-as-a-border-width
Here's a proof-of-concept to demonstrate that. When loaded, it prints out the observed window.devicePixelRatio
and also an inferred one based on border sizing measurements.
On my (non-HiDPI) display, when I zoom to these percentages, I get these inferred devicePixelRatios:
120%: 1.2048192771084338
133%: 1.3333333333333333
200%: 2
300%: 3.0303030303030303
Comment 17•18 days ago
|
||
(I haven't tried but I'm pretty sure you could build a similar PoC using only CSS, with CSS container queries, where e.g. a background-image depends on the available space in a container, and the container has a fractional-valued border width that gets snapped to an exact number of device pixels.)
Comment 18•18 days ago
•
|
||
daniel ... a border PoC has been in TZP since jesus, just saying .. https://arkenfox.github.io/TZP/tzp.html#screen - second to last item in the screen section. Note, this is not a zero sum game - making scripts use the dom and elements as a method is a bonus. Maybe we'll close those loops in the future.
- edit: made https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/42048 no longer confidential
Also your PoC returns doesn't output the inferred value when viewed as an attachment :-(2
for me with RFP on, but on TZP, I still get my real value of 1
. Not sure if your PoC works, unless I've misunderstood what it does
Comment 19•13 days ago
•
|
||
(In reply to Thorin [:thorin] from comment #18)
daniel ... a border PoC has been in TZP since jesus, just saying .. https://arkenfox.github.io/TZP/tzp.html#screen
[...]
- edit: made https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/42048 no longer confidential
Thanks; that does seem like it's the same thing I'm talking about, yeah.
My hypothetical CSS-only container-query-style approach in comment 17 is probably not in TZP (since container queries are relatively recent). Writing a PoC for that would be a small amount of work, but it's quite possible to do so in theory.
Note, this is not a zero sum game - making scripts use the dom and elements as a method is a bonus. Maybe we'll close those loops in the future.
Right. Just wanted to be sure that the existing hurdle's bypassability is considered, when evaluating the benefits against the costs, with e.g. the site-breakage referenced in comment 1 being an example of a cost. (I know the cost-vs-benefit judgement is fuzzy as with all rFP-style interventions; and maybe the benefit is indeed worth it in this case; I'm not sure.)
Also your PoC ~~returns
2
for me with RFP on, but on TZP, I still get my real value of1
. Not sure if your PoC works,
Yeah, sorry -- I should've said, my PoC only tries to output an inferred value if your true devicePixelRatio is greater than 1. That was just laziness on my part in writing the PoC. If your true devicePixelRatio is 1 and you want to try the attached PoC, you can try applying full-page-zoom which should make the logic activate and start printing out an inferred devicePixelRatio (which would then be your full-page-zoom zoom-factor; full-page-zoom is essentially the same sort of scaling as HiDPI scaling, under the hood).
doesn't output the inferred value when viewed as an attachment :-(
FWIW I'm not seeing any difference in behavior when viewing it as an attachment vs. not; but I don't want to sidetrack on PoC diagnosis too much here so I won't worry about this. :)
Description
•