Check CSP for inline event handlers (#36510)

This also ensures that document now reports all violations and we set
the correct directive.

With these changes, all `script-src-attr-elem` WPT tests pass.

Part of #36437 

Requires servo/rust-content-security-policy#3 to land first

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe 2025-04-17 23:11:25 +02:00 committed by GitHub
parent 70b3e24816
commit 2a81987590
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 58 additions and 569 deletions

View file

@ -4017,13 +4017,18 @@ impl Document {
.get_attribute(&ns!(), &local_name!("nonce"))
.map(|attr| Cow::Owned(attr.value().to_string())),
};
// TODO: Instead of ignoring violations, report them.
self.get_csp_list()
.map(|c| {
c.should_elements_inline_type_behavior_be_blocked(&element, type_, source)
.0
})
.unwrap_or(csp::CheckResult::Allowed)
let (result, violations) = match self.get_csp_list() {
None => {
return csp::CheckResult::Allowed;
},
Some(csp_list) => {
csp_list.should_elements_inline_type_behavior_be_blocked(&element, type_, source)
},
};
self.global().report_csp_violations(violations);
result
}
/// Prevent any JS or layout from running until the corresponding call to

View file

@ -11,6 +11,7 @@ use std::mem;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use content_security_policy as csp;
use deny_public_fields::DenyPublicFields;
use dom_struct::dom_struct;
use fnv::FnvHasher;
@ -551,9 +552,25 @@ impl EventTarget {
url: ServoUrl,
line: usize,
ty: &str,
source: DOMString,
source: &str,
) {
let handler = InternalRawUncompiledHandler { source, line, url };
if let Some(element) = self.downcast::<Element>() {
let doc = element.owner_document();
if doc.should_elements_inline_type_behavior_be_blocked(
element.upcast(),
csp::InlineCheckType::ScriptAttribute,
source,
) == csp::CheckResult::Blocked
{
return;
}
};
let handler = InternalRawUncompiledHandler {
source: DOMString::from(source),
line,
url,
};
self.set_inline_event_listener(
Atom::from(ty),
Some(InlineEventListener::Uncompiled(handler)),

View file

@ -3450,12 +3450,15 @@ impl GlobalScope {
pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) {
for violation in violations {
let sample = match violation.resource {
ViolationResource::Inline { .. } | ViolationResource::Url(_) => None,
ViolationResource::TrustedTypePolicy { sample } => Some(sample),
let (sample, resource) = match violation.resource {
ViolationResource::Inline { .. } => (None, "inline".to_owned()),
ViolationResource::Url(url) => (None, url.into()),
ViolationResource::TrustedTypePolicy { sample } => {
(Some(sample), "trusted-types-policy".to_owned())
},
};
let report = CSPViolationReportBuilder::default()
.resource("eval".to_owned())
.resource(resource)
.sample(sample)
.effective_directive(violation.directive.name)
.build(self);

View file

@ -201,13 +201,14 @@ impl VirtualMethods for HTMLBodyElement {
&local_name!("onresize") |
&local_name!("onunload") |
&local_name!("onerror") => {
let source = &**attr.value();
let evtarget = window.upcast::<EventTarget>(); // forwarded event
let source_line = 1; //TODO(#9604) obtain current JS execution line
evtarget.set_event_handler_uncompiled(
window.get_url(),
source_line,
&name[2..],
DOMString::from((**attr.value()).to_owned()),
source,
);
false
},

View file

@ -1084,14 +1084,14 @@ impl VirtualMethods for HTMLElement {
let element = self.as_element();
match (attr.local_name(), mutation) {
(name, AttributeMutation::Set(_)) if name.starts_with("on") => {
let source = &**attr.value();
let evtarget = self.upcast::<EventTarget>();
let source_line = 1; //TODO(#9604) get current JS execution line
evtarget.set_event_handler_uncompiled(
self.owner_window().get_url(),
source_line,
&name[2..],
// FIXME(ajeffrey): Convert directly from AttrValue to DOMString
DOMString::from(&**attr.value()),
source,
);
},
(&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {