Closed Bug 1709867 Opened 4 years ago Closed 2 years ago

Apply fine-grained control of the RFP Timezone Spoofing

Categories

(Core :: JavaScript: Internationalization API, enhancement, P1)

enhancement

Tracking

()

RESOLVED FIXED
113 Branch
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.

It might be worthwhile to implement 3 levels of spoofing:

  1. UTC (this is the current RFP behaviour)
  2. inspired by brave #8574, do not spoof the timezone offset (like UTC+3 remains UTC+3) but simplify the timezone name (like America/Toronto becomes America/New York; that is, pick the most popular timezone name in that timezone offset.)
  3. 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).

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.

Summary: Apply fine-grained control of the RFP Timzezone Spoofing → Apply fine-grained control of the RFP Timezone Spoofing
Depends on: 1364261

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.

Flags: needinfo?(andrebargull)

(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.

Flags: needinfo?(andrebargull)

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: nobody → tschuster
Depends on: 1816092

Depends on D169465

(In reply to Tom Schuster (MoCo) from comment #7)

I am going to start by making js::DateTimeInfo per Realm 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.

Component: Security → JavaScript: Internationalization API
Attachment #9317043 - Attachment is obsolete: true

Depends on D169611

Depends on D169613

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)

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.

Attachment #9317306 - Attachment description: WIP: Bug 1709867 - Add a disabled pref to stop setting TZ=UTC. → WIP: Bug 1709867 - Add a testing pref to stop setting TZ=UTC.

Depends on D169614

Attachment #9317305 - Attachment description: WIP: Bug 1709867 - Select between default or UTC timezone based on the RFP mode. → Bug 1709867 - Select between default or UTC timezone based on the RFP mode. r?anba!,jandem!
Attachment #9317306 - Attachment description: WIP: Bug 1709867 - Add a testing pref to stop setting TZ=UTC. → Bug 1709867 - Add a testing pref to stop setting TZ=UTC. r?tjr
Attachment #9317833 - Attachment description: WIP: Bug 1709867 - Test time zone spoofing exemptions. → Bug 1709867 - Test time zone spoofing exemptions. r?tjr
Severity: -- → N/A
Priority: -- → P1

Hi André! Do you think you might have time to look at my patches? I think you probably know this code the best nowadays.

Flags: needinfo?(andrebargull)

Sorry for the delay, I was sick until last week.

Flags: needinfo?(andrebargull)

No problem at all André. Thanks for your reviews and take care!

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.

No longer blocks: 1377744
Duplicate of this bug: 1377744
Pushed by tschuster@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/758564d35c15 Add a ICU-only DateTimeInfo::timeZoneId method. r=anba,platform-i18n-reviewers,dminor https://hg.mozilla.org/integration/autoland/rev/e6ff77dc46c1 Add a ICU-only DateTimeInfo::getRawOffsetMs method. r=anba https://hg.mozilla.org/integration/autoland/rev/0e4c8e097868 Select between default or UTC timezone based on the RFP mode. r=anba,jandem https://hg.mozilla.org/integration/autoland/rev/4c5ba97813ba Add a testing pref to stop setting TZ=UTC. r=tjr https://hg.mozilla.org/integration/autoland/rev/6f4c845bf6aa Test time zone spoofing exemptions. r=tjr
Regressions: 1822862
Regressions: 1822952
No longer regressions: 1822952
Regressions: 1822980
Regressions: 1826998
Blocks: 1837582
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: