mirror of
https://github.com/lexmount/moli.git
synced 2026-09-26 08:01:32 +00:00
fix(csp): retain all violations in document policy checks
This commit is contained in:
@@ -672,6 +672,7 @@ pub(crate) struct DocumentPolicyContainer {
|
||||
pub(crate) document_content_security_policies: Vec<String>,
|
||||
pub(crate) response_content_security_policies: Vec<String>,
|
||||
pub(crate) response_content_security_report_only_policies: Vec<String>,
|
||||
pub(crate) inherited_meta_content_security_policies: Vec<String>,
|
||||
pub(crate) content_security_reporting_endpoints:
|
||||
crate::content_security_policy::ContentSecurityPolicyReportingEndpoints,
|
||||
pub(crate) credentialless: bool,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -324,6 +324,10 @@ impl JsContextHost {
|
||||
expected_current_owner,
|
||||
)?;
|
||||
debug_assert_eq!(owner_transition.retired_owner(), expected_current_owner);
|
||||
unsafe { &*self.runtime }.initialize_inherited_meta_content_security_policies(
|
||||
document_handle,
|
||||
&document_policy_container.inherited_meta_content_security_policies,
|
||||
);
|
||||
let ancestor_origins_refreshed =
|
||||
self.refresh_current_child_document_ancestor_origins(handle);
|
||||
debug_assert!(
|
||||
|
||||
@@ -41,10 +41,16 @@ impl JsContextHost {
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.and_then(crate::dom::native::Node::owner_document)
|
||||
&& let Some(permissions_policy) =
|
||||
self.document_permissions_policy_for_document_handle(parent_document)
|
||||
{
|
||||
policy_container.permissions_policy = permissions_policy;
|
||||
if let Some(permissions_policy) =
|
||||
self.document_permissions_policy_for_document_handle(parent_document)
|
||||
{
|
||||
policy_container.permissions_policy = permissions_policy;
|
||||
}
|
||||
// Inheritance clones the creator's policy list at navigation time.
|
||||
// Later parent meta mutations must not alter the child's policies.
|
||||
policy_container.inherited_meta_content_security_policies = unsafe { &*self.runtime }
|
||||
.meta_content_security_policy_strings_for_document(parent_document);
|
||||
}
|
||||
policy_container.document_referrer =
|
||||
self.document_url_for_child_context(handle).to_string();
|
||||
@@ -101,6 +107,12 @@ impl JsContextHost {
|
||||
let document_url = init.document_url.clone();
|
||||
let document_handle =
|
||||
self.create_empty_live_child_html_document(document_url.clone(), Some("text/html"));
|
||||
unsafe { &*self.runtime }.initialize_inherited_meta_content_security_policies(
|
||||
document_handle,
|
||||
&init
|
||||
.policy_container
|
||||
.inherited_meta_content_security_policies,
|
||||
);
|
||||
self.dom_host_mut()
|
||||
.set_document_fallback_base_url_for_handle(
|
||||
document_handle,
|
||||
|
||||
@@ -419,16 +419,16 @@ impl JsContextHost {
|
||||
.clear_content_security_policy_for_bypass();
|
||||
}
|
||||
let ancestor_origins = self.child_document_frame_ancestor_origins(handle);
|
||||
let (report_only_violation, enforced_violation) = loaded
|
||||
let (report_only_violations, enforced_violations) = loaded
|
||||
.policy_container
|
||||
.navigation_response_frame_ancestors_check(
|
||||
&loaded.final_url,
|
||||
DocumentNavigationEmbeddingContext::Nested(&ancestor_origins),
|
||||
)
|
||||
.into_violations();
|
||||
for violation in report_only_violation
|
||||
for violation in report_only_violations
|
||||
.iter()
|
||||
.chain(enforced_violation.iter())
|
||||
.chain(enforced_violations.iter())
|
||||
{
|
||||
// The protected response never receives a Document when enforcement blocks
|
||||
// it, so there is no target on which to dispatch a DOM event. Keep the
|
||||
@@ -446,7 +446,7 @@ impl JsContextHost {
|
||||
&violation.report_to_endpoints,
|
||||
);
|
||||
}
|
||||
if let Some(violation) = enforced_violation {
|
||||
if let Some(violation) = enforced_violations.into_iter().next() {
|
||||
Err(format!(
|
||||
"child document response blocked by Content Security Policy `{}` for `{}`",
|
||||
violation.effective_directive, violation.blocked_uri
|
||||
|
||||
@@ -28,9 +28,12 @@ impl JsContextHost {
|
||||
handle: DomHandle,
|
||||
url: &Url,
|
||||
) -> Option<ChildBrowsingContextSnapshot> {
|
||||
let snapshot = self.apply_page_csp_bypass_to_child_snapshot(
|
||||
self.materialize_local_child_snapshot_for_url(url)?,
|
||||
);
|
||||
let mut snapshot = self.materialize_local_child_snapshot_for_url(url)?;
|
||||
if ChildBrowsingContextBootstrap::Url(url.clone()).content_security_policy_inherited() {
|
||||
snapshot.policy_container =
|
||||
self.initial_child_about_blank_policy_container_from_parent(handle);
|
||||
}
|
||||
let snapshot = self.apply_page_csp_bypass_to_child_snapshot(snapshot);
|
||||
if moli_url::is_about_blank(&snapshot.url) {
|
||||
Some(snapshot.with_fallback_base_url(self.document_base_url_for_child_context(handle)))
|
||||
} else {
|
||||
@@ -151,31 +154,29 @@ impl JsContextHost {
|
||||
handle: DomHandle,
|
||||
bootstrap: &ChildBrowsingContextBootstrap,
|
||||
) -> Option<ChildBrowsingContextSnapshot> {
|
||||
match bootstrap {
|
||||
let mut snapshot = match bootstrap {
|
||||
ChildBrowsingContextBootstrap::AboutBlank => {
|
||||
let policy_container =
|
||||
self.initial_child_about_blank_policy_container_from_parent(handle);
|
||||
Some(
|
||||
self.apply_page_csp_bypass_to_child_snapshot(
|
||||
ChildBrowsingContextSnapshot::about_blank(
|
||||
self.document_base_url_for_child_context(handle),
|
||||
)
|
||||
.with_policy_container(policy_container),
|
||||
),
|
||||
)
|
||||
Some(ChildBrowsingContextSnapshot::about_blank(
|
||||
self.document_base_url_for_child_context(handle),
|
||||
))
|
||||
}
|
||||
ChildBrowsingContextBootstrap::Srcdoc { base_url, markup } => Some(
|
||||
self.apply_page_csp_bypass_to_child_snapshot(ChildBrowsingContextSnapshot::srcdoc(
|
||||
ChildBrowsingContextBootstrap::Srcdoc { base_url, markup } => {
|
||||
Some(ChildBrowsingContextSnapshot::srcdoc(
|
||||
base_url.clone(),
|
||||
markup.clone(),
|
||||
self.document_character_set().to_owned(),
|
||||
)),
|
||||
),
|
||||
))
|
||||
}
|
||||
ChildBrowsingContextBootstrap::Url(url) => {
|
||||
self.materialize_local_child_snapshot_for_navigation_url(handle, url)
|
||||
return self.materialize_local_child_snapshot_for_navigation_url(handle, url);
|
||||
}
|
||||
ChildBrowsingContextBootstrap::Request(_) => None,
|
||||
}?;
|
||||
if bootstrap.content_security_policy_inherited() {
|
||||
snapshot.policy_container =
|
||||
self.initial_child_about_blank_policy_container_from_parent(handle);
|
||||
}
|
||||
Some(self.apply_page_csp_bypass_to_child_snapshot(snapshot))
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn child_document_fallback_character_set(
|
||||
|
||||
@@ -230,10 +230,6 @@ impl ChildBrowsingContextEntry {
|
||||
self.live_bootstrap.security_origin_inherited()
|
||||
}
|
||||
|
||||
pub(super) fn content_security_policy_inherited(&self) -> bool {
|
||||
self.live_bootstrap.content_security_policy_inherited()
|
||||
}
|
||||
|
||||
pub(super) fn document_policy_container_snapshot(&self) -> ChildDocumentPolicyContainer {
|
||||
self.document_policy_container.clone()
|
||||
}
|
||||
@@ -338,10 +334,6 @@ impl ChildBrowsingContextEntry {
|
||||
.as_slice()
|
||||
}
|
||||
|
||||
pub(super) fn has_response_content_security_policies(&self) -> bool {
|
||||
!self.response_content_security_policies().is_empty()
|
||||
}
|
||||
|
||||
pub(super) fn content_security_reporting_endpoints(
|
||||
&self,
|
||||
) -> crate::content_security_policy::ContentSecurityPolicyReportingEndpoints {
|
||||
@@ -367,6 +359,10 @@ impl ChildBrowsingContextEntry {
|
||||
self.document_policy_container
|
||||
.document_content_security_policies =
|
||||
policy_container.document_content_security_policies.clone();
|
||||
self.document_policy_container
|
||||
.inherited_meta_content_security_policies = policy_container
|
||||
.inherited_meta_content_security_policies
|
||||
.clone();
|
||||
self.document_policy_container
|
||||
.response_content_security_policies =
|
||||
policy_container.response_content_security_policies.clone();
|
||||
@@ -405,6 +401,11 @@ impl ChildBrowsingContextEntry {
|
||||
snapshot.policy_container.document_isolation_policy;
|
||||
self.document_policy_container.cross_origin_isolated =
|
||||
snapshot.policy_container.cross_origin_isolated;
|
||||
self.document_policy_container
|
||||
.inherited_meta_content_security_policies = snapshot
|
||||
.policy_container
|
||||
.inherited_meta_content_security_policies
|
||||
.clone();
|
||||
self.document_policy_container
|
||||
.response_content_security_policies = snapshot
|
||||
.policy_container
|
||||
|
||||
@@ -363,6 +363,9 @@ impl JsContextHost {
|
||||
content_security_reporting_endpoints: refresh_policy_source
|
||||
.map(|policy| policy.content_security_reporting_endpoints.clone())
|
||||
.unwrap_or_default(),
|
||||
inherited_meta_content_security_policies: refresh_policy_source
|
||||
.map(|policy| policy.inherited_meta_content_security_policies.clone())
|
||||
.unwrap_or_default(),
|
||||
permissions_policy: if is_new {
|
||||
// The synchronous initial about:blank Document is
|
||||
// already subject to the iframe's container policy.
|
||||
|
||||
@@ -508,6 +508,9 @@ impl LightweightPopupDocumentState {
|
||||
self.policy_container.cross_origin_isolated = response.cross_origin_isolated;
|
||||
self.policy_container.document_content_security_policies =
|
||||
response.document_content_security_policies;
|
||||
self.policy_container
|
||||
.inherited_meta_content_security_policies =
|
||||
response.inherited_meta_content_security_policies;
|
||||
self.policy_container.response_content_security_policies =
|
||||
response.response_content_security_policies;
|
||||
self.policy_container
|
||||
|
||||
@@ -99,14 +99,8 @@ impl JsContextHost {
|
||||
})
|
||||
}
|
||||
OwnerDispatchScope::Child(handle) => {
|
||||
let mut policy_container =
|
||||
let policy_container =
|
||||
self.child_browsing_context_policy_container_snapshot(handle)?;
|
||||
policy_container.response_content_security_policies =
|
||||
self.child_effective_enforced_content_security_policies(handle);
|
||||
policy_container.response_content_security_report_only_policies =
|
||||
self.child_effective_response_content_security_report_only_policies(handle);
|
||||
policy_container.content_security_reporting_endpoints =
|
||||
self.child_effective_content_security_reporting_endpoints(handle);
|
||||
Some(OwnerDocumentPolicySnapshot {
|
||||
document_handle: self.child_browsing_context_document_handle(handle),
|
||||
document_url: self.child_browsing_context_current_url(handle)?,
|
||||
@@ -300,22 +294,24 @@ impl JsContextHost {
|
||||
if policy_owner_dispatch_scope(scope) != OwnerDispatchScope::Top {
|
||||
return DocumentCspOutcome::SkippedNonTopContext;
|
||||
}
|
||||
let (report_only_violation, enforced_violation) = self
|
||||
let (report_only_violations, enforced_violations) = self
|
||||
.document_subresource_csp_check(request_url, kind)
|
||||
.into_violations();
|
||||
let host_ptr: *mut JsContextHost = self;
|
||||
if let Some(violation) = report_only_violation {
|
||||
for violation in report_only_violations {
|
||||
self.dispatch_content_security_policy_violation_event_best_effort(
|
||||
scope, host_ptr, &violation,
|
||||
);
|
||||
}
|
||||
let Some(violation) = enforced_violation else {
|
||||
return DocumentCspOutcome::Allowed;
|
||||
};
|
||||
self.dispatch_content_security_policy_violation_event_best_effort(
|
||||
scope, host_ptr, &violation,
|
||||
);
|
||||
DocumentCspOutcome::Blocked(violation)
|
||||
for violation in &enforced_violations {
|
||||
self.dispatch_content_security_policy_violation_event_best_effort(
|
||||
scope, host_ptr, violation,
|
||||
);
|
||||
}
|
||||
match enforced_violations.into_iter().next() {
|
||||
Some(violation) => DocumentCspOutcome::Blocked(violation),
|
||||
None => DocumentCspOutcome::Allowed,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_element_subresource_csp<'s>(
|
||||
@@ -334,7 +330,7 @@ impl JsContextHost {
|
||||
// The element's Document owns this request even when a different
|
||||
// Window's script changes its source or queues the update microtask.
|
||||
// SAFETY: this host and its DocumentRuntime are owned by the same ScriptVm.
|
||||
let (report_only_violation, enforced_violation) = unsafe { &*self.runtime }
|
||||
let (report_only_violations, enforced_violations) = unsafe { &*self.runtime }
|
||||
.document_subresource_csp_check_for_document(
|
||||
snapshot.document_handle,
|
||||
&snapshot.document_url,
|
||||
@@ -344,18 +340,20 @@ impl JsContextHost {
|
||||
)
|
||||
.into_violations();
|
||||
let host_ptr: *mut JsContextHost = self;
|
||||
if let Some(violation) = report_only_violation {
|
||||
for violation in report_only_violations {
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, &violation,
|
||||
);
|
||||
}
|
||||
let Some(violation) = enforced_violation else {
|
||||
return DocumentCspOutcome::Allowed;
|
||||
};
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, &violation,
|
||||
);
|
||||
DocumentCspOutcome::Blocked(violation)
|
||||
for violation in &enforced_violations {
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, violation,
|
||||
);
|
||||
}
|
||||
match enforced_violations.into_iter().next() {
|
||||
Some(violation) => DocumentCspOutcome::Blocked(violation),
|
||||
None => DocumentCspOutcome::Allowed,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn owner_dispatch_scope_for_node(
|
||||
@@ -404,34 +402,33 @@ impl JsContextHost {
|
||||
else {
|
||||
return true;
|
||||
};
|
||||
let (mut report_only_violation, mut enforced_violation) = check.into_violations();
|
||||
let (mut report_only_violations, mut enforced_violations) = check.into_violations();
|
||||
if owner == OwnerDispatchScope::Top
|
||||
&& let Some(position) = unsafe { &*self.runtime }.parser_script_start_position(script)
|
||||
{
|
||||
let line = i32::try_from(position.line).unwrap_or(i32::MAX);
|
||||
let column = i32::try_from(position.column).unwrap_or(i32::MAX);
|
||||
if let Some(violation) = report_only_violation.as_mut() {
|
||||
for violation in &mut report_only_violations {
|
||||
violation.line_number = line;
|
||||
violation.column_number = column;
|
||||
}
|
||||
if let Some(violation) = enforced_violation.as_mut() {
|
||||
for violation in &mut enforced_violations {
|
||||
violation.line_number = line;
|
||||
violation.column_number = column;
|
||||
}
|
||||
}
|
||||
let host_ptr: *mut JsContextHost = self;
|
||||
if let Some(violation) = report_only_violation {
|
||||
for violation in report_only_violations {
|
||||
self.dispatch_content_security_policy_violation_event_for_element_owner_best_effort(
|
||||
scope, host_ptr, owner, script, &violation,
|
||||
);
|
||||
}
|
||||
let Some(violation) = enforced_violation else {
|
||||
return true;
|
||||
};
|
||||
self.dispatch_content_security_policy_violation_event_for_element_owner_best_effort(
|
||||
scope, host_ptr, owner, script, &violation,
|
||||
);
|
||||
false
|
||||
for violation in &enforced_violations {
|
||||
self.dispatch_content_security_policy_violation_event_for_element_owner_best_effort(
|
||||
scope, host_ptr, owner, script, violation,
|
||||
);
|
||||
}
|
||||
enforced_violations.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn allows_inline_javascript_navigation_by_csp<'s>(
|
||||
@@ -463,20 +460,19 @@ impl JsContextHost {
|
||||
// owner. Committed documents must always produce a check.
|
||||
return true;
|
||||
};
|
||||
let (report_only_violation, enforced_violation) = check.into_violations();
|
||||
let (report_only_violations, enforced_violations) = check.into_violations();
|
||||
let host_ptr: *mut JsContextHost = self;
|
||||
if let Some(violation) = report_only_violation {
|
||||
for violation in report_only_violations {
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, &violation,
|
||||
);
|
||||
}
|
||||
let Some(violation) = enforced_violation else {
|
||||
return true;
|
||||
};
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, &violation,
|
||||
);
|
||||
false
|
||||
for violation in &enforced_violations {
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, violation,
|
||||
);
|
||||
}
|
||||
enforced_violations.is_empty()
|
||||
}
|
||||
|
||||
fn inline_source_csp_check_for_owner(
|
||||
@@ -574,20 +570,22 @@ impl JsContextHost {
|
||||
) else {
|
||||
return DocumentCspOutcome::Allowed;
|
||||
};
|
||||
let (report_only_violation, enforced_violation) = check.into_violations();
|
||||
let (report_only_violations, enforced_violations) = check.into_violations();
|
||||
let host_ptr: *mut JsContextHost = self;
|
||||
if let Some(violation) = report_only_violation {
|
||||
for violation in report_only_violations {
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, &violation,
|
||||
);
|
||||
}
|
||||
let Some(violation) = enforced_violation else {
|
||||
return DocumentCspOutcome::Allowed;
|
||||
};
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, &violation,
|
||||
);
|
||||
DocumentCspOutcome::Blocked(violation)
|
||||
for violation in &enforced_violations {
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, violation,
|
||||
);
|
||||
}
|
||||
match enforced_violations.into_iter().next() {
|
||||
Some(violation) => DocumentCspOutcome::Blocked(violation),
|
||||
None => DocumentCspOutcome::Allowed,
|
||||
}
|
||||
}
|
||||
|
||||
fn document_connect_csp_check_for_owner_with_redirect_status(
|
||||
@@ -637,7 +635,7 @@ impl JsContextHost {
|
||||
request_url,
|
||||
ContentSecurityPolicyRedirectStatus::NoRedirect,
|
||||
)
|
||||
.map(|check| check.into_violations().1.is_none())
|
||||
.map(|check| check.into_violations().1.is_empty())
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
@@ -726,133 +724,16 @@ impl JsContextHost {
|
||||
)
|
||||
}
|
||||
|
||||
fn child_effective_enforced_content_security_policies(
|
||||
&self,
|
||||
child_handle: DomHandle,
|
||||
) -> Vec<String> {
|
||||
let mut policies = Vec::new();
|
||||
if self
|
||||
.child_browsing_contexts
|
||||
.get(&child_handle)
|
||||
.is_some_and(|entry| entry.content_security_policy_inherited())
|
||||
{
|
||||
policies.extend(self.parent_effective_enforced_content_security_policies(child_handle));
|
||||
}
|
||||
policies.extend(
|
||||
self.child_browsing_contexts
|
||||
.get(&child_handle)
|
||||
.map(|entry| entry.response_content_security_policies().to_vec())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
policies
|
||||
}
|
||||
|
||||
fn child_effective_response_content_security_report_only_policies(
|
||||
&self,
|
||||
child_handle: DomHandle,
|
||||
) -> Vec<String> {
|
||||
let mut policies = Vec::new();
|
||||
if self
|
||||
.child_browsing_contexts
|
||||
.get(&child_handle)
|
||||
.is_some_and(|entry| entry.content_security_policy_inherited())
|
||||
{
|
||||
policies.extend(
|
||||
self.parent_effective_response_content_security_report_only_policies(child_handle),
|
||||
);
|
||||
}
|
||||
policies.extend(
|
||||
self.child_browsing_contexts
|
||||
.get(&child_handle)
|
||||
.map(|entry| {
|
||||
entry
|
||||
.response_content_security_report_only_policies()
|
||||
.to_vec()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
policies
|
||||
}
|
||||
|
||||
fn parent_effective_enforced_content_security_policies(
|
||||
&self,
|
||||
child_handle: DomHandle,
|
||||
) -> Vec<String> {
|
||||
match self.child_browsing_context_parent_handle(child_handle) {
|
||||
Some(parent) => {
|
||||
let mut policies = self.child_effective_enforced_content_security_policies(parent);
|
||||
if let Some(document) = self.child_browsing_context_document_handle(parent) {
|
||||
// SAFETY: JsContextHost is owned by the ScriptVm that owns this
|
||||
// DocumentRuntime.
|
||||
policies.extend(
|
||||
unsafe { &*self.runtime }
|
||||
.meta_content_security_policy_strings_for_document(document),
|
||||
);
|
||||
}
|
||||
policies
|
||||
}
|
||||
None => {
|
||||
// SAFETY: JsContextHost is owned by the ScriptVm that owns this
|
||||
// DocumentRuntime.
|
||||
let runtime = unsafe { &*self.runtime };
|
||||
let mut policies = runtime.response_content_security_policies().to_vec();
|
||||
policies.extend(
|
||||
runtime.meta_content_security_policy_strings_for_document(
|
||||
runtime.document_handle(),
|
||||
),
|
||||
);
|
||||
policies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_effective_response_content_security_report_only_policies(
|
||||
&self,
|
||||
child_handle: DomHandle,
|
||||
) -> Vec<String> {
|
||||
match self.child_browsing_context_parent_handle(child_handle) {
|
||||
Some(parent) => {
|
||||
self.child_effective_response_content_security_report_only_policies(parent)
|
||||
}
|
||||
None => unsafe { &*self.runtime }
|
||||
.response_content_security_report_only_policies()
|
||||
.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
fn child_effective_content_security_reporting_endpoints(
|
||||
&self,
|
||||
child_handle: DomHandle,
|
||||
) -> ContentSecurityPolicyReportingEndpoints {
|
||||
let inherits_parent = self
|
||||
.child_browsing_contexts
|
||||
.get(&child_handle)
|
||||
.is_some_and(|entry| entry.content_security_policy_inherited());
|
||||
let has_own_response_policies = self
|
||||
.child_browsing_contexts
|
||||
.get(&child_handle)
|
||||
.is_some_and(|entry| entry.has_response_content_security_policies());
|
||||
if inherits_parent && !has_own_response_policies {
|
||||
return self.parent_effective_content_security_reporting_endpoints(child_handle);
|
||||
}
|
||||
self.child_browsing_contexts
|
||||
.get(&child_handle)
|
||||
.map(|entry| entry.content_security_reporting_endpoints())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn parent_effective_content_security_reporting_endpoints(
|
||||
&self,
|
||||
child_handle: DomHandle,
|
||||
) -> ContentSecurityPolicyReportingEndpoints {
|
||||
match self.child_browsing_context_parent_handle(child_handle) {
|
||||
Some(parent) => self.child_effective_content_security_reporting_endpoints(parent),
|
||||
None => unsafe { &*self.runtime }
|
||||
.content_security_reporting_endpoints()
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn allows_eval_code_generation_by_csp<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
@@ -921,15 +802,15 @@ impl JsContextHost {
|
||||
check: DocumentContentSecurityPolicyCheck,
|
||||
include_call_location: bool,
|
||||
) -> bool {
|
||||
let (mut report_only_violation, mut enforced_violation) = check.into_violations();
|
||||
if report_only_violation.is_none() && enforced_violation.is_none() {
|
||||
let (mut report_only_violations, mut enforced_violations) = check.into_violations();
|
||||
if report_only_violations.is_empty() && enforced_violations.is_empty() {
|
||||
return true;
|
||||
}
|
||||
if include_call_location
|
||||
&& let Some((source_file, line_number, column_number)) =
|
||||
current_script_violation_location(scope)
|
||||
{
|
||||
for violation in [&mut report_only_violation, &mut enforced_violation]
|
||||
for violation in [&mut report_only_violations, &mut enforced_violations]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
@@ -939,18 +820,17 @@ impl JsContextHost {
|
||||
}
|
||||
}
|
||||
let host_ptr: *mut JsContextHost = self;
|
||||
if let Some(violation) = report_only_violation {
|
||||
for violation in report_only_violations {
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, &violation,
|
||||
);
|
||||
}
|
||||
let Some(violation) = enforced_violation else {
|
||||
return true;
|
||||
};
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, &violation,
|
||||
);
|
||||
false
|
||||
for violation in &enforced_violations {
|
||||
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
|
||||
scope, host_ptr, owner, violation,
|
||||
);
|
||||
}
|
||||
enforced_violations.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn child_wasm_eval_csp_violation(
|
||||
@@ -964,6 +844,8 @@ impl JsContextHost {
|
||||
)?
|
||||
.into_violations()
|
||||
.1
|
||||
.into_iter()
|
||||
.next()
|
||||
}
|
||||
|
||||
pub(crate) fn allows_trusted_type_policy_name_by_csp<'s>(
|
||||
|
||||
@@ -4530,7 +4530,7 @@ impl PageVm {
|
||||
);
|
||||
}
|
||||
|
||||
let (_, enforced_violation) = self
|
||||
let (_, enforced_violations) = self
|
||||
.vm()
|
||||
.document_runtime
|
||||
.style_element_request_csp_check(
|
||||
@@ -4540,7 +4540,7 @@ impl PageVm {
|
||||
},
|
||||
)
|
||||
.into_violations();
|
||||
if enforced_violation.is_some() {
|
||||
if !enforced_violations.is_empty() {
|
||||
return ScannedStylesheetAdmission::DeferredToParser(
|
||||
ScannedStylesheetDeferral::ContentSecurityPolicy,
|
||||
);
|
||||
@@ -4577,7 +4577,7 @@ impl PageVm {
|
||||
ScannedImageDeferral::FetchInterception,
|
||||
);
|
||||
}
|
||||
let (_, enforced_violation) = self
|
||||
let (_, enforced_violations) = self
|
||||
.vm()
|
||||
.document_runtime
|
||||
.document_subresource_csp_check(
|
||||
@@ -4585,7 +4585,7 @@ impl PageVm {
|
||||
crate::document_runtime::DocumentSubresourceCspKind::Image,
|
||||
)
|
||||
.into_violations();
|
||||
if enforced_violation.is_some() {
|
||||
if !enforced_violations.is_empty() {
|
||||
return ScannedImageAdmission::DeferredToParser(
|
||||
ScannedImageDeferral::ContentSecurityPolicy,
|
||||
);
|
||||
|
||||
@@ -45,11 +45,11 @@ impl ScriptVm {
|
||||
return complete_default_app_manifest(&document_url, Some(&manifest_url));
|
||||
}
|
||||
|
||||
let (_report_only_violation, enforced_violation) = self
|
||||
let (_report_only_violation, enforced_violations) = self
|
||||
.document_runtime
|
||||
.document_subresource_csp_check(&manifest_url, DocumentSubresourceCspKind::Manifest)
|
||||
.into_violations();
|
||||
if enforced_violation.is_some() {
|
||||
if !enforced_violations.is_empty() {
|
||||
return complete_default_app_manifest(&document_url, Some(&manifest_url));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,72 @@ fn inline_script_and_handler_csp_accept_base64url_hashes() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_script_reports_every_policy_and_only_enforced_policies_block() {
|
||||
let policies = [
|
||||
"script-src 'nonce-allowed' 'report-sample'".to_owned(),
|
||||
"script-src 'nonce-allowed'".to_owned(),
|
||||
];
|
||||
for enforce in [false, true] {
|
||||
let mut vm = new_storage_test_vm("https://multiple-inline-csp.test/page.html");
|
||||
if enforce {
|
||||
vm.set_response_content_security_policies(&policies);
|
||||
}
|
||||
vm.set_response_content_security_report_only_policies(&policies);
|
||||
vm.eval(
|
||||
r#"
|
||||
globalThis.multipleCspEvents = [];
|
||||
document.addEventListener('securitypolicyviolation', event => {
|
||||
multipleCspEvents.push({
|
||||
policy: event.originalPolicy,
|
||||
disposition: event.disposition,
|
||||
directive: event.effectiveDirective,
|
||||
blockedURI: event.blockedURI,
|
||||
sample: event.sample,
|
||||
target: event.target.id,
|
||||
});
|
||||
});
|
||||
const root = document.documentElement || document.appendChild(document.createElement('html'));
|
||||
const blocked = document.createElement('script');
|
||||
blocked.id = 'reported';
|
||||
blocked.text = 'globalThis.untrustedRan = true';
|
||||
root.appendChild(blocked);
|
||||
const allowed = document.createElement('script');
|
||||
allowed.nonce = 'allowed';
|
||||
allowed.text = 'globalThis.trustedRan = true';
|
||||
root.appendChild(allowed);
|
||||
"#,
|
||||
)
|
||||
.expect("inline script probe");
|
||||
drain_pre_domcontentloaded_non_script_page_tasks_for_test(&mut vm);
|
||||
assert_eq!(
|
||||
vm.eval("globalThis.untrustedRan === true").unwrap(),
|
||||
(!enforce).to_string(),
|
||||
);
|
||||
assert_eq!(vm.eval("globalThis.trustedRan === true").unwrap(), "true");
|
||||
let events: serde_json::Value =
|
||||
serde_json::from_str(&vm.eval("JSON.stringify(multipleCspEvents)").unwrap()).unwrap();
|
||||
let mut expected = Vec::new();
|
||||
for disposition in if enforce {
|
||||
vec!["report", "enforce"]
|
||||
} else {
|
||||
vec!["report"]
|
||||
} {
|
||||
for (index, policy) in policies.iter().enumerate() {
|
||||
expected.push(serde_json::json!({
|
||||
"policy": policy,
|
||||
"disposition": disposition,
|
||||
"directive": "script-src-elem",
|
||||
"blockedURI": "inline",
|
||||
"sample": if index == 0 { "globalThis.untrustedRan = true" } else { "" },
|
||||
"target": "reported",
|
||||
}));
|
||||
}
|
||||
}
|
||||
assert_eq!(events, serde_json::json!(expected));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_fetch_csp_uses_captured_parser_metadata_and_nonce() {
|
||||
let mut vm = new_storage_test_vm("https://module-csp-provenance.test/page.html");
|
||||
|
||||
@@ -43,7 +43,7 @@ impl DocumentRuntime {
|
||||
request_resource_type: moli_fetch::RequestResourceType,
|
||||
link_preload: bool,
|
||||
) -> Result<StylesheetFetch, OwnerlessStylesheetAdmissionError> {
|
||||
let (_, enforced_violation) = self
|
||||
let (_, enforced_violations) = self
|
||||
.response_style_element_request_csp_check(
|
||||
&request_url,
|
||||
crate::content_security_policy::ContentSecurityPolicyStyleElementRequest {
|
||||
@@ -51,7 +51,7 @@ impl DocumentRuntime {
|
||||
},
|
||||
)
|
||||
.into_violations();
|
||||
if enforced_violation.is_some() {
|
||||
if !enforced_violations.is_empty() {
|
||||
// The eventual DOM client owns violation reporting and its
|
||||
// load/error event. Speculation only decides whether a physical
|
||||
// resource may start.
|
||||
|
||||
Reference in New Issue
Block a user