Opened 5 years ago

Last modified 3 months ago

#13018 new defect

Math routines are OS fingerprintable

Reported by: mikeperry Owned by: tbb-team
Priority: Medium Milestone:
Component: Applications/Tor Browser Version:
Severity: Normal Keywords: tbb-fingerprinting-os-version, ff31-esr
Cc: Thorin Actual Points:
Parent ID: Points:
Reviewer: Sponsor:

Description

The Math class now exposes high-precision versions of several mathematical functions. If these are OS-specific, they may be fingerprintable.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math

OS-level fingerprinting probably is not a terribly high priority. The only situation where this is high priority is if different OS versions and library versions end up producing different results for these functions.

Child Tickets

Attachments (1)

cos-results.png (62.9 KB) - added by Thorin 6 months ago.
some more recent relevant results

Download all attachments as: .zip

Change History (34)

comment:1 Changed 5 years ago by mikeperry

Keywords: TorBrowserTeam201409 added

comment:2 Changed 5 years ago by mikeperry

Keywords: tbb-easy added

This may be easy for someone who has a bunch of different OSes and writes some test JS to print out values from these functions. Well, easy to test for differences anyway.

comment:3 Changed 5 years ago by mikeperry

Keywords: TorBrowserTeam201409Easy added; TorBrowserTeam201409 removed

comment:4 Changed 5 years ago by mikeperry

Keywords: TorBrowserTeam201410 added

comment:5 Changed 5 years ago by mikeperry

Keywords: TorBrowserTeam201410Easy added; TorBrowserTeam201409Easy removed

comment:6 Changed 5 years ago by mikeperry

Keywords: TorBrowserTeam201410 removed

comment:7 Changed 5 years ago by gk

I did not test all functions but just some using https://people.torproject.org/~gk/misc/MathHighPrecisionAPI.html. While we have differences between OSes we have it seems differences between 32bit and 64bit architectures as well. I tested on OS X 10.6.8, 32 bit Debian testing, 32 bit Ubuntu Precise, 64 bit Ubuntu Precise, Windows 7 64 bit and Windows 8 64 bit. Small differences between Linux/OS X /Windows aside, the 64bit Ubuntu Precise gives me:

    asinh(1) = 0.881373587019543
    acosh(1e300) = 691.4686750787736
    atanh(0.5) = 0.5493061443340548
    expm1(1) = 1.718281828459045
    log1p(10) = 2.3978952727983707
    sinh(1) = 1.1752011936438014
    cosh(10) = 11013.232920103324
    tanh(1) = 0.7615941559557649

While my 32bit systems give

    asinh(1) = 0.8813735870195429
    acosh(1e300) = 691.4686750787737
    atanh(0.5) = 0.5493061443340549
    expm1(1) = 1.718281828459045
    log1p(10) = 2.3978952727983707
    sinh(1) = 1.1752011936438014
    cosh(10) = 11013.232920103328
    tanh(1) = 0.7615941559557649

comment:8 Changed 5 years ago by gk

The Windows values are:

    asinh(1) = 0.8813735870195429
    acosh(1e300) = 691.4686750787737
    atanh(0.5) = 0.5493061443340549
    expm1(1) = 1.718281828459045
    log1p(10) = 2.3978952727983707
    sinh(1) = 1.1752011936438014
    cosh(10) = 11013.232920103323
    tanh(1) = 0.7615941559557649

And the OS X values are only in cosh(10) different, too. I wonder whether we could get bigger differences by picking better values for these functions (I almost bet this is the case).

Last edited 5 years ago by gk (previous) (diff)

comment:9 Changed 5 years ago by cypherpunks

Values computed using standard algebraic expression:

    asinh(1) = 0.8813735870195429
    acosh(1e300) = NaN
    atanh(0.5) = 0.5493061443340549
    expm1(1) = 1.718281828459045
    log1p(10) = 2.3978952727983707
    sinh(1) = 1.1752011936438014
    cosh(10) = 11013.232920103324
    tanh(1) = 0.7615941559557649

