27

Safely reviving shared memory

 3 years ago
source link: https://hacks.mozilla.org/2020/07/safely-reviving-shared-memory/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

At Mozilla, we want the web to be capable of running high-performance applications so that users and content authors can choose the safety, agency, and openness of the web platform. One essential low-level building block for many high-performance applications is shared-memory multi-threading. That’s why it was so exciting to deliver shared memory to JavaScript and WebAssembly in 2016. This provided extremely fast communication between threads.

However, we also want the web to be secure from attackers. Keeping users safe is paramount, which is why shared memory and high-resolution timers were effectively disabled at the start of 2018 , in light of Spectre . Unfortunately, Spectre-attacks are made significantly more effective with high-resolution timers. And such timers can be created with shared memory. (This is accomplished by having one thread increment a shared memory location in a tight loop that another thread can sample as a nanosecond-resolution timer.)

Back to the drawing board

Fundamentally, for a Spectre attack to work, an attacker and victim need to reside in the same process. Like most applications on your computer, browsers used to use a single process. This would allow two open sites, say attacker.example and victim.example , to Spectre-attack each other’s data as well as other data the browser might keep such as bookmarks or history. Browsers have long since become multi-process. With Chrome’s Site Isolation and Firefox’sProject Fission, browsers will isolate each site into its own process. This is possible due to the web platform’s retrofittedsame-origin policy.

Unfortunately, isolating each site into its own process is still not sufficient for these reasons:

  1. The same-origin policy has a number of holes, two of which strongly informed our thinking during the design process:
    1. attacker.example can fetch arbitrary victim.example resources into attacker.example ’s process, e.g., through the <img> element.
    2. Due to the existence of document.domain , the minimal isolation boundary is a site (roughly the scheme and registrable domain of a website’s host) and not an origin (roughly a website’s scheme, host, and port).
  2. At this point, we don’t know if it’s feasible to isolate each site into its own process across all platforms. It is still a challenging endeavor on mobile. While possibly not a long-term problem, we would prefer a solution that allows reviving shared memory on mobile soon .

Distilling requirements

We need to address the issues above to revive shared memory and high-resolution timers. As such, we have been working on a system that meets the following requirements:

<img>

Due to these requirements, the system must provide an opt-in mechanism. We cannot forbid websites from fetching cross-origin subresources, as this would not be backwards compatible. Sadly, restricting document.domain is not backwards compatible either. More importantly, it would be unsafe to allow a website to embed cross-origin documents via an <iframe> element and have those cross-origin resources end up in the same process without opting in.

Cross-origin isolated

New headers

Together with others in the WHATWG community, we designed a set of headers that meet these requirements.

The Cross-Origin-Opener-Policy header allows you to process-isolate yourself from attackers. It also has the desirable effect that attackers cannot have access to your global object if they were to open you in a popup. This prevents XS-Leaks and various navigation attacks. Adopt this header even if you have no intention of using shared memory!

The Cross-Origin-Embedder-Policy header with value require-corp tells the browser to only allow this document to fetch cross-origin subresources from consenting websites. Technically, the way that this works is that those cross-origin resources need to specify the Cross-Origin-Resource-Policy header with value cross-origin to indicate consent.

Impact on documents

If the Cross-Origin-Opener Policy and Cross-Origin-Embedder-Policy headers are set for a top-level document with the same-origin and require-corp values respectively, then:

  1. That document will be cross-origin isolated.
  2. Any descendant documents that also set Cross-Origin-Embedder-Policy to require-corp will be cross-origin isolated. (Not setting it results in a network error.)
  3. Any popups these documents open will either be cross-origin isolated or will not have a direct relationship with these documents. This is to say that there is no direct access through window.opener or equivalent (i.e., it’s as if they were created using rel="noopener" ).

A document that is cross-origin isolated will have access to shared memory, both in JavaScript and WebAssembly. It will only be able to share memory with same-origin documents and dedicated workers in the same “tab” and its popups (technically, same-origin agents in a single browsing context group ). It will also have access to the highest-resolution performance.now() available. Evidently, it will not have access to a functional document.domain .

The way these headers ensure mutual consent between origins gives browsers the freedom to put an entire website into a single process or put each of the origins into their own process, or something in between. While process-per-origin would be ideal, this is not always feasible on all devices. So having everything that is pulled into these one-or-more processes consent is a decent middle ground.

Safety backstop

We created a safety backstop to be able to deal with novel cross-process attacks. And used an approach that avoids having to disable shared memory entirely to remain web compatible.

The result is Firefox’s JSExecutionManager . This allows us to regulate the execution of different JavaScript contexts with relation to each other. The JSExecutionManager can be used to throttle CPU and power usage by background tabs. Using the JSExecutionManager , we created a dynamic switch ( dom.workers.serialized-sab-access in about:config ) that prevents all JavaScript threads that share memory from ever running code concurrently, effectively executing these threads as if on a single-core machine. Because creating a high-resolution timer using shared memory requires two threads to run simultaneously, this switch effectively prevents the creation of a high-resolution timer without breaking websites.

By default, this switch is off, but in the case of a novel cross-process attack, we could quickly flip it on. With this switch as a backstop, we can feel confident enabling shared memory in cross-origin isolated websites even when considering unlikely future worst-case scenarios.

Acknowledgments

Many thanks to Bas Schouten and Luke Wagner for their contributions to this post. And also, in no particular order, many thanks to Nika Layzell, Tom Tung, Valentin Gosu, Eden Chuang, Jens Manuel Stutte, Luke Wagner, Bas Schouten, Neha Kochar, Andrew Sutherland, Andrew Overholt, 蔡欣宜 (Hsin-Yi Tsai), Perry Jiang, Steve Fink, Mike Conca, Lars Thomas Hansen, Jeff Walden, Junior Hsu, Selena Deckelmann, and Eric Rescorla for their help getting this done in Firefox!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK