From 8df173e1bd22efff8f04b33450101b1a950a06f5 Mon Sep 17 00:00:00 2001 From: Tete17 Date: Tue, 5 Aug 2025 14:36:11 +0200 Subject: [PATCH] LibWeb: Add require-trusted-types-for Directive This is meant to configure the behaviour of an injection sinks when a string is passed. --- Libraries/LibWeb/CMakeLists.txt | 1 + .../Directives/Directive.h | 2 +- .../Directives/DirectiveFactory.cpp | 4 + .../Directives/FormActionDirective.cpp | 2 +- .../Directives/FormActionDirective.h | 2 +- .../ContentSecurityPolicy/Directives/Names.h | 49 ++++++------ Libraries/LibWeb/TrustedTypes/InjectionSink.h | 1 + .../RequireTrustedTypesForDirective.cpp | 76 +++++++++++++++++++ .../RequireTrustedTypesForDirective.h | 27 +++++++ .../LibWeb/TrustedTypes/TrustedTypePolicy.cpp | 71 +++++++++++++++++ .../LibWeb/TrustedTypes/TrustedTypePolicy.h | 14 +++- 11 files changed, 218 insertions(+), 31 deletions(-) create mode 100644 Libraries/LibWeb/TrustedTypes/RequireTrustedTypesForDirective.cpp create mode 100644 Libraries/LibWeb/TrustedTypes/RequireTrustedTypesForDirective.h diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index b0a12497724..1e407022831 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -922,6 +922,7 @@ set(SOURCES SVG/SVGViewElement.cpp SVG/TagNames.cpp TrustedTypes/InjectionSink.cpp + TrustedTypes/RequireTrustedTypesForDirective.cpp TrustedTypes/TrustedHTML.cpp TrustedTypes/TrustedScript.cpp TrustedTypes/TrustedScriptURL.cpp diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Directives/Directive.h b/Libraries/LibWeb/ContentSecurityPolicy/Directives/Directive.h index 71e49a0293e..712aa19e6cc 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/Directives/Directive.h +++ b/Libraries/LibWeb/ContentSecurityPolicy/Directives/Directive.h @@ -77,7 +77,7 @@ public: // 5. A pre-navigation check, which takes a request, a navigation type string ("form-submission" or "other") // and a policy as arguments, and is executed during § 4.2.4 Should navigation request of type be blocked by // Content Security Policy?. It returns "Allowed" unless otherwise specified. - virtual Result pre_navigation_check(GC::Ref, NavigationType, GC::Ref) const { return Result::Allowed; } + virtual Result pre_navigation_check(GC::Ref, NavigationType, GC::Ref) const { return Result::Allowed; } // https://w3c.github.io/webappsec-csp/#directive-navigation-response-check // 6. A navigation response check, which takes a request, a navigation type string ("form-submission" or "other"), diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveFactory.cpp b/Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveFactory.cpp index 0be27e439a2..193b60498b0 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveFactory.cpp +++ b/Libraries/LibWeb/ContentSecurityPolicy/Directives/DirectiveFactory.cpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace Web::ContentSecurityPolicy::Directives { @@ -78,6 +79,9 @@ GC::Ref create_directive(GC::Heap& heap, String name, Vector if (name == Names::ReportUri) return heap.allocate(move(name), move(value)); + if (name == Names::RequireTrustedTypesFor) + return heap.allocate(move(name), move(value)); + if (name == Names::Sandbox) return heap.allocate(move(name), move(value)); diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Directives/FormActionDirective.cpp b/Libraries/LibWeb/ContentSecurityPolicy/Directives/FormActionDirective.cpp index 9180065e3d9..c6dbfaca09d 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/Directives/FormActionDirective.cpp +++ b/Libraries/LibWeb/ContentSecurityPolicy/Directives/FormActionDirective.cpp @@ -18,7 +18,7 @@ FormActionDirective::FormActionDirective(String name, Vector value) { } -Directive::Result FormActionDirective::pre_navigation_check(GC::Ref request, NavigationType navigation_type, GC::Ref policy) const +Directive::Result FormActionDirective::pre_navigation_check(GC::Ref request, NavigationType navigation_type, GC::Ref policy) const { // 1. Assert: policy is unused in this algorithm. // FIXME: File spec issue, because this is not the case. The policy is required to resolve 'self'. diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Directives/FormActionDirective.h b/Libraries/LibWeb/ContentSecurityPolicy/Directives/FormActionDirective.h index 44eec17a3e8..897b8d35120 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/Directives/FormActionDirective.h +++ b/Libraries/LibWeb/ContentSecurityPolicy/Directives/FormActionDirective.h @@ -18,7 +18,7 @@ class FormActionDirective final : public Directive { public: virtual ~FormActionDirective() = default; - virtual Result pre_navigation_check(GC::Ref, NavigationType, GC::Ref) const override; + virtual Result pre_navigation_check(GC::Ref, NavigationType, GC::Ref) const override; private: FormActionDirective(String name, Vector value); diff --git a/Libraries/LibWeb/ContentSecurityPolicy/Directives/Names.h b/Libraries/LibWeb/ContentSecurityPolicy/Directives/Names.h index 4709d88cb5d..d1a77e5f048 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/Directives/Names.h +++ b/Libraries/LibWeb/ContentSecurityPolicy/Directives/Names.h @@ -10,30 +10,31 @@ namespace Web::ContentSecurityPolicy::Directives::Names { -#define ENUMERATE_DIRECTIVE_NAMES \ - __ENUMERATE_DIRECTIVE_NAME(BaseUri, "base-uri") \ - __ENUMERATE_DIRECTIVE_NAME(ChildSrc, "child-src") \ - __ENUMERATE_DIRECTIVE_NAME(ConnectSrc, "connect-src") \ - __ENUMERATE_DIRECTIVE_NAME(DefaultSrc, "default-src") \ - __ENUMERATE_DIRECTIVE_NAME(FontSrc, "font-src") \ - __ENUMERATE_DIRECTIVE_NAME(FormAction, "form-action") \ - __ENUMERATE_DIRECTIVE_NAME(FrameAncestors, "frame-ancestors") \ - __ENUMERATE_DIRECTIVE_NAME(FrameSrc, "frame-src") \ - __ENUMERATE_DIRECTIVE_NAME(ImgSrc, "img-src") \ - __ENUMERATE_DIRECTIVE_NAME(ManifestSrc, "manifest-src") \ - __ENUMERATE_DIRECTIVE_NAME(MediaSrc, "media-src") \ - __ENUMERATE_DIRECTIVE_NAME(ObjectSrc, "object-src") \ - __ENUMERATE_DIRECTIVE_NAME(ReportTo, "report-to") \ - __ENUMERATE_DIRECTIVE_NAME(ReportUri, "report-uri") \ - __ENUMERATE_DIRECTIVE_NAME(Sandbox, "sandbox") \ - __ENUMERATE_DIRECTIVE_NAME(ScriptSrc, "script-src") \ - __ENUMERATE_DIRECTIVE_NAME(ScriptSrcElem, "script-src-elem") \ - __ENUMERATE_DIRECTIVE_NAME(ScriptSrcAttr, "script-src-attr") \ - __ENUMERATE_DIRECTIVE_NAME(StyleSrc, "style-src") \ - __ENUMERATE_DIRECTIVE_NAME(StyleSrcElem, "style-src-elem") \ - __ENUMERATE_DIRECTIVE_NAME(StyleSrcAttr, "style-src-attr") \ - __ENUMERATE_DIRECTIVE_NAME(TrustedTypes, "trusted-types") \ - __ENUMERATE_DIRECTIVE_NAME(WebRTC, "webrtc") \ +#define ENUMERATE_DIRECTIVE_NAMES \ + __ENUMERATE_DIRECTIVE_NAME(BaseUri, "base-uri") \ + __ENUMERATE_DIRECTIVE_NAME(ChildSrc, "child-src") \ + __ENUMERATE_DIRECTIVE_NAME(ConnectSrc, "connect-src") \ + __ENUMERATE_DIRECTIVE_NAME(DefaultSrc, "default-src") \ + __ENUMERATE_DIRECTIVE_NAME(FontSrc, "font-src") \ + __ENUMERATE_DIRECTIVE_NAME(FormAction, "form-action") \ + __ENUMERATE_DIRECTIVE_NAME(FrameAncestors, "frame-ancestors") \ + __ENUMERATE_DIRECTIVE_NAME(FrameSrc, "frame-src") \ + __ENUMERATE_DIRECTIVE_NAME(ImgSrc, "img-src") \ + __ENUMERATE_DIRECTIVE_NAME(ManifestSrc, "manifest-src") \ + __ENUMERATE_DIRECTIVE_NAME(MediaSrc, "media-src") \ + __ENUMERATE_DIRECTIVE_NAME(ObjectSrc, "object-src") \ + __ENUMERATE_DIRECTIVE_NAME(ReportTo, "report-to") \ + __ENUMERATE_DIRECTIVE_NAME(ReportUri, "report-uri") \ + __ENUMERATE_DIRECTIVE_NAME(RequireTrustedTypesFor, "require-trusted-types-for") \ + __ENUMERATE_DIRECTIVE_NAME(Sandbox, "sandbox") \ + __ENUMERATE_DIRECTIVE_NAME(ScriptSrc, "script-src") \ + __ENUMERATE_DIRECTIVE_NAME(ScriptSrcElem, "script-src-elem") \ + __ENUMERATE_DIRECTIVE_NAME(ScriptSrcAttr, "script-src-attr") \ + __ENUMERATE_DIRECTIVE_NAME(StyleSrc, "style-src") \ + __ENUMERATE_DIRECTIVE_NAME(StyleSrcElem, "style-src-elem") \ + __ENUMERATE_DIRECTIVE_NAME(StyleSrcAttr, "style-src-attr") \ + __ENUMERATE_DIRECTIVE_NAME(TrustedTypes, "trusted-types") \ + __ENUMERATE_DIRECTIVE_NAME(WebRTC, "webrtc") \ __ENUMERATE_DIRECTIVE_NAME(WorkerSrc, "worker-src") #define __ENUMERATE_DIRECTIVE_NAME(name, value) extern FlyString name; diff --git a/Libraries/LibWeb/TrustedTypes/InjectionSink.h b/Libraries/LibWeb/TrustedTypes/InjectionSink.h index 35f8127cfbe..1246bd9b7eb 100644 --- a/Libraries/LibWeb/TrustedTypes/InjectionSink.h +++ b/Libraries/LibWeb/TrustedTypes/InjectionSink.h @@ -22,6 +22,7 @@ namespace Web::TrustedTypes { __ENUMERATE_INJECTION_SINKS(Function, "Function") \ __ENUMERATE_INJECTION_SINKS(HTMLIFrameElementsrcdoc, "HTMLIFrameElement srcdoc") \ __ENUMERATE_INJECTION_SINKS(HTMLScriptElementsrc, "HTMLScriptElement src") \ + __ENUMERATE_INJECTION_SINKS(Locationhref, "Location href") \ __ENUMERATE_INJECTION_SINKS(SVGScriptElementhref, "SVGScriptElement href") \ ENUMERATE_GLOBAL_EVENT_HANDLERS(EVENT_HANDLERS_INJECTION_SINKS) \ ENUMERATE_WINDOW_EVENT_HANDLERS(EVENT_HANDLERS_INJECTION_SINKS) diff --git a/Libraries/LibWeb/TrustedTypes/RequireTrustedTypesForDirective.cpp b/Libraries/LibWeb/TrustedTypes/RequireTrustedTypesForDirective.cpp new file mode 100644 index 00000000000..4deb5ec4307 --- /dev/null +++ b/Libraries/LibWeb/TrustedTypes/RequireTrustedTypesForDirective.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025, Miguel Sacristán Izcue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include +#include + +namespace Web::TrustedTypes { + +GC_DEFINE_ALLOCATOR(RequireTrustedTypesForDirective); + +RequireTrustedTypesForDirective::RequireTrustedTypesForDirective(String name, Vector value) + : Directive(move(name), move(value)) +{ +} + +// https://www.w3.org/TR/trusted-types/#require-trusted-types-for-pre-navigation-check +ContentSecurityPolicy::Directives::Directive::Result RequireTrustedTypesForDirective::pre_navigation_check(GC::Ref request, NavigationType, GC::Ref) const +{ + // 1. If request’s url’s scheme is not "javascript", return "Allowed" and abort further steps. + if (request->url().scheme() != "javascript"sv) + return Result::Allowed; + + // 2. Let urlString be the result of running the URL serializer on request’s url. + auto url_string = request->url().serialize(); + + // 3. Let encodedScriptSource be the result of removing the leading "javascript:" from urlString. + auto const encoded_script_source = MUST(url_string.substring_from_byte_offset("javascript:"sv.length())); + + // 4. Let convertedScriptSource be the result of executing Process value with a default policy algorithm, with the following arguments: + // expectedType: + // TrustedScript + // global: + // request’s clients’s global object: + // input: + // encodedScriptSource: + // sink: + // "Location href": + auto converted_script_source = process_value_with_a_default_policy( + TrustedTypeName::TrustedScript, + request->client()->global_object(), + Utf16String::from_utf8(encoded_script_source), + InjectionSink::Locationhref); + + // If that algorithm threw an error or convertedScriptSource is not a TrustedScript object, return "Blocked" and abort further steps. + if (converted_script_source.is_error() || !converted_script_source.value().has_value()) + return Result::Blocked; + + auto const* converted_script_source_value = converted_script_source.value().value().get_pointer>(); + + if (!converted_script_source_value) + return Result::Blocked; + + // 5. Set urlString to be the result of prepending "javascript:" to stringified convertedScriptSource. + url_string = MUST(String::formatted("javascript:{}", (*converted_script_source_value)->to_string())); + + // 6. Let newURL be the result of running the URL parser on urlString. If the parser returns a failure, return "Blocked" and abort further steps. + auto const new_url = DOMURL::parse(url_string); + if (!new_url.has_value()) + return Result::Blocked; + + // 7. Set request’s url to newURL. + request->set_url(new_url.value()); + + // 8. Return "Allowed". + return Result::Allowed; +} + +} diff --git a/Libraries/LibWeb/TrustedTypes/RequireTrustedTypesForDirective.h b/Libraries/LibWeb/TrustedTypes/RequireTrustedTypesForDirective.h new file mode 100644 index 00000000000..14a7a03e1ef --- /dev/null +++ b/Libraries/LibWeb/TrustedTypes/RequireTrustedTypesForDirective.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025, Miguel Sacristán Izcue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::TrustedTypes { + +// https://www.w3.org/TR/trusted-types/#require-trusted-types-for-csp-directive +class RequireTrustedTypesForDirective final : public ContentSecurityPolicy::Directives::Directive { + GC_CELL(RequireTrustedTypesForDirective, ContentSecurityPolicy::Directives::Directive) + GC_DECLARE_ALLOCATOR(RequireTrustedTypesForDirective); + +public: + ~RequireTrustedTypesForDirective() override = default; + + Result pre_navigation_check(GC::Ref, NavigationType, GC::Ref) const override; + +private: + RequireTrustedTypesForDirective(String name, Vector value); +}; + +} diff --git a/Libraries/LibWeb/TrustedTypes/TrustedTypePolicy.cpp b/Libraries/LibWeb/TrustedTypes/TrustedTypePolicy.cpp index 27302ca515d..d3841719e0a 100644 --- a/Libraries/LibWeb/TrustedTypes/TrustedTypePolicy.cpp +++ b/Libraries/LibWeb/TrustedTypes/TrustedTypePolicy.cpp @@ -8,10 +8,13 @@ #include #include +#include #include +#include #include #include #include +#include #include #include #include @@ -182,4 +185,72 @@ WebIDL::ExceptionOr TrustedTypePolicy::get_trusted_type_policy_value( return policy_value; } +// https://www.w3.org/TR/trusted-types/#process-value-with-a-default-policy-algorithm +WebIDL::ExceptionOr> process_value_with_a_default_policy(TrustedTypeName trusted_type_name, JS::Object& global, Variant, GC::Root, GC::Root, Utf16String> input, InjectionSink sink) +{ + auto& vm = global.vm(); + auto& realm = HTML::relevant_realm(global); + + // 1. Let defaultPolicy be the value of global’s trusted type policy factory’s default policy. + auto const& default_policy = as(global).trusted_types()->default_policy(); + + // This algorithm routes a value to be assigned to an injection sink through a default policy, should one exist. + // FIXME: Open an issue upstream. It is not immediately clear what to do if the default policy does not exist. + // Ref: https://github.com/w3c/trusted-types/issues/595 + if (!default_policy) + return Optional {}; + + // 2. Let policyValue be the result of executing Get Trusted Type policy value, with the following arguments: + // policy: + // defaultPolicy + // value: + // stringified input + // trustedTypeName: + // expectedType’s type name + // arguments: + // « trustedTypeName, sink » + // throwIfMissing: + // false + // 3. If the algorithm threw an error, rethrow the error and abort the following steps. + auto arguments = GC::RootVector(vm.heap()); + arguments.append(JS::PrimitiveString::create(vm, to_string(trusted_type_name))); + arguments.append(JS::PrimitiveString::create(vm, to_string(sink))); + auto policy_value = TRY(default_policy->get_trusted_type_policy_value( + trusted_type_name, + input.visit( + [](auto& value) { return value->to_string(); }, + [](Utf16String& value) { return value; }), + arguments, + ThrowIfCallbackMissing::No)); + + // 4. If policyValue is null or undefined, return policyValue. + if (policy_value.is_nullish()) + return Optional {}; + + // 5. Let dataString be the result of stringifying policyValue. + Utf16String data_string; + switch (trusted_type_name) { + case TrustedTypeName::TrustedHTML: + case TrustedTypeName::TrustedScript: + data_string = TRY(WebIDL::to_utf16_string(vm, policy_value)); + break; + case TrustedTypeName::TrustedScriptURL: + data_string = TRY(WebIDL::to_utf16_usv_string(vm, policy_value)); + break; + default: + VERIFY_NOT_REACHED(); + } + + // 6. Return a new instance of an interface with a type name trustedTypeName, with its associated data value set to dataString. + switch (trusted_type_name) { + case TrustedTypeName::TrustedHTML: + return realm.create(realm, move(data_string)); + case TrustedTypeName::TrustedScript: + return realm.create(realm, move(data_string)); + case TrustedTypeName::TrustedScriptURL: + return realm.create(realm, move(data_string)); + } + VERIFY_NOT_REACHED(); +} + } diff --git a/Libraries/LibWeb/TrustedTypes/TrustedTypePolicy.h b/Libraries/LibWeb/TrustedTypes/TrustedTypePolicy.h index cbe48cb46c1..105ad6ac918 100644 --- a/Libraries/LibWeb/TrustedTypes/TrustedTypePolicy.h +++ b/Libraries/LibWeb/TrustedTypes/TrustedTypePolicy.h @@ -9,13 +9,17 @@ #include #include #include +#include namespace Web::TrustedTypes { -using TrustedTypesVariants = WebIDL::ExceptionOr, GC::Root, - GC::Root>>; + GC::Root>; + +using TrustedTypesVariants = WebIDL::ExceptionOr; enum class TrustedTypeName { TrustedHTML, @@ -49,16 +53,18 @@ public: WebIDL::ExceptionOr> create_script(Utf16String const&, GC::RootVector const&); WebIDL::ExceptionOr> create_script_url(Utf16String const&, GC::RootVector const&); + WebIDL::ExceptionOr get_trusted_type_policy_value(TrustedTypeName, Utf16String const& value, GC::RootVector const& values, ThrowIfCallbackMissing throw_if_missing); + private: explicit TrustedTypePolicy(JS::Realm&, Utf16String const&, TrustedTypePolicyOptions const&); virtual void initialize(JS::Realm&) override; TrustedTypesVariants create_a_trusted_type(TrustedTypeName, Utf16String const&, GC::RootVector const& values); - WebIDL::ExceptionOr get_trusted_type_policy_value(TrustedTypeName, Utf16String const& value, GC::RootVector const& values, ThrowIfCallbackMissing throw_if_missing); - Utf16String const m_name; TrustedTypePolicyOptions const m_options; }; +WebIDL::ExceptionOr> process_value_with_a_default_policy(TrustedTypeName, JS::Object&, Variant, GC::Root, GC::Root, Utf16String>, InjectionSink); + }