aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/net/fetch/methods.rs67
-rw-r--r--components/script/dom/globalscope.rs54
-rw-r--r--components/script/script_runtime.rs55
-rw-r--r--components/script/timers.rs3
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;