diff --git a/Cargo.lock b/Cargo.lock index 5918a8cf23a..630834fa61f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "content-security-policy" version = "0.5.4" -source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#58a09ee320fd6fbb828748ae04255e4c8d3f9c9e" +source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#dc1fd32b2b32b704a43f4ae170bb2cbb80a7cf59" dependencies = [ "base64 0.22.1", "bitflags 2.9.1", diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 33d0da952fb..e99776be350 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -170,8 +170,12 @@ pub async fn fetch_with_cors_cache( // TODO: We don't implement fetchParams as defined in the spec } -fn convert_request_to_csp_request(request: &Request, origin: &ImmutableOrigin) -> csp::Request { - csp::Request { +pub(crate) fn convert_request_to_csp_request(request: &Request) -> Option { + let origin = match &request.origin { + Origin::Client => return None, + Origin::Origin(origin) => origin, + }; + let csp_request = csp::Request { url: request.url().into_url(), origin: origin.clone().into_url_origin(), redirect_count: request.redirect_count, @@ -190,45 +194,58 @@ fn convert_request_to_csp_request(request: &Request, origin: &ImmutableOrigin) - ParserMetadata::NotParserInserted => csp::ParserMetadata::NotParserInserted, ParserMetadata::Default => csp::ParserMetadata::None, }, - } + }; + Some(csp_request) } /// pub fn should_request_be_blocked_by_csp( - request: &Request, + csp_request: &csp::Request, policy_container: &PolicyContainer, ) -> (csp::CheckResult, Vec) { - let origin = match &request.origin { - Origin::Client => return (csp::CheckResult::Allowed, Vec::new()), - Origin::Origin(origin) => origin, - }; - let csp_request = convert_request_to_csp_request(request, origin); - policy_container .csp_list .as_ref() - .map(|c| c.should_request_be_blocked(&csp_request)) + .map(|c| c.should_request_be_blocked(csp_request)) .unwrap_or((csp::CheckResult::Allowed, Vec::new())) } /// pub fn report_violations_for_request_by_csp( - request: &Request, + csp_request: &csp::Request, policy_container: &PolicyContainer, ) -> Vec { - let origin = match &request.origin { - Origin::Client => return Vec::new(), - Origin::Origin(origin) => origin, - }; - let csp_request = convert_request_to_csp_request(request, origin); - policy_container .csp_list .as_ref() - .map(|c| c.report_violations_for_request(&csp_request)) + .map(|c| c.report_violations_for_request(csp_request)) .unwrap_or_default() } +fn should_response_be_blocked_by_csp( + csp_request: &csp::Request, + response: &Response, + policy_container: &PolicyContainer, +) -> (csp::CheckResult, Vec) { + if response.is_network_error() { + return (csp::CheckResult::Allowed, Vec::new()); + } + let csp_response = csp::Response { + url: response + .actual_response() + .url() + .cloned() + .expect("response must have a url") + .into_url(), + redirect_count: csp_request.redirect_count, + }; + policy_container + .csp_list + .as_ref() + .map(|c| c.should_response_to_request_be_blocked(csp_request, &csp_response)) + .unwrap_or((csp::CheckResult::Allowed, Vec::new())) +} + /// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch) pub async fn main_fetch( fetch_params: &mut FetchParams, @@ -270,13 +287,15 @@ pub async fn main_fetch( RequestPolicyContainer::Client => PolicyContainer::default(), RequestPolicyContainer::PolicyContainer(container) => container.to_owned(), }; + let csp_request = convert_request_to_csp_request(request); + if let Some(csp_request) = csp_request.as_ref() { + // Step 2.2. + let violations = report_violations_for_request_by_csp(csp_request, &policy_container); - // Step 2.2. - let violations = report_violations_for_request_by_csp(request, &policy_container); - - if !violations.is_empty() { - target.process_csp_violations(request, violations); - } + if !violations.is_empty() { + target.process_csp_violations(request, violations); + } + }; // Step 3. // TODO: handle request abort. @@ -309,22 +328,24 @@ pub async fn main_fetch( request.insecure_requests_policy ); } + if let Some(csp_request) = csp_request.as_ref() { + // 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. + let (check_result, violations) = + should_request_be_blocked_by_csp(csp_request, &policy_container); - // 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. - let (check_result, violations) = should_request_be_blocked_by_csp(request, &policy_container); + if !violations.is_empty() { + target.process_csp_violations(request, violations); + } - 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(), - ))) - } + if check_result == csp::CheckResult::Blocked { + warn!("Request blocked by CSP"); + response = Some(Response::network_error(NetworkError::Internal( + "Blocked by Content-Security-Policy".into(), + ))) + } + }; if should_request_be_blocked_due_to_a_bad_port(&request.current_url()) { response = Some(Response::network_error(NetworkError::Internal( "Request attempted on bad port".into(), @@ -530,6 +551,14 @@ pub async fn main_fetch( should_be_blocked_due_to_mime_type(request.destination, &response.headers); let should_replace_with_mixed_content = !response_is_network_error && should_response_be_blocked_as_mixed_content(request, &response, &context.protocols); + let should_replace_with_csp_error = csp_request.is_some_and(|csp_request| { + let (check_result, violations) = + should_response_be_blocked_by_csp(&csp_request, &response, &policy_container); + if !violations.is_empty() { + target.process_csp_violations(request, violations); + } + check_result == csp::CheckResult::Blocked + }); // Step 15. let mut network_error_response = response @@ -553,7 +582,7 @@ pub async fn main_fetch( // Step 19. If response is not a network error and any of the following returns blocked // * should internalResponse to request be blocked as mixed content - // TODO: * should internalResponse to request be blocked by Content Security Policy + // * should internalResponse to request be blocked by Content Security Policy // * should internalResponse to request be blocked due to its MIME type // * should internalResponse to request be blocked due to nosniff let mut blocked_error_response; @@ -572,6 +601,10 @@ pub async fn main_fetch( blocked_error_response = Response::network_error(NetworkError::Internal("Blocked as mixed content".into())); &blocked_error_response + } else if should_replace_with_csp_error { + blocked_error_response = + Response::network_error(NetworkError::Internal("Blocked due to CSP".into())); + &blocked_error_response } else { internal_response }; diff --git a/components/net/websocket_loader.rs b/components/net/websocket_loader.rs index 128436ac47c..8ad7df5d376 100644 --- a/components/net/websocket_loader.rs +++ b/components/net/websocket_loader.rs @@ -43,7 +43,8 @@ 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_by_csp, should_request_be_blocked_due_to_a_bad_port, + convert_request_to_csp_request, 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; @@ -390,14 +391,18 @@ fn connect( RequestPolicyContainer::PolicyContainer(container) => container.to_owned(), }; - let (check_result, violations) = should_request_be_blocked_by_csp(&request, &policy_container); + if let Some(csp_request) = convert_request_to_csp_request(&request) { + let (check_result, violations) = + should_request_be_blocked_by_csp(&csp_request, &policy_container); - if !violations.is_empty() { - let _ = resource_event_sender.send(WebSocketNetworkEvent::ReportCSPViolations(violations)); - } + 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()); + if check_result == csp::CheckResult::Blocked { + return Err("Blocked by Content-Security-Policy".to_string()); + } } let client = match create_request( diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index 3a7faebb345..bb64cbc3810 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -397312,7 +397312,7 @@ [] ], "script-src-strict_dynamic_parser_inserted.html.headers": [ - "b7918c93323eff9db66ad26a73b78798d35e5f7b", + "9d0b3b93d44db43be7d19c34483bc1e63ef777a0", [] ], "script-src-strict_dynamic_parser_inserted_correct_nonce.html.headers": [ @@ -568648,7 +568648,7 @@ ] ], "default-src-sri_hash.sub.html": [ - "87fce5961fd1854303377ee939b21b6275b312cf", + "87389c306a53fdffa9806ba05f08a097713bcc37", [ null, {} @@ -573246,7 +573246,7 @@ ] ], "script-src-sri_hash.sub.html": [ - "9216e2b0d4971fc46d0010e8dfa7375845187a8d", + "e290911183d0b9a5dccf4a6a2eaa3b12ee25c682", [ null, {} @@ -573351,7 +573351,7 @@ ] ], "script-src-strict_dynamic_parser_inserted.html": [ - "c5e33dc4253dbf3ce2b0c6cb2fca4b0306d68244", + "9a8ad7a4ef2b5592af70d4dcc56f291e75da8e1b", [ null, {} diff --git a/tests/wpt/meta/content-security-policy/connect-src/connect-src-syncxmlhttprequest-redirect-to-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/connect-src/connect-src-syncxmlhttprequest-redirect-to-blocked.sub.html.ini deleted file mode 100644 index e6156eab928..00000000000 --- a/tests/wpt/meta/content-security-policy/connect-src/connect-src-syncxmlhttprequest-redirect-to-blocked.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[connect-src-syncxmlhttprequest-redirect-to-blocked.sub.html] - [Expecting logs: ["PASS Sync XMLHttpRequest.send() did not follow the disallowed redirect.","TEST COMPLETE","violated-directive=connect-src"\]] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/connect-src/connect-src-xmlhttprequest-redirect-to-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/connect-src/connect-src-xmlhttprequest-redirect-to-blocked.sub.html.ini deleted file mode 100644 index 9b86b6b2d9c..00000000000 --- a/tests/wpt/meta/content-security-policy/connect-src/connect-src-xmlhttprequest-redirect-to-blocked.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[connect-src-xmlhttprequest-redirect-to-blocked.sub.html] - [Expecting logs: ["PASS XMLHttpRequest.send() did not follow the disallowed redirect.","TEST COMPLETE","violated-directive=connect-src"\]] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini index 5891a18681e..0c6a81792db 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini @@ -1,6 +1,3 @@ [script-tag.http.html] [Content Security Policy: Expects blocked for script-tag to same-http origin and swap-origin redirection from http context.] expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to same-http origin and swap-origin redirection from http context.: securitypolicyviolation] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini index 699a0dd6238..b5c643337e2 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini @@ -1,6 +1,3 @@ [script-tag.https.html] [Content Security Policy: Expects blocked for script-tag to same-https origin and swap-origin redirection from https context.] expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to same-https origin and swap-origin redirection from https context.: securitypolicyviolation] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini index 5891a18681e..0c6a81792db 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini @@ -1,6 +1,3 @@ [script-tag.http.html] [Content Security Policy: Expects blocked for script-tag to same-http origin and swap-origin redirection from http context.] expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to same-http origin and swap-origin redirection from http context.: securitypolicyviolation] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini index 699a0dd6238..b5c643337e2 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini @@ -1,6 +1,3 @@ [script-tag.https.html] [Content Security Policy: Expects blocked for script-tag to same-https origin and swap-origin redirection from https context.] expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to same-https origin and swap-origin redirection from https context.: securitypolicyviolation] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/generic/wildcard-host-part.sub.window.js.ini b/tests/wpt/meta/content-security-policy/generic/wildcard-host-part.sub.window.js.ini deleted file mode 100644 index 7770d4e4454..00000000000 --- a/tests/wpt/meta/content-security-policy/generic/wildcard-host-part.sub.window.js.ini +++ /dev/null @@ -1,2 +0,0 @@ -[wildcard-host-part.sub.window.html] - expected: CRASH diff --git a/tests/wpt/meta/content-security-policy/inside-worker/dedicatedworker-connect-src.html.ini b/tests/wpt/meta/content-security-policy/inside-worker/dedicatedworker-connect-src.html.ini index ad01d630c28..72db221f4dd 100644 --- a/tests/wpt/meta/content-security-policy/inside-worker/dedicatedworker-connect-src.html.ini +++ b/tests/wpt/meta/content-security-policy/inside-worker/dedicatedworker-connect-src.html.ini @@ -1,7 +1,4 @@ [dedicatedworker-connect-src.html] - [Same-origin => cross-origin 'fetch()' in http: with connect-src 'self'] - expected: FAIL - [Reports match in http: with connect-src 'self'] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini b/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini index 66a2ee93f3b..f160ad3d25d 100644 --- a/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini +++ b/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini @@ -1,10 +1,3 @@ [report-original-url.sub.html] - expected: TIMEOUT - [Block after redirect, same-origin = original URL in report] - expected: TIMEOUT - - [Block after redirect, cross-origin = original URL in report] - expected: TIMEOUT - [Violation report status OK.] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/img-src-redirect.sub.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/img-src-redirect.sub.html.ini deleted file mode 100644 index 95cb135df4e..00000000000 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/img-src-redirect.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[img-src-redirect.sub.html] - [The blocked URI in the security policy violation event should be the original URI before redirects.] - expected: FAIL diff --git a/tests/wpt/tests/content-security-policy/default-src/default-src-sri_hash.sub.html b/tests/wpt/tests/content-security-policy/default-src/default-src-sri_hash.sub.html index 87fce5961fd..87389c306a5 100644 --- a/tests/wpt/tests/content-security-policy/default-src/default-src-sri_hash.sub.html +++ b/tests/wpt/tests/content-security-policy/default-src/default-src-sri_hash.sub.html @@ -7,6 +7,9 @@ + @@ -18,6 +21,8 @@ var port = "{{ports[http][0]}}"; if (location.protocol === "https:") port = "{{ports[https][0]}}"; + // Since {{domains[www]}} is allowed by the CSP policy, regardless of the integrity check + // the request would be allowed. var crossorigin_base = location.protocol + "//{{domains[www]}}:" + port; // Test name, src, integrity, expected to run. diff --git a/tests/wpt/tests/content-security-policy/script-src/script-src-sri_hash.sub.html b/tests/wpt/tests/content-security-policy/script-src/script-src-sri_hash.sub.html index 9216e2b0d49..e290911183d 100644 --- a/tests/wpt/tests/content-security-policy/script-src/script-src-sri_hash.sub.html +++ b/tests/wpt/tests/content-security-policy/script-src/script-src-sri_hash.sub.html @@ -7,6 +7,9 @@ + @@ -18,6 +21,8 @@ var port = "{{ports[http][0]}}"; if (location.protocol === "https:") port = "{{ports[https][0]}}"; + // Since {{domains[www]}} is allowed by the CSP policy, regardless of the integrity check + // the request would be allowed. var crossorigin_base = location.protocol + "//{{domains[www]}}:" + port; // Test name, src, integrity, expected to run. diff --git a/tests/wpt/tests/content-security-policy/script-src/script-src-strict_dynamic_parser_inserted.html b/tests/wpt/tests/content-security-policy/script-src/script-src-strict_dynamic_parser_inserted.html index c5e33dc4253..9a8ad7a4ef2 100644 --- a/tests/wpt/tests/content-security-policy/script-src/script-src-strict_dynamic_parser_inserted.html +++ b/tests/wpt/tests/content-security-policy/script-src/script-src-strict_dynamic_parser_inserted.html @@ -2,11 +2,12 @@ - Parser-inserted scripts without a correct nonce are not allowed with `strict-dynamic` in the script-src directive. + Parser-inserted scripts without a correct nonce are not allowed with `Strict-Dynamic` in the script-src directive. - + + diff --git a/tests/wpt/tests/content-security-policy/script-src/script-src-strict_dynamic_parser_inserted.html.headers b/tests/wpt/tests/content-security-policy/script-src/script-src-strict_dynamic_parser_inserted.html.headers index b7918c93323..9d0b3b93d44 100644 --- a/tests/wpt/tests/content-security-policy/script-src/script-src-strict_dynamic_parser_inserted.html.headers +++ b/tests/wpt/tests/content-security-policy/script-src/script-src-strict_dynamic_parser_inserted.html.headers @@ -2,4 +2,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache -Content-Security-Policy: script-src 'strict-dynamic' 'nonce-dummy' +Content-Security-Policy: script-src 'Strict-Dynamic' 'nonce-dummy'