LibWeb/CSP: Implement the sandbox directive

This commit is contained in:
Luke Wilde 2024-12-06 16:01:02 +00:00 committed by Alexander Kalenik
parent 40bb50ac60
commit 1d57df6e26
8 changed files with 126 additions and 5 deletions

View File

@ -62,6 +62,7 @@ set(SOURCES
ContentSecurityPolicy/Directives/ObjectSourceDirective.cpp
ContentSecurityPolicy/Directives/ReportToDirective.cpp
ContentSecurityPolicy/Directives/ReportUriDirective.cpp
ContentSecurityPolicy/Directives/SandboxDirective.cpp
ContentSecurityPolicy/Directives/ScriptSourceAttributeDirective.cpp
ContentSecurityPolicy/Directives/ScriptSourceDirective.cpp
ContentSecurityPolicy/Directives/ScriptSourceElementDirective.cpp

View File

@ -22,6 +22,7 @@
#include <LibWeb/ContentSecurityPolicy/Directives/ObjectSourceDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/ReportToDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/ReportUriDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/SandboxDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/ScriptSourceAttributeDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/ScriptSourceDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/ScriptSourceElementDirective.h>
@ -77,6 +78,9 @@ GC::Ref<Directive> create_directive(GC::Heap& heap, String name, Vector<String>
if (name == Names::ReportUri)
return heap.allocate<ReportUriDirective>(move(name), move(value));
if (name == Names::Sandbox)
return heap.allocate<SandboxDirective>(move(name), move(value));
if (name == Names::ScriptSrcAttr)
return heap.allocate<ScriptSourceAttributeDirective>(move(name), move(value));

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2024, Luke Wilde <luke@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/ContentSecurityPolicy/Directives/SandboxDirective.h>
#include <LibWeb/ContentSecurityPolicy/Policy.h>
#include <LibWeb/HTML/SandboxingFlagSet.h>
namespace Web::ContentSecurityPolicy::Directives {
GC_DEFINE_ALLOCATOR(SandboxDirective);
SandboxDirective::SandboxDirective(String name, Vector<String> value)
: Directive(move(name), move(value))
{
}
// https://w3c.github.io/webappsec-csp/#sandbox-init
Directive::Result SandboxDirective::initialization(Variant<GC::Ref<DOM::Document const>, GC::Ref<HTML::WorkerGlobalScope const>> context, GC::Ref<Policy const> policy) const
{
// 1. If policys disposition is not "enforce", or context is not a WorkerGlobalScope, then abort this algorithm.
// FIXME: File spec issue that this step doesn't specify the return value. It must be allowed, because Document
// asserts that the result of this algorithm is Allowed.
if (policy->disposition() != Policy::Disposition::Enforce || !context.has<GC::Ref<HTML::WorkerGlobalScope const>>())
return Result::Allowed;
// 2. Let sandboxing flag set be a new sandboxing flag set.
// 3. Parse a sandboxing directive using this directives value as the input, and sandboxing flag set as the output.
// FIXME: File spec issue that "parse a sandboxing directive" does not accept a set of tokens.
auto sandboxing_flag_set = HTML::parse_a_sandboxing_directive(value());
// 4. If sandboxing flag set contains either the sandboxed scripts browsing context flag or the sandboxed origin
// browsing context flag flags, return "Blocked".
// Spec Note: This will need to change if we allow Workers to be sandboxed into unique origins, which seems like a
// pretty reasonable thing to do.
if (has_flag(sandboxing_flag_set, HTML::SandboxingFlagSet::SandboxedScripts) || has_flag(sandboxing_flag_set, HTML::SandboxingFlagSet::SandboxedOrigin))
return Result::Blocked;
// 5. Return "Allowed".
return Result::Allowed;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2024, Luke Wilde <luke@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/ContentSecurityPolicy/Directives/Directive.h>
namespace Web::ContentSecurityPolicy::Directives {
// https://w3c.github.io/webappsec-csp/#directive-sandbox
class SandboxDirective final : public Directive {
GC_CELL(SandboxDirective, Directive)
GC_DECLARE_ALLOCATOR(SandboxDirective);
public:
virtual ~SandboxDirective() = default;
[[nodiscard]] virtual Result initialization(Variant<GC::Ref<DOM::Document const>, GC::Ref<HTML::WorkerGlobalScope const>>, GC::Ref<Policy const>) const override;
private:
SandboxDirective(String name, Vector<String> value);
};
}

View File

@ -6,6 +6,7 @@
#include <LibGC/RootVector.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/ContentSecurityPolicy/Directives/Names.h>
#include <LibWeb/ContentSecurityPolicy/PolicyList.h>
#include <LibWeb/ContentSecurityPolicy/SerializedPolicy.h>
#include <LibWeb/DOM/Document.h>
@ -74,9 +75,38 @@ bool PolicyList::contains_header_delivered_policy() const
return !header_delivered_entry.is_end();
}
// https://html.spec.whatwg.org/multipage/browsers.html#csp-derived-sandboxing-flags
HTML::SandboxingFlagSet PolicyList::csp_derived_sandboxing_flags() const
{
return HTML::SandboxingFlagSet {};
// 1. Let directives be an empty ordered set.
// NOTE: Since the algorithm only uses the last entry, we instead use a pointer to the last entry.
GC::Ptr<Directives::Directive> sandbox_directive = nullptr;
// 2. For each policy in cspList:
for (auto const policy : m_policies) {
// 1. If policy's disposition is not "enforce", then continue.
if (policy->disposition() != Policy::Disposition::Enforce)
continue;
// 2. If policy's directive set contains a directive whose name is "sandbox", then append that directive to
// directives.
auto maybe_sandbox_directive = policy->directives().find_if([](auto const& directive) {
return directive->name() == Directives::Names::Sandbox;
});
if (!maybe_sandbox_directive.is_end())
sandbox_directive = *maybe_sandbox_directive;
}
// 3. If directives is empty, then return an empty sandboxing flag set.
if (!sandbox_directive)
return HTML::SandboxingFlagSet {};
// 4. Let directive be directives[directives's size 1].
// NOTE: Already done.
// 5. Return the result of parsing the sandboxing directive directive.
return HTML::parse_a_sandboxing_directive(sandbox_directive->value());
}
// https://w3c.github.io/webappsec-csp/#enforced

View File

@ -151,6 +151,7 @@ class MediaSourceDirective;
class ObjectSourceDirective;
class ReportToDirective;
class ReportUriDirective;
class SandboxDirective;
class ScriptSourceAttributeDirective;
class ScriptSourceDirective;
class ScriptSourceElementDirective;

View File

@ -10,11 +10,24 @@
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/browsers.html#parse-a-sandboxing-directive
SandboxingFlagSet parse_a_sandboxing_directive(String const& input)
SandboxingFlagSet parse_a_sandboxing_directive(Variant<String, Vector<String>> input)
{
// 1. Split input on ASCII whitespace, to obtain tokens.
auto lowercase_input = input.to_ascii_lowercase();
auto tokens = lowercase_input.bytes_as_string_view().split_view_if(Infra::is_ascii_whitespace);
Vector<String> tokens;
if (input.has<String>()) {
auto lowercase_input = input.get<String>().to_ascii_lowercase();
auto token_views = lowercase_input.bytes_as_string_view().split_view_if(Infra::is_ascii_whitespace);
tokens.ensure_capacity(token_views.size());
for (auto token : token_views) {
tokens.unchecked_append(MUST(String::from_utf8(token)));
}
} else {
auto const& pre_parsed_tokens = input.get<Vector<String>>();
tokens.ensure_capacity(pre_parsed_tokens.size());
for (auto const& token : pre_parsed_tokens) {
tokens.unchecked_append(token.to_ascii_lowercase());
}
}
// 2. Let output be empty.
SandboxingFlagSet output {};

View File

@ -36,6 +36,6 @@ enum class SandboxingFlagSet {
AK_ENUM_BITWISE_OPERATORS(SandboxingFlagSet);
inline bool is_empty(SandboxingFlagSet s) { return (to_underlying(s) & 0x1FFU) == 0; }
SandboxingFlagSet parse_a_sandboxing_directive(String const& input);
SandboxingFlagSet parse_a_sandboxing_directive(Variant<String, Vector<String>> input);
}