Apply fine-grained control of the RFP Timezone Spoofing
Categories
(Core :: JavaScript: Internationalization API, enhancement, P1)
Tracking
()
Tracking | Status | |
---|---|---|
firefox113 | --- | fixed |
People
(Reporter: tjr, Assigned: tschuster)
References
(Blocks 1 open bug, Regressed 1 open bug)
Details
Attachments
(5 files, 1 obsolete file)
After Bug 1635603 was landed and (partial) RFP exemptions became possible it was inspiring so I dug in a little bit on TimeZone, which is one of the ugliest/dirtiest hacks that we have for RFP. When we first started spoofing timezone (or at least, when we uplifted it in Bug 1330890), we used the environment variable.
Some browser 'stuff' implicitly uses the TZ environment variable (like system file dialogs) - so if we want those to be accurate, we can't edit that. Fortunately, I dug into the JS Engine and it seems like it uses local variables for the timezone, which means we could have two and use whichever the correct one is for the context.
We might also need to do the same thing in network protocols (we'd need to audit them for a timezone exposure I suppose) - HTTP/TLS/QUIC/WebRTC/SCTP....
There might also be DOM elements that store some sort of datetime on them that we would need to edit - I spot-checked File objects but their time is stored unix-style (msec since Jan 1 1970 GMT).
It's unfortunately possible that there might be side-channels caused by system elements. e.g. you could distinguish the text width of a system element that displayed the timezone (because presumably the system element uses system time which we would not be spoofing anymore). Probably won't give you the exact timezone, but would let you distinguish them.
This might become a game of whack-a-mole and it'll be the bad type: instead of spoofing somewhere we shouldn't be; it would be more likely that we'd not be spoofing somewhere we should.
Nonetheless, Timezone is one of the most common requests for RFP exemptions, so if we want to do exemptions at all we will have to tackle this.
Reporter | ||
Updated•3 years ago
|
It might be worthwhile to implement 3 levels of spoofing:
- UTC (this is the current RFP behaviour)
- inspired by brave #8574, do not spoof the timezone offset (like UTC+3 remains UTC+3) but simplify the timezone name (like
America/Toronto
becomesAmerica/New York
; that is, pick the most popular timezone name in that timezone offset.) - no spoofing
(2) is interesting because it looks like it will unbreak a lot of functionality while not leaking too much entropy (for instance, fingerprintjs uses timezone name for fingerprinting). See, also, my proof-of-concept patch.
If it is possible, it would be nice to make (2) the default in Firefox, but I am worried that it might cause non-trivial breakage (if we get a chance, we should measure this).
Reporter | ||
Comment 4•3 years ago
|
||
Note that there is a patch at Bug 1716541 which looks like it could be the basis of this patch. They aim to do very similar things, and doing either will make the other much easier.
Updated•2 years ago
|
Assignee | ||
Comment 5•2 years ago
|
||
André how hard do you think it would be to control the timezone per realm in SpiderMonkey? The code setting/resetting the timezone is a bit complicated, but I think we end up calling ucal_setDefaultTimeZone()
, which seems to be per process.
Of course even after fixing the Intl API, there are probably still other date/time API users in Firefox that use libc and depend on the TZ environment variable directly.
Comment 6•2 years ago
|
||
(In reply to Tom Schuster (MoCo) from comment #5)
André how hard do you think it would be to control the timezone per realm in SpiderMonkey? The code setting/resetting the timezone is a bit complicated, but I think we end up calling
ucal_setDefaultTimeZone()
, which seems to be per process.
It should be doable to make it per-realm, but requires some refactoring. The relevant classes are mozilla::intl::TimeZone
(wrapper for ICU's time zone), js::DateTimeInfo
, and maybe also DateTimeHelper
.
Here's a short overview:
ICU stores its default time zone in the static
DEFAULT_ZONE variable. The default time zone is initialised with the host time zone, but we're later reinitialising it from js::DateTimeInfo::internalResyncICUDefaultTimeZone().
ICU's time zone functions aren't directly called from SpiderMonkey/Gecko, but instead everything is encapsulated through mozilla::intl::TimeZone
. That class has different code paths depending on whether or not it's possible to use ICU's C++ API or if we have to use ICU's C API.
And then there's also js::DateTimeInfo
, which is used to compute date-time offsets and to detect time zone changes. (Bug 1343826 added time zone change observers, but those aren't available for all platforms, so we still have to use our old code to check for time zones changes in Realm::init()
.)
js::DateTimeInfo
also has different code paths, to support both JS_HAS_INTL_API
and non-JS_HAS_INTL_API
builds.
js::DateTimeInfo
is currently a singleton class, so to make the time zone per-realm, each JS::Realm
needs to have its own js::DateTimeInfo
instance. js::intl_defaultTimeZone
, js::intl_defaultTimeZoneOffset
, and js::intl_isDefaultTimeZone
then need to be changed to use the time zone information from the mozilla::intl::TimeZone
object in the current realm's js::DateTimeInfo
. The time zone change detection in js::DateTimeInfo
probably needs to be moved into a separate class, too.
I don't know how much more memory will be used when each realm gets its own js::DateTimeInfo
. And I don't know if per-realm offset caches will affect the score on SunSpi... , err, the unnamed benchmark. (At least I think this comment refers to SunSpider, which is still kind of relevant because it's included in the JetStream benchmark suite. :-/ )
And we also need to define what should happen when Date
objects from globals with different time zones interact. For example:
globalWithUTC.Date.prototype.getTimezoneOffset.call(new globalWithNonUTC.Date())
Of course even after fixing the Intl API, there are probably still other date/time API users in Firefox that use libc and depend on the TZ environment variable directly.
We can't actually guarantee that libc and ICU both always use the same default time zone. This comment lists some cases where ICU can end up using a different default time zone.
Assignee | ||
Comment 7•2 years ago
|
||
Thank you André for your very detailed answered. It's a huge help. I am going to start by making js::DateTimeInfo
per Realm
and see where that leads me.
Assignee | ||
Comment 8•2 years ago
|
||
Depends on D169465
Assignee | ||
Comment 9•2 years ago
|
||
(In reply to Tom Schuster (MoCo) from comment #7)
I am going to start by making
js::DateTimeInfo
perRealm
and see where that leads me.
So I've basically managed to implement it, but after familiarizing myself more with this code I am not convinced anymore that it's the right approach.
Almost the whole raison d'être for DateTimeInfo is efficient caching. Constructing the Timezone object might actually involve local file I/O for resolving timezone data! If we have a DateTimeInfo per realm that caching becomes almost useless. I realized I would have to add some kind of global cache anyway, which just leads to a lot more complexity.
I think what might work better is to have two timezone objects per DataTimeInfo: One normal one with the actual local timezone and one just for resist fingerprinting mode that is always UTC. The different accessors on DateTimeInfo
like getOffsetMilliseconds
will than dynamically select the right timezone object based on a new shouldResistFingerprinting
parameter.
All of my investigation currently relies on us using ICU, I am not sure what needs to be done for the non-ICU path.
Assignee | ||
Updated•2 years ago
|
Updated•2 years ago
|
Assignee | ||
Comment 10•2 years ago
|
||
Assignee | ||
Comment 11•2 years ago
|
||
Depends on D169611
Assignee | ||
Comment 12•2 years ago
|
||
Depends on D169612
Assignee | ||
Comment 13•2 years ago
|
||
Depends on D169613
Assignee | ||
Comment 14•2 years ago
|
||
And we also need to define what should happen when Date objects from globals with different time zones interact
Yeah. I ran into this question as well. We can either use the time zone (default vs RFP) of the current context or of the date object. I currently implement the former. I can see both making sense.
(On the Firefox/Gecko side I think we haven't really thought too much about this case either: i.e. should it even be possible that two realms with different RFP mode can interact directly)
Assignee | ||
Comment 15•2 years ago
|
||
Oh: Additionally I am not sure if I found everything that uses intl::TimeZone
's implicit default timezone. I also think we might want to remove TimeZone::TryCreate
without an argument and TimeZone::GetDefaultTimeZone
.
Updated•2 years ago
|
Assignee | ||
Comment 16•2 years ago
|
||
Depends on D169614
Assignee | ||
Updated•2 years ago
|
Assignee | ||
Comment 17•2 years ago
|
||
Updated•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Assignee | ||
Comment 18•2 years ago
|
||
Hi André! Do you think you might have time to look at my patches? I think you probably know this code the best nowadays.
Comment 19•2 years ago
|
||
Sorry for the delay, I was sick until last week.
Assignee | ||
Comment 20•2 years ago
|
||
No problem at all André. Thanks for your reviews and take care!
Reporter | ||
Comment 21•2 years ago
|
||
Simon - just a heads up. The intent of this patchset is to change the plumbing for Timezone spoofing. It will affect the normal RFP pref - meaning that even without fiddling testGranularityMask things will be exercising the new code.
We're introducing a new pref: privacy.resistFingerprinting.testing.setTZtoUTC
. When this pref is false (as it is on Nightly) we will no longer set the TZ enviroment variable, which is how we previously made the browser spoof timezones. We would really appreciate it if you let us know if you find any way to leak the real timezone when privacy.resistFingerprinting
is true and privacy.resistFingerprinting.testing.setTZtoUTC
is false. The most likely candidates would be places where we expose the timezone that aren't in JavaScript datetime-related stuff. Maybe the File API's lastModifiedDate or something. We think we have all the JavaScript datetime APIs handled, but it's always possible there's a bug.
Setting privacy.resistFingerprinting.testing.setTZtoUTC
to true and restarting the browser (a restart is always necessary when modifying this pref) should go back to setting the environment variable, and help figure out what configuration ((a) the nightly before this lands (b) the nightly where this landed with setTZtoUTC
true and (c) with it false) exhibits what behavior.
Reporter | ||
Updated•2 years ago
|
Assignee | ||
Updated•2 years ago
|
Comment 23•2 years ago
|
||
Comment 24•2 years ago
|
||
bugherder |
https://hg.mozilla.org/mozilla-central/rev/758564d35c15
https://hg.mozilla.org/mozilla-central/rev/e6ff77dc46c1
https://hg.mozilla.org/mozilla-central/rev/0e4c8e097868
https://hg.mozilla.org/mozilla-central/rev/4c5ba97813ba
https://hg.mozilla.org/mozilla-central/rev/6f4c845bf6aa
Description
•