Closed Bug 1167489 Opened 9 years ago Closed 9 years ago

"Spy in the Sandbox" - Security issue related to High Resolution Time API

Categories

(Core :: DOM: Core & HTML, defect)

34 Branch
defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla42
Tracking Status
firefox40 --- wontfix
firefox41 + fixed
firefox42 --- fixed
firefox-esr31 --- unaffected
firefox-esr38 --- wontfix
b2g-v2.0 --- unaffected
b2g-v2.0M --- unaffected
b2g-v2.1 --- wontfix
b2g-v2.1S --- wontfix
b2g-v2.2 --- affected
b2g-v2.2r --- affected
b2g-master --- fixed

People

(Reporter: bugzilla, Assigned: bzbarsky)

References

Details

(Keywords: csectype-disclosure, privacy, sec-moderate, Whiteboard: [post-critsmash-triage][adv-main41+])

Attachments

(2 files)

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/600.6.3 (KHTML, like Gecko) Version/8.0.6 Safari/600.6.3

Steps to reproduce:

My name is Yossi and I’m a post-doc at Columbia University’s Network Security Lab.  We’ve recently published a report describing a new Javascript-based network attack.  Our attack lets a malicious website learn a surprising amount of personal information about an innocent user, and is largely based upon repeated calls to performance.now() as a measurement method.

Here is a proof of concept that works on Firefox 34 and later for Mac OS and Linux:
http://s.codepen.io/yossioren/debug/ba94a14dd5942fda5db1a3e70809181d?

From our testing it is very apparent that somewhat reducing the resolution of performance.now() will make the attack much more difficult. We noticed that various browser vendors implement the call with different precisions, with Firefox for Linux and MacOS going down to the single nanosecond.  Our attack as described becomes very difficult to launch once the resolution of performance.now() is upper-bounded to 5 thousandths of a millisecond.

Assuming there is not too much of a usability loss (to games, music, VR, etc) if this resolution is upper-bounded, perhaps you should look at changing your implementation.  We’re also in touch with the relevant parties at the W3C to modify the standard accordingly.

I would welcome answering any questions or concerns you may have.  We can have a phone call / teleconf if it’s easier for you.
Flags: sec-bounty?
Doug who's the right person to think about this?
Component: Untriaged → DOM
Flags: needinfo?(dougt)
Product: Firefox → Core
Note related bug 1153672.  In that bug there is discussion about possibly clamping the performance.now() values to exact microsecond precision (as in, only returning values that are some integer number of microseconds).

That's not quite the 5 microseconds that comment 0 says would be an effective mitigation here, but 5 microseconds would also be a clear violation of the current spec...

Yossi, how much of a win is going to integer microseconds for performance.now in handling your attack?
Status: UNCONFIRMED → NEW
Ever confirmed: true
See Also: → 1153672
Hi, we have a microsecond attack that's currently aimed at IE and Chrome. Profiling takes a little longer and the attack has a slightly lower resolution, but the exploitability is still there.  I will update the bug with a microsecond PoC in a couple of days.

Regarding the current spec - perhaps you can reach out to the relevant W3G parties to understand why a resolution of exactly one microsecond was put in there, and what would go wrong if it was changed to 5 microseconds.  FWIW I think it was just to get nice animations at 60fps.

Also, could you please authorize me to access #1153672?
I can reach out to them, but so can you.  It's a public mailing list.  <public-web-perf@w3.org>.  It seems that a more direct conversation would work better than a long chain of messages (you to me to them) that has more potential for confusion and misunderstandings.

If you want to reach out to just the spec editors, that would be <jmann@microsoft.com> and <simonjam@google.com>.

> FWIW I think it was just to get nice animations at 60fps.

The idea of the performance.now() API was to allow timing of operations at a higher granularity than 1ms.  That can be used for things like frame time budgets, yes, but there were other use cases too.  http://w3c.github.io/hr-time/#introduction lists some; note the bit about audio/video synchronization in particular.

> Also, could you please authorize me to access #1153672?

Done.
I reached out to jmann@microsoft.com back in March and haven't heard back.  I'll try contacting the mailing list, as you suggested.
Flags: needinfo?(dougt)
Adding Hovav Shacham to see if he can offer any insight here.
Just updating that the issue was resolved in WebKit by reducing the resolution of performance.now() to 5 microseconds:

https://bugs.webkit.org/show_bug.cgi?id=146531

The issue was also resolved in Chromium by the same method:

https://code.google.com/p/chromium/issues/detail?id=506723

That leaves Firefox and IE.
<sigh>.  I guess we should reduce resolution to 5us too...

