/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/security/csp.rs
553 строки
19 KB
Tim van der Lippe
script: Move task related files to `script/tasks/` (#47071)
08 авг 2026, 07:17
Не верифицирован
08 авг 2026, 07:17
1aab9ba
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::borrow::Cow; /// Used to determine which inline check to run pub use content_security_policy::InlineCheckType; /// Used to report CSP violations in Fetch handlers pub use content_security_policy::Violation; use content_security_policy::{ CheckResult, CspList, Destination, Element as CspElement, Initiator, NavigationCheckType, Origin, ParserMetadata, PolicyDisposition, PolicySource, Request, Response as CspResponse, ViolationResource, }; use http::header::{HeaderMap, HeaderValue, ValueIter}; use hyper_serde::Serde; use js::context::JSContext; use js::realm::CurrentRealm; use js::rust::describe_scripted_caller_safe; use log::warn; use servo_constellation_traits::{LoadData, LoadOrigin}; use url::Url; use super::csppolicyviolationreport::CSPViolationReportBuilder; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::UnionTypes::TrustedScriptOrString; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::root::DomRoot; use crate::dom::element::Element; use crate::dom::globalscope::GlobalScope; use crate::dom::node::{Node, NodeTraits}; use crate::dom::reporting::reportingobserver::ReportingObserver; use crate::dom::security::cspviolationreporttask::CSPViolationReportTask; use crate::dom::trustedtypes::trustedscript::TrustedScript; use crate::dom::window::Window; use crate::tasks::task::TaskOnce; pub(crate) trait CspReporting { fn is_js_evaluation_allowed( &self, cx: &mut JSContext, global: &GlobalScope, source: &str, ) -> bool; fn is_wasm_evaluation_allowed(&self, cx: &mut JSContext, global: &GlobalScope) -> bool; fn should_navigation_request_be_blocked( &self, cx: &mut JSContext, global: &GlobalScope, load_data: &mut LoadData, element: Option<&Element>, ) -> bool; fn should_navigation_response_to_navigation_request_be_blocked( &self, cx: &mut JSContext, window: &Window, url: Url, self_origin: &url::Origin, ) -> bool; fn should_elements_inline_type_behavior_be_blocked( &self, cx: &mut JSContext, global: &GlobalScope, el: &Element, type_: InlineCheckType, source: &str, current_line: u32, ) -> bool; fn is_trusted_type_policy_creation_allowed( &self, cx: &mut JSContext, global: &GlobalScope, policy_name: &str, created_policy_names: &[&str], ) -> bool; fn does_sink_type_require_trusted_types( &self, sink_group: &str, include_report_only_policies: bool, ) -> bool; fn should_sink_type_mismatch_violation_be_blocked_by_csp( &self, cx: &mut JSContext, global: &GlobalScope, sink: &str, sink_group: &str, source: &str, ) -> bool; fn is_base_allowed_for_document( &self, cx: &mut JSContext, global: &GlobalScope, base: &url::Url, self_origin: &url::Origin, ) -> bool; fn concatenate(self, new_csp_list: Option<CspList>) -> Option<CspList>; } impl CspReporting for Option<CspList> { /// <https://www.w3.org/TR/CSP/#can-compile-strings> fn is_js_evaluation_allowed( &self, cx: &mut JSContext, global: &GlobalScope, source: &str, ) -> bool { let Some(csp_list) = self else { return true; }; let (is_js_evaluation_allowed, violations) = csp_list.is_js_evaluation_allowed(source); global.report_csp_violations(cx, violations, None, None); is_js_evaluation_allowed == CheckResult::Allowed } /// <https://www.w3.org/TR/CSP/#can-compile-wasm-bytes> fn is_wasm_evaluation_allowed(&self, cx: &mut JSContext, global: &GlobalScope) -> bool { let Some(csp_list) = self else { return true; }; let (is_wasm_evaluation_allowed, violations) = csp_list.is_wasm_evaluation_allowed(); global.report_csp_violations(cx, violations, None, None); is_wasm_evaluation_allowed == CheckResult::Allowed } /// <https://www.w3.org/TR/CSP/#should-block-navigation-request> fn should_navigation_request_be_blocked( &self, cx: &mut JSContext, global: &GlobalScope, load_data: &mut LoadData, element: Option<&Element>, ) -> bool { let Some(csp_list) = self else { return false; }; let mut request = Request { url: load_data.url.clone().into_url(), // TODO: Figure out how to propagate redirect data from LoadData into here current_url: load_data.url.clone().into_url(), origin: match &load_data.load_origin { LoadOrigin::Script(origin) => origin.immutable().clone().into_url_origin(), _ => Origin::new_opaque(), }, // TODO: populate this field correctly redirect_count: 0, destination: Destination::None, initiator: Initiator::None, nonce: "".to_owned(), integrity_metadata: "".to_owned(), parser_metadata: ParserMetadata::None, }; // TODO: set correct navigation check type for form submission if applicable let (result, violations) = csp_list.should_navigation_request_be_blocked( &mut request, NavigationCheckType::Other, |script_source| { // Step 4. Let convertedScriptSource be the result of executing // Process value with a default policy algorithm, with the following arguments: TrustedScript::get_trusted_type_compliant_string( cx, global, TrustedScriptOrString::String(script_source.into()), "Location href", ) .ok() .map(|s| s.into()) }, ); // In case trusted types processing has changed the Javascript contents load_data.url = request.url.into(); global.report_csp_violations(cx, violations, element, None); result == CheckResult::Blocked } /// <https://w3c.github.io/webappsec-csp/#should-block-navigation-response> fn should_navigation_response_to_navigation_request_be_blocked( &self, cx: &mut JSContext, window: &Window, url: Url, self_origin: &url::Origin, ) -> bool { let Some(csp_list) = self else { return false; }; let mut window_proxy = window.window_proxy(); let mut parent_navigable_origins = vec![]; loop { // Same-origin parents can go via their own script-thread (fast-path) if let Some(container_element) = window_proxy.frame_element() { let container_document = container_element.owner_document(); let parent_origin = Url::parse( &container_document .origin() .immutable() .ascii_serialization(), ) .expect("Must always be able to parse document origin"); parent_navigable_origins.push(parent_origin); window_proxy = container_document.window().window_proxy(); continue; } // Cross-origin parents go via the constellation (slower) if let Some(parent_proxy) = window_proxy.parent() { let Some(parent_origin) = parent_proxy.document_origin() else { break; }; let parent_origin = Url::parse(&parent_origin) .expect("Must always be able to parse document origin"); parent_navigable_origins.push(parent_origin); window_proxy = DomRoot::from_ref(parent_proxy); continue; } // We don't have a parent, hence we stop traversing break; } let (is_navigation_response_blocked, violations) = csp_list .should_navigation_response_to_navigation_request_be_blocked( &CspResponse { url, redirect_count: 0, }, self_origin, &parent_navigable_origins, ); window .as_global_scope() .report_csp_violations(cx, violations, None, None); is_navigation_response_blocked == CheckResult::Blocked } /// <https://www.w3.org/TR/CSP/#should-block-inline> fn should_elements_inline_type_behavior_be_blocked( &self, cx: &mut JSContext, global: &GlobalScope, el: &Element, type_: InlineCheckType, source: &str, current_line: u32, ) -> bool { let Some(csp_list) = self else { return false; }; let element = CspElement { nonce: if el.is_nonceable() { Some(Cow::Owned(el.nonce_value().trim().to_owned())) } else { None }, }; let (result, violations) = csp_list.should_elements_inline_type_behavior_be_blocked(&element, type_, source); let source_position = el.compute_source_position(current_line.saturating_sub(2).max(1)); global.report_csp_violations(cx, violations, Some(el), Some(source_position)); result == CheckResult::Blocked } /// <https://w3c.github.io/trusted-types/dist/spec/#should-block-create-policy> fn is_trusted_type_policy_creation_allowed( &self, cx: &mut JSContext, global: &GlobalScope, policy_name: &str, created_policy_names: &[&str], ) -> bool { let Some(csp_list) = self else { return true; }; let (allowed_by_csp, violations) = csp_list.is_trusted_type_policy_creation_allowed(policy_name, created_policy_names); global.report_csp_violations(cx, violations, None, None); allowed_by_csp == CheckResult::Allowed } /// <https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-does-sink-type-require-trusted-types> fn does_sink_type_require_trusted_types( &self, sink_group: &str, include_report_only_policies: bool, ) -> bool { let Some(csp_list) = self else { return false; }; csp_list.does_sink_type_require_trusted_types(sink_group, include_report_only_policies) } /// <https://w3c.github.io/trusted-types/dist/spec/#should-block-sink-type-mismatch> fn should_sink_type_mismatch_violation_be_blocked_by_csp( &self, cx: &mut JSContext, global: &GlobalScope, sink: &str, sink_group: &str, source: &str, ) -> bool { let Some(csp_list) = self else { return false; }; let (allowed_by_csp, violations) = csp_list .should_sink_type_mismatch_violation_be_blocked_by_csp(sink, sink_group, source); global.report_csp_violations(cx, violations, None, None); allowed_by_csp == CheckResult::Blocked } /// <https://www.w3.org/TR/CSP3/#allow-base-for-document> fn is_base_allowed_for_document( &self, cx: &mut JSContext, global: &GlobalScope, base: &url::Url, self_origin: &url::Origin, ) -> bool { let Some(csp_list) = self else { return true; }; let (is_base_allowed, violations) = csp_list.is_base_allowed_for_document(base, self_origin); global.report_csp_violations(cx, violations, None, None); is_base_allowed == CheckResult::Allowed } fn concatenate(self, new_csp_list: Option<CspList>) -> Option<CspList> { let Some(new_csp_list) = new_csp_list else { return self; }; match self { None => Some(new_csp_list), Some(mut old_csp_list) => { old_csp_list.append(new_csp_list); Some(old_csp_list) }, } } } pub(crate) struct SourcePosition { pub(crate) source_file: String, pub(crate) line_number: u32, pub(crate) column_number: u32, } pub(crate) trait GlobalCspReporting { fn report_csp_violations( &self, cx: &mut JSContext, violations: Vec<Violation>, element: Option<&Element>, source_position: Option<SourcePosition>, ); } fn compute_scripted_caller_source_position(cx: &mut JSContext) -> SourcePosition { match describe_scripted_caller_safe(cx) { Ok(scripted_caller) => SourcePosition { source_file: scripted_caller.filename, line_number: scripted_caller.line, column_number: scripted_caller.col + 1, }, Err(()) => SourcePosition { source_file: String::new(), line_number: 0, column_number: 0, }, } } /// <https://www.w3.org/TR/CSP3/#obtain-violation-blocked-uri> fn obtain_blocked_uri_for_violation_resource_with_sample( resource: ViolationResource, ) -> (Option<String>, String) { // Step 1. Assert: resource is a URL or a string. // // Already done since we destructure the relevant enum value // Step 3. Return resource. match resource { ViolationResource::Inline { sample } => (sample, "inline".to_owned()), // Step 2. If resource is a URL, return the result of executing § 5.4 Strip URL for use in reports on resource. ViolationResource::Url(url) => ( Some(String::new()), ReportingObserver::strip_url_for_reports(url.into()), ), ViolationResource::TrustedTypePolicy { sample } => { (Some(sample), "trusted-types-policy".to_owned()) }, ViolationResource::TrustedTypeSink { sample } => { (Some(sample), "trusted-types-sink".to_owned()) }, ViolationResource::Eval { sample } => (sample, "eval".to_owned()), ViolationResource::WasmEval => (None, "wasm-eval".to_owned()), } } fn csp_violation_report_tasks( cx: &mut JSContext, global: &GlobalScope, violations: Vec<Violation>, element: Option<&Element>, source_position: Option<SourcePosition>, ) -> Vec<CSPViolationReportTask> { if violations.is_empty() { return Vec::new(); } warn!("Reporting CSP violations: {:?}", violations); let source_position = source_position.unwrap_or_else(|| compute_scripted_caller_source_position(cx)); violations .into_iter() .map(|violation| { let (sample, resource) = obtain_blocked_uri_for_violation_resource_with_sample(violation.resource); let report = CSPViolationReportBuilder::default() .resource(resource) .sample(sample) .effective_directive(violation.directive.name) .original_policy(violation.policy.to_string()) .report_only(violation.policy.disposition == PolicyDisposition::Report) .source_file(source_position.source_file.clone()) .line_number(source_position.line_number) .column_number(source_position.column_number) .build(global); // Step 1: Let global be violation’s global object. // We use the passed-in `global` as the violation's global object. // Step 2: Let target be violation’s element. let target = element.and_then(|event_target| { // Step 3.1: If target is not null, and global is a Window, // and target’s shadow-including root is not global’s associated Document, set target to null. if let Some(window) = global.downcast::<Window>() { // If a node is connected, its owner document is always the shadow-including root. // If it isn't connected, then it also doesn't have a corresponding document, hence // it can't be this document. if event_target.upcast::<Node>().owner_document() != window.Document() { return None; } } Some(event_target) }); let target = match target { // Step 3.2: If target is null: None => { // Step 3.2.2: If target is a Window, set target to target’s associated Document. if let Some(window) = global.downcast::<Window>() { Trusted::new(window.Document().upcast()) } else { // Step 3.2.1: Set target to violation’s global object. Trusted::new(global.upcast()) } }, Some(event_target) => Trusted::new(event_target.upcast()), }; CSPViolationReportTask::new(Trusted::new(global), target, report, violation.policy) }) .collect() } impl GlobalScope { pub(crate) fn run_worker_csp_violation_report_tasks( &self, violations: Vec<Violation>, cx: &mut CurrentRealm, ) { // Worker CSP violations already crossed an event-loop boundary via // `CommonScriptMsg::ReportCspViolations`, so run the queued report // task here instead of adding another queued task on the owner global. for task in csp_violation_report_tasks(cx, self, violations, None, None) { task.run_once(cx); } } } impl GlobalCspReporting for GlobalScope { /// <https://www.w3.org/TR/CSP/#report-violation> fn report_csp_violations( &self, cx: &mut JSContext, violations: Vec<Violation>, element: Option<&Element>, source_position: Option<SourcePosition>, ) { // Step 3: Queue a task to run the following steps: for task in csp_violation_report_tasks(cx, self, violations, element, source_position) { self.task_manager() .dom_manipulation_task_source() .queue(task); } } } fn parse_and_potentially_append_to_csp_list( old_csp_list: Option<CspList>, csp_header_iter: ValueIter<HeaderValue>, disposition: PolicyDisposition, ) -> Option<CspList> { let mut csp_list = old_csp_list; for header in csp_header_iter { // This silently ignores the CSP if it contains invalid Unicode. // We should probably report an error somewhere. let new_csp_list = header .to_str() .ok() .map(|value| CspList::parse(value, PolicySource::Header, disposition)); csp_list = csp_list.concatenate(new_csp_list); } csp_list } /// <https://www.w3.org/TR/CSP/#parse-response-csp> pub(crate) fn parse_csp_list_from_metadata(headers: &Option<Serde<HeaderMap>>) -> Option<CspList> { let headers = headers.as_ref()?; let csp_enforce_list = parse_and_potentially_append_to_csp_list( None, headers.get_all("content-security-policy").iter(), PolicyDisposition::Enforce, ); parse_and_potentially_append_to_csp_list( csp_enforce_list, headers .get_all("content-security-policy-report-only") .iter(), PolicyDisposition::Report, ) }