diff options
Diffstat (limited to 'components')
36 files changed, 287 insertions, 48 deletions
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 0cfe698106c..69547e3f6a0 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -1329,6 +1329,7 @@ where ReferrerPolicy::EmptyString, None, None, + false, ); let ctx_id = BrowsingContextId::from(webview_id); let pipeline_id = match self.browsing_contexts.get(&ctx_id) { @@ -3046,6 +3047,7 @@ where ReferrerPolicy::EmptyString, None, None, + false, ); let sandbox = IFrameSandboxState::IFrameUnsandboxed; let is_private = false; @@ -4464,6 +4466,7 @@ where ReferrerPolicy::EmptyString, None, None, + false, ); self.load_url_for_webdriver( webview_id, diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 8c5758830c1..49b063f1bc8 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -34,7 +34,7 @@ use net_traits::{ use rustls_pki_types::CertificateDer; use serde::{Deserialize, Serialize}; use servo_arc::Arc as ServoArc; -use servo_url::{Host, ServoUrl}; +use servo_url::{Host, ImmutableOrigin, ServoUrl}; use tokio::sync::mpsc::{UnboundedReceiver as TokioReceiver, UnboundedSender as TokioSender}; use super::fetch_params::FetchParams; @@ -278,7 +278,6 @@ 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. - // TODO: check "should fetching request be blocked as mixed content" if should_request_be_blocked_by_csp(request, &policy_container) == csp::CheckResult::Blocked { warn!("Request blocked by CSP"); response = Some(Response::network_error(NetworkError::Internal( @@ -290,6 +289,11 @@ pub async fn main_fetch( "Request attempted on bad port".into(), ))); } + if should_request_be_blocked_as_mixed_content(request) { + response = Some(Response::network_error(NetworkError::Internal( + "Blocked as mixed content".into(), + ))); + } // Step 8: If request’s referrer policy is the empty string, then set request’s referrer policy // to request’s policy container’s referrer policy. @@ -480,6 +484,8 @@ pub async fn main_fetch( should_be_blocked_due_to_nosniff(request.destination, &response.headers); let should_replace_with_mime_type_error = !response_is_network_error && 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); // Step 15. let mut network_error_response = response @@ -502,7 +508,7 @@ pub async fn main_fetch( } // Step 19. If response is not a network error and any of the following returns blocked - // TODO: * should internalResponse to request be blocked as mixed content + // * 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 due to its MIME type // * should internalResponse to request be blocked due to nosniff @@ -518,6 +524,10 @@ pub async fn main_fetch( blocked_error_response = Response::network_error(NetworkError::Internal("Blocked by mime type".into())); &blocked_error_response + } else if should_replace_with_mixed_content { + blocked_error_response = + Response::network_error(NetworkError::Internal("Blocked as mixed content".into())); + &blocked_error_response } else { internal_response }; @@ -525,7 +535,10 @@ pub async fn main_fetch( // Step 20. If response’s type is "opaque", internalResponse’s status is 206, internalResponse’s // range-requested flag is set, and request’s header list does not contain `Range`, then set // response and internalResponse to a network error. - let internal_response = if response_type == ResponseType::Opaque && + // Also checking if internal response is a network error to prevent crash from attemtping to + // read status of a network error if we blocked the request above. + let internal_response = if !internal_response.is_network_error() && + response_type == ResponseType::Opaque && internal_response.status.code() == StatusCode::PARTIAL_CONTENT && internal_response.range_requested && !request.headers.contains_key(RANGE) @@ -914,6 +927,66 @@ pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool { false } +/// <https://w3c.github.io/webappsec-mixed-content/#should-block-fetch> +pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool { + // Step 1. Return allowed if one or more of the following conditions are met: + // 1.1. Does settings prohibit mixed security contexts? + // returns "Does Not Restrict Mixed Security Contexts" when applied to request’s client. + if do_settings_prohibit_mixed_security_contexts(request) == + MixedSecurityProhibited::NotProhibited + { + return false; + } + + // 1.2. request’s URL is a potentially trustworthy URL. + if request.url().is_potentially_trustworthy() { + return false; + } + + // 1.3. The user agent has been instructed to allow mixed content. + + // 1.4. request’s destination is "document", and request’s target browsing context has + // no parent browsing context. + if request.destination == Destination::Document { + // TODO: request's target browsing context has no parent browsing context + return false; + } + + true +} + +/// <https://w3c.github.io/webappsec-mixed-content/#should-block-response> +pub fn should_response_be_blocked_as_mixed_content(request: &Request, response: &Response) -> bool { + // Step 1. Return allowed if one or more of the following conditions are met: + // 1.1. Does settings prohibit mixed security contexts? returns Does Not Restrict Mixed Content + // when applied to request’s client. + if do_settings_prohibit_mixed_security_contexts(request) == + MixedSecurityProhibited::NotProhibited + { + return false; + } + + // 1.2. response’s url is a potentially trustworthy URL. + if response + .actual_response() + .url() + .is_some_and(|response_url| response_url.is_potentially_trustworthy()) + { + return false; + } + + // 1.3. TODO: The user agent has been instructed to allow mixed content. + + // 1.4. request’s destination is "document", and request’s target browsing context + // has no parent browsing context. + if request.destination == Destination::Document { + // TODO: if requests target browsing context has no parent browsing context + return false; + } + + true +} + /// <https://fetch.spec.whatwg.org/#bad-port> fn is_bad_port(port: u16) -> bool { static BAD_PORTS: [u16; 78] = [ @@ -983,14 +1056,36 @@ fn should_upgrade_request_to_potentially_trustworty( request.insecure_requests_policy == InsecureRequestsPolicy::Upgrade } -// TODO : Needs to revisit +#[derive(Debug, PartialEq)] +pub enum MixedSecurityProhibited { + Prohibited, + NotProhibited, +} + /// <https://w3c.github.io/webappsec-mixed-content/#categorize-settings-object> -fn does_settings_prohibit_mixed_security_contexts(url: &ServoUrl) -> bool { - if url.is_origin_trustworthy() { - return true; +fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecurityProhibited { + if let Origin::Origin(ref origin) = request.origin { + // Workers created from a data: url are secure if they were created from secure contexts + let is_origin_data_url_worker = matches!( + *origin, + ImmutableOrigin::Opaque(servo_url::OpaqueOrigin::SecureWorkerFromDataUrl(_)) + ); + + // Step 1. If settings’ origin is a potentially trustworthy origin, + // then return "Prohibits Mixed Security Contexts". + if origin.is_potentially_trustworthy() || is_origin_data_url_worker { + return MixedSecurityProhibited::Prohibited; + } } - false + // Step 2.2. For each navigable navigable in document’s ancestor navigables: + // Step 2.2.1. If navigable’s active document's origin is a potentially trustworthy origin, + // then return "Prohibits Mixed Security Contexts". + if request.has_trustworthy_ancestor_origin { + return MixedSecurityProhibited::Prohibited; + } + + MixedSecurityProhibited::NotProhibited } /// <https://w3c.github.io/webappsec-mixed-content/#upgrade-algorithm> @@ -1008,12 +1103,14 @@ fn should_upgrade_mixed_content_request(request: &Request) -> bool { } // Step 1.3 - if !does_settings_prohibit_mixed_security_contexts(&url) { + if do_settings_prohibit_mixed_security_contexts(request) == + MixedSecurityProhibited::NotProhibited + { return false; } // Step 1.4 : request’s destination is not "image", "audio", or "video". - if matches!( + if !matches!( request.destination, Destination::Audio | Destination::Image | Destination::Video ) { diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index b6f107878a4..609507529dd 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -1010,7 +1010,7 @@ pub async fn http_redirect_fetch( // Step 8: Increase request’s redirect count by 1. request.redirect_count += 1; - // Step 7 + // Step 9 let same_origin = match request.origin { Origin::Origin(ref origin) => *origin == location_url.origin(), Origin::Client => panic!( diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index cc426494171..5bfde8dcfc7 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -355,6 +355,8 @@ impl DedicatedWorkerGlobalScope { let referrer = current_global.get_referrer(); let parent = current_global.runtime_handle(); let current_global_https_state = current_global.get_https_state(); + let current_global_ancestor_trustworthy = current_global.has_trustworthy_ancestor_origin(); + let is_secure_context = current_global.is_secure_context(); thread::Builder::new() .name(format!("WW:{}", worker_url.debug_compact())) @@ -384,8 +386,8 @@ impl DedicatedWorkerGlobalScope { .use_url_credentials(true) .pipeline_id(Some(pipeline_id)) .referrer_policy(referrer_policy) - .referrer_policy(referrer_policy) .insecure_requests_policy(insecure_requests_policy) + .has_trustworthy_ancestor_origin(current_global_ancestor_trustworthy) .origin(origin); let runtime = unsafe { @@ -418,7 +420,12 @@ impl DedicatedWorkerGlobalScope { // > scope`'s url's scheme is "data", and `inherited origin` // > otherwise. if worker_url.scheme() == "data" { - init.origin = ImmutableOrigin::new_opaque(); + // Workers created from a data: url are secure if they were created from secure contexts + if is_secure_context { + init.origin = ImmutableOrigin::new_opaque_data_url_worker(); + } else { + init.origin = ImmutableOrigin::new_opaque(); + } } let global = DedicatedWorkerGlobalScope::new( diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index e7cd9521de2..2e1d3859b4b 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -524,6 +524,8 @@ pub(crate) struct Document { /// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#insecure-requests-policy> #[no_trace] inherited_insecure_requests_policy: Cell<Option<InsecureRequestsPolicy>>, + //// <https://w3c.github.io/webappsec-mixed-content/#categorize-settings-object> + has_trustworthy_ancestor_origin: Cell<bool>, /// <https://w3c.github.io/IntersectionObserver/#document-intersectionobservertaskqueued> intersection_observer_task_queued: Cell<bool>, /// Active intersection observers that should be processed by this document in @@ -2479,7 +2481,9 @@ impl Document { mut request: RequestBuilder, listener: Listener, ) { - request = request.insecure_requests_policy(self.insecure_requests_policy()); + request = request + .insecure_requests_policy(self.insecure_requests_policy()) + .has_trustworthy_ancestor_origin(self.has_trustworthy_ancestor_or_current_origin()); let callback = NetworkListener { context: std::sync::Arc::new(Mutex::new(listener)), task_source: self @@ -2498,7 +2502,9 @@ impl Document { mut request: RequestBuilder, listener: Listener, ) { - request = request.insecure_requests_policy(self.insecure_requests_policy()); + request = request + .insecure_requests_policy(self.insecure_requests_policy()) + .has_trustworthy_ancestor_origin(self.has_trustworthy_ancestor_or_current_origin()); let callback = NetworkListener { context: std::sync::Arc::new(Mutex::new(listener)), task_source: self @@ -3735,6 +3741,7 @@ impl Document { is_initial_about_blank: bool, allow_declarative_shadow_roots: bool, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, + has_trustworthy_ancestor_origin: bool, ) -> Document { let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap()); @@ -3895,6 +3902,7 @@ impl Document { is_initial_about_blank: Cell::new(is_initial_about_blank), allow_declarative_shadow_roots: Cell::new(allow_declarative_shadow_roots), inherited_insecure_requests_policy: Cell::new(inherited_insecure_requests_policy), + has_trustworthy_ancestor_origin: Cell::new(has_trustworthy_ancestor_origin), intersection_observer_task_queued: Cell::new(false), intersection_observers: Default::default(), active_keyboard_modifiers: Cell::new(Modifiers::empty()), @@ -4052,6 +4060,7 @@ impl Document { is_initial_about_blank: bool, allow_declarative_shadow_roots: bool, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, + has_trustworthy_ancestor_origin: bool, can_gc: CanGc, ) -> DomRoot<Document> { Self::new_with_proto( @@ -4072,6 +4081,7 @@ impl Document { is_initial_about_blank, allow_declarative_shadow_roots, inherited_insecure_requests_policy, + has_trustworthy_ancestor_origin, can_gc, ) } @@ -4095,6 +4105,7 @@ impl Document { is_initial_about_blank: bool, allow_declarative_shadow_roots: bool, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, + has_trustworthy_ancestor_origin: bool, can_gc: CanGc, ) -> DomRoot<Document> { let document = reflect_dom_object_with_proto( @@ -4115,6 +4126,7 @@ impl Document { is_initial_about_blank, allow_declarative_shadow_roots, inherited_insecure_requests_policy, + has_trustworthy_ancestor_origin, )), window, proto, @@ -4248,6 +4260,7 @@ impl Document { false, self.allow_declarative_shadow_roots(), Some(self.insecure_requests_policy()), + self.has_trustworthy_ancestor_or_current_origin(), can_gc, ); new_doc @@ -4795,6 +4808,15 @@ impl Document { pub fn set_allow_declarative_shadow_roots(&self, value: bool) { self.allow_declarative_shadow_roots.set(value) } + + pub fn has_trustworthy_ancestor_origin(&self) -> bool { + self.has_trustworthy_ancestor_origin.get() + } + + pub fn has_trustworthy_ancestor_or_current_origin(&self) -> bool { + self.has_trustworthy_ancestor_origin.get() || + self.origin().immutable().is_potentially_trustworthy() + } } #[allow(non_snake_case)] @@ -4825,6 +4847,7 @@ impl DocumentMethods<crate::DomTypeHolder> for Document { false, doc.allow_declarative_shadow_roots(), Some(doc.insecure_requests_policy()), + doc.has_trustworthy_ancestor_or_current_origin(), can_gc, )) } diff --git a/components/script/dom/domimplementation.rs b/components/script/dom/domimplementation.rs index 09916e9a4dc..4adae2f4229 100644 --- a/components/script/dom/domimplementation.rs +++ b/components/script/dom/domimplementation.rs @@ -110,6 +110,7 @@ impl DOMImplementationMethods<crate::DomTypeHolder> for DOMImplementation { DocumentSource::NotFromParser, loader, Some(self.document.insecure_requests_policy()), + self.document.has_trustworthy_ancestor_or_current_origin(), can_gc, ); @@ -176,6 +177,7 @@ impl DOMImplementationMethods<crate::DomTypeHolder> for DOMImplementation { false, self.document.allow_declarative_shadow_roots(), Some(self.document.insecure_requests_policy()), + self.document.has_trustworthy_ancestor_or_current_origin(), can_gc, ); diff --git a/components/script/dom/domparser.rs b/components/script/dom/domparser.rs index 42782420bda..85d7cd0870f 100644 --- a/components/script/dom/domparser.rs +++ b/components/script/dom/domparser.rs @@ -90,6 +90,7 @@ impl DOMParserMethods<crate::DomTypeHolder> for DOMParser { false, false, Some(doc.insecure_requests_policy()), + doc.has_trustworthy_ancestor_or_current_origin(), can_gc, ); ServoParser::parse_html_document(&document, Some(s), url, can_gc); @@ -114,6 +115,7 @@ impl DOMParserMethods<crate::DomTypeHolder> for DOMParser { false, false, Some(doc.insecure_requests_policy()), + doc.has_trustworthy_ancestor_or_current_origin(), can_gc, ); ServoParser::parse_xml_document(&document, Some(s), url, can_gc); diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index 8b3dc4d5113..f5b2124eb2e 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -561,6 +561,7 @@ impl EventSourceMethods<crate::DomTypeHolder> for EventSource { Some(true), global.get_referrer(), global.insecure_requests_policy(), + global.has_trustworthy_ancestor_or_current_origin(), ) .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 a3f4f929ef8..d0d7830e2bb 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -2386,6 +2386,21 @@ impl GlobalScope { InsecureRequestsPolicy::DoNotUpgrade } + /// Whether this document has ancestor navigables that are trustworthy + pub(crate) fn has_trustworthy_ancestor_origin(&self) -> bool { + self.downcast::<Window>() + .is_some_and(|window| window.Document().has_trustworthy_ancestor_origin()) + } + + // Whether this document has a trustworthy origin or has trustowrthy ancestor navigables + pub(crate) fn has_trustworthy_ancestor_or_current_origin(&self) -> bool { + self.downcast::<Window>().is_some_and(|window| { + window + .Document() + .has_trustworthy_ancestor_or_current_origin() + }) + } + /// <https://html.spec.whatwg.org/multipage/#report-the-error> pub(crate) fn report_an_error(&self, error_info: ErrorInfo, value: HandleValue, can_gc: CanGc) { // Step 1. diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index e4b16a73cb4..fd85e3fd1a9 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -867,6 +867,7 @@ impl HTMLFormElement { target_document.get_referrer_policy(), Some(target_window.as_global_scope().is_secure_context()), Some(target_document.insecure_requests_policy()), + target_document.has_trustworthy_ancestor_origin(), ); // Step 22 diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index bfef6cebbe6..04127ef1b17 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -271,6 +271,7 @@ impl HTMLIFrameElement { document.get_referrer_policy(), Some(window.as_global_scope().is_secure_context()), Some(document.insecure_requests_policy()), + document.has_trustworthy_ancestor_or_current_origin(), ); let element = self.upcast::<Element>(); load_data.srcdoc = String::from(element.get_string_attribute(&local_name!("srcdoc"))); @@ -362,6 +363,7 @@ impl HTMLIFrameElement { referrer_policy, Some(window.as_global_scope().is_secure_context()), Some(document.insecure_requests_policy()), + document.has_trustworthy_ancestor_or_current_origin(), ); let pipeline_id = self.pipeline_id(); @@ -407,6 +409,7 @@ impl HTMLIFrameElement { document.get_referrer_policy(), Some(window.as_global_scope().is_secure_context()), Some(document.insecure_requests_policy()), + document.has_trustworthy_ancestor_or_current_origin(), ); let browsing_context_id = BrowsingContextId::new(); let webview_id = window.window_proxy().webview_id(); diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index ee23270a549..e7990561a55 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -424,6 +424,7 @@ impl HTMLImageElement { None, document.global().get_referrer(), document.insecure_requests_policy(), + document.has_trustworthy_ancestor_or_current_origin(), ) .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 aab9f28b48e..427153fefd9 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -82,6 +82,7 @@ struct LinkProcessingOptions { source_set: Option<()>, base_url: ServoUrl, insecure_requests_policy: InsecureRequestsPolicy, + has_trustworthy_ancestor_origin: bool, // Some fields that we don't need yet are missing } @@ -335,6 +336,7 @@ impl HTMLLinkElement { source_set: None, // FIXME base_url: document.borrow().base_url(), insecure_requests_policy: document.insecure_requests_policy(), + has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(), }; // Step 3. If el has an href attribute, then set options's href to the value of el's href attribute. @@ -669,6 +671,7 @@ impl LinkProcessingOptions { None, Referrer::NoReferrer, self.insecure_requests_policy, + self.has_trustworthy_ancestor_origin, ) .integrity_metadata(self.integrity) .policy_container(self.policy_container) diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 062f1749d28..a608ba40c92 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -894,6 +894,7 @@ impl HTMLMediaElement { None, self.global().get_referrer(), document.insecure_requests_policy(), + document.has_trustworthy_ancestor_or_current_origin(), ) .headers(headers) .origin(document.origin().immutable().clone()) diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 61047817594..02c2fd4c73a 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -558,6 +558,7 @@ impl PreInvoke for ClassicContext {} /// Steps 1-2 of <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> // This function is also used to prefetch a script in `script::dom::servoparser::prefetch`. +#[allow(clippy::too_many_arguments)] pub(crate) fn script_fetch_request( webview_id: WebViewId, url: ServoUrl, @@ -566,6 +567,7 @@ pub(crate) fn script_fetch_request( pipeline_id: PipelineId, options: ScriptFetchOptions, insecure_requests_policy: InsecureRequestsPolicy, + has_trustworthy_ancestor_origin: bool, ) -> RequestBuilder { // We intentionally ignore options' credentials_mode member for classic scripts. // The mode is initialized by create_a_potential_cors_request. @@ -577,6 +579,7 @@ pub(crate) fn script_fetch_request( None, options.referrer, insecure_requests_policy, + has_trustworthy_ancestor_origin, ) .origin(origin) .pipeline_id(Some(pipeline_id)) @@ -605,6 +608,7 @@ fn fetch_a_classic_script( script.global().pipeline_id(), options.clone(), doc.insecure_requests_policy(), + doc.has_trustworthy_ancestor_origin(), ); let request = doc.prepare_request(request); diff --git a/components/script/dom/location.rs b/components/script/dom/location.rs index 3241717fdf8..eabf9c83f81 100644 --- a/components/script/dom/location.rs +++ b/components/script/dom/location.rs @@ -126,6 +126,7 @@ impl Location { referrer_policy, None, // Top navigation doesn't inherit secure context Some(source_document.insecure_requests_policy()), + source_document.has_trustworthy_ancestor_origin(), ); self.window .load_url(history_handling, reload_triggered, load_data, can_gc); diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 0caac6be09e..eff078c49c4 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -2661,6 +2661,7 @@ impl Node { false, document.allow_declarative_shadow_roots(), Some(document.insecure_requests_policy()), + document.has_trustworthy_ancestor_or_current_origin(), can_gc, ); DomRoot::upcast::<Node>(document) diff --git a/components/script/dom/notification.rs b/components/script/dom/notification.rs index 0cdf0a74d69..11d786c8216 100644 --- a/components/script/dom/notification.rs +++ b/components/script/dom/notification.rs @@ -820,6 +820,7 @@ impl Notification { None, global.get_referrer(), global.insecure_requests_policy(), + global.has_trustworthy_ancestor_or_current_origin(), ) .origin(global.origin().immutable().clone()) .pipeline_id(Some(global.pipeline_id())) diff --git a/components/script/dom/request.rs b/components/script/dom/request.rs index 406b6b4222d..f7d73e6a2b4 100644 --- a/components/script/dom/request.rs +++ b/components/script/dom/request.rs @@ -113,6 +113,7 @@ fn net_request_from_global(global: &GlobalScope, url: ServoUrl) -> NetTraitsRequ .pipeline_id(Some(global.pipeline_id())) .https_state(global.get_https_state()) .insecure_requests_policy(global.insecure_requests_policy()) + .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin()) .build() } diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index a3881ef7f2e..0cc0854e34a 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -228,6 +228,7 @@ impl ServoParser { false, allow_declarative_shadow_roots, Some(context_document.insecure_requests_policy()), + context_document.has_trustworthy_ancestor_or_current_origin(), can_gc, ); diff --git a/components/script/dom/servoparser/prefetch.rs b/components/script/dom/servoparser/prefetch.rs index f2873406cd5..250b92f8c8b 100644 --- a/components/script/dom/servoparser/prefetch.rs +++ b/components/script/dom/servoparser/prefetch.rs @@ -73,6 +73,7 @@ impl Tokenizer { // block the main parser. prefetching: Cell::new(false), insecure_requests_policy: document.insecure_requests_policy(), + has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(), }; let options = Default::default(); let inner = TraceableTokenizer(HtmlTokenizer::new(sink, options)); @@ -105,6 +106,7 @@ struct PrefetchSink { prefetching: Cell<bool>, #[no_trace] insecure_requests_policy: InsecureRequestsPolicy, + has_trustworthy_ancestor_origin: bool, } /// The prefetch tokenizer produces trivial results @@ -146,6 +148,7 @@ impl TokenSink for PrefetchSink { parser_metadata: ParserMetadata::ParserInserted, }, self.insecure_requests_policy, + self.has_trustworthy_ancestor_origin, ); let _ = self .resource_threads @@ -164,6 +167,7 @@ impl TokenSink for PrefetchSink { None, self.referrer.clone(), self.insecure_requests_policy, + self.has_trustworthy_ancestor_origin, ) .origin(self.origin.clone()) .pipeline_id(Some(self.pipeline_id)) @@ -198,6 +202,7 @@ impl TokenSink for PrefetchSink { None, self.referrer.clone(), self.insecure_requests_policy, + self.has_trustworthy_ancestor_origin, ) .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 760318771fa..4cfb143a10d 100644 --- a/components/script/dom/websocket.rs +++ b/components/script/dom/websocket.rs @@ -261,6 +261,7 @@ impl WebSocketMethods<crate::DomTypeHolder> for WebSocket { let request = RequestBuilder::new(global.webview_id(), url_record, Referrer::NoReferrer) .origin(global.origin().immutable().clone()) .insecure_requests_policy(global.insecure_requests_policy()) + .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin()) .mode(RequestMode::WebSocket { protocols }) .service_workers_mode(ServiceWorkersMode::None) .credentials_mode(CredentialsMode::Include) diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs index feeece3a8e8..f4e429ee731 100644 --- a/components/script/dom/windowproxy.rs +++ b/components/script/dom/windowproxy.rs @@ -307,6 +307,7 @@ impl WindowProxy { document.get_referrer_policy(), None, // Doesn't inherit secure context None, + false, ); let load_info = AuxiliaryWebViewCreationRequest { load_data: load_data.clone(), @@ -485,6 +486,11 @@ impl WindowProxy { Some(target_document) => target_document, None => return Ok(None), }; + let has_trustworthy_ancestor_origin = if new { + target_document.has_trustworthy_ancestor_or_current_origin() + } else { + false + }; let target_window = target_document.window(); // Step 13, and 14.4, will have happened elsewhere, // since we've created a new browsing context and loaded it with about:blank. @@ -517,6 +523,7 @@ impl WindowProxy { referrer_policy, Some(secure), Some(target_document.insecure_requests_policy()), + has_trustworthy_ancestor_origin, ); let history_handling = if new { NavigationHistoryBehavior::Replace diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index f084d429730..6e70ffe34e0 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -296,6 +296,9 @@ impl WorkerGlobalScopeMethods<crate::DomTypeHolder> for WorkerGlobalScope { .use_url_credentials(true) .origin(global_scope.origin().immutable().clone()) .insecure_requests_policy(self.insecure_requests_policy()) + .has_trustworthy_ancestor_origin( + global_scope.has_trustworthy_ancestor_or_current_origin(), + ) .pipeline_id(Some(self.upcast::<GlobalScope>().pipeline_id())); let (url, source) = match fetch::load_whole_resource( diff --git a/components/script/dom/xmldocument.rs b/components/script/dom/xmldocument.rs index e438c7780b8..c21a0ad99ee 100644 --- a/components/script/dom/xmldocument.rs +++ b/components/script/dom/xmldocument.rs @@ -43,6 +43,7 @@ impl XMLDocument { source: DocumentSource, doc_loader: DocumentLoader, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, + has_trustworthy_ancestor_origin: bool, ) -> XMLDocument { XMLDocument { document: Document::new_inherited( @@ -62,6 +63,7 @@ impl XMLDocument { false, false, inherited_insecure_requests_policy, + has_trustworthy_ancestor_origin, ), } } @@ -79,6 +81,7 @@ impl XMLDocument { source: DocumentSource, doc_loader: DocumentLoader, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, + has_trustworthy_ancestor_origin: bool, can_gc: CanGc, ) -> DomRoot<XMLDocument> { let doc = reflect_dom_object( @@ -94,6 +97,7 @@ impl XMLDocument { source, doc_loader, inherited_insecure_requests_policy, + has_trustworthy_ancestor_origin, )), window, can_gc, diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 10836cd12c9..aa634d61ccc 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -688,6 +688,7 @@ impl XMLHttpRequestMethods<crate::DomTypeHolder> for XMLHttpRequest { .origin(self.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())); // step 4 (second half) @@ -1515,6 +1516,7 @@ impl XMLHttpRequest { false, false, Some(doc.insecure_requests_policy()), + doc.has_trustworthy_ancestor_origin(), can_gc, ) } diff --git a/components/script/fetch.rs b/components/script/fetch.rs index ca46eb6ae60..92610febc43 100644 --- a/components/script/fetch.rs +++ b/components/script/fetch.rs @@ -123,6 +123,7 @@ fn request_init_from_request(request: NetTraitsRequest) -> RequestBuilder { initiator: request.initiator, policy_container: request.policy_container, insecure_requests_policy: request.insecure_requests_policy, + has_trustworthy_ancestor_origin: request.has_trustworthy_ancestor_origin, https_state: request.https_state, response_tainting: request.response_tainting, crash: None, @@ -374,6 +375,7 @@ pub(crate) fn load_whole_resource( } /// <https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request> +#[allow(clippy::too_many_arguments)] pub(crate) fn create_a_potential_cors_request( webview_id: Option<WebViewId>, url: ServoUrl, @@ -382,6 +384,7 @@ pub(crate) fn create_a_potential_cors_request( same_origin_fallback: Option<bool>, referrer: Referrer, insecure_requests_policy: InsecureRequestsPolicy, + has_trustworthy_ancestor_origin: bool, ) -> RequestBuilder { RequestBuilder::new(webview_id, url, referrer) // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request @@ -401,4 +404,5 @@ pub(crate) fn create_a_potential_cors_request( .destination(destination) .use_url_credentials(true) .insecure_requests_policy(insecure_requests_policy) + .has_trustworthy_ancestor_origin(has_trustworthy_ancestor_origin) } diff --git a/components/script/links.rs b/components/script/links.rs index f997f50ebc2..55c7435cdb9 100644 --- a/components/script/links.rs +++ b/components/script/links.rs @@ -440,6 +440,7 @@ pub(crate) fn follow_hyperlink( referrer_policy, Some(secure), Some(document.insecure_requests_policy()), + document.has_trustworthy_ancestor_origin(), ); let target = Trusted::new(target_window); let task = task!(navigate_follow_hyperlink: move || { diff --git a/components/script/navigation.rs b/components/script/navigation.rs index 2c22e25c123..4a960299813 100644 --- a/components/script/navigation.rs +++ b/components/script/navigation.rs @@ -212,6 +212,7 @@ impl InProgressLoad { .inherited_insecure_requests_policy .unwrap_or(InsecureRequestsPolicy::DoNotUpgrade), ) + .has_trustworthy_ancestor_origin(self.load_data.has_trustworthy_ancestor_origin) .headers(self.load_data.headers.clone()) .body(self.load_data.data.clone()) .redirect_mode(RedirectMode::Manual) diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 51b48499844..5b5aa4b9254 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -3193,6 +3193,7 @@ impl ScriptThread { is_initial_about_blank, true, incomplete.load_data.inherited_insecure_requests_policy, + incomplete.load_data.has_trustworthy_ancestor_origin, can_gc, ); diff --git a/components/script/serviceworker_manager.rs b/components/script/serviceworker_manager.rs index dba412d9a42..d4089714031 100644 --- a/components/script/serviceworker_manager.rs +++ b/components/script/serviceworker_manager.rs @@ -321,7 +321,7 @@ impl ServiceWorkerManager { /// <https://w3c.github.io/ServiceWorker/#register-algorithm> fn handle_register_job(&mut self, mut job: Job) { - if !job.script_url.is_origin_trustworthy() { + if !job.script_url.origin().is_potentially_trustworthy() { // Step 1.1 let _ = job .client diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index 6570c3df284..6290d2709cc 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -351,6 +351,7 @@ impl StylesheetLoader<'_> { None, self.elem.global().get_referrer(), document.insecure_requests_policy(), + document.has_trustworthy_ancestor_or_current_origin(), ) .origin(document.origin().immutable().clone()) .pipeline_id(Some(self.elem.global().pipeline_id())) diff --git a/components/shared/net/request.rs b/components/shared/net/request.rs index 58cba5cba5c..8dec7668077 100644 --- a/components/shared/net/request.rs +++ b/components/shared/net/request.rs @@ -292,6 +292,7 @@ pub struct RequestBuilder { /// <https://fetch.spec.whatwg.org/#concept-request-policy-container> pub policy_container: RequestPolicyContainer, pub insecure_requests_policy: InsecureRequestsPolicy, + pub has_trustworthy_ancestor_origin: bool, /// <https://fetch.spec.whatwg.org/#concept-request-referrer> pub referrer: Referrer, @@ -344,6 +345,7 @@ impl RequestBuilder { origin: ImmutableOrigin::new_opaque(), policy_container: RequestPolicyContainer::default(), insecure_requests_policy: InsecureRequestsPolicy::DoNotUpgrade, + has_trustworthy_ancestor_origin: false, referrer, referrer_policy: ReferrerPolicy::EmptyString, pipeline_id: None, @@ -493,6 +495,14 @@ impl RequestBuilder { self } + pub fn has_trustworthy_ancestor_origin( + mut self, + has_trustworthy_ancestor_origin: bool, + ) -> RequestBuilder { + self.has_trustworthy_ancestor_origin = has_trustworthy_ancestor_origin; + self + } + /// <https://fetch.spec.whatwg.org/#request-service-workers-mode> pub fn service_workers_mode( mut self, @@ -546,6 +556,7 @@ impl RequestBuilder { request.crash = self.crash; request.policy_container = self.policy_container; request.insecure_requests_policy = self.insecure_requests_policy; + request.has_trustworthy_ancestor_origin = self.has_trustworthy_ancestor_origin; request } } @@ -621,6 +632,7 @@ pub struct Request { pub policy_container: RequestPolicyContainer, /// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#insecure-requests-policy> pub insecure_requests_policy: InsecureRequestsPolicy, + pub has_trustworthy_ancestor_origin: bool, pub https_state: HttpsState, /// Servo internal: if crash details are present, trigger a crash error page with these details. pub crash: Option<String>, @@ -668,6 +680,7 @@ impl Request { response_tainting: ResponseTainting::Basic, policy_container: RequestPolicyContainer::Client, insecure_requests_policy: InsecureRequestsPolicy::DoNotUpgrade, + has_trustworthy_ancestor_origin: false, https_state, crash: None, } diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index f73deb29e7f..91e4ca33dc4 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -117,7 +117,8 @@ pub struct LoadData { pub inherited_secure_context: Option<bool>, /// The inherited policy for upgrading insecure requests; None if not inherited. pub inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, - + /// Whether the page's ancestors have potentially trustworthy origin + pub has_trustworthy_ancestor_origin: bool, /// Servo internal: if crash details are present, trigger a crash error page with these details. pub crash: Option<String>, } @@ -134,6 +135,7 @@ pub enum JsEvalResult { impl LoadData { /// Create a new `LoadData` object. + #[allow(clippy::too_many_arguments)] pub fn new( load_origin: LoadOrigin, url: ServoUrl, @@ -142,6 +144,7 @@ impl LoadData { referrer_policy: ReferrerPolicy, inherited_secure_context: Option<bool>, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, + has_trustworthy_ancestor_origin: bool, ) -> LoadData { LoadData { load_origin, @@ -157,6 +160,7 @@ impl LoadData { inherited_secure_context, crash: None, inherited_insecure_requests_policy, + has_trustworthy_ancestor_origin, } } } diff --git a/components/url/lib.rs b/components/url/lib.rs index 17a8e6523a4..8aa04fd5ae4 100644 --- a/components/url/lib.rs +++ b/components/url/lib.rs @@ -224,33 +224,7 @@ impl ServoUrl { return true; } // Step 3 - self.is_origin_trustworthy() - } - - /// <https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy> - pub fn is_origin_trustworthy(&self) -> bool { - // Step 1 - if !self.origin().is_tuple() { - return false; - } - - // Step 3 - if self.scheme() == "https" || self.scheme() == "wss" { - true - // Steps 4-5 - } else if self.host().is_some() { - let host = self.host_str().unwrap(); - // Step 4 - if let Ok(ip_addr) = host.parse::<IpAddr>() { - ip_addr.is_loopback() - // Step 5 - } else { - host == "localhost" || host.ends_with(".localhost") - } - // Step 6 - } else { - self.scheme() == "file" - } + self.origin().is_potentially_trustworthy() } } diff --git a/components/url/origin.rs b/components/url/origin.rs index ede6b134135..a19d45b966c 100644 --- a/components/url/origin.rs +++ b/components/url/origin.rs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::RefCell; +use std::net::IpAddr; use std::rc::Rc; use malloc_size_of::malloc_size_of_is_0; @@ -39,7 +40,14 @@ impl ImmutableOrigin { /// Creates a new opaque origin that is only equal to itself. pub fn new_opaque() -> ImmutableOrigin { - ImmutableOrigin::Opaque(OpaqueOrigin(servo_rand::random_uuid())) + ImmutableOrigin::Opaque(OpaqueOrigin::Opaque(servo_rand::random_uuid())) + } + + // For use in mixed security context tests because data: URL workers inherit contexts + pub fn new_opaque_data_url_worker() -> ImmutableOrigin { + ImmutableOrigin::Opaque(OpaqueOrigin::SecureWorkerFromDataUrl( + servo_rand::random_uuid(), + )) } pub fn scheme(&self) -> Option<&str> { @@ -79,6 +87,43 @@ impl ImmutableOrigin { } } + /// <https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy> + pub fn is_potentially_trustworthy(&self) -> bool { + // 1. If origin is an opaque origin return "Not Trustworthy" + if matches!(self, ImmutableOrigin::Opaque(_)) { + return false; + } + + if let ImmutableOrigin::Tuple(scheme, host, _) = self { + // 3. If origin’s scheme is either "https" or "wss", return "Potentially Trustworthy" + if scheme == "https" || scheme == "wss" { + return true; + } + // 6. If origin’s scheme is "file", return "Potentially Trustworthy". + if scheme == "file" { + return true; + } + + // 4. If origin’s host matches one of the CIDR notations 127.0.0.0/8 or ::1/128, + // return "Potentially Trustworthy". + if let Ok(ip_addr) = host.to_string().parse::<IpAddr>() { + return ip_addr.is_loopback(); + } + // 5. If the user agent conforms to the name resolution rules in + // [let-localhost-be-localhost] and one of the following is true: + // * origin’s host is "localhost" or "localhost." + // * origin’s host ends with ".localhost" or ".localhost." + // then return "Potentially Trustworthy". + if let Host::Domain(domain) = host { + if domain == "localhost" || domain.ends_with(".localhost") { + return true; + } + } + } + // 9. Return "Not Trustworthy". + false + } + /// <https://html.spec.whatwg.org/multipage/#ascii-serialisation-of-an-origin> pub fn ascii_serialization(&self) -> String { self.clone().into_url_origin().ascii_serialization() @@ -87,8 +132,13 @@ impl ImmutableOrigin { /// Opaque identifier for URLs that have file or other schemes #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct OpaqueOrigin(Uuid); - +pub enum OpaqueOrigin { + Opaque(Uuid), + // Workers created from `data:` urls will have opaque origins but need to be treated + // as inheriting the secure context they were created in. This tracks that the origin + // was created in such a context + SecureWorkerFromDataUrl(Uuid), +} malloc_size_of_is_0!(OpaqueOrigin); /// A representation of an [origin](https://html.spec.whatwg.org/multipage/#origin-2). |