This note describes a recommendation to the WebAppSec WG to extend Content Security Policy (CSP) to support compiling and executing WebAssembly modules.
In order for a user to experience the benefits of using a particular WebAssembly module there are three (at least) parties that must collaborate: the browser (or other host environment), the publisher (the author of the WebAssembly module) and the user. Each party seeks some form of guarantee from the other parties that allow that party to trust the others. This trust centric modeling allows us to paint a more accurate picture of WebAssembly security.
WebAssembly has a sandbox-style security model which focuses on limiting the potential damage a WebAssembly module can do to its host environment. For example, a WebAssembly module is not permitted to access any functions from the host other than those explicitly passed to it via imports. Similarly, a WebAssembly module cannot directly access the evaluation stack; which also limits the potential for attacks based on manipulating return addresses and other important stack data.
By imposing this sandbox on the execution of a WebAssembly model, the browser (or other host) gains sufficient trust that browser is willing to permit the WebAssembly code to execute.
This does not, however, provide any guarantees that WebAssembly modules compute correct results: it is still possible that an incorrectly programmed module may corrupt data, produce invalid results and be subject to attacks such as SQL injection and even buffer overrun affecting data structures within an application. Since the memory used by a WebAssembly module may be shared via ArrayBuffers these faults may be visible to and affect other WebAssembly and JavaScript modules that also share the same memory. Other memory faults - such as use-after-free and accessing uninitialized memory - are also similarly not protected against by the engine.
We should also note that a malicious module may be completely safe in terms of the resources from the host that it uses and still cause significant harm to the user. A classic example of this would be a surruptiously loaded crypto-mining WebAssembly module.
In addition, the sandbox model does not manage which WebAssembly modules are executed. Controlling which WebAssembly modules are executed is the primary focus of CSP.
CSP, broadly, allows a publisher to control what resources can be loaded as part of a site. These resources can include images, audio, video, or scripts. In particular, a suitable CSP should allow the publisher to declare to the host which WebAssembly modules may be executed by the browser on behalf of the user.
It is important to manage this as malicious WebAssembly modules could exfiltrate data from the site. Images could display misleading or incorrect information. Fetching resources leaks information about the user to untrusted third parties.
Viewed in terms of trust modeling, CSP allows the content publisher to establish a trust contract with the browser -- and therefore be willing to let the browser execute the publisher's code. It does not, however, address the trust that a user must express when accessing functionality from a WebAssembly module. This is crucially important; however, it is also beyond the scope of this note.
Executing WebAssembly has several steps.
-
First there are the raw WebAssembly bytes, which typically are loaded using the fetch API.
-
Next, the bytes are compiled using
WebAssembly.compile
orWebAssembly.compileStreaming
into aWebAssembly.Module
. This module is not yet executable, but WebAssembly implementations may choose to translate the WebAssembly code into machine code at this step. -
Finally, a WebAssembly module is combined with an import object using
WebAssembly.instantiate
to create anWebAssembly.Instance
object. The import object, broadly, defines the capabilities of the resulting instance, optionally including aWebAssembly.Memory
, bindings for the WebAssembly function imports, and an indirect function call table.Note that, via the
start
function, some functionality within a WebAssembly module may start executing at this point. However, for the most part, the host accesses WebAssembly functions through the instance's exports.
These steps provide the core of the WebAssembly API, but there are several other methods provided as well. These are summarized below along with their risks that are related to CSP.
WebAssembly.validate
checks whether the given bytes comprise a valid WebAssembly program. In other
words, it checks whether the bytes are syntactically correct and valid according
to the WebAssembly type system.
Risks: Limited to certain denial of service style attacks. Currently the cost of validating a WebAssembly module is linear on the size of the module; however, certain anticipated changes to the WebAssembly type system may change that.
new WebAssembly.Module
synchronously creates a WebAssembly.Module
from WebAssembly bytes. This is a
synchronous version of WebAssembly.compile
.
Risks: many implementations will generate machine code at this step, even though it is not yet exposed as executable code to the surrounding program.
WebAssembly.compile
provides a Promise
that resolves to a WebAssembly.Module
generated from the
provided WebAssembly bytes. This is an asynchronous version of new WebAssembly.Module
.
Risks: equivalent to new WebAssembly.Module
.
WebAssembly.compileStreaming
creates a WebAssembly.Module
from the WebAssembly bytes contained in the
provided Response
object.
Risks: equivalent to new WebAssembly.Module
.
WebAssembly.instantiate
accepts either WebAssembly bytes or a WebAssembly.Module
and an import object.
The function returns a WebAssembly.Instance
that allows executing the
WebAssembly code. If WebAssembly bytes are provided, instantiate
will first
perform the steps of WebAssembly.compile
.
Risks: loads executable code into the running program and execute any included start
function.
As noted above, WebAssembly functions are only able to access objects reachable from the import object. The instance does not have unrestricted access to the JavaScript global object.
WebAssembly.instantiateStreaming
accepts a Response
containing WebAssembly bytes and an import object, performs
the operations behind WebAssembly.compileStreaming
on these bytes and then
creates a WebAssembly.Instance
.
Risks: equivalent to WebAssembly.instantiate
.
- Bugs in the browser. We assume correct implementations of image decoders, script compilers, etc. CSP does not protect against malicious inputs that can, for example, trigger buffer overflows.
- Resource exhaustion. Computation performed by scripts uses memory and CPU time and can therefore cause a denial of service on the browser. Protecting against this is one reason site owners use CSP, but denial of service is not a first order consideration for CSP. Scripts are dangerous not because of their resource consumption but because of other effects that can cause.
As noted earlier, CSP allows publishers to control what resources are loaded into a browser from a Web application. This includes resources that are dynamically created as well as those loaded directly. There is no direct equivalent of a script
element for WebAssembly modules; although it is possible that such an feature may be specified in the future. Instead, WebAssembly modules are compiled and instantiated via a JavaScript API. Thus our focus on CSP for WebAssembly is on that API.
It is proposed that the above APIs are gated by a policy point: HostEnsureCanCompileWasmBytes
. (This is sometimes referred to as an abstract operation that must be performed by the host to permit the compilation.)
If HostEnsureCanCompileWasmBytes
is not enabled, then the WebAssembly.compile
and WebAssembly.compileStreaming
functions fail with a WebAssembly.CompileError
exception. Otherwise, these API functions return results depending on the internal integrity of the WebAssembly module being compiled.
We recommend that a new CSP policy directive wasm-unsafe-eval
be created. If set in the headers of a page, then the HostEnsureCanCompileWasmBytes
policy point is enabled; which, in turn, allows the page to load, compile and instantiate WebAssembly code. This would apply to both the inline APIs (WebAssembly.compile
etc.) and the streaming APIs (WebAssembly.compileStreaming
et el.) The details of these abstract operations will be incorporated into a future version of the CSP specification.
Given the current usage of the CSP policy unsafe-eval
to gate both JavaScript eval
and instantiating WebAssembly modules, we propose that that behavior be allowed to continue; but that wasm-unsafe-eval
should have no implication for JavaScript loading or evaluation or use of eval
in JavaScript. NOTE: Providing a directive to allow JavaScript eval
without WebAssembly doesn't seem immediately useful, and so has been left out intentionally.
With the wasm-unsafe-eval
source, there are two options for managing WebAssembly execution: unsafe-eval
and wasm-unsafe-eval
. The former is primarily intended to govern JavaScript execution -- specifically those JS features that can be used to construct programs from text. However, it has been extended to allow WebAssembly execution. The second is a more targeted source keyword that only applies to WebAssembly.
-
If the
unsafe-eval
source keyword is used, then this overrides any occurence ofwasm-unsafe-eval
in the CSP policy. -
If the
wasm-unsafe-eval
source keyword is used, and theunsafe-eval
keyword is not present, then WebAssembly modules may be compiled and instantiated.One advantage of this is that a website can permit WebAssembly modules to be used without also enabling JavaScript's
eval
keyword.
On the event of failure, then a CompileError
should be thrown by the WebAssembly.compile
and related API calls.
Currently, WebAssembly modules are anomolous within the Web platform because there is no specific HTML element that references WebAssembly modules. It is reasonable to suggest that the existing script
element may one day be extended to also include WebAssembly modules. In that event, it is also reasonable to consider the use of the CSP script-src
source keyword to include WebAssembly as well as JavaScript.
However, it may not be wise to extend the scope of CSP's script-src
source in this way. The reasons for this are that it could break existing websites and that the specific rules for script-src
are too tailored to the requirements of JavaScript.
-
Extending
script-src
to include WebAssembly has the potential to compromise a website that currently uses a CSP policy for JavaScript that was not intended to support WebAssembly. -
Using a white list approach for allowable domains to source executable can be shown to be difficult to manage in practice. This is especially true for JavaScript, but is also true for WebAssembly.
- The primary issue with white listing a domain is that it allows all code from that domain. However, many domains -- such as CDNs -- host many code modules; many more than owners of websites can reasonably be aware of.