denoland / deno

A modern runtime for JavaScript and TypeScript.
https://deno.com
MIT License
94.56k stars 5.25k forks source link

Custom precision on timers in JS #9388

Open mihai1voicescu opened 3 years ago

mihai1voicescu commented 3 years ago

Hello, I am in the process of building a custom lambda platform for my master thesis.

deno and v8 seems to be a perfect candidate for the application level sandbox (good job btw 👍 ).

As far as I know the greater the precision of the timers the faster you can do a Spectre style attack. I see the hp timers are guarded by a flag, but some applications (such as mine) would like to trade precision for an increased degree a security (preferably configurable).

When I say timers I am referring of course to the setInterval/setTimeout and Date.now API.

  1. Do you think this is necessary?
  2. Would you be open to adding this as a configuration flag (I would be more then happy to make a PR)?

This is even more important if you extend deno with more native code from Rust, or plan to run multiple Isolates in the same process, such as I do (as far as I know, no other problems except security and process crashes can occur here).

bnoordhuis commented 3 years ago

Have you seen the --allow-hrtime flag?

If yes and it's not good enough for your purposes, can you explain where it falls short?

If no, then that's probably what you want to use. :-)

mihai1voicescu commented 3 years ago

Yes I am aware of the flag:

I see the hp timers are guarded by a flag

My point is: are you sure this is enough to mitigate an attack given that this isolate can run for a long time?

I saw Cludflare is building a lambda service and they came to the conclusion that allowing timers is too risky (in their platform Date.now points to the last request received and timeouts and workers are disabled), check here.

Adding an extra layer of protection (for those who can afford the lowered precision) seems like a nice feature.

kitsonk commented 3 years ago

I'm still not sure what you are requesting or proposing. When running Deno without --allow-hrtime there is only millisecond granularity, even on performance.now(). --allow-hrtime only increases that precision for performance.now(). setTimer() and setInterval() are notoriously inaccurate, even in our implementation, as they are subject to vagaries of the event loop. Date.now() again only has millisecond granularity. We discussed fuzzing performance.now() but we have kept it at just millisecond granularity.

The Spectre exploit specifically requires a high-precision timer, and as mentioned, they are disabled by default in Deno. The other way that Spectre was able to create a proxy high-precision timer is to use a SharedArrayBuffer and have the web worker increment a value in the buffer, which the main process reads. That good/bad news is that SharedArrayBuffers don't work in Deno #6433. It does make sense that we look at how to mitigate what if/when we introduce SharedArrayBuffers in Deno.

mihai1voicescu commented 3 years ago

I was proposing adding a flag that limits the precision/resolution (eg: Date.now/performance.now has seconds resolution instead of milliseconds => Date.now = Math.floor(Date.now / 1000) * 1000).

As far as I know (fell free to correct me on this, I would be glad if I were wrong on this) no one has proved beyond a reasonable doubt that side channel attacks can not be done with millisecond resolution timers.

For example this paper claims:

our attack requires accurate microsecond measurements. However, usually more pages are attacked and thus less accurate timers are sufficient. For instance, when checking for deduplication of a 600 kilobyte image, even an accurate millisecond-based timer can be used to implement our attack. Thus, performance.now() is sufficient to distinguish copy-on-write page faults from regular write accesses.

In the v8 blog we also have a section which states:

It became clear early on in our offensive research that timer mitigations alone would not be sufficient. One reason why is that an attacker may simply repeatedly execute their gadget so that the cumulative time difference is much larger than a single cache hit or miss. We were able to engineer reliable gadgets that use many cache lines at a time, up to the cache capacity, yielding timing differences as large as 600 microseconds. We later discovered arbitrary amplification techniques that are not limited by the cache capacity. Such amplification techniques rely on multiple attempts to read the secret data.

I am not a security expert, but it seems setting an even more coarse resolution than milliseconds (eg: seconds) could provide an additional layer of security, or at least slow down an attack (even one that is not discovered yet). Of course, only for those who can afford to use it.

kitsonk commented 3 years ago

Having timers that have a one second resolution is 1000x more inaccurate and would break a lot of code that expects resolution. As I stated before, JavaScript timers are quite unreliable at their current millisecond resolution and are unreliable.

Browsers have only considered addressing performance.now(). Safari and Firefox reduced it to millisecond grainularity like we have. Chromium is 100us plus jitter. As stated in the v8 article, they have stopped mitigating it further because there are real threats out there than speculative ones that would allow you to read the memory of the isolate you are running on.

My opinion is that millisecond resolution is sufficient mitigation coupled with the rest of the security model for Deno.

mihai1voicescu commented 3 years ago

I agree, changing the timers may break JS code. But in some cases, such as mine, this is something you can live with if it might add more security to running unsafe code in a process that multiple isolates share.

bnoordhuis commented 3 years ago

I think your question then is if we'd be open to adding a feature flag that neuters timers even more?

Speaking for myself, I'm not thrilled by the idea.

mihai1voicescu commented 3 years ago

In short, yes. Or at least changing the deno/runtime/ops/timers.rs to allow a custom timer implementations and default to the current implementation GlobalTimer.

Currently the only way I can think of to modify the resolution without forking deno is basically copy pasting the code from deno/runtime/worker.rs and replacing the call to ops::timers::init(js_runtime); with a custom one.

If you think any of the 2 options (specified in the first paragraph of this comment) are acceptable I would be more then happy to make a PR.

rajsite commented 1 year ago

That good/bad news is that SharedArrayBuffers don't work in Deno #6433. It does make sense that we look at how to mitigate what if/when we introduce SharedArrayBuffers in Deno.

Does look like Deno has SharedArrayBuffer support now. Are there mitigations in place to lock down that API? Was surprised it was enabled by default as in browsers you need all the COEP / COOP opt-ins.