From 1228063a85c97989baecfefe97fd9cd2aa69a832 Mon Sep 17 00:00:00 2001 From: Kenneth Myhra Date: Thu, 7 Aug 2025 22:41:54 +0200 Subject: [PATCH] LibWeb: Enforce Integrity Policy on Fetch requests --- .../BlockingAlgorithms.cpp | 64 +++++++++++++++++++ .../BlockingAlgorithms.h | 1 + Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 8 ++- .../Fetch/Infrastructure/HTTP/Requests.h | 1 - Libraries/LibWeb/HTML/PolicyContainers.h | 18 ++++++ 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp index ed6163a54dd..d8c510b9294 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp +++ b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2025, Luke Wilde + * Copyright (c) 2025, Kenneth Myhra * * SPDX-License-Identifier: BSD-2-Clause */ @@ -14,8 +15,11 @@ #include #include #include +#include #include +#include #include +#include #include namespace Web::ContentSecurityPolicy { @@ -131,6 +135,66 @@ Directives::Directive::Result should_request_be_blocked_by_content_security_poli return result; } +// https://w3c.github.io/webappsec-subresource-integrity/#should-request-be-blocked-by-integrity-policy +Directives::Directive::Result should_request_be_blocked_by_integrity_policy(GC::Ref request) +{ + VERIFY(request->policy_container().has>()); + + // 1. Let policyContainer be request’s policy container. + auto const& policy_container = request->policy_container().get>(); + + // 2. Let parsedMetadata be the result of calling parse metadata with request’s integrity metadata. + auto parsed_metadata = MUST(SRI::parse_metadata(request->integrity_metadata())); + + // 3. If parsedMetadata is not the empty set and request’s mode is either "cors" or "same-origin", return "Allowed". + if (!parsed_metadata.is_empty() && (request->mode() == Fetch::Infrastructure::Request::Mode::CORS || request->mode() == Fetch::Infrastructure::Request::Mode::SameOrigin)) + return Directives::Directive::Result::Allowed; + + // 4. If request’s url is local, return "Allowed". + if (Fetch::Infrastructure::is_local_url(request->url())) + return Directives::Directive::Result::Allowed; + + // 5. Let policy be policyContainer’s integrity policy. + auto const& policy = policy_container->integrity_policy; + + // 6. Let reportPolicy be policyContainer’s report only integrity policy. + auto const& report_policy = policy_container->report_only_integrity_policy; + + // 7. If both policy and reportPolicy are empty integrity policys, return "Allowed". + if (policy.is_empty() && report_policy.is_empty()) + return Directives::Directive::Result::Allowed; + + // 8. Let global be request’s client’s global object. + auto& global = request->client()->global_object(); + + // 9. If global is not a Window nor a WorkerGlobalScope, return "Allowed". + if (!is(global) && !is(global)) + return Directives::Directive::Result::Allowed; + + // 10. Let block be a boolean, initially false. + bool block = false; + + // FIXME: 11. Let reportBlock be a boolean, initially false. + [[maybe_unused]] auto report_block = false; + + // 12. If policy’s sources contains "inline" and policy’s blocked destinations contains request’s destination, set block to true. + if (policy.sources.contains_slow("inline"sv) + && request->destination().has_value() + && policy.blocked_destinations.contains_slow(request->destination().value())) + block = true; + + // 13. If reportPolicy’s sources contains "inline" and reportPolicy’s blocked destinations contains request’s destination, set reportBlock to true. + if (report_policy.sources.contains_slow("inline"sv) + && request->destination().has_value() + && report_policy.blocked_destinations.contains_slow(request->destination().value())) + report_block = true; + + // FIXME: 14. If block is true or reportBlock is true, then report violation with request, block, reportBlock, policy and reportPolicy. + + // 15. If block is true, then return "Blocked"; otherwise "Allowed". + return block ? Directives::Directive::Result::Blocked : Directives::Directive::Result::Allowed; +} + // https://w3c.github.io/webappsec-csp/#should-block-response Directives::Directive::Result should_response_to_request_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref response, GC::Ref request) { diff --git a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h index 716f4c63824..139255f5917 100644 --- a/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h +++ b/Libraries/LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h @@ -14,6 +14,7 @@ namespace Web::ContentSecurityPolicy { void report_content_security_policy_violations_for_request(JS::Realm&, GC::Ref); Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref); +Directives::Directive::Result should_request_be_blocked_by_integrity_policy(GC::Ref); Directives::Directive::Result should_response_to_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref, GC::Ref); Directives::Directive::Result should_navigation_request_of_type_be_blocked_by_content_security_policy(GC::Ref navigation_request, Directives::Directive::NavigationType navigation_type); diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index 20f1811f092..20821aa540a 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -319,11 +319,13 @@ WebIDL::ExceptionOr> main_fetch(JS::Realm& realm, Infra // 6. Upgrade a mixed content request to a potentially trustworthy URL, if appropriate. MixedContent::upgrade_a_mixed_content_request_to_a_potentially_trustworthy_url_if_appropriate(request); - // 7. If should request be blocked due to a bad port, should fetching request be blocked as mixed content, or - // should request be blocked by Content Security Policy returns blocked, then set response to a network error. + // 7. If should request be blocked due to a bad port, should fetching request be blocked as mixed content, should + // request be blocked by Content Security Policy, or should request be blocked by Integrity Policy Policy + // returns blocked, then set response to a network error. if (Infrastructure::block_bad_port(request) == Infrastructure::RequestOrResponseBlocking::Blocked || MixedContent::should_fetching_request_be_blocked_as_mixed_content(request) == Infrastructure::RequestOrResponseBlocking::Blocked - || ContentSecurityPolicy::should_request_be_blocked_by_content_security_policy(realm, request) == ContentSecurityPolicy::Directives::Directive::Result::Blocked) { + || ContentSecurityPolicy::should_request_be_blocked_by_content_security_policy(realm, request) == ContentSecurityPolicy::Directives::Directive::Result::Blocked + || ContentSecurityPolicy::should_request_be_blocked_by_integrity_policy(request) == ContentSecurityPolicy::Directives::Directive::Result::Blocked) { response = Infrastructure::Response::network_error(vm, "Request was blocked"_string); } diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.h b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.h index 21806761287..0a83a4d1410 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.h +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Requests.h @@ -22,7 +22,6 @@ #include #include #include -#include namespace Web::Fetch::Infrastructure { diff --git a/Libraries/LibWeb/HTML/PolicyContainers.h b/Libraries/LibWeb/HTML/PolicyContainers.h index 5ee29b66083..23ab6c9d885 100644 --- a/Libraries/LibWeb/HTML/PolicyContainers.h +++ b/Libraries/LibWeb/HTML/PolicyContainers.h @@ -10,12 +10,22 @@ #include #include #include +#include #include #include #include namespace Web::HTML { +// https://w3c.github.io/webappsec-subresource-integrity/#integrity-policy +struct IntegrityPolicy { + Vector sources; + Vector blocked_destinations; + Vector endpoints; + + bool is_empty() const { return sources.is_empty() && blocked_destinations.is_empty() && endpoints.is_empty(); } +}; + // https://html.spec.whatwg.org/multipage/origin.html#policy-container // A policy container is a struct containing policies that apply to a Document, a WorkerGlobalScope, or a WorkletGlobalScope. It has the following items: struct PolicyContainer : public GC::Cell { @@ -37,6 +47,14 @@ public: // A referrer policy, which is a referrer policy. It is initially the default referrer policy. ReferrerPolicy::ReferrerPolicy referrer_policy { ReferrerPolicy::DEFAULT_REFERRER_POLICY }; + // https://html.spec.whatwg.org/multipage/browsers.html#policy-container-integrity-policy + // An integrity policy, which is an integrity policy, initially a new integrity policy. + IntegrityPolicy integrity_policy {}; + + // https://html.spec.whatwg.org/multipage/browsers.html#policy-container-report-only-integrity-policy + // A report only integrity policy, which is an integrity policy, initially a new integrity policy. + IntegrityPolicy report_only_integrity_policy {}; + [[nodiscard]] GC::Ref clone(GC::Heap&) const; [[nodiscard]] SerializedPolicyContainer serialize() const;