diff options
Diffstat (limited to 'components')
-rw-r--r-- | components/net/fetch/methods.rs | 67 | ||||
-rw-r--r-- | components/script/dom/globalscope.rs | 54 | ||||
-rw-r--r-- | components/script/script_runtime.rs | 55 | ||||
-rw-r--r-- | components/script/timers.rs | 3 |
4 files changed, 93 insertions, 86 deletions
diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 53bc2817292..b1ad01b81e0 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -23,8 +23,8 @@ use net_traits::http_status::HttpStatus; use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer}; use net_traits::request::{ BodyChunkRequest, BodyChunkResponse, CredentialsMode, Destination, Initiator, - InsecureRequestsPolicy, Origin, RedirectMode, Referrer, Request, RequestMode, ResponseTainting, - Window, is_cors_safelisted_method, is_cors_safelisted_request_header, + InsecureRequestsPolicy, Origin, ParserMetadata, RedirectMode, Referrer, Request, RequestMode, + ResponseTainting, Window, is_cors_safelisted_method, is_cors_safelisted_request_header, }; use net_traits::response::{Response, ResponseBody, ResponseType}; use net_traits::{ @@ -169,6 +169,29 @@ 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 { + url: request.url().into_url(), + origin: origin.clone().into_url_origin(), + redirect_count: request.redirect_count, + destination: request.destination, + initiator: match request.initiator { + Initiator::Download => csp::Initiator::Download, + Initiator::ImageSet => csp::Initiator::ImageSet, + Initiator::Manifest => csp::Initiator::Manifest, + Initiator::Prefetch => csp::Initiator::Prefetch, + _ => csp::Initiator::None, + }, + nonce: request.cryptographic_nonce_metadata.clone(), + integrity_metadata: request.integrity_metadata.clone(), + parser_metadata: match request.parser_metadata { + ParserMetadata::ParserInserted => csp::ParserMetadata::ParserInserted, + ParserMetadata::NotParserInserted => csp::ParserMetadata::NotParserInserted, + ParserMetadata::Default => csp::ParserMetadata::None, + }, + } +} + /// <https://www.w3.org/TR/CSP/#should-block-request> pub fn should_request_be_blocked_by_csp( request: &Request, @@ -178,17 +201,7 @@ pub fn should_request_be_blocked_by_csp( Origin::Client => return (csp::CheckResult::Allowed, Vec::new()), 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, - destination: request.destination, - initiator: csp::Initiator::None, - nonce: request.cryptographic_nonce_metadata.clone(), - integrity_metadata: request.integrity_metadata.clone(), - parser_metadata: csp::ParserMetadata::None, - }; + let csp_request = convert_request_to_csp_request(request, origin); policy_container .csp_list @@ -197,6 +210,24 @@ pub fn should_request_be_blocked_by_csp( .unwrap_or((csp::CheckResult::Allowed, Vec::new())) } +/// <https://www.w3.org/TR/CSP/#report-for-request> +pub fn report_violations_for_request_by_csp( + request: &Request, + policy_container: &PolicyContainer, +) -> Vec<csp::Violation> { + 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)) + .unwrap_or_default() +} + /// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch) pub async fn main_fetch( fetch_params: &mut FetchParams, @@ -232,9 +263,6 @@ pub async fn main_fetch( ))); } - // Step 2.2. - // TODO: Report violations. - // The request should have a valid policy_container associated with it. // TODO: This should not be `Client` here let policy_container = match &request.policy_container { @@ -242,6 +270,13 @@ pub async fn main_fetch( RequestPolicyContainer::PolicyContainer(container) => container.to_owned(), }; + // Step 2.2. + let violations = report_violations_for_request_by_csp(request, &policy_container); + + if !violations.is_empty() { + target.process_csp_violations(request, violations); + } + // Step 3. // TODO: handle request abort. diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 77d1ee37c03..b3345b90fc0 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -2422,7 +2422,8 @@ impl GlobalScope { headers: &Option<Serde<HeaderMap>>, ) -> Option<CspList> { // TODO: Implement step 1 (local scheme special case) - let mut csp = headers.as_ref()?.get_all("content-security-policy").iter(); + let headers = headers.as_ref()?; + let mut csp = headers.get_all("content-security-policy").iter(); // This silently ignores the CSP if it contains invalid Unicode. // We should probably report an error somewhere. let c = csp.next().and_then(|c| c.to_str().ok())?; @@ -2435,6 +2436,19 @@ impl GlobalScope { PolicyDisposition::Enforce, )); } + let csp_report = headers + .get_all("content-security-policy-report-only") + .iter(); + // This silently ignores the CSP if it contains invalid Unicode. + // We should probably report an error somewhere. + for c in csp_report { + let c = c.to_str().ok()?; + csp_list.append(CspList::parse( + c, + PolicySource::Header, + PolicyDisposition::Report, + )); + } Some(csp_list) } @@ -2822,36 +2836,16 @@ impl GlobalScope { })) } - #[allow(unsafe_code)] - pub(crate) fn is_js_evaluation_allowed(&self, cx: SafeJSContext) -> bool { + pub(crate) fn is_js_evaluation_allowed(&self, source: &str) -> bool { let Some(csp_list) = self.get_csp_list() else { return true; }; - let scripted_caller = unsafe { describe_scripted_caller(*cx) }.unwrap_or_default(); - let is_js_evaluation_allowed = csp_list.is_js_evaluation_allowed() == CheckResult::Allowed; - - if !is_js_evaluation_allowed { - // FIXME: Don't fire event if `script-src` and `default-src` - // were not passed. - for policy in csp_list.0 { - let report = CSPViolationReportBuilder::default() - .resource("eval".to_owned()) - .effective_directive("script-src".to_owned()) - .report_only(policy.disposition == PolicyDisposition::Report) - .source_file(scripted_caller.filename.clone()) - .line_number(scripted_caller.line) - .column_number(scripted_caller.col) - .build(self); - let task = CSPViolationReportTask::new(self, report); + let (is_js_evaluation_allowed, violations) = csp_list.is_js_evaluation_allowed(source); - self.task_manager() - .dom_manipulation_task_source() - .queue(task); - } - } + self.report_csp_violations(violations); - is_js_evaluation_allowed + is_js_evaluation_allowed == CheckResult::Allowed } pub(crate) fn create_image_bitmap( @@ -3464,10 +3458,13 @@ impl GlobalScope { unreachable!(); } + #[allow(unsafe_code)] pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) { + let scripted_caller = + unsafe { describe_scripted_caller(*GlobalScope::get_cx()) }.unwrap_or_default(); for violation in violations { let (sample, resource) = match violation.resource { - ViolationResource::Inline { .. } => (None, "inline".to_owned()), + ViolationResource::Inline { sample } => (sample, "inline".to_owned()), ViolationResource::Url(url) => (None, url.into()), ViolationResource::TrustedTypePolicy { sample } => { (Some(sample), "trusted-types-policy".to_owned()) @@ -3475,6 +3472,8 @@ impl GlobalScope { ViolationResource::TrustedTypeSink { sample } => { (Some(sample), "trusted-types-sink".to_owned()) }, + ViolationResource::Eval { sample } => (sample, "eval".to_owned()), + ViolationResource::WasmEval => (None, "wasm-eval".to_owned()), }; let report = CSPViolationReportBuilder::default() .resource(resource) @@ -3482,6 +3481,9 @@ impl GlobalScope { .effective_directive(violation.directive.name) .original_policy(violation.policy.to_string()) .report_only(violation.policy.disposition == PolicyDisposition::Report) + .source_file(scripted_caller.filename.clone()) + .line_number(scripted_caller.line) + .column_number(scripted_caller.col + 1) .build(self); let task = CSPViolationReportTask::new(self, report); self.task_manager() diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index d6832a644ec..1f05c15d74e 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -19,7 +19,7 @@ use std::time::{Duration, Instant}; use std::{os, ptr, thread}; use background_hang_monitor_api::ScriptHangAnnotation; -use content_security_policy::{CheckResult, PolicyDisposition}; +use content_security_policy::CheckResult; use js::conversions::jsstr_to_string; use js::glue::{ CollectServoSizes, CreateJobQueue, DeleteJobQueue, DispatchableRun, JobQueueTraps, @@ -45,7 +45,7 @@ pub(crate) use js::rust::ThreadSafeJSContext; use js::rust::wrappers::{GetPromiseIsHandled, JS_GetPromiseResult}; use js::rust::{ Handle, HandleObject as RustHandleObject, IntoHandle, JSEngine, JSEngineHandle, ParentRuntime, - Runtime as RustRuntime, describe_scripted_caller, + Runtime as RustRuntime, }; use malloc_size_of::MallocSizeOfOps; use malloc_size_of_derive::MallocSizeOf; @@ -82,7 +82,6 @@ use crate::microtask::{EnqueuedPromiseCallback, Microtask, MicrotaskQueue}; use crate::realms::{AlreadyInRealm, InRealm}; use crate::script_module::EnsureModuleHooksInitialized; use crate::script_thread::trace_thread; -use crate::security_manager::{CSPViolationReportBuilder, CSPViolationReportTask}; use crate::task_source::SendableTaskSource; static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps { @@ -373,10 +372,6 @@ unsafe extern "C" fn content_security_policy_allows( let cx = JSContext::from_ptr(cx); wrap_panic(&mut || { // SpiderMonkey provides null pointer when executing webassembly. - let sample = match sample { - sample if !sample.is_null() => Some(jsstr_to_string(*cx, *sample)), - _ => None, - }; let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); let global = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)); let Some(csp_list) = global.get_csp_list() else { @@ -384,43 +379,19 @@ unsafe extern "C" fn content_security_policy_allows( return; }; - let is_js_evaluation_allowed = csp_list.is_js_evaluation_allowed() == CheckResult::Allowed; - let is_wasm_evaluation_allowed = - csp_list.is_wasm_evaluation_allowed() == CheckResult::Allowed; - let scripted_caller = describe_scripted_caller(*cx).unwrap_or_default(); - - let resource = match runtime_code { - RuntimeCode::JS => "eval".to_owned(), - RuntimeCode::WASM => "wasm-eval".to_owned(), - }; - - allowed = match runtime_code { - RuntimeCode::JS if is_js_evaluation_allowed => true, - RuntimeCode::WASM if is_wasm_evaluation_allowed => true, - _ => false, + let (is_evaluation_allowed, violations) = match runtime_code { + RuntimeCode::JS => { + let source = match sample { + sample if !sample.is_null() => &jsstr_to_string(*cx, *sample), + _ => "", + }; + csp_list.is_js_evaluation_allowed(source) + }, + RuntimeCode::WASM => csp_list.is_wasm_evaluation_allowed(), }; - if !allowed { - // FIXME: Don't fire event if `script-src` and `default-src` - // were not passed. - for policy in csp_list.0 { - let report = CSPViolationReportBuilder::default() - .resource(resource.clone()) - .sample(sample.clone()) - .report_only(policy.disposition == PolicyDisposition::Report) - .source_file(scripted_caller.filename.clone()) - .line_number(scripted_caller.line) - .column_number(scripted_caller.col) - .effective_directive("script-src".to_owned()) - .build(&global); - let task = CSPViolationReportTask::new(&global, report); - - global - .task_manager() - .dom_manipulation_task_source() - .queue(task); - } - } + global.report_csp_violations(violations); + allowed = is_evaluation_allowed == CheckResult::Allowed; }); allowed } diff --git a/components/script/timers.rs b/components/script/timers.rs index 244aa2df4ed..0afc3da164a 100644 --- a/components/script/timers.rs +++ b/components/script/timers.rs @@ -421,8 +421,7 @@ impl JsTimers { ) -> i32 { let callback = match callback { TimerCallback::StringTimerCallback(code_str) => { - let cx = GlobalScope::get_cx(); - if global.is_js_evaluation_allowed(cx) { + if global.is_js_evaluation_allowed(code_str.as_ref()) { InternalTimerCallback::StringTimerCallback(code_str) } else { return 0; |