Spoofing Navigator API when resisting fingerprinting is enabled

RESOLVED FIXED in Firefox 56

Status

()

defect
P1
normal
RESOLVED FIXED
3 years ago
Last year

People

(Reporter: timhuang, Assigned: timhuang)

Tracking

(Blocks 1 bug)

unspecified
mozilla56
Points:
---
Dependency tree / graph

Firefox Tracking Flags

(firefox56 fixed)

Details

(Whiteboard: [tor][fingerprinting][domsecurity-backlog1][fp:m2])

Attachments

(3 attachments)

The navigator API reveals certain information about the user's browser, like its buildId. So we should spoof these values of navigator API to disable fingerprinting through these values when resisting fingerprinting is enabled.
Which values should we spoof?
IMHO:
1 User-Agent header value should be replaced with "webbrowser;<html standard version>;<css standard version>;<ECMAScript standard version>"
2 Permissions for retrieving sensitive information about environment should be introduced. I mean that in order to get something meaningful from that variables the app should request a permission, a user can either grant ut, providing true info, or refuse (providing user-defined fake info, an app must see it as "granted" in both cases.)
Priority: -- → P2
Depends on: 1337161
Whiteboard: [tor][fingerprinting] → [tor][fingerprinting][domsecurity-backlog1]
Priority: P2 → P1
Assignee: nobody → tihuang
Whiteboard: [tor][fingerprinting][domsecurity-backlog1] → [tor][fingerprinting][domsecurity-backlog1][fp:m1]
Status: NEW → ASSIGNED
The navigator is an ideal place for browser fingerprinting, so spoof and disable some features of navigator object would be beneficial for fingerprinting resistance.

Following is what I am going to do with navigator object when 'privacy.resistFingerprinting' is true.
* Spoofing
  - navigator.appName
  - navigator.appVersion
  - navigator.platform
  - navigator.userAgent
  - navigator.mimeTypes (already done)
  - navigator.plugins (already done)
  - navigator.oscpu
  - navigator.buildID


* Disabling
  - navigator.geolocation
  - navigator.connection
  - navigator.mediaDevices
Following are reasons why we want to disable geo location API, network information API and media devices API when 'privacy.resistFingerprinting' is true.

a) For geo location API, although, it requires users to grant its permission to access this API. But, I think we still need to disable this API becasue of two reasons. For the one hand, one user could grant this permission before 'privacy.resistFingerprinting' has been turned on, so this API can still be accessed in this case. On the other hand, users may incautiously grant this permission while 'privacy.resistFingerprinting' is on. Therefore, we'd better to disable this.

b) For network information API, it will reveal your connection type which is a fingerprintable vector.

c) For media devices, this can be used to enumerate a user's media devices. We should not reveal users' hardware settings when fingerprinting resistance is on.
Making this depends on Bug 1369303 since patches here need to check 'privacy.resistFingerprinting' in worker thread and we will expose this pref to the worker thread in Bug 1369303.
Depends on: 1369303
(In reply to Tim Huang[:timhuang] from comment #3)
> c) For media devices, this can be used to enumerate a user's media devices.
> We should not reveal users' hardware settings when fingerprinting resistance
> is on.

Are you trying to be comprehensive about this?  For example, have you looked at navigator.hardwareConcurrency?  There is also a WebGL API that reveals your graphics card driver (I don't remember what it's called off the top of my head).  There's probably other similar examples if you looked hard enough...
Comment on attachment 8876029 [details]
Bug 1333651 - Part 1: Spoofing the User-Agent header when 'privacy.resistFingerprinting' is true.

https://reviewboard.mozilla.org/r/147474/#review151924

::: netwerk/protocol/http/nsHttpHandler.cpp:797
(Diff revision 1)
>      if (mUserAgentOverride) {
>          LOG(("using general.useragent.override : %s\n", mUserAgentOverride.get()));
>          return mUserAgentOverride;
>      }
>  
> +    if (nsContentUtils::ShouldResistFingerprinting() &&

This seems wrong, FWIW perhaps, depending on what you intended it to do.  Right now with your patch if Firefox decides to override the UA string for a site, that would override the Tor Browser's fingerprinting resistance.  It seems like things should be the other way around?
(In reply to :Ehsan Akhgari (needinfo please, extremely long backlog) from comment #9)
> (In reply to Tim Huang[:timhuang] from comment #3)
> > c) For media devices, this can be used to enumerate a user's media devices.
> > We should not reveal users' hardware settings when fingerprinting resistance
> > is on.
> 
> Are you trying to be comprehensive about this?  For example, have you looked
> at navigator.hardwareConcurrency?  There is also a WebGL API that reveals
> your graphics card driver (I don't remember what it's called off the top of
> my head).  There's probably other similar examples if you looked hard
> enough...

We have certainly tried to be comprehensive! =)

hardwareConcurreny: Bug 1360039
WebGL: Bug 1217290

If there's any others that come to mind, and you don't see it at https://bugzilla.mozilla.org/showdependencytree.cgi?id=1329996&hide_resolved=1 please let know (or even if you do see it, feel free to leave a drive-by comment to confirm we are addressing what you're thinking of.)
Comment on attachment 8876030 [details]
Bug 1333651 - Part 2: Spoofing Navigator object when 'privacy.resistFingerprinting' is true.

https://reviewboard.mozilla.org/r/147476/#review151928

::: dom/base/Navigator.cpp:469
(Diff revision 1)
>  Navigator::GetOscpu(nsAString& aOSCPU, CallerType aCallerType,
>                      ErrorResult& aRv) const
>  {
>    if (aCallerType != CallerType::System) {
> +    // If fingerprinting resistance is on, we will spoof this value to 'Windows NT 6.1'.
> +    if (nsContentUtils::ShouldResistFingerprinting()) {

This function isn't thread-safe.

::: dom/base/Navigator.cpp:470
(Diff revision 1)
>                      ErrorResult& aRv) const
>  {
>    if (aCallerType != CallerType::System) {
> +    // If fingerprinting resistance is on, we will spoof this value to 'Windows NT 6.1'.
> +    if (nsContentUtils::ShouldResistFingerprinting()) {
> +      aOSCPU.AssignLiteral("Windows NT 6.1");

Please don't hard-code strings like this in the source code.  Please use some #defines in the beginning of the file, preferrably all in the same place so that it's clear what the Navigator object returns by skimming the code quickly when fingerprinting resistence is turned on without having to examine each one of these functions.

::: dom/base/Navigator.cpp:650
(Diff revision 1)
>                        ErrorResult& aRv) const
>  {
>    if (aCallerType != CallerType::System) {
> +    // If fingerprinting resistance is on, we will spoof this value to '20100101'.
> +    if (nsContentUtils::ShouldResistFingerprinting()) {
> +      aBuildID.AssignLiteral("20100101");

This is repeated in GetProductSub() as well.  Please use the same macro in both functions.  Bonus points if you move all of these macros to a helper header and also use them in nsHttpHandler::Init()!

::: dom/base/Navigator.cpp:1760
(Diff revision 1)
> +bool
> +Navigator::HasGeoLocationSupport(JSContext* /* unused */,
> +                                 JSObject* /* unused */)
> +{
> +  return !nsContentUtils::ShouldResistFingerprinting() &&
> +         Preferences::GetBool("geo.enabled", true);

Do you have any reason to believe that doing this is Web compatible?  I don't think it would be.  I think the way to disable this is similar to bug 1072859 where we disabled this API for non-secure origins, i.e. by throwing an exception when the API entry points are called as opposed to hiding Navigator.geolocation.  I suggest breaking this part into a separate bug.

(In the future, please try to keep changes like this as separate patches per each logical change, for example make a separate patch per each API you are disabling, to avoid making things difficult when one of the changes are wrong...)
Attachment #8876030 - Flags: review?(ehsan) → review-
Comment on attachment 8876031 [details]
Bug 1333651 - Part 3: Add a test case to verify that Navigator object has been spoofed/disabled correctly when 'privacy.resistFingerprinting' is true.

https://reviewboard.mozilla.org/r/147478/#review151938

This needs to be changed as per my review on part 2.
Attachment #8876031 - Flags: review?(ehsan)
(In reply to Tom Ritter [:tjr] from comment #11)
> (In reply to :Ehsan Akhgari (needinfo please, extremely long backlog) from
> comment #9)
> > (In reply to Tim Huang[:timhuang] from comment #3)
> > > c) For media devices, this can be used to enumerate a user's media devices.
> > > We should not reveal users' hardware settings when fingerprinting resistance
> > > is on.
> > 
> > Are you trying to be comprehensive about this?  For example, have you looked
> > at navigator.hardwareConcurrency?  There is also a WebGL API that reveals
> > your graphics card driver (I don't remember what it's called off the top of
> > my head).  There's probably other similar examples if you looked hard
> > enough...
> 
> We have certainly tried to be comprehensive! =)
> 
> hardwareConcurreny: Bug 1360039
> WebGL: Bug 1217290

Oh great!

> If there's any others that come to mind, and you don't see it at
> https://bugzilla.mozilla.org/showdependencytree.
> cgi?id=1329996&hide_resolved=1 please let know (or even if you do see it,
> feel free to leave a drive-by comment to confirm we are addressing what
> you're thinking of.)

I'll try to think of more examples as drive-by's, but to be honest I don't have a lot of cycles these days to devote to thinking about this systematically...  I suggest doing an audit of dom/webidl at some point when we're about to get to a finishing point of this project.  I have been following things from very far away so I have no idea how close we are, but no need to panic unless if we're about to wrap up.  :-)
(You should feel free to reach out to other DOM peers about this general issue who may have more free time these days if you need help with a more broad audit, btw.)
(In reply to :Ehsan Akhgari (needinfo please, extremely long backlog) from comment #10)
> Comment on attachment 8876029 [details]
> Bug 1333651 - Part 1: Spoofing the User-Agent header when
> 'privacy.resistFingerprinting' is true.
> 
> This seems wrong, FWIW perhaps, depending on what you intended it to do. 
> Right now with your patch if Firefox decides to override the UA string for a
> site, that would override the Tor Browser's fingerprinting resistance.  It
> seems like things should be the other way around?

Thanks for pointing this out. Yes, you are right, this should be put before userAgentOverride. I will fix this at next push.
(In reply to :Ehsan Akhgari (needinfo please, extremely long backlog) from comment #12)
> Comment on attachment 8876030 [details]
> Bug 1333651 - Part 2: Spoofing Navigator object when
> 'privacy.resistFingerprinting' is true.
> 
> https://reviewboard.mozilla.org/r/147476/#review151928
> 
> ::: dom/base/Navigator.cpp:469
> (Diff revision 1)
> >  Navigator::GetOscpu(nsAString& aOSCPU, CallerType aCallerType,
> >                      ErrorResult& aRv) const
> >  {
> >    if (aCallerType != CallerType::System) {
> > +    // If fingerprinting resistance is on, we will spoof this value to 'Windows NT 6.1'.
> > +    if (nsContentUtils::ShouldResistFingerprinting()) {
> 
> This function isn't thread-safe.

This function has been turned into thread-safe in Bug 1217238.

> ::: dom/base/Navigator.cpp:1760
> (Diff revision 1)
> > +bool
> > +Navigator::HasGeoLocationSupport(JSContext* /* unused */,
> > +                                 JSObject* /* unused */)
> > +{
> > +  return !nsContentUtils::ShouldResistFingerprinting() &&
> > +         Preferences::GetBool("geo.enabled", true);
> 
> Do you have any reason to believe that doing this is Web compatible?  I
> don't think it would be.  I think the way to disable this is similar to bug
> 1072859 where we disabled this API for non-secure origins, i.e. by throwing
> an exception when the API entry points are called as opposed to hiding
> Navigator.geolocation.  I suggest breaking this part into a separate bug.
> 

After thinking about this, I think we should try our best to neutralize the threat of fingerprinting without breaking the web compatibility and treat disabling them as a last resort.    

> (In the future, please try to keep changes like this as separate patches per
> each logical change, for example make a separate patch per each API you are
> disabling, to avoid making things difficult when one of the changes are
> wrong...)

I will open separate bugs for geo location, network information and media devices.
Whiteboard: [tor][fingerprinting][domsecurity-backlog1][fp:m1] → [tor][fingerprinting][domsecurity-backlog1][fp:m2]
Comment on attachment 8876030 [details]
Bug 1333651 - Part 2: Spoofing Navigator object when 'privacy.resistFingerprinting' is true.

https://reviewboard.mozilla.org/r/147476/#review152516

::: commit-message-3f4a4:4
(Diff revision 2)
> +Bug 1333651 - Part 2: Spoofing Navigator object when 'privacy.resistFingerprinting' is true. r?Ehsan,arthuredelstein
> +
> +This patch makes navigator object to return spoofed value for fields have fingerprintable
> +concerns. This changes the worker navigatoras well.

Nit: s/navigartoras/navigator as/

::: toolkit/components/resistfingerprinting/nsRFPService.h:14
(Diff revision 2)
>  #include "mozilla/Atomics.h"
>  #include "nsIObserver.h"
>  
>  #include "nsString.h"
>  
> +// Defines regarding spoofed values of Navigator object.

Please extend this comment to also say that these are returned when the fingerprinting resisting mode is activated.
Attachment #8876030 - Flags: review?(ehsan) → review+
Comment on attachment 8876031 [details]
Bug 1333651 - Part 3: Add a test case to verify that Navigator object has been spoofed/disabled correctly when 'privacy.resistFingerprinting' is true.

https://reviewboard.mozilla.org/r/147478/#review152518
Attachment #8876031 - Flags: review?(ehsan) → review+
Comment on attachment 8876029 [details]
Bug 1333651 - Part 1: Spoofing the User-Agent header when 'privacy.resistFingerprinting' is true.

https://reviewboard.mozilla.org/r/147474/#review153150
Attachment #8876029 - Flags: review?(arthuredelstein) → review+
Comment on attachment 8876031 [details]
Bug 1333651 - Part 3: Add a test case to verify that Navigator object has been spoofed/disabled correctly when 'privacy.resistFingerprinting' is true.

https://reviewboard.mozilla.org/r/147478/#review153160
Attachment #8876031 - Flags: review?(arthuredelstein) → review+
Comment on attachment 8876030 [details]
Bug 1333651 - Part 2: Spoofing Navigator object when 'privacy.resistFingerprinting' is true.

https://reviewboard.mozilla.org/r/147476/#review153164
Attachment #8876030 - Flags: review?(arthuredelstein) → review+
Comment on attachment 8876029 [details]
Bug 1333651 - Part 1: Spoofing the User-Agent header when 'privacy.resistFingerprinting' is true.

https://reviewboard.mozilla.org/r/147474/#review154508

can you justify why we need a fourth way to set the UA? (default, general.useragent.override, and the per-domain override) Why does the existing override pref not do the job?
Attachment #8876029 - Flags: review?(mcmanus)
(In reply to Patrick McManus [:mcmanus] from comment #29)
> can you justify why we need a fourth way to set the UA? (default,
> general.useragent.override, and the per-domain override) Why does the
> existing override pref not do the job?

The main idea is that we want to put all anti-fingerprinting features behind one pref, 'privacy.resistFingerprinting'. The UA spoofing is one of them. At first place, we did want to use existing pref, general.useragent.override, to do the job and treat the 'privacy.resistFingerprinting' as a meta pref. So, when a user flip 'privacy.resistFingerprinting', it will automatically flip other prefs, like 'general.useragent.override'. However, it turned out to be not a good solution since we need to handle store and restore of user setting prefs and it will have lots of ramifications that need to be considered [1].

So, right now, we think a better approach is that patching fingerprintiable features directly and not relies on the pref system. There are two benefits of doing this.

1. When 'privacy.resistFingerprinting' is true, the fingerprinting resistance is enforced regardless of other prefs value. This behavior is much simpler for users and developers to understand.
2. Building anti-fingerprinting feature directly into gecko is concrete than building it on top of pref system since prefs could be removed or changed.

So, go back to the UA spoofing, we need a way other than the existing 'general.useragent.override' solution to do the job.

Does this answer your concern? :)

[1] Bug 1333933 comment 17
Flags: needinfo?(mcmanus)
Comment on attachment 8876029 [details]
Bug 1333651 - Part 1: Spoofing the User-Agent header when 'privacy.resistFingerprinting' is true.

https://reviewboard.mozilla.org/r/147474/#review154986

let's do it
Attachment #8876029 - Flags: review+
Flags: needinfo?(mcmanus)
Pushed by cbook@mozilla.com:
https://hg.mozilla.org/integration/autoland/rev/a9c101b46ab6
Part 1: Spoofing the User-Agent header when 'privacy.resistFingerprinting' is true. r=arthuredelstein,mcmanus
https://hg.mozilla.org/integration/autoland/rev/76040a8fabaa
Part 2: Spoofing Navigator object when 'privacy.resistFingerprinting' is true. r=arthuredelstein,Ehsan
https://hg.mozilla.org/integration/autoland/rev/5131e38858f9
Part 3: Add a test case to verify that Navigator object has been spoofed/disabled correctly when 'privacy.resistFingerprinting' is true. r=arthuredelstein,Ehsan
Keywords: checkin-needed
https://hg.mozilla.org/mozilla-central/rev/a9c101b46ab6
https://hg.mozilla.org/mozilla-central/rev/76040a8fabaa
https://hg.mozilla.org/mozilla-central/rev/5131e38858f9
Status: ASSIGNED → RESOLVED
Closed: 2 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla56
See Also: → 1383495
See Also: → 1409269
Verified successfully that the User-Agent header is correctly spoofed.
Verified on Mac Os 10.12.6 with Nightly 58.0a1 (2017-10-23) (64-bit)

Verification steps:
1. Start the browser, open a new tab and go to https://browserprint.info/test
2. Open a new tab and go to about:config, then change the privacy.resistFingerprinting to true
3. Open a new tab and go to https://browserprint.info/test

Expected results:
Compare the results from step 1 and 3. In step 1, user-agent should show correct information about the current system. In step 3, user-agent should show spoofed information

Actual results:
From step 1: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:58.0) Gecko/20100101 Firefox/58.0
From step 3: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0
You need to log in before you can comment on or make changes to this bug.