Nathan, you seem to have most of the recent reviews in the timestamp code.  Is it worth trying to add faster ToSanitizedMilliseconds methods on the platform utils stuff that try to get by with integer arithmetic (after converting the double to a 64-bit int of nanoseconds or microseconds) or should we just bite the bullet and do a floor() in the DOM performance code (or in shared TimeStamp code, e.g. a shared ToSanitizedMilliseconds) and not worry about the performance too much?
Flags: needinfo?(nfroyd)
(In reply to Boris Zbarsky [:bz] from comment #10)
> Is it worth trying to add faster ToSanitizedMilliseconds methods on the
> platform utils stuff that try to get by with integer arithmetic (after
> converting the double to a 64-bit int of nanoseconds or microseconds) or
> should we just bite the bullet and do a floor() in the DOM performance code
> (or in shared TimeStamp code, e.g. a shared ToSanitizedMilliseconds) and not
> worry about the performance too much?

I guess it partly depends on how fast the divide + floor operation is in float vs. integer code?  If the former is faster, you want to do it outside of the platform-specific code; if the latter is faster, you want to do it in the platform-specific code.

At least for the divide step, my sense is that float division is somewhat-to-significantly faster than integer division: I don't know whether all our supported ARM chips actually have hardware divide or not, for instance, and Agner Fog's x86 instruction tables suggest that floating-point divides are somewhat-to-significantly faster (assuming I'm reading them correctly).  And even if the compiler (or us) can optimize the division into a multiply, floating point is still at least even.

For the flooring...I don't have good ideas here, but I'd be willing to bet the floating-point library function is fast enough, or we could write a fast-enough version with knowledge of the input ranges (i.e. we know infinities and NaNs are not a problem here...I think).

The above reasoning points to putting this in the DOM performance code.  And if we're only going for a resolution of 5us, then we have a lot of time to "waste" sanitizing the value anyway, right?

I'm having a hard time thinking of other places where we'd want to sanitize these values, so I don't think the shared TimeStamp code is the right place.  (Undoubtedly, some clever person will probably come along with another similar exploit, and we'll bite the bullet then...)
Flags: needinfo?(nfroyd)
> I guess it partly depends on how fast the divide + floor operation is in float vs.
> integer code?

At least on my Mac it's faster in integer code, because floor seems to involve a function call.  But I have no idea what it's like across platforms/compilers/libcs.  :(

OK, I'll just do the simple thing in shared code.
(In reply to Boris Zbarsky [:bz] from comment #12)
> > I guess it partly depends on how fast the divide + floor operation is in float vs.
> > integer code?
> 
> At least on my Mac it's faster in integer code, because floor seems to
> involve a function call.  But I have no idea what it's like across
> platforms/compilers/libcs.  :(

Probably just as bad. =/

How much slower is it?
Flags: needinfo?(bzbarsky)
Bug 1153672 comment 14 has some data, but that was using round().  Also see bug 1153672 comment 19.  Specifically, times without the rounding are in the 40ns range for a performance.now() call.  With round() they're more like 55ns.

If I use floor() times are about 47ns.  I guess we can just live with that; it's no worse than competitors at this point.
Flags: needinfo?(bzbarsky)
Assignee: nobody → bzbarsky
Status: NEW → ASSIGNED
Blocks: 1153672
So I think we should probably land this on m-c and Aurora.  I'm less sure about doing beta; would prefer to just let things ride from Aurora.
Comment on attachment 8630479 [details] [diff] [review]
and bug 1153672.  Clamp the resolution of performance.now() calls to 5us, because otherwise we allow various timing attacks that depend on high accuracy timers

Review of attachment 8630479 [details] [diff] [review]:
-----------------------------------------------------------------

::: dom/base/nsPerformance.cpp
@@ +503,5 @@
>  {
> +  double nowTime = GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now());
> +  // Round down to the nearest 5us, because if the timer is too
> +  // accurate people can do nasty timing attacks with it.
> +  const double maxResolutionMs = 0.005;

Minor brain confusion between |us| in the comment and |Ms| in the variable name, but I don't know of a good way to fix that.
Attachment #8630479 - Flags: review?(nfroyd) → review+
I can just make the comment say "0.005ms (5us)" to make it clear.  And I guess rename nowTime to nowTimeMs.
Comment on attachment 8630479 [details] [diff] [review]
and bug 1153672.  Clamp the resolution of performance.now() calls to 5us, because otherwise we allow various timing attacks that depend on high accuracy timers

Approval Request Comment
[Feature/regressing bug #]: performance.now() implementation
[User impact if declined]: Various privacy leaks; see this bug and bug 1153672
[Describe test coverage new/current, TreeHerder]:
[Risks and why]: Medium risk.  Clamping to 1us would be pretty safe (since Chrome
   does that already), but no browser right now clamps to 5us.  That said, I
   don't expect anything in the wild depends on higher resolution than that right
   now...
[String/UUID change made/needed]:
Attachment #8630479 - Flags: approval-mozilla-aurora?
https://hg.mozilla.org/mozilla-central/rev/48ae8b5e62ab
Status: ASSIGNED → RESOLVED
Closed: 9 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla42
Attachment #8630479 - Flags: approval-mozilla-aurora? → approval-mozilla-aurora+
Do we need this on Beta or ESR38?
I think it's a bit risky for beta.  Not sure about ESR38; we should see how it goes on Aurora.
Yossi: Nice work!

All sorts of timing side-channel vulnerabilities are facilitated by the availability of high-resolution timers.  See Paul Stone's "Pixel Perfect" work for another example.  I would recommend aiming for a granularity substantially closer to (1/60) s than to 5 us, at least in the absence of a user permission.
1/60 s is way too low for anything useful.  And the web has had millisecond-accurate timers for going on 20 years now; _tons_ of stuff depends on those.
None of the examples listed at http://w3c.github.io/hr-time/#introduction self-evidently calls for timer granularity even as fine as 1 ms, let alone lower.

Keep in mind, also, that when you clamp a timer to 5 us, you still allow JS to recover more like a 2 us timer, due to edge effects.
The examples section there is rather incomplete.  The use cases for finer-grained timers I know of are:

1)  Knowing how much frame budget you have left.  The frame budget is 16ms, so this needs decent timer granularity; 16ms is clearly too coarse.  1ms was being too coarse in practice, because people have more than 16 things they want to do in a frame.

2)  A/V sync.  16ms is clearly way to coarse, again.  1ms may be good enough.

3)  Figuring out the order of events within a frame (the example in the spec).  The resolution you want here depends on how many events you expect to have.  1ms means you can only get 16 things ordered.
Flags: sec-bounty? → sec-bounty+
Boris, what about the other implementation of Performance, in dom/workers/Performance.cpp?
Blocks: 1186489
Gah, good catch.  I forgot that it doesn't share this code, because my initial patches that changed the TimeDuration behavior applied to it too...  Filed bug 1186489.
Hi Yossi, we have made several changes to this API that should address the issues raised. We'd really appreciate it if you could take a look at a current build of Fx41 and comment on how the fixes work for you. Thank you.
Flags: needinfo?(bugzilla)
Fine Matt, I will test it and report here.
Flags: needinfo?(bugzilla)
Test machine - Intel core i7-3667U running Mac OS 10.10.3 (Ivy Bridge, 4MB cache)

Using the proof of concept webpage: http://s.codepen.io/yossioren/debug/ba94a14dd5942fda5db1a3e70809181d?

Testing on Firefox 40.0.3 from https://download-installer.cdn.mozilla.net/pub/firefox/releases/40.0.3/mac/en-US/Firefox%2040.0.3.dmg
—
Profiling was successful, measurement was successful - Spy in the Sandbox attack is possible


Testing on Firefox 41.0b4 from https://ftp.mozilla.org/pub/mozilla.org/firefox/releases/41.0b4/mac/en-US/Firefox%2041.0b4.dmg
—
Profiling was unsuccessful.  Whatever you did to the code mitigated the attack.  Congratulations :-)

Just a cautionary note: it seems possible to polyfill high resolution time using SharedArrayBuffers.  You can get down to the nanosecond, if you don’t mind the crazy amounts of jitter.  Here’s a demo, which works on the latest Firefox nightly (although not on 41.0b4):
http://s.codepen.io/yossioren/debug/LVBNMR?
 
There’s already an issue opened about it on the SharedArrayBuffer spec:
https://github.com/lars-t-hansen/ecmascript_sharedmem/issues/1
Thanks Yossi.

Reading comment 34, it sounds like you feel that there still might be another vector for attack here. If you believe so, please feel free to file another bug with as much information - and working PoC - as you can to help us investigate and/or fix that as well.
Group: core-security → core-security-release
Whiteboard: [post-critsmash-triage]
I'm writing a security advisory for this unless someone says not to do so. Is there any reason not to disclose this issue and fix?
Flags: needinfo?(bugzilla)
Flags: needinfo?(bzbarsky)
Whiteboard: [post-critsmash-triage] → [post-critsmash-triage][adv-main41+]
Apart from coordinating with the reporter of bug 1153672 I'm not aware of anything.
Flags: needinfo?(bzbarsky)
No objections on my side, as long as you don't release actual exploit code before Apple rolls out their fix for Safari.
Flags: needinfo?(bugzilla)
(In reply to Yossi Oren from comment #38)
> No objections on my side, as long as you don't release actual exploit code
> before Apple rolls out their fix for Safari.

That's a new wrinkle. Do you know when they are shipping? Generally, we'd avoid even the chance of causing Safari difficulty if they're doing the right thing and fixing with appropriate speed.
Flags: needinfo?(bugzilla)
We got pinged by Apple Product Security on 26/Aug, asking how to credit us on the disclosure in a coming Security Update, so probably it's coming out real soon now.
Flags: needinfo?(bugzilla)
OK, Apple is acknowledging our disclosure and patching it in today's Security Update (APPLE-SA-2015-09-16-1).  It's listed as CVE-2015-5825

https://support.apple.com/en-us/HT205212
Alias: CVE-2015-4514
Alias: CVE-2015-4514
Group: core-security-release
Component: DOM → DOM: Core & HTML
You need to log in before you can comment on or make changes to this bug.