CSP interaction with WebAssembly JS APIs
This was previously discussed in https://github.com/WebAssembly/design/issues/205, mostly about CSP as applied to JS module integration, where it will fall out automatically, and in https://github.com/WebAssembly/design/issues/972, which touches on the more immediately pertinent issue of the impact of CSP on the WebAssembly.instantiate/compile APIs.
It seems like currently this is not specified, but Chrome has implemented a restriction that prevents WebAssembly.* APIs from being used if unsafe-eval is disallowed via CSP. The group should work toward interoperable semantics for this.
One thing that has evolved since the discussions in #972 is the compileStreaming/instantiateStreaming APIs. At the time of that discussion, the idea was that some hypothetical "from a URL" APIs would not be blocked by CSP, whereas the existing from-byte-array APIs would be. We ended up adding from-Response APIs, not from-URL APIs, and to me those seem closer to from-byte-array APIs:
const response = new Response(new Uint8Array(...));
WebAssembly.compileStreaming(response).then(...);
So probably any decision made should apply to all current APIs, not just the original byte-array-taking ones.
Approaches for reaching interop here are varied. One path may be for Chrome to continue diverge from the spec and gather data for a while. Alternately, this may be a topic worthy of discussion, either here or at a F2F meeting, to come to a consensus sooner.
Another approach might be waiting until ES module integration is ready, so that CSP-using sites have some way of running wasm code (via <script type="module" src="code.wasm">) even with unsafe-eval set, which I believe is not currently possible in Chrome. Maybe non-Chrome vendors would find it more palatable to disable WebAssembly.* APIs if <script src> tags were able to accomplish the same thing, similar to how unsafe-eval disables eval() but <script src> tags still work in that setting.
/cc @mikewest
We used unsafe-eval since the team didn't want to block on introducing a wasm specific CSP directive.
For reference: if unsafe-eval is set, can can script-created HTMLScriptElements be added and, if so, can they have a data or blob URLs?
/cc @mikewest to double-check my spec-reading
if unsafe-eval is set, can can script-created HTMLScriptElements be added
Sort of. They can be added, but if the element does not have a src="" attribute (i.e. is an inline script), it will not evaluate.
if so, can they have a data or blob URLs?
Script elements with src="" attributes are not governed by unsafe-eval. They are governed by a separate directive, script-src, which has to separately opt in to data: and blob:.
Forgive the naive questions:
-
How does the proposed approach interact with
postMessageofWebAssembly.Module? -
Can you clarify what specifically is proposed by "restriction that prevents WebAssembly.* APIs from being used"?
- Do the APIs simply not exits on the
WebAssemblyobject (or does theWebAssemblyobject not even exist)? or - Do the APIs simply auto-throw (which APIs, and what do they throw)?
- Do the APIs simply not exits on the
if unsafe-eval is set, can can script-created HTMLScriptElements be added
Sort of. They can be added, but if the element does not have a src="" attribute (i.e. is an inline script), it will not evaluate.
A policy whose script-src directive includes unsafe-eval would allow eval() (and, apparently allow WebASM to compile response data). The rest of the directive's contents would determine whether or not a given <script> element would execute. For example, script-src https://example.com 'unsafe-eval' would allow <script src="https://example.com/script.js"></script>, but not <script>whatever();</script>. script-src https://example.com 'unsafe-inline' 'unsafe-eval' would allow both.
if so, can they have a data or blob URLs?
Script elements with src="" attributes are not governed by unsafe-eval. They are governed by a separate directive, script-src, which has to separately opt in to data: and blob:.
Right. If the policy enables data: or blob: (e.g. script-src data: 'unsafe-eval'), then yes. If not, then no.
Ah, thanks for the clarification. So 'unsafe-eval' is more literally referring to eval (and new Function) here and it's not a blanket catch-all for dynamically-generated code. Given the quite different security characteristics of evaluating strings vs. ArrayBuffers/Responses, it seems to me like the current wasm APIs should be controlled by a separate directive than 'unsafe-eval'.
Forgive the naive questions:
I'd best let others (in this case @jeisinger I think?) answer this, as I'm a little fuzzy on the details. I mostly just wanted to open this thread to highlight and track the non-interoperability here.
unsafe-eval also blocks setTimeout with a string as handler.
I still don't understand what is being proposed. I'm worried that developers use a pattern such as this to detect if wasm is available:
"object" == typeof WebAssembly && "function" == typeof WebAssembly.validate && "function" == typeof WebAssembly.compile
IIUC that'll be true, but will fail under CSP.
I think we should keep WebAssembly.instantiate but disallow WebAssembly.compile. If we don't do this, we won't be future proofed against instantiating a thing that was compiled using compileStreaming.
This will allow instantiation of Modules that are postMessaged from a worker if that worker was allowed to use WebAssembly.compile
I haven't seen a concrete proposal yet, so I went ahead and implemented something for the medium-term. I went conservative because it's easy to loosen later, and I don't see this as being a particular thing that people mix and expect to work anyways so the less tricky the better.
At a high level here's what I propose (and have implemented):
Disables:
WebAssembly.Instance/WebAssembly.instantiate/WebAssembly.instantiateStreamingWebAssembly.MemoryWebAssembly.Table
Leaves:
WebAssemblyon the global objectWebAssembly.validateWebAssembly.Module/WebAssembly.compile/WebAssembly.compileStreamingWebAssembly.CompileErrorWebAssembly.LinkError
That way it won't be possible to call WebAssembly-compiled code, or create memories (which use fancy 4GiB allocations sometimes). Table isn't really useful on its own, and eventually we may make them shareable so without more details it seems benign to disable them (and useless if we don't).
I haven't done anything with postMessage, so you can still postMessage a WebAssembly.Module cross-CSP, but you can't instantiate it so it's useless. Because of this I elected to leave WebAssembly.Module and friends available.
I haven't added any new directives. It's still unsafe-eval. We can add something else later, but it seems odd to add a new capability and tell developers "you should have been using this directive which we just implemented". So IMO we should keep unsafe-eval as it currently is, add WebAssembly to what it does, and later consider having two new directives which do each individually or something.
In all cases I throw something like:
EvalError: Refused to create a WebAssembly object because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'unsafe-inline'".
Note that I'll throw CSP errors before other types of WebAssembly errors. I haven't written tests for exception-order, but we should specify it.
Concretely, here's what a test looks like:
<script>
const empty = Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x1, 0x00, 0x00, 0x00);
</script>
</head>
<body>
<!-- The WebAssembly global object and some of its members aren't blocked. -->
<script>if (typeof WebAssembly !== "object") throw new Error(`Expected WebAssembly object to be accessible under CSP`)</script>
<script>new WebAssembly.CompileError(`This is OK`)</script>
<script>new WebAssembly.LinkError(`This is OK`)</script>
<script>new WebAssembly.Module(empty)</script>
<script>if (!WebAssembly.validate(empty)) throw new Error(`Expected validation to succeed`)</script>
<!-- The following APIs aren't accessible under CSP. -->
<script>new WebAssembly.Memory({ initial: 1 })</script>
<script>new WebAssembly.Memory({ initial: 1, maximum: 1 })</script>
<script>new WebAssembly.Memory({ initial: 1, maximum: 1, shared: true })</script>
<script>new WebAssembly.Table({ element: "anyfunc", initial: 1 })</script>
<script>new WebAssembly.Table({ element: "anyfunc", initial: 1, maximum: 1 })</script>
<script>new WebAssembly.Instance(new WebAssembly.Module(empty))</script>
Thoughts? If not I'll send a PR with this.
Assuming that the point of unsafe-eval is to defend against an XSS attack where user content is mixed into code:
- It seems like the cut point would be any function/overload that takes an
ArrayBufferor aResponseconstructed from anArrayBufferor stream. If I pull aModulefrom IDB or compile aModulefrom some appropriate URL, both would seem to avoid the XSS attack. - Disabling
Memory/Tableseems odd (unless the entireWebAssembly.*namespace is disabled)
I haven't added any new directives. It's still unsafe-eval. We can add something else later, but it seems odd to add a new capability and tell developers "you should have been using this directive which we just implemented".
If unsafe-eval was a blanket "no dynamic code" directive, perhaps, but because it's already targeted to JS-isms for loading strings of code and because the risk profile of mixing user content into strings is quite different than for binary, I don't think it's odd to have a separate directive. Since unsafe-eval is already a popular directive to set, extending it to wasm could end up preventing popular frameworks/libraries from doing cool stuff with wasm like using streams+compileStreaming for layer 1 compression or patching-based polyfills, to name a few of the things we've discussed in the past.
I'm happy to implement something else if you write a proposal and we agree on it.
I don't think it's odd to have a separate directive.
I don't either. I simply think existing directives should also disable WebAssembly somehow.
It's definitely best that we align CSP policies for WASM across browsers and develop some shared tests to enforce some standardization. The right place for these tests is probably in the webplatform tests (https://github.com/w3c/web-platform-tests)
Looks like there is already a content-security-policy subdirectory. @mikewest, does that seem like a good long-term home for WebAssembly-specific policies?
I generally agree with @lukewagner that a separate directive that basically disables "from-raw-bytes" module instantiation makes sense. That I think accomplishes the integrity aspect of CSP, i.e. to protect against corruption of WASM modules by any malicious user code.
Not sure that disabling Memory or Table accomplishes anything; that only restricts JS's ability to create these objects itself; malicious user JS could still get access to exported memories or tables as soon as it got access to an instance, and then generally cause havoc.
I'm looking forward to a concrete proposal.
I think that if in the CSP header you have something like script-src 'sha256-076c8.....71db5ad5a2743545f' the browser should not disable compilation and instantiation of the byte array whose sha256 is the one indicated. In this way Web browsers supporting WebAssembly MVP functionality would work correctly with CSP.
@alpertron Are you suggesting that the WebAssembly engine should hash all WASM module compilations can compare them against hashes put into script tags (at least if CSP is enabled)? At first glance that sounds interesting, but there are a couple of drawbacks:
- It lengthens the critical path of WASM module compilation to now include a hashing step
- It potentially duplicates the hash computation, which should be done in the network layer (for modules that are fetched with XHR and become Response objects).
Hashing only has to be done if there is an explicit hash in the CSP header or meta tag, as it is done actually on Javascript.
Maybe a benchmark is needed in order to establish whether the hash adds significantly more time to start the execution of the WebAssembly module.
According to https://www.cryptopp.com/benchmarks.html the library Crypto++ can hash sha256 at 233 MB/sec and sha512 at 343 MB/sec in a modern processor, so I believe that hashing is not a bottleneck. Most time is lost downloading the resource from the Web server.
I think you might be referring to sub-resource integrity which would also be valuable for WASM to integrate with: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
Sub-resource integrity is intended for using resources in other servers to check that they were not changed.
In this thread what I am talking about is similar as using Javascript in the HTML page where a hacker can start running code using XSS attacks. So the code needs to be hashed so no random code can be executed.
As discussed in the latest video call, I filed tracking issue #1122 and assigned @flagxor to it.
Link to MDN Web docs about script-src used on CSP header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src Scroll down to < hash-source > in section named Sources.