Using code:

function asinh(x) {
  if (x === -Infinity) {
    return x;
  } else {
    return Math.log(x + Math.sqrt(x * x + 1));
  }
}
function acosh(x) {
  return Math.log(x + Math.sqrt(x * x - 1));
}
function atanh(x) {
  return Math.log((1+x)/(1-x)) / 2;
}
function cbrt(x) {
    var y = Math.pow(Math.abs(x), 1/3);
    return x < 0 ? -y : y;
}
function cosh(x) {
    var y = Math.exp(x);
    return (y + 1 / y) / 2;
}
function expm1(x) {
    return Math.exp(x) - 1;
}
function log1p(x) {
  return Math.log(1 + x);
}
function sinh(x){
    var y = Math.exp(x);
    return (y - 1/y) / 2;
}
function tanh(x) {
  if(x === Infinity) {
    return 1;
  } else if(x === -Infinity) {
    return -1;
  } else {
    var y = Math.exp(2 * x);
    return (y - 1) / (y + 1);
  }
}

comment:10 Changed 5 years ago by gk

Just to add to my last comment: Windows7/8 and Ubuntu Precise 32/64 run on the same computer without VM. The Debian and the OS X are running on two other machines.

comment:11 Changed 5 years ago by mikeperry

Keywords: tbb-fingerprinting-os added; tbb-fingerprinting removed
Summary: Determine if high-precision Math routines are fingerprintableHigh-precision Math routines are OS fingerprintable

Hrmm.. Sounds like we may need to pick a library we like and include its versions of these functions rather than calling out to the OS.

I wonder why there are 32 vs 64bit differences here, though.. I guess the Linux versions probably use 'long' instead of something standard like int64_t... Bleh.

comment:12 Changed 5 years ago by mikeperry

I guess the next question is to determine if this is any worse than just an OS+arch fingerprinting vector, so we can decide how to prioritize this versus other fingerprinting issues.

comment:13 in reply to:  12 Changed 5 years ago by gk

Replying to mikeperry:

I guess the next question is to determine if this is any worse than just an OS+arch fingerprinting vector, so we can decide how to prioritize this versus other fingerprinting issues.

We should ask SpiderMonkey folks for a best way to fingerprint users using the Math object. They might know some really good corner cases due to the algorithms they chose. https://bugzilla.mozilla.org/show_bug.cgi?id=892671 could be relevant here, too.

Last edited 5 years ago by gk (previous) (diff)

comment:14 Changed 5 years ago by cypherpunks

32/64 difference leaks without high-precision math routines and works for ff24-esr. Math.exp(x) output is different for x >= 5.

comment:15 Changed 5 years ago by mikeperry

Summary: High-precision Math routines are OS fingerprintableMath routines are OS fingerprintable

comment:16 Changed 5 years ago by cypherpunks

comment:17 Changed 5 years ago by cypherpunks

<html>
  <head>
    <title>https://bugzilla.mozilla.org/show_bug.cgi?id=531915</title>
  </head>
  <body>
    <p>Detected platform for Tor Browser (based on ff24-esr):</p>
    <script>
      var check_tan = Math.tan(-1e300);

      if (check_tan == -1.4214488238747245)
        document.write("Linux 64");
      else if (check_tan == 0.8831488831618285)
        document.write("Linux 32");
      else if (check_tan == -4.987183803371025)
        document.write("Windows");
      else
        document.write("Mac OS X");
    </script>
  </body>
</html>

comment:18 Changed 5 years ago by cypherpunks

Detected platform for Tor Browser

For Linux it depends run-time environment (libm system library), it's possible to detect some distros such way.

Windows depends compile-time environment (mingw).

comment:19 Changed 5 years ago by cypherpunks

Non-informative. Confusing.

Last edited 5 years ago by cypherpunks (previous) (diff)

