From 41f2d912d26b7155d61c921d4e53ee7d846522ed Mon Sep 17 00:00:00 2001 From: =?utf8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 4 May 2023 16:54:23 +0200 Subject: [PATCH] Allow "wasm-unsafe-eval" in CSP MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit If a page has a Content Security Policy header and the `script-src` (or `default-src`) directive does not contain neither `wasm-unsafe-eval` nor `unsafe-eval` loading and executing WebAssembly is blocked in the page (although it is still possible to load and execute WebAssembly in a worker thread). Although the Nextcloud classes to manage the CSP already supported allowing `unsafe-eval` this affects not only WebAssembly, but also the `eval` operation in JavaScript. To make possible to allow WebAssembly execution without allowing JavaScript `eval` this commit adds support for allowing `wasm-unsafe-eval`. Signed-off-by: Daniel Calviño Sánchez --- .../Security/CSP/ContentSecurityPolicy.php | 8 ++++++++ .../Http/ContentSecurityPolicy.php | 2 ++ .../Http/EmptyContentSecurityPolicy.php | 18 +++++++++++++++++- .../Http/StrictContentSecurityPolicy.php | 2 ++ .../Http/ContentSecurityPolicyTest.php | 7 +++++++ .../Http/EmptyContentSecurityPolicyTest.php | 7 +++++++ 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/private/Security/CSP/ContentSecurityPolicy.php b/lib/private/Security/CSP/ContentSecurityPolicy.php index 8d9551c8978..e2d115cf34e 100644 --- a/lib/private/Security/CSP/ContentSecurityPolicy.php +++ b/lib/private/Security/CSP/ContentSecurityPolicy.php @@ -64,6 +64,14 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy $this->evalScriptAllowed = $evalScriptAllowed; } + public function isEvalWasmAllowed(): ?bool { + return $this->evalWasmAllowed; + } + + public function setEvalWasmAllowed(bool $evalWasmAllowed): void { + $this->evalWasmAllowed = $evalWasmAllowed; + } + /** * @return array */ diff --git a/lib/public/AppFramework/Http/ContentSecurityPolicy.php b/lib/public/AppFramework/Http/ContentSecurityPolicy.php index 0e3a6a705d5..f17dd9bd270 100644 --- a/lib/public/AppFramework/Http/ContentSecurityPolicy.php +++ b/lib/public/AppFramework/Http/ContentSecurityPolicy.php @@ -44,6 +44,8 @@ class ContentSecurityPolicy extends EmptyContentSecurityPolicy { protected $inlineScriptAllowed = false; /** @var bool Whether eval in JS scripts is allowed */ protected $evalScriptAllowed = false; + /** @var bool Whether WebAssembly compilation is allowed */ + protected ?bool $evalWasmAllowed = false; /** @var bool Whether strict-dynamic should be set */ protected $strictDynamicAllowed = false; /** @var array Domains from which scripts can get loaded */ diff --git a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php index 035b4f01f60..7e1de2ef2eb 100644 --- a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php +++ b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php @@ -47,6 +47,8 @@ class EmptyContentSecurityPolicy { * @link https://github.com/owncloud/core/issues/11925 */ protected $evalScriptAllowed = null; + /** @var bool Whether WebAssembly compilation is allowed */ + protected ?bool $evalWasmAllowed = null; /** @var array Domains from which scripts can get loaded */ protected $allowedScriptDomains = null; /** @@ -116,6 +118,17 @@ class EmptyContentSecurityPolicy { return $this; } + /** + * Whether WebAssembly compilation is allowed or forbidden + * @param bool $state + * @return $this + * @since 28.0.0 + */ + public function allowEvalWasm(bool $state = true) { + $this->evalWasmAllowed = $state; + return $this; + } + /** * Allows to execute JavaScript files from a specific domain. Use * to * allow JavaScript from all domains. @@ -433,7 +446,7 @@ class EmptyContentSecurityPolicy { $policy .= "base-uri 'none';"; $policy .= "manifest-src 'self';"; - if (!empty($this->allowedScriptDomains) || $this->evalScriptAllowed) { + if (!empty($this->allowedScriptDomains) || $this->evalScriptAllowed || $this->evalWasmAllowed) { $policy .= 'script-src '; if (is_string($this->useJsNonce)) { if ($this->strictDynamicAllowed) { @@ -453,6 +466,9 @@ class EmptyContentSecurityPolicy { if ($this->evalScriptAllowed) { $policy .= ' \'unsafe-eval\''; } + if ($this->evalWasmAllowed) { + $policy .= ' \'wasm-unsafe-eval\''; + } $policy .= ';'; } diff --git a/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php b/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php index ed137bad930..96c03673d5c 100644 --- a/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php +++ b/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php @@ -46,6 +46,8 @@ class StrictContentSecurityPolicy extends EmptyContentSecurityPolicy { protected $inlineScriptAllowed = false; /** @var bool Whether eval in JS scripts is allowed */ protected $evalScriptAllowed = false; + /** @var bool Whether WebAssembly compilation is allowed */ + protected ?bool $evalWasmAllowed = false; /** @var array Domains from which scripts can get loaded */ protected $allowedScriptDomains = [ '\'self\'', diff --git a/tests/lib/AppFramework/Http/ContentSecurityPolicyTest.php b/tests/lib/AppFramework/Http/ContentSecurityPolicyTest.php index 53632da93d1..8e6ac32b416 100644 --- a/tests/lib/AppFramework/Http/ContentSecurityPolicyTest.php +++ b/tests/lib/AppFramework/Http/ContentSecurityPolicyTest.php @@ -456,6 +456,13 @@ class ContentSecurityPolicyTest extends \Test\TestCase { $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } + public function testGetPolicyUnsafeWasmEval() { + $expectedPolicy = "default-src 'none';base-uri 'none';manifest-src 'self';script-src 'self' 'wasm-unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' data:;connect-src 'self';media-src 'self';frame-ancestors 'self';form-action 'self'"; + + $this->contentSecurityPolicy->allowEvalWasm(true); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + public function testGetPolicyNonce() { $nonce = 'my-nonce'; $expectedPolicy = "default-src 'none';base-uri 'none';manifest-src 'self';script-src 'nonce-".base64_encode($nonce) . "';style-src 'self' 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' data:;connect-src 'self';media-src 'self';frame-ancestors 'self';form-action 'self'"; diff --git a/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php b/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php index dc10d095ab5..328e464f981 100644 --- a/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php +++ b/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php @@ -75,6 +75,13 @@ class EmptyContentSecurityPolicyTest extends \Test\TestCase { $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } + public function testGetPolicyScriptAllowWasmEval() { + $expectedPolicy = "default-src 'none';base-uri 'none';manifest-src 'self';script-src 'wasm-unsafe-eval';frame-ancestors 'none'"; + + $this->contentSecurityPolicy->allowEvalWasm(true); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + public function testGetPolicyStyleDomainValid() { $expectedPolicy = "default-src 'none';base-uri 'none';manifest-src 'self';style-src www.owncloud.com;frame-ancestors 'none'"; -- 2.39.5