From 85e4a2b5c7b4d6422d29d65e9948e61c5d2b00f9 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Sun, 13 Apr 2025 22:54:59 +0200 Subject: [PATCH] Update FetchTaskTarget to propagate CSP violations. (#36409) It also updates the FetchResponseListener to process CSP violations to ensure that iframe elements (amongst others) properly generate the CSP events. These iframe elements are used in the Trusted Types tests themselves and weren't propagating the violations before. However, the tests themselves are still not passing since they also use Websockets, which currently aren't using the fetch machinery itself. That is fixed as part of [1]. [1]: https://github.com/servo/servo/issues/35028 --------- Signed-off-by: Tim van der Lippe Signed-off-by: Josh Matthews Co-authored-by: Josh Matthews --- Cargo.lock | 3 +- Cargo.toml | 2 +- components/fonts/font_context.rs | 6 +-- components/net/fetch/methods.rs | 17 +++--- components/net/image_cache.rs | 3 +- components/net/tests/fetch.rs | 2 + components/net/tests/http_loader.rs | 2 + components/net/tests/main.rs | 2 + components/net/websocket_loader.rs | 37 ++++++++++--- .../script/dom/dedicatedworkerglobalscope.rs | 3 ++ components/script/dom/eventsource.rs | 7 +++ components/script/dom/globalscope.rs | 22 +++++++- components/script/dom/htmlimageelement.rs | 10 +++- components/script/dom/htmllinkelement.rs | 8 ++- components/script/dom/htmlmediaelement.rs | 10 +++- components/script/dom/htmlscriptelement.rs | 12 ++++- components/script/dom/htmlvideoelement.rs | 6 +++ components/script/dom/notification.rs | 7 +++ components/script/dom/servoparser/mod.rs | 10 ++++ components/script/dom/servoparser/prefetch.rs | 12 ++++- components/script/dom/websocket.rs | 21 ++++++++ components/script/dom/worker.rs | 1 + components/script/dom/xmlhttprequest.rs | 18 +++++-- components/script/fetch.rs | 15 ++++-- components/script/layout_image.rs | 6 +++ components/script/script_module.rs | 6 +++ components/script/script_thread.rs | 14 ++++- components/script/stylesheet_loader.rs | 10 +++- components/shared/net/lib.rs | 19 ++++++- .../url/url-in-tags-revoke.window.js.ini | 3 ++ .../FileAPI/url/url-with-fetch.any.js.ini | 3 ++ tests/wpt/meta/MANIFEST.json | 4 +- ...uri-does-not-respect-base-uri.sub.html.ini | 6 +-- .../child-src-worker-blocked.sub.html.ini | 5 +- ...rc-syncxmlhttprequest-blocked.sub.html.ini | 3 -- ...connect-src-websocket-blocked.sub.html.ini | 3 -- ...ct-src-xmlhttprequest-blocked.sub.html.ini | 3 -- ...frame-ancestors-path-ignored.window.js.ini | 3 -- .../worker-classic.http.html.ini | 6 --- .../worker-classic.https.html.ini | 6 --- .../worker-module.http.html.ini | 6 --- .../worker-module.https.html.ini | 6 --- .../worker-classic.http.html.ini | 6 --- .../worker-classic.https.html.ini | 6 --- .../worker-module.http.html.ini | 6 --- .../worker-module.https.html.ini | 6 --- ...rective-name-case-insensitive.sub.html.ini | 6 +-- .../generic/generic-0_1-img-src.html.ini | 6 +-- .../generic/generic-0_1-script-src.html.ini | 3 +- .../generic/generic-0_10_1.sub.html.ini | 3 +- .../generic/generic-0_2_2.sub.html.ini | 3 +- .../generic/generic-0_2_3.html.ini | 3 +- .../wildcard-host-part.sub.window.js.ini | 2 + ...rc-full-host-wildcard-blocked.sub.html.ini | 3 -- .../img-src-none-blocks-data-uri.html.ini | 3 -- .../img-src/img-src-none-blocks.html.ini | 3 -- .../report-blocked-data-uri.sub.html.ini | 3 -- ...-csp-list-modifications-are-local.html.ini | 7 --- .../media-src/media-src-7_1_2.sub.html.ini | 6 +-- .../media-src/media-src-7_2_2.sub.html.ini | 7 +-- .../media-src/media-src-blocked.sub.html.ini | 7 +-- .../meta/meta-img-src.html.ini | 3 -- .../meta/meta-modified.html.ini | 3 -- .../parsing/invalid-directive.html.ini | 3 +- ...rective-allowed-in-meta.https.sub.html.ini | 6 +-- ...ports-to-first-endpoint.https.sub.html.ini | 6 +-- ...-overrides-report-uri-1.https.sub.html.ini | 6 +-- ...-overrides-report-uri-2.https.sub.html.ini | 6 +-- ...ds-reports-on-violation.https.sub.html.ini | 6 +-- .../reporting/report-and-enforce.html.ini | 3 -- .../report-same-origin-with-cookies.html.ini | 3 -- ...amic-elem-blocked-src-allowed.sub.html.ini | 3 +- .../script-src/script-src-1_10.html.ini | 3 +- ...y-works-with-external-hash-policy.html.ini | 3 +- ...ross-origin-image-from-script.sub.html.ini | 3 +- ...tion-block-cross-origin-image.sub.html.ini | 3 +- ...ation-block-image-from-script.sub.html.ini | 3 +- ...typolicyviolation-block-image.sub.html.ini | 3 +- .../style-src/style-blocked.html.ini | 6 ++- .../style-src-error-event-fires.html.ini | 3 -- ...c-injected-stylesheet-blocked.sub.html.ini | 3 -- .../style-src/style-src-none-blocked.html.ini | 3 -- ...tyle-src-stylesheet-nonce-allowed.html.ini | 3 ++ ...tyle-src-stylesheet-nonce-blocked.html.ini | 3 -- .../worker-src/dedicated-none.sub.html.ini | 5 +- ...er-src-child-fallback-blocked.sub.html.ini | 3 +- ...table-cell-overflow-auto-scrolled.html.ini | 2 + .../navigating-across-documents/009.html.ini | 3 -- ...avigation-unload-same-origin.window.js.ini | 3 -- .../refresh/same-document-refresh.html.ini | 3 ++ .../traverse_the_history_5.html.ini | 3 ++ .../2d.canvas.host.size.large.worker.js.ini | 2 - .../supported-elements.html.ini | 10 ++-- .../images/blocked-by-csp.html.ini | 2 + .../iframe_sandbox_popups_escaping-1.html.ini | 1 - .../iframe_sandbox_popups_escaping-2.html.ini | 3 +- .../iframe_sandbox_popups_escaping-3.html.ini | 4 +- ...rame_sandbox_popups_nonescaping-3.html.ini | 2 +- ...ta-csp-img-src-none.tentative.sub.html.ini | 3 -- ...actory-createPolicy-cspTests-none.html.ini | 5 +- ...licyFactory-createPolicy-cspTests.html.ini | 5 +- ...ode-insertion-into-script-element.html.ini | 13 ++--- ...insertion-into-svg-script-element.html.ini | 12 ++--- ...ire-trusted-types-for-report-only.html.ini | 10 ++-- .../require-trusted-types-for.html.ini | 10 ++-- ...h-violation-be-blocked-by-csp-001.html.ini | 16 +++--- ...h-violation-be-blocked-by-csp-003.html.ini | 3 +- ...cy-creation-be-blocked-by-csp-001.html.ini | 51 ++++++++---------- ...cy-creation-be-blocked-by-csp-002.html.ini | 53 +++---------------- ...cy-creation-be-blocked-by-csp-003.html.ini | 14 ++--- ...cy-creation-be-blocked-by-csp-005.html.ini | 3 +- ...pes-eval-reporting-no-unsafe-eval.html.ini | 7 ++- ...-types-eval-reporting-report-only.html.ini | 8 +-- .../trusted-types-navigation.html.ini | 2 +- .../trusted-types-report-only.html.ini | 11 ++-- ...ypes-reporting-clipping-of-sample.html.ini | 25 +++++---- ...ting-clipping-of-sample.tentative.html.ini | 17 +++--- ...ing-for-DOMParser-parseFromString.html.ini | 6 +-- ...eporting-for-Document-execCommand.html.ini | 7 ++- ...ting-for-Document-parseHTMLUnsafe.html.ini | 5 +- ...ypes-reporting-for-Document-write.html.ini | 11 +--- ...s-reporting-for-Element-innerHTML.html.ini | 6 +-- ...ng-for-Element-insertAdjacentHTML.html.ini | 6 +-- ...s-reporting-for-Element-outerHTML.html.ini | 6 +-- ...eporting-for-Element-setAttribute.html.ini | 22 ++++---- ...porting-for-Element-setHTMLUnsafe.html.ini | 6 +-- ...ting-for-HTMLIFrameElement-srcdoc.html.ini | 6 +-- ...g-for-HTMLScriptElement-innerHTML.html.ini | 6 +-- ...s-reporting-for-HTMLScriptElement.html.ini | 21 ++------ ...or-Range-createContextualFragment.html.ini | 6 +-- ...ng-for-SVGScriptElement-innerHTML.html.ini | 6 +-- ...eporting-for-ShadowRoot-innerHTML.html.ini | 6 +-- ...ting-for-ShadowRoot-setHTMLUnsafe.html.ini | 5 +- ...indow-DedicatedWorker-constructor.html.ini | 6 +-- ...iceWorkerContainer-register.https.html.ini | 5 +- ...r-Window-SharedWorker-constructor.html.ini | 5 +- ...d-types-reporting-for-Window-eval.html.ini | 5 +- ...g-for-Window-function-constructor.html.ini | 33 ++---------- ...for-Window-setTimeout-setInterval.html.ini | 11 +--- .../trusted-types-reporting.html.ini | 17 +++--- .../trusted-types-source-file-path.html.ini | 7 ++- ...trusted-types-svg-script-set-href.html.ini | 38 ++++++------- .../sub-sample-buffer-stitching.html.ini | 6 +-- .../audiocontextoptions.html.ini | 1 + .../connect-src-websocket-allowed.sub.html | 11 ++-- .../connect-src-websocket-blocked.sub.html | 9 +++- 146 files changed, 511 insertions(+), 612 deletions(-) delete mode 100644 tests/wpt/meta/content-security-policy/connect-src/connect-src-syncxmlhttprequest-blocked.sub.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/connect-src/connect-src-websocket-blocked.sub.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/connect-src/connect-src-xmlhttprequest-blocked.sub.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/frame-ancestors/frame-ancestors-path-ignored.window.js.ini create mode 100644 tests/wpt/meta/content-security-policy/generic/wildcard-host-part.sub.window.js.ini delete mode 100644 tests/wpt/meta/content-security-policy/img-src/img-src-full-host-wildcard-blocked.sub.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/img-src/img-src-none-blocks-data-uri.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/img-src/img-src-none-blocks.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/img-src/report-blocked-data-uri.sub.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/inheritance/inherited-csp-list-modifications-are-local.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/meta/meta-img-src.html.ini delete mode 100644 tests/wpt/meta/content-security-policy/meta/meta-modified.html.ini create mode 100644 tests/wpt/meta/content-security-policy/style-src/style-src-stylesheet-nonce-allowed.html.ini create mode 100644 tests/wpt/meta/css/css-tables/table-cell-overflow-auto-scrolled.html.ini delete mode 100644 tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/009.html.ini delete mode 100644 tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin.window.js.ini create mode 100644 tests/wpt/meta/html/browsers/browsing-the-web/navigating-across-documents/refresh/same-document-refresh.html.ini create mode 100644 tests/wpt/meta/html/browsers/history/the-history-interface/traverse_the_history_5.html.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.worker.js.ini create mode 100644 tests/wpt/meta/html/rendering/replaced-elements/images/blocked-by-csp.html.ini delete mode 100644 tests/wpt/meta/html/syntax/speculative-parsing/generated/document-write/meta-csp-img-src-none.tentative.sub.html.ini diff --git a/Cargo.lock b/Cargo.lock index 5e617894f4f..cf3d61c9497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1224,8 +1224,7 @@ dependencies = [ [[package]] name = "content-security-policy" version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33b8ed5a7a80fdf6b7b1946f0d804c08ba348d72725b09a58fe804c48b7354f" +source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#babd99e8fbafe42434186c252f14b17a3f8dad22" dependencies = [ "base64 0.22.1", "bitflags 2.9.0", diff --git a/Cargo.toml b/Cargo.toml index e4c13d3678e..5a9698dc43a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ chrono = { version = "0.4", features = ["serde"] } cipher = { version = "0.4.4", features = ["alloc"] } compositing_traits = { path = "components/shared/compositing" } constellation_traits = { path = "components/shared/constellation" } -content-security-policy = { version = "0.5", features = ["serde"] } +content-security-policy = { git = "https://github.com/servo/rust-content-security-policy/", branch = "servo-csp", features = ["serde"] } cookie = { package = "cookie", version = "0.18" } crossbeam-channel = "0.5" cssparser = { version = "0.35", features = ["serde"] } diff --git a/components/fonts/font_context.rs b/components/fonts/font_context.rs index 81a730ed951..58c08b29d3e 100644 --- a/components/fonts/font_context.rs +++ b/components/fonts/font_context.rs @@ -900,9 +900,9 @@ impl RemoteWebFontDownloader { response_message: FetchResponseMsg, ) -> DownloaderResponseResult { match response_message { - FetchResponseMsg::ProcessRequestBody(..) | FetchResponseMsg::ProcessRequestEOF(..) => { - DownloaderResponseResult::InProcess - }, + FetchResponseMsg::ProcessRequestBody(..) | + FetchResponseMsg::ProcessRequestEOF(..) | + FetchResponseMsg::ProcessCspViolations(..) => DownloaderResponseResult::InProcess, FetchResponseMsg::ProcessResponse(_, meta_result) => { trace!( "@font-face {} metadata ok={:?}", diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 49b063f1bc8..697a46fedda 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -173,9 +173,9 @@ pub async fn fetch_with_cors_cache( pub fn should_request_be_blocked_by_csp( request: &Request, policy_container: &PolicyContainer, -) -> csp::CheckResult { +) -> (csp::CheckResult, Vec) { let origin = match &request.origin { - Origin::Client => return csp::CheckResult::Allowed, + Origin::Client => return (csp::CheckResult::Allowed, Vec::new()), Origin::Origin(origin) => origin, }; @@ -190,12 +190,11 @@ pub fn should_request_be_blocked_by_csp( parser_metadata: csp::ParserMetadata::None, }; - // TODO: Instead of ignoring violations, report them. policy_container .csp_list .as_ref() - .map(|c| c.should_request_be_blocked(&csp_request).0) - .unwrap_or(csp::CheckResult::Allowed) + .map(|c| c.should_request_be_blocked(&csp_request)) + .unwrap_or((csp::CheckResult::Allowed, Vec::new())) } /// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch) @@ -278,7 +277,13 @@ pub async fn main_fetch( // Step 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. - if should_request_be_blocked_by_csp(request, &policy_container) == csp::CheckResult::Blocked { + let (check_result, violations) = should_request_be_blocked_by_csp(request, &policy_container); + + if !violations.is_empty() { + target.process_csp_violations(request, violations); + } + + if check_result == csp::CheckResult::Blocked { warn!("Request blocked by CSP"); response = Some(Response::network_error(NetworkError::Internal( "Blocked by Content-Security-Policy".into(), diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index 1e87467ca7f..81d9a09bf39 100644 --- a/components/net/image_cache.rs +++ b/components/net/image_cache.rs @@ -546,7 +546,8 @@ impl ImageCache for ImageCacheImpl { fn notify_pending_response(&self, id: PendingImageId, action: FetchResponseMsg) { match (action, id) { (FetchResponseMsg::ProcessRequestBody(..), _) | - (FetchResponseMsg::ProcessRequestEOF(..), _) => (), + (FetchResponseMsg::ProcessRequestEOF(..), _) | + (FetchResponseMsg::ProcessCspViolations(..), _) => (), (FetchResponseMsg::ProcessResponse(_, response), _) => { debug!("Received {:?} for {:?}", response.as_ref().map(|_| ()), id); let mut store = self.store.lock().unwrap(); diff --git a/components/net/tests/fetch.rs b/components/net/tests/fetch.rs index 55d2241fb02..7dbf1ca0047 100644 --- a/components/net/tests/fetch.rs +++ b/components/net/tests/fetch.rs @@ -12,6 +12,7 @@ use std::sync::{Arc, Mutex, Weak}; use std::time::{Duration, SystemTime}; use base::id::TEST_PIPELINE_ID; +use content_security_policy as csp; use crossbeam_channel::{Sender, unbounded}; use devtools_traits::{HttpRequest as DevtoolsHttpRequest, HttpResponse as DevtoolsHttpResponse}; use headers::{ @@ -163,6 +164,7 @@ fn test_fetch_blob() { assert_eq!(self.buffer, self.expected); let _ = self.sender.send(response.clone()); } + fn process_csp_violations(&mut self, _: &Request, _: Vec) {} } let context = new_fetch_context(None, None, None); diff --git a/components/net/tests/http_loader.rs b/components/net/tests/http_loader.rs index b3afe11852c..1fc2d1b662d 100644 --- a/components/net/tests/http_loader.rs +++ b/components/net/tests/http_loader.rs @@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex, RwLock}; use std::time::Duration; use base::id::{TEST_PIPELINE_ID, TEST_WEBVIEW_ID}; +use content_security_policy as csp; use cookie::Cookie as CookiePair; use crossbeam_channel::{Receiver, unbounded}; use devtools_traits::{ @@ -1537,6 +1538,7 @@ fn test_fetch_compressed_response_update_count() { fn process_response_eof(&mut self, _: &Request, _: &Response) { let _ = self.sender.take().unwrap().send(self.update_count); } + fn process_csp_violations(&mut self, _: &Request, _: Vec) {} } let (sender, receiver) = tokio::sync::oneshot::channel(); diff --git a/components/net/tests/main.rs b/components/net/tests/main.rs index 504fea09777..ba58c4a4c1d 100644 --- a/components/net/tests/main.rs +++ b/components/net/tests/main.rs @@ -26,6 +26,7 @@ use std::net::TcpListener as StdTcpListener; use std::path::{Path, PathBuf}; use std::sync::{Arc, LazyLock, Mutex, RwLock, Weak}; +use content_security_policy as csp; use crossbeam_channel::{Receiver, Sender, unbounded}; use devtools_traits::DevtoolsControlMsg; use embedder_traits::{AuthenticationResponse, EmbedderMsg, EmbedderProxy, EventLoopWaker}; @@ -196,6 +197,7 @@ impl FetchTaskTarget for FetchResponseCollector { fn process_response_eof(&mut self, _: &Request, response: &Response) { let _ = self.sender.take().unwrap().send(response.clone()); } + fn process_csp_violations(&mut self, _: &Request, _: Vec) {} } fn fetch(request: Request, dc: Option>) -> Response { diff --git a/components/net/websocket_loader.rs b/components/net/websocket_loader.rs index 63f3da1cef3..95f66558482 100644 --- a/components/net/websocket_loader.rs +++ b/components/net/websocket_loader.rs @@ -11,19 +11,22 @@ //! over events from the network and events from the DOM, using async/await to avoid //! the need for a dedicated thread per websocket. +use std::mem; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use async_tungstenite::WebSocketStream; use async_tungstenite::tokio::{ConnectStream, client_async_tls_with_connector_and_config}; use base64::Engine; +use content_security_policy as csp; use futures::future::TryFutureExt; use futures::stream::StreamExt; use http::header::{self, HeaderName, HeaderValue}; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use log::{debug, trace, warn}; -use net_traits::request::{RequestBuilder, RequestMode}; +use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer}; +use net_traits::request::{Origin, RequestBuilder, RequestMode}; use net_traits::{CookieSource, MessageData, WebSocketDomAction, WebSocketNetworkEvent}; use servo_url::ServoUrl; use tokio::net::TcpStream; @@ -39,7 +42,9 @@ use url::Url; use crate::async_runtime::HANDLE; use crate::connector::{CACertificates, TlsConfig, create_tls_config}; use crate::cookie::ServoCookie; -use crate::fetch::methods::should_request_be_blocked_due_to_a_bad_port; +use crate::fetch::methods::{ + should_request_be_blocked_by_csp, should_request_be_blocked_due_to_a_bad_port, +}; use crate::hosts::replace_host; use crate::http_loader::HttpState; /// Create a tungstenite Request object for the initial HTTP request. @@ -353,7 +358,7 @@ fn connect( ignore_certificate_errors: bool, ) -> Result<(), String> { let protocols = match req_builder.mode { - RequestMode::WebSocket { protocols } => protocols, + RequestMode::WebSocket { ref mut protocols } => mem::take(protocols), _ => { return Err( "Received a RequestBuilder with a non-websocket mode in websocket_loader" @@ -368,16 +373,36 @@ fn connect( .read() .unwrap() .apply_hsts_rules(&mut req_builder.url); + let request = req_builder.build(); - let req_url = req_builder.url.clone(); + let req_url = request.url(); + let req_origin = match request.origin { + Origin::Client => unreachable!(), + Origin::Origin(ref origin) => origin, + }; if should_request_be_blocked_due_to_a_bad_port(&req_url) { return Err("Port blocked".to_string()); } + let policy_container = match &request.policy_container { + RequestPolicyContainer::Client => PolicyContainer::default(), + RequestPolicyContainer::PolicyContainer(container) => container.to_owned(), + }; + + let (check_result, violations) = should_request_be_blocked_by_csp(&request, &policy_container); + + if !violations.is_empty() { + let _ = resource_event_sender.send(WebSocketNetworkEvent::ReportCSPViolations(violations)); + } + + if check_result == csp::CheckResult::Blocked { + return Err("Blocked by Content-Security-Policy".to_string()); + } + let client = match create_request( &req_url, - &req_builder.origin.ascii_serialization(), + &req_origin.ascii_serialization(), &protocols, &http_state, ) { @@ -397,7 +422,7 @@ fn connect( Some(handle) => handle.spawn( start_websocket( http_state, - req_builder.url.clone(), + req_url.clone(), resource_event_sender, protocols, client, diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index 3f6b6ee7d90..b014a85701b 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -18,6 +18,7 @@ use js::jsval::UndefinedValue; use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; use net_traits::IpcSend; use net_traits::image_cache::ImageCache; +use net_traits::policy_container::PolicyContainer; use net_traits::request::{ CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder, RequestMode, @@ -347,6 +348,7 @@ impl DedicatedWorkerGlobalScope { control_receiver: Receiver, context_sender: Sender, insecure_requests_policy: InsecureRequestsPolicy, + policy_container: PolicyContainer, ) -> JoinHandle<()> { let serialized_worker_url = worker_url.to_string(); let webview_id = WebViewId::installed(); @@ -388,6 +390,7 @@ impl DedicatedWorkerGlobalScope { .referrer_policy(referrer_policy) .insecure_requests_policy(insecure_requests_policy) .has_trustworthy_ancestor_origin(current_global_ancestor_trustworthy) + .policy_container(policy_container) .origin(origin); let runtime = unsafe { diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index f5b2124eb2e..428eabc8ad8 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -8,6 +8,7 @@ use std::str::{Chars, FromStr}; use std::sync::{Arc, Mutex}; use std::time::Duration; +use content_security_policy as csp; use dom_struct::dom_struct; use headers::ContentType; use http::header::{self, HeaderName, HeaderValue}; @@ -431,6 +432,11 @@ impl FetchResponseListener for EventSourceContext { fn submit_resource_timing(&mut self) { network_listener::submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for EventSourceContext { @@ -562,6 +568,7 @@ impl EventSourceMethods for EventSource { global.get_referrer(), global.insecure_requests_policy(), global.has_trustworthy_ancestor_or_current_origin(), + global.policy_container(), ) .origin(global.origin().immutable().clone()) .pipeline_id(Some(global.pipeline_id())); diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index aaa0721c748..b488daf24e4 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -21,7 +21,9 @@ use constellation_traits::{ BlobData, BlobImpl, BroadcastMsg, FileBlob, MessagePortImpl, MessagePortMsg, PortMessageTask, ScriptToConstellationChan, ScriptToConstellationMessage, }; -use content_security_policy::{CheckResult, CspList, PolicyDisposition}; +use content_security_policy::{ + CheckResult, CspList, PolicyDisposition, Violation, ViolationResource, +}; use crossbeam_channel::Sender; use devtools_traits::{PageError, ScriptToDevtoolsControlMsg}; use dom_struct::dom_struct; @@ -3310,6 +3312,24 @@ impl GlobalScope { } unreachable!(); } + + pub(crate) fn report_csp_violations(&self, violations: Vec) { + for violation in violations { + let sample = match violation.resource { + ViolationResource::Inline { .. } | ViolationResource::Url(_) => None, + ViolationResource::TrustedTypePolicy { sample } => Some(sample), + }; + let report = CSPViolationReportBuilder::default() + .resource("eval".to_owned()) + .sample(sample) + .effective_directive(violation.directive.name) + .build(self); + let task = CSPViolationReportTask::new(self, report); + self.task_manager() + .dom_manipulation_task_source() + .queue(task); + } + } } /// Returns the Rust global scope from a JS global object. diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 28ed3c2d13a..e6b4336fe54 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -10,6 +10,7 @@ use std::sync::Arc; use std::{char, mem}; use app_units::{AU_PER_PX, Au}; +use content_security_policy as csp; use cssparser::{Parser, ParserInput}; use dom_struct::dom_struct; use euclid::Point2D; @@ -294,6 +295,11 @@ impl FetchResponseListener for ImageContext { fn submit_resource_timing(&mut self) { network_listener::submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for ImageContext { @@ -416,15 +422,17 @@ impl HTMLImageElement { // https://html.spec.whatwg.org/multipage/#update-the-image-data steps 17-20 // This function is also used to prefetch an image in `script::dom::servoparser::prefetch`. + let global = document.global(); let mut request = create_a_potential_cors_request( Some(window.webview_id()), img_url.clone(), Destination::Image, cors_setting_for_element(self.upcast()), None, - document.global().get_referrer(), + global.get_referrer(), document.insecure_requests_policy(), document.has_trustworthy_ancestor_or_current_origin(), + global.policy_container(), ) .origin(document.origin().immutable().clone()) .pipeline_id(Some(document.global().pipeline_id())) diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 07d7a5d7305..7fd6927ba13 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -7,6 +7,7 @@ use std::cell::Cell; use std::default::Default; use base::id::WebViewId; +use content_security_policy as csp; use cssparser::{Parser as CssParser, ParserInput}; use dom_struct::dom_struct; use embedder_traits::EmbedderMsg; @@ -706,9 +707,9 @@ impl LinkProcessingOptions { Referrer::NoReferrer, self.insecure_requests_policy, self.has_trustworthy_ancestor_origin, + self.policy_container, ) .integrity_metadata(self.integrity) - .policy_container(self.policy_container) .cryptographic_nonce_metadata(self.cryptographic_nonce_metadata) .referrer_policy(self.referrer_policy); @@ -788,6 +789,11 @@ impl FetchResponseListener for PrefetchContext { fn submit_resource_timing(&mut self) { submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for PrefetchContext { diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 1c802b38f72..d1baab7d6d2 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -10,6 +10,7 @@ use std::time::{Duration, Instant}; use std::{f64, mem}; use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData}; +use content_security_policy as csp; use dom_struct::dom_struct; use embedder_traits::resources::{self, Resource as EmbedderResource}; use embedder_traits::{MediaPositionState, MediaSessionEvent, MediaSessionPlaybackState}; @@ -892,15 +893,17 @@ impl HTMLMediaElement { }; let cors_setting = cors_setting_for_element(self.upcast()); + let global = self.global(); let request = create_a_potential_cors_request( Some(document.webview_id()), url.clone(), destination, cors_setting, None, - self.global().get_referrer(), + global.get_referrer(), document.insecure_requests_policy(), document.has_trustworthy_ancestor_or_current_origin(), + global.policy_container(), ) .headers(headers) .origin(document.origin().immutable().clone()) @@ -2903,6 +2906,11 @@ impl FetchResponseListener for HTMLMediaElementFetchListener { fn submit_resource_timing(&mut self) { network_listener::submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for HTMLMediaElementFetchListener { diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 71e3d4ed72b..b1de1f41b16 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -21,6 +21,7 @@ use ipc_channel::ipc; use js::jsval::UndefinedValue; use js::rust::{CompileOptionsWrapper, HandleObject, Stencil, transform_str_to_source_text}; use net_traits::http_status::HttpStatus; +use net_traits::policy_container::PolicyContainer; use net_traits::request::{ CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, RequestBuilder, RequestId, @@ -536,6 +537,11 @@ impl FetchResponseListener for ClassicContext { fn submit_resource_timing(&mut self) { network_listener::submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for ClassicContext { @@ -569,6 +575,7 @@ pub(crate) fn script_fetch_request( options: ScriptFetchOptions, insecure_requests_policy: InsecureRequestsPolicy, has_trustworthy_ancestor_origin: bool, + policy_container: PolicyContainer, ) -> RequestBuilder { // We intentionally ignore options' credentials_mode member for classic scripts. // The mode is initialized by create_a_potential_cors_request. @@ -581,6 +588,7 @@ pub(crate) fn script_fetch_request( options.referrer, insecure_requests_policy, has_trustworthy_ancestor_origin, + policy_container, ) .origin(origin) .pipeline_id(Some(pipeline_id)) @@ -601,15 +609,17 @@ fn fetch_a_classic_script( ) { // Step 1, 2. let doc = script.owner_document(); + let global = script.global(); let request = script_fetch_request( doc.webview_id(), url.clone(), cors_setting, doc.origin().immutable().clone(), - script.global().pipeline_id(), + global.pipeline_id(), options.clone(), doc.insecure_requests_policy(), doc.has_trustworthy_ancestor_origin(), + global.policy_container(), ); let request = doc.prepare_request(request); diff --git a/components/script/dom/htmlvideoelement.rs b/components/script/dom/htmlvideoelement.rs index 49f81f78d8d..a6f9103200b 100644 --- a/components/script/dom/htmlvideoelement.rs +++ b/components/script/dom/htmlvideoelement.rs @@ -5,6 +5,7 @@ use std::cell::Cell; use std::sync::Arc; +use content_security_policy as csp; use dom_struct::dom_struct; use euclid::default::Size2D; use html5ever::{LocalName, Prefix, local_name, namespace_url, ns}; @@ -416,6 +417,11 @@ impl FetchResponseListener for PosterFrameFetchContext { fn submit_resource_timing(&mut self) { network_listener::submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for PosterFrameFetchContext { diff --git a/components/script/dom/notification.rs b/components/script/dom/notification.rs index 11d786c8216..4dbb97430e5 100644 --- a/components/script/dom/notification.rs +++ b/components/script/dom/notification.rs @@ -7,6 +7,7 @@ use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{SystemTime, UNIX_EPOCH}; +use content_security_policy as csp; use content_security_policy::Destination; use dom_struct::dom_struct; use embedder_traits::{ @@ -791,6 +792,11 @@ impl FetchResponseListener for ResourceFetchListener { fn submit_resource_timing(&mut self) { network_listener::submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for ResourceFetchListener { @@ -821,6 +827,7 @@ impl Notification { global.get_referrer(), global.insecure_requests_policy(), global.has_trustworthy_ancestor_or_current_origin(), + global.policy_container(), ) .origin(global.origin().immutable().clone()) .pipeline_id(Some(global.pipeline_id())) diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 2652e44e02c..970c1543d09 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -1068,6 +1068,16 @@ impl FetchResponseListener for ParserContext { CanGc::note(), ); } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let parser = match self.parser.as_ref() { + Some(parser) => parser.root(), + None => return, + }; + let document = &parser.document; + let global = &document.global(); + global.report_csp_violations(violations); + } } impl PreInvoke for ParserContext {} diff --git a/components/script/dom/servoparser/prefetch.rs b/components/script/dom/servoparser/prefetch.rs index 8191363bf20..f0faec8df89 100644 --- a/components/script/dom/servoparser/prefetch.rs +++ b/components/script/dom/servoparser/prefetch.rs @@ -15,6 +15,7 @@ use html5ever::tokenizer::{ use html5ever::{Attribute, LocalName, local_name}; use js::jsapi::JSTracer; use markup5ever::TokenizerResult; +use net_traits::policy_container::PolicyContainer; use net_traits::request::{ CorsSettings, CredentialsMode, InsecureRequestsPolicy, ParserMetadata, Referrer, }; @@ -60,13 +61,14 @@ unsafe impl CustomTraceable for PrefetchSink { impl Tokenizer { pub(crate) fn new(document: &Document) -> Self { + let global = document.global(); let sink = PrefetchSink { origin: document.origin().immutable().clone(), - pipeline_id: document.global().pipeline_id(), + pipeline_id: global.pipeline_id(), webview_id: document.webview_id(), base_url: RefCell::new(None), document_url: document.url(), - referrer: document.global().get_referrer(), + referrer: global.get_referrer(), referrer_policy: document.get_referrer_policy(), resource_threads: document.loader().resource_threads().clone(), // Initially we set prefetching to false, and only set it @@ -75,6 +77,7 @@ impl Tokenizer { prefetching: Cell::new(false), insecure_requests_policy: document.insecure_requests_policy(), has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(), + policy_container: global.policy_container(), }; let options = Default::default(); let inner = TraceableTokenizer(HtmlTokenizer::new(sink, options)); @@ -108,6 +111,8 @@ struct PrefetchSink { #[no_trace] insecure_requests_policy: InsecureRequestsPolicy, has_trustworthy_ancestor_origin: bool, + #[no_trace] + policy_container: PolicyContainer, } /// The prefetch tokenizer produces trivial results @@ -150,6 +155,7 @@ impl TokenSink for PrefetchSink { }, self.insecure_requests_policy, self.has_trustworthy_ancestor_origin, + self.policy_container.clone(), ); let _ = self .resource_threads @@ -169,6 +175,7 @@ impl TokenSink for PrefetchSink { self.referrer.clone(), self.insecure_requests_policy, self.has_trustworthy_ancestor_origin, + self.policy_container.clone(), ) .origin(self.origin.clone()) .pipeline_id(Some(self.pipeline_id)) @@ -204,6 +211,7 @@ impl TokenSink for PrefetchSink { self.referrer.clone(), self.insecure_requests_policy, self.has_trustworthy_ancestor_origin, + self.policy_container.clone(), ) .origin(self.origin.clone()) .pipeline_id(Some(self.pipeline_id)) diff --git a/components/script/dom/websocket.rs b/components/script/dom/websocket.rs index 68e59384c60..b9b1b50c901 100644 --- a/components/script/dom/websocket.rs +++ b/components/script/dom/websocket.rs @@ -7,6 +7,7 @@ use std::cell::Cell; use std::ptr; use constellation_traits::BlobImpl; +use content_security_policy::Violation; use dom_struct::dom_struct; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; @@ -266,6 +267,7 @@ impl WebSocketMethods for WebSocket { .service_workers_mode(ServiceWorkersMode::None) .credentials_mode(CredentialsMode::Include) .cache_mode(CacheMode::NoCache) + .policy_container(global.policy_container()) .redirect_mode(RedirectMode::Error); let channels = FetchChannels::WebSocket { @@ -280,6 +282,13 @@ impl WebSocketMethods for WebSocket { ROUTER.add_typed_route( dom_event_receiver.to_ipc_receiver(), Box::new(move |message| match message.unwrap() { + WebSocketNetworkEvent::ReportCSPViolations(violations) => { + let task = ReportCSPViolationTask { + websocket: address.clone(), + violations, + }; + task_source.queue(task); + }, WebSocketNetworkEvent::ConnectionEstablished { protocol_in_use } => { let open_thread = ConnectionEstablishedTask { address: address.clone(), @@ -454,6 +463,18 @@ impl WebSocketMethods for WebSocket { } } +struct ReportCSPViolationTask { + websocket: Trusted, + violations: Vec, +} + +impl TaskOnce for ReportCSPViolationTask { + fn run_once(self) { + let global = self.websocket.root().global(); + global.report_csp_violations(self.violations); + } +} + /// Task queued when *the WebSocket connection is established*. /// struct ConnectionEstablishedTask { diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs index e07f88f5ec1..429234c7a8e 100644 --- a/components/script/dom/worker.rs +++ b/components/script/dom/worker.rs @@ -243,6 +243,7 @@ impl WorkerMethods for Worker { control_receiver, context_sender, global.insecure_requests_policy(), + global.policy_container(), ); let context = context_receiver diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 3c74189d1b9..d1aab5c14d5 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use constellation_traits::BlobImpl; +use content_security_policy as csp; use dom_struct::dom_struct; use encoding_rs::{Encoding, UTF_8}; use headers::{ContentLength, ContentType, HeaderMapExt}; @@ -143,6 +144,11 @@ impl FetchResponseListener for XHRContext { fn submit_resource_timing(&mut self) { network_listener::submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for XHRContext { @@ -671,8 +677,9 @@ impl XMLHttpRequestMethods for XMLHttpRequest { None => None, }; + let global = self.global(); let mut request = RequestBuilder::new( - self.global().webview_id(), + global.webview_id(), self.request_url.borrow().clone().unwrap(), self.referrer.clone(), ) @@ -686,11 +693,12 @@ impl XMLHttpRequestMethods for XMLHttpRequest { .use_cors_preflight(self.upload_listener.get()) .credentials_mode(credentials_mode) .use_url_credentials(use_url_credentials) - .origin(self.global().origin().immutable().clone()) + .origin(global.origin().immutable().clone()) .referrer_policy(self.referrer_policy) - .insecure_requests_policy(self.global().insecure_requests_policy()) - .has_trustworthy_ancestor_origin(self.global().has_trustworthy_ancestor_or_current_origin()) - .pipeline_id(Some(self.global().pipeline_id())); + .insecure_requests_policy(global.insecure_requests_policy()) + .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin()) + .policy_container(global.policy_container()) + .pipeline_id(Some(global.pipeline_id())); // step 4 (second half) if let Some(content_type) = content_type { diff --git a/components/script/fetch.rs b/components/script/fetch.rs index 92610febc43..9192a030b66 100644 --- a/components/script/fetch.rs +++ b/components/script/fetch.rs @@ -6,8 +6,9 @@ use std::rc::Rc; use std::sync::{Arc, Mutex}; use base::id::WebViewId; +use content_security_policy as csp; use ipc_channel::ipc; -use net_traits::policy_container::RequestPolicyContainer; +use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer}; use net_traits::request::{ CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, Referrer, Request as NetTraitsRequest, RequestBuilder, RequestId, RequestMode, ServiceWorkersMode, @@ -309,6 +310,11 @@ impl FetchResponseListener for FetchContext { network_listener::submit_timing(self, CanGc::note()) } } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for FetchContext { @@ -352,8 +358,9 @@ pub(crate) fn load_whole_resource( let mut metadata = None; loop { match action_receiver.recv().unwrap() { - FetchResponseMsg::ProcessRequestBody(..) | FetchResponseMsg::ProcessRequestEOF(..) => { - }, + FetchResponseMsg::ProcessRequestBody(..) | + FetchResponseMsg::ProcessRequestEOF(..) | + FetchResponseMsg::ProcessCspViolations(..) => {}, FetchResponseMsg::ProcessResponse(_, Ok(m)) => { metadata = Some(match m { FetchMetadata::Unfiltered(m) => m, @@ -385,6 +392,7 @@ pub(crate) fn create_a_potential_cors_request( referrer: Referrer, insecure_requests_policy: InsecureRequestsPolicy, has_trustworthy_ancestor_origin: bool, + policy_container: PolicyContainer, ) -> RequestBuilder { RequestBuilder::new(webview_id, url, referrer) // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request @@ -405,4 +413,5 @@ pub(crate) fn create_a_potential_cors_request( .use_url_credentials(true) .insecure_requests_policy(insecure_requests_policy) .has_trustworthy_ancestor_origin(has_trustworthy_ancestor_origin) + .policy_container(policy_container) } diff --git a/components/script/layout_image.rs b/components/script/layout_image.rs index 7f71216309d..7fd23804ffd 100644 --- a/components/script/layout_image.rs +++ b/components/script/layout_image.rs @@ -8,6 +8,7 @@ use std::sync::Arc; +use content_security_policy as csp; use net_traits::image_cache::{ImageCache, PendingImageId}; use net_traits::request::{Destination, RequestBuilder as FetchRequestInit, RequestId}; use net_traits::{ @@ -77,6 +78,11 @@ impl FetchResponseListener for LayoutImageContext { fn submit_resource_timing(&mut self) { network_listener::submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for LayoutImageContext { diff --git a/components/script/script_module.rs b/components/script/script_module.rs index 8b1df9af1d4..689f4a3b0a7 100644 --- a/components/script/script_module.rs +++ b/components/script/script_module.rs @@ -11,6 +11,7 @@ use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::{mem, ptr}; +use content_security_policy as csp; use encoding_rs::UTF_8; use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader}; use html5ever::local_name; @@ -1273,6 +1274,11 @@ impl FetchResponseListener for ModuleContext { fn submit_resource_timing(&mut self) { network_listener::submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for ModuleContext { diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 6cae83ebe95..14834413232 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -41,6 +41,7 @@ use constellation_traits::{ JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, ScriptToConstellationChan, ScriptToConstellationMessage, ScrollState, StructuredSerializedData, WindowSizeType, }; +use content_security_policy::{self as csp}; use crossbeam_channel::unbounded; use devtools_traits::{ CSSError, DevtoolScriptControlMsg, DevtoolsPageInfo, NavigationState, @@ -3420,8 +3421,11 @@ impl ScriptThread { FetchResponseMsg::ProcessResponseEOF(request_id, eof) => { self.handle_fetch_eof(pipeline_id, request_id, eof) }, - FetchResponseMsg::ProcessRequestBody(..) => {}, - FetchResponseMsg::ProcessRequestEOF(..) => {}, + FetchResponseMsg::ProcessCspViolations(request_id, violations) => { + self.handle_csp_violations(pipeline_id, request_id, violations) + }, + FetchResponseMsg::ProcessRequestBody(..) | FetchResponseMsg::ProcessRequestEOF(..) => { + }, } } @@ -3477,6 +3481,12 @@ impl ScriptThread { } } + fn handle_csp_violations(&self, id: PipelineId, _: RequestId, violations: Vec) { + if let Some(global) = self.documents.borrow().find_global(id) { + global.report_csp_violations(violations); + } + } + fn handle_navigation_redirect(&self, id: PipelineId, metadata: &Metadata) { // TODO(mrobinson): This tries to accomplish some steps from // , but it's diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index e88c0b8ed69..67e186c7f6a 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -5,6 +5,7 @@ use std::io::{Read, Seek, Write}; use std::sync::atomic::AtomicBool; +use content_security_policy as csp; use cssparser::SourceLocation; use encoding_rs::UTF_8; use mime::{self, Mime}; @@ -282,6 +283,11 @@ impl FetchResponseListener for StylesheetContext { fn submit_resource_timing(&mut self) { network_listener::submit_timing(self, CanGc::note()) } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = &self.resource_timing_global(); + global.report_csp_violations(violations); + } } impl ResourceTimingListener for StylesheetContext { @@ -353,15 +359,17 @@ impl StylesheetLoader<'_> { } // https://html.spec.whatwg.org/multipage/#default-fetch-and-process-the-linked-resource + let global = self.elem.global(); let request = create_a_potential_cors_request( Some(document.webview_id()), url.clone(), Destination::Style, cors_setting, None, - self.elem.global().get_referrer(), + global.get_referrer(), document.insecure_requests_policy(), document.has_trustworthy_ancestor_or_current_origin(), + global.policy_container(), ) .origin(document.origin().immutable().clone()) .pipeline_id(Some(self.elem.global().pipeline_id())) diff --git a/components/shared/net/lib.rs b/components/shared/net/lib.rs index 0f1a1712ab7..05ad102f42f 100644 --- a/components/shared/net/lib.rs +++ b/components/shared/net/lib.rs @@ -11,6 +11,7 @@ use std::thread; use base::cross_process_instant::CrossProcessInstant; use base::id::HistoryStateId; +use content_security_policy::{self as csp}; use cookie::Cookie; use crossbeam_channel::{Receiver, Sender, unbounded}; use headers::{ContentType, HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader}; @@ -198,6 +199,7 @@ pub enum FetchResponseMsg { ProcessResponse(RequestId, Result), ProcessResponseChunk(RequestId, Vec), ProcessResponseEOF(RequestId, Result), + ProcessCspViolations(RequestId, Vec), } impl FetchResponseMsg { @@ -207,7 +209,8 @@ impl FetchResponseMsg { FetchResponseMsg::ProcessRequestEOF(id) | FetchResponseMsg::ProcessResponse(id, ..) | FetchResponseMsg::ProcessResponseChunk(id, ..) | - FetchResponseMsg::ProcessResponseEOF(id, ..) => *id, + FetchResponseMsg::ProcessResponseEOF(id, ..) | + FetchResponseMsg::ProcessCspViolations(id, ..) => *id, } } } @@ -235,6 +238,8 @@ pub trait FetchTaskTarget { /// /// Fired when the response is fully fetched fn process_response_eof(&mut self, request: &Request, response: &Response); + + fn process_csp_violations(&mut self, request: &Request, violations: Vec); } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -282,6 +287,7 @@ pub trait FetchResponseListener { fn resource_timing(&self) -> &ResourceFetchTiming; fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming; fn submit_resource_timing(&mut self); + fn process_csp_violations(&mut self, request_id: RequestId, violations: Vec); } impl FetchTaskTarget for IpcSender { @@ -313,6 +319,12 @@ impl FetchTaskTarget for IpcSender { let _ = self.send(FetchResponseMsg::ProcessResponseEOF(request.id, payload)); } + + fn process_csp_violations(&mut self, request: &Request, violations: Vec) { + let _ = self.send(FetchResponseMsg::ProcessCspViolations( + request.id, violations, + )); + } } /// A fetch task that discards all data it's sent, @@ -326,6 +338,7 @@ impl FetchTaskTarget for DiscardFetch { fn process_response(&mut self, _: &Request, _: &Response) {} fn process_response_chunk(&mut self, _: &Request, _: Vec) {} fn process_response_eof(&mut self, _: &Request, _: &Response) {} + fn process_csp_violations(&mut self, _: &Request, _: Vec) {} } pub trait Action { @@ -366,6 +379,9 @@ impl Action for FetchResponseMsg { Err(e) => listener.process_response_eof(request_id, Err(e)), } }, + FetchResponseMsg::ProcessCspViolations(request_id, violations) => { + listener.process_csp_violations(request_id, violations) + }, } } } @@ -455,6 +471,7 @@ pub enum WebSocketDomAction { #[derive(Debug, Deserialize, Serialize)] pub enum WebSocketNetworkEvent { + ReportCSPViolations(Vec), ConnectionEstablished { protocol_in_use: Option }, MessageReceived(MessageData), Close(Option, String), diff --git a/tests/wpt/meta/FileAPI/url/url-in-tags-revoke.window.js.ini b/tests/wpt/meta/FileAPI/url/url-in-tags-revoke.window.js.ini index 62c2d998e8f..745f3e84edb 100644 --- a/tests/wpt/meta/FileAPI/url/url-in-tags-revoke.window.js.ini +++ b/tests/wpt/meta/FileAPI/url/url-in-tags-revoke.window.js.ini @@ -14,3 +14,6 @@ [Opening a blob URL in a new window by clicking an tag works immediately before revoking the URL.] expected: TIMEOUT + + [Fetching a blob URL immediately before revoking it works in @@ -18,9 +18,14 @@ }); try { - var ws = new WebSocket("ws://{{domains[www1]}}:{{ports[http][0]}}/echo"); + var ws = new WebSocket("ws://{{domains[www1]}}:{{ports[ws][0]}}/echo"); - if (ws.readyState == WebSocket.CLOSING || ws.readyState == WebSocket.CLOSED) { + // Not all browsers fail the connection in a synchronous fashion. + if (ws.readyState == WebSocket.CONNECTING) { + setTimeout( function() { + ws.readyState != WebSocket.CLOSED ? log("allowed") : log("blocked"); + }, 1000); + } else if (ws.readyState == WebSocket.CLOSED) { log("blocked"); } else { log("allowed"); diff --git a/tests/wpt/tests/content-security-policy/connect-src/connect-src-websocket-blocked.sub.html b/tests/wpt/tests/content-security-policy/connect-src/connect-src-websocket-blocked.sub.html index 02c52837bb8..2cc3f1b5a05 100644 --- a/tests/wpt/tests/content-security-policy/connect-src/connect-src-websocket-blocked.sub.html +++ b/tests/wpt/tests/content-security-policy/connect-src/connect-src-websocket-blocked.sub.html @@ -18,9 +18,14 @@ }); try { - var ws = new WebSocket("ws://{{domains[www1]}}:{{ports[http][0]}}/echo"); + var ws = new WebSocket("ws://{{domains[www1]}}:{{ports[ws][0]}}/echo"); - if (ws.readyState == WebSocket.CLOSING || ws.readyState == WebSocket.CLOSED) { + // Not all browsers fail the connection in a synchronous fashion. + if (ws.readyState == WebSocket.CONNECTING) { + setTimeout( function() { + ws.readyState != WebSocket.CLOSED ? log("allowed") : log("blocked"); + }, 1000); + } else if (ws.readyState == WebSocket.CLOSED) { log("blocked"); } else { log("allowed");