comment:20 Changed 5 years ago by gk

Type: taskdefect

comment:19 was

More to puzzle. Output of testing sample linked with x86 libm (not reproducible for cross compiling):

Before FIX_FPU: tan(-1e300) = -1.421449, tan(strtod('-1e300')) = -4.802497 
After  FIX_FPU: tan(-1e300) = -1.421449, tan(strtod('-1e300')) = 0.883149 


Code of sample:

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 
	 
inline void FIX_FPU() { 
    short control; 
    asm("fstcw %0" : "=m" (control) : ); 
    control &= ~0x300; // Lower bits 8 and 9 (precision control). 
    control |= 0x2f3;  // Raise bits 0-5 (exception masks) and 9 (64-bit precision). 
    asm("fldcw %0" : : "m" (control) ); 
} 
 	 
int main () { 
char *end; 
double x; 
	 
x = strtod ("-1e300", &end); // eq "x=-1e300"; 
	 
printf("Before FIX_FPU: tan(-1e300) = %f, tan(strtod('-1e300')) = %f\n", tan(-1e300), tan(x)); 
FIX_FPU(); 
printf("After  FIX_FPU: tan(-1e300) = %f, tan(strtod('-1e300')) = %f\n", tan(-1e300), tan(x)); 
 	 
return 0; 
} 

FIX_FPU

comment:21 Changed 5 years ago by mikeperry

Keywords: TorBrowserTeam201410Easy removed

comment:22 Changed 4 years ago by mikeperry

Keywords: tbb-fingerprinting-os-version added; tbb-fingerprinting-os removed

We may want to ship libm.so in our Linux bundles for this, at least to push back against the version fingerprinting property.

comment:23 Changed 3 years ago by arthuredelstein

Severity: Normal

Here's a very interesting overview of sources of floating-point inconsistencies:
https://randomascii.wordpress.com/2013/07/16/floating-point-determinism/

comment:24 Changed 3 years ago by mikeperry

This may also be the cause of the fingerprintability of the WebAudio stuff in #13017 (or at least part of that fingerprintability). See https://trac.torproject.org/projects/tor/ticket/13017#comment:27 for a useful test.

comment:25 Changed 3 years ago by gk

Keywords: tbb-easy removed

comment:26 Changed 2 years ago by yawning

A few notes:

  • A quick check with the browser console gives me the impression that simple JS math expressions are evaluated with 64 bit intermediaries (as opposed to 80 bit). I am uncertain about the JS JIT behavior. (1.0 + Number.EPSILON * 0.5) + Number.EPSILON * 0.5)
  • Assuming calls are made to libm (or equivalent) blindly, the results on each system are library version and implementation dependent. A particularly egregious example would be the output of double sin(double x); being flat out wrong for glibc < 2.19 for certain values. MS's VC++ runtime is less wrong for a different set of certain values, but is still wrong. This probably applies to most transcendental functions.
  • Even if we fix the JS that calls into libm, higher level apis that just happen to do math are not guaranteed to give the correct results, depending on how the native code it's called into is written or built. If we can assume that x87 is never used at all, then we'd still need to check for things like rsqrtss.

comment:27 Changed 6 months ago by gk

Cc: Thorin added

From #29566 (which I closed as duplicate):

**part2: math.cos Windows: FF vs TB**

results: see attachment
test: https://thorin-oakenpants.github.io/testing/ (for as long as I leave it there)

I do not know if that ticket/patch causes this, but there is a difference between TB vs FF for no discernible reason (e.g Linux doesn't differ between FF and TB)

Look at the first result. FF: `minus 0.374...` vs TB `plus 0.840...`

**part3: math.cos reveals platform**

finally, to the meat and potatoes. See attachment. I'm using math.cos because it always returns a value between -1 and 1 (i.e no NaN or Infinity). The following tests show that, so far, the last four values can be used to detect windows or Linux, and so far one Android major version (v5.*). I am fully expecting the first four value to betray other Android and macOS/macOS X. My testing is incomplete, but enough to prove os FP'ing

and

Thanks :) Yup, that was the ticket. Wow, 4 years. That ticket is about the functions added in FF25+ - e.g like those in https://ghacksuserjs.github.io/TorZillaPrint/TorZillaPrint.html#math - which doesn't **seem** to differ in 60+ anyway (those FF25+ functions probably need more testing I guess)

Also note, that sin() can also have differences, I'm just not sure on which values over which platforms produce the desired results (and I could probably find more functions) - I'm sure the solution for this would fix any functions, so I'm not going to dig any further (except to show combos for mac and other android versions using cos)

Edit: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math#Browser_compatibility
- `cos`, `sin` etc were FF version 1 compatible

Changed 6 months ago by Thorin

Attachment: cos-results.png added

some more recent relevant results

comment:28 Changed 5 months ago by tom

I was worried that we might be exposing hardware information via this.

I compared the numbers I got on my x64 Nightly running on a Surface Go and got the same results as those of Thorin's Win7/Win10 x64 on x64 here: https://github.com/ghacksuserjs/TorZillaPrint/issues/30 . I'm reasonably convinced this means that hardware is not a factor in these values, and it comes down to OS and related libraries.

comment:29 Changed 5 months ago by cypherpunks

tom: could ff use standard cbrt() instead of awful custom implementation?

comment:30 Changed 4 months ago by Thorin

FYI: https://bugzilla.mozilla.org/show_bug.cgi?id=1380031 (FF68+) introduced a change (over my head) that reduced some entropy (namely that of precision in the number of decimal places) in some ECMAScript Edition 6 functions

The one test affected in my PoC was expm1(1): windows example below
1.7182818284590455 FF67 or lower
1.718281828459045 FF68+

But this is not enough to affect overall FP'ing of 32 vs 64 builds and platforms. Combined Edition 1 (the set of cos tests) and Edition 6 (3 tests) are still enough.

Last edited 4 months ago by Thorin (previous) (diff)

comment:31 in reply to:  18 ; Changed 4 months ago by cypherpunks

Replying to cypherpunks:

Detected platform for Tor Browser

For Linux it depends run-time environment (libm system library), it's possible to detect some distros such way.

Why isn't https://bugzilla.mozilla.org/show_bug.cgi?id=531915 added to RFP?

Windows depends compile-time environment (mingw).

https://sourceforge.net/p/mingw-w64/mingw-w64/ci/c61763cc740f8f4986755eeafce832baa3655ee8/

comment:32 in reply to:  31 Changed 4 months ago by Thorin

Replying to cypherpunks:

Why isn't https://bugzilla.mozilla.org/show_bug.cgi?id=531915 added to RFP?

Alrighty! I've been trying to re-find that ticket for quite a few weeks. Used it many months ago and promptly lost it. Thanks. I will pass on the ticket number to the Tor Uplift guys

it's possible to detect some distros such way

Comment 18 was 5 years ago. So far, and my resources are limited (and as an upstream problem means more than Tor Browser, which already changes their math FP), I have found nothing so far that leaks anything more than major platform (win/linux/mac) and in some instances 32/64 bit builds or OS architecture (some by default: eg a 64 bit build must be on a 64bit OS).

I wanted to get a ticket at bugzilla opened. I have no idea how much work or complexity and potential issues lie with using the same libraries over all platforms (which is what Chrome seems to be doing: they have the same FP regardless of anything I test on).

comment:33 Changed 3 months ago by Thorin

I have done more testing, and improved the output in my test: including a red info if I haven't seen the hash before. I have now found that TB on Linux actually has more entropy than originally thought. After testing 5 distros (a mix of flavors and architecture) I have 3 distinct Linux buckets (it's not enough to distinguish the actual platform, at least not in all cases, yet). I will be adding more distros to investigate further.

Note: See TracTickets for help on using tickets.