mirror of
https://github.com/lexmount/moli.git
synced 2026-10-12 16:01:22 +00:00
fix(trusted-types): enforce Element attribute sinks
This commit is contained in:
@@ -264,7 +264,7 @@ pub(crate) fn trusted_script_url_string_or_throw<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
requirements: TrustedTypesForScriptRequirements,
|
||||
sink: &'static str,
|
||||
sink: &str,
|
||||
api_name: &'static str,
|
||||
) -> Option<String> {
|
||||
trusted_type_string_or_throw(
|
||||
@@ -282,7 +282,7 @@ pub(crate) fn trusted_html_string_or_throw<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
requirements: TrustedTypesForScriptRequirements,
|
||||
sink: &'static str,
|
||||
sink: &str,
|
||||
api_name: &'static str,
|
||||
) -> Option<String> {
|
||||
trusted_type_string_or_throw(
|
||||
@@ -307,7 +307,7 @@ pub(crate) fn trusted_script_string_or_type_error<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
requirements: TrustedTypesForScriptRequirements,
|
||||
sink: &'static str,
|
||||
sink: &str,
|
||||
api_name: &'static str,
|
||||
) -> Option<String> {
|
||||
trusted_type_string_or_throw(
|
||||
@@ -324,7 +324,7 @@ pub(crate) fn trusted_script_string_or_type_error<'s>(
|
||||
pub(crate) fn trusted_script_string_for_script_element_execution(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
original: &str,
|
||||
sink: &'static str,
|
||||
sink: &str,
|
||||
) -> Option<String> {
|
||||
let default_value = {
|
||||
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
||||
@@ -572,7 +572,7 @@ fn trusted_type_string_or_throw<'s>(
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
kind: TrustedTypeKind,
|
||||
requirements: TrustedTypesForScriptRequirements,
|
||||
sink: &'static str,
|
||||
sink: &str,
|
||||
api_name: &'static str,
|
||||
error_kind: TrustedTypeErrorKind,
|
||||
) -> Option<String> {
|
||||
@@ -607,7 +607,7 @@ fn apply_default_trusted_type_policy<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
input: &str,
|
||||
kind: TrustedTypeKind,
|
||||
sink: &'static str,
|
||||
sink: &str,
|
||||
error_kind: TrustedTypeErrorKind,
|
||||
) -> Option<String> {
|
||||
match apply_default_trusted_type_policy_outcome(scope, input, kind, sink) {
|
||||
@@ -625,7 +625,7 @@ fn apply_default_trusted_type_policy_outcome<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
input: &str,
|
||||
kind: TrustedTypeKind,
|
||||
sink: &'static str,
|
||||
sink: &str,
|
||||
) -> DefaultTrustedTypePolicyOutcome {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let Some(policy) = get_private_value(scope, global, TRUSTED_TYPES_DEFAULT_POLICY_SLOT)
|
||||
@@ -637,7 +637,9 @@ fn apply_default_trusted_type_policy_outcome<'s>(
|
||||
return DefaultTrustedTypePolicyOutcome::Exception;
|
||||
};
|
||||
let type_name = v8str(scope, kind.constructor_name());
|
||||
let sink = v8str(scope, sink);
|
||||
let Some(sink) = v8_string(scope, sink) else {
|
||||
return DefaultTrustedTypePolicyOutcome::Exception;
|
||||
};
|
||||
let args = [input.into(), type_name.into(), sink.into()];
|
||||
match invoke_policy_callback(scope, policy, kind, &args) {
|
||||
TrustedTypePolicyCallbackOutcome::Missing => DefaultTrustedTypePolicyOutcome::Unavailable,
|
||||
@@ -1240,7 +1242,7 @@ fn throw_trusted_type_error(
|
||||
error_kind: TrustedTypeErrorKind,
|
||||
api_name: &'static str,
|
||||
kind: TrustedTypeKind,
|
||||
sink: &'static str,
|
||||
sink: &str,
|
||||
) {
|
||||
let message = format!(
|
||||
"Failed to execute '{api_name}': This document requires '{}' assignment for the '{}' sink.",
|
||||
|
||||
+18
-10
@@ -233,11 +233,15 @@ pub(in crate::native_bridge) fn bridge_detached_set_attribute_callback<'a>(
|
||||
);
|
||||
return;
|
||||
}
|
||||
let Some(value) = args
|
||||
.get(2)
|
||||
.to_string(scope)
|
||||
.map(|value| value.to_rust_string_lossy(scope))
|
||||
else {
|
||||
let runtime_and_handle = detached_surface_runtime_and_handle(scope, args.this(), element);
|
||||
let Some(value) = super::super::super::element::trusted_attribute_value_string(
|
||||
scope,
|
||||
runtime_and_handle,
|
||||
None,
|
||||
&normalized,
|
||||
args.get(2),
|
||||
super::super::super::element::TrustedAttributeSetter::SetAttribute,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
with_detached_surface_attribute_reaction_scope(scope, args.this(), element, |scope| {
|
||||
@@ -293,11 +297,15 @@ pub(in crate::native_bridge) fn bridge_detached_set_attribute_ns_callback<'a>(
|
||||
);
|
||||
return;
|
||||
}
|
||||
let Some(value) = args
|
||||
.get(3)
|
||||
.to_string(scope)
|
||||
.map(|value| value.to_rust_string_lossy(scope))
|
||||
else {
|
||||
let runtime_and_handle = detached_surface_runtime_and_handle(scope, args.this(), element);
|
||||
let Some(value) = super::super::super::element::trusted_attribute_value_string(
|
||||
scope,
|
||||
runtime_and_handle,
|
||||
namespace.as_deref(),
|
||||
&local_name,
|
||||
args.get(3),
|
||||
super::super::super::element::TrustedAttributeSetter::SetAttributeNs,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
with_detached_surface_attribute_reaction_scope(scope, args.this(), element, |scope| {
|
||||
|
||||
@@ -54,6 +54,9 @@ mod trusted_types;
|
||||
pub(crate) use script_execution::{
|
||||
inline_script_source_for_execution, prepare_inline_classic_frame_script_job_for_execution,
|
||||
};
|
||||
pub(in crate::native_bridge) use trusted_types::{
|
||||
TrustedAttributeSetter, trusted_attribute_value_string,
|
||||
};
|
||||
use trusted_types::{
|
||||
TrustedScriptElementSink, trusted_script_element_sink_string, trusted_script_url_sink_string,
|
||||
};
|
||||
|
||||
@@ -22,22 +22,22 @@ struct AttributeNamespaceNameArgs {
|
||||
|
||||
#[derive(crate::webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Element setAttribute")]
|
||||
struct SetAttributeArgs {
|
||||
struct SetAttributeArgs<'s> {
|
||||
#[webidl(required)]
|
||||
name: String,
|
||||
#[webidl(required)]
|
||||
value: String,
|
||||
#[webidl(required, converter = "raw")]
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
}
|
||||
|
||||
#[derive(crate::webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Element setAttributeNS")]
|
||||
struct SetAttributeNsArgs {
|
||||
struct SetAttributeNsArgs<'s> {
|
||||
#[webidl(required, nullable)]
|
||||
namespace: Option<String>,
|
||||
#[webidl(required)]
|
||||
qualified_name: String,
|
||||
#[webidl(required)]
|
||||
value: String,
|
||||
#[webidl(required, converter = "raw")]
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
}
|
||||
|
||||
#[derive(crate::webidl::WebIdlArgs)]
|
||||
|
||||
@@ -12,11 +12,12 @@ use super::super::super::{
|
||||
throw_dom_exception, validate_attribute_name, validate_qualified_name_and_namespace,
|
||||
};
|
||||
use super::super::{
|
||||
element_has_attribute, remove_live_element_attribute_appending_to_current_reaction_queue,
|
||||
TrustedAttributeSetter, element_has_attribute,
|
||||
remove_live_element_attribute_appending_to_current_reaction_queue,
|
||||
remove_live_element_attribute_ns_appending_to_current_reaction_queue,
|
||||
set_live_element_attribute_appending_to_current_reaction_queue,
|
||||
set_live_element_attribute_ns_appending_to_current_reaction_queue,
|
||||
update_iframe_snapshot_navigation,
|
||||
trusted_attribute_value_string, update_iframe_snapshot_navigation,
|
||||
};
|
||||
use super::{
|
||||
AttributeNameArgs, AttributeNamespaceNameArgs, SetAttributeArgs, SetAttributeNsArgs,
|
||||
@@ -88,12 +89,28 @@ pub(in crate::native_bridge) fn node_set_attribute_callback<'s>(
|
||||
throw_invalid_attribute_name(scope);
|
||||
return;
|
||||
}
|
||||
let runtime = unsafe { &*runtime_ptr };
|
||||
let normalized_name = runtime
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.and_then(Node::as_element)
|
||||
.map(|element| element.normalized_attribute_name(&parsed.name))
|
||||
.unwrap_or_else(|| parsed.name.clone());
|
||||
let Some(value) = trusted_attribute_value_string(
|
||||
scope,
|
||||
Some((runtime_ptr, handle)),
|
||||
None,
|
||||
&normalized_name,
|
||||
parsed.value,
|
||||
TrustedAttributeSetter::SetAttribute,
|
||||
) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
if parsed.name.eq_ignore_ascii_case("src")
|
||||
&& unsafe { &*runtime_ptr }
|
||||
.dom_host()
|
||||
.is_html_element_named(handle, "iframe")
|
||||
&& runtime.dom_host().is_html_element_named(handle, "iframe")
|
||||
{
|
||||
update_iframe_snapshot_navigation(scope, runtime_ptr, handle, &parsed.value);
|
||||
update_iframe_snapshot_navigation(scope, runtime_ptr, handle, &value);
|
||||
rv.set_undefined();
|
||||
return;
|
||||
}
|
||||
@@ -103,7 +120,7 @@ pub(in crate::native_bridge) fn node_set_attribute_callback<'s>(
|
||||
runtime_ptr,
|
||||
handle,
|
||||
&parsed.name,
|
||||
&parsed.value,
|
||||
&value,
|
||||
);
|
||||
});
|
||||
rv.set_undefined();
|
||||
@@ -136,6 +153,17 @@ pub(in crate::native_bridge) fn node_set_attribute_ns_callback<'s>(
|
||||
return;
|
||||
}
|
||||
};
|
||||
let Some(value) = trusted_attribute_value_string(
|
||||
scope,
|
||||
Some((runtime_ptr, handle)),
|
||||
namespace.as_deref(),
|
||||
&local_name,
|
||||
parsed.value,
|
||||
TrustedAttributeSetter::SetAttributeNs,
|
||||
) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
custom_elements::with_custom_element_reaction_scope(scope, runtime_ptr, |scope| {
|
||||
let _ = set_live_element_attribute_ns_appending_to_current_reaction_queue(
|
||||
scope,
|
||||
@@ -145,7 +173,7 @@ pub(in crate::native_bridge) fn node_set_attribute_ns_callback<'s>(
|
||||
prefix.as_deref(),
|
||||
&local_name,
|
||||
&parsed.qualified_name,
|
||||
&parsed.value,
|
||||
&value,
|
||||
);
|
||||
});
|
||||
rv.set_undefined();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
context_bootstrap::WINDOW_EVENT_HANDLER_PROPERTIES,
|
||||
document_runtime::EventTargetHandle,
|
||||
util::{
|
||||
context_host_ptr_from_global_bridge, node_wrapper_from_handle, throw_type_error, v8_string,
|
||||
@@ -110,6 +111,25 @@ const DOCUMENT_EVENT_HANDLER_PROPERTIES: &[&str] = &[
|
||||
"onpointerlockerror",
|
||||
"onreadystatechange",
|
||||
];
|
||||
const ELEMENT_SPECIFIC_EVENT_HANDLER_PROPERTIES: &[&str] = &[
|
||||
"onencrypted",
|
||||
"onwaitingforkey",
|
||||
"onbegin",
|
||||
"onend",
|
||||
"onrepeat",
|
||||
];
|
||||
|
||||
pub(in crate::native_bridge::element) fn is_element_event_handler_content_attribute_name(
|
||||
name: &str,
|
||||
) -> bool {
|
||||
GENERIC_EVENT_HANDLER_PROPERTIES
|
||||
.iter()
|
||||
.chain(WINDOW_EVENT_HANDLER_PROPERTIES)
|
||||
.chain(ELEMENT_SPECIFIC_EVENT_HANDLER_PROPERTIES)
|
||||
.copied()
|
||||
.chain(["onmessageerror"])
|
||||
.any(|candidate| candidate == name)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum GlobalEventHandlerOwner {
|
||||
|
||||
@@ -14,6 +14,7 @@ pub(crate) use body_window::compile_window_body_onmessageerror_attribute;
|
||||
pub(super) use body_window::{
|
||||
body_onmessageerror_getter_function, body_onmessageerror_setter_function,
|
||||
};
|
||||
pub(super) use generic::is_element_event_handler_content_attribute_name;
|
||||
pub(crate) use generic::{
|
||||
GlobalEventHandlerOwner, install_global_event_handler_template_bindings,
|
||||
node_event_handler_getter_function, node_event_handler_setter_function,
|
||||
|
||||
@@ -1,6 +1,33 @@
|
||||
use super::JsContextHost;
|
||||
use crate::document_runtime::DomHandle;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(in crate::native_bridge) enum TrustedAttributeSetter {
|
||||
SetAttribute,
|
||||
SetAttributeNs,
|
||||
}
|
||||
|
||||
impl TrustedAttributeSetter {
|
||||
fn api_name(self) -> &'static str {
|
||||
match self {
|
||||
Self::SetAttribute => "setAttribute",
|
||||
Self::SetAttributeNs => "setAttributeNS",
|
||||
}
|
||||
}
|
||||
|
||||
fn conversion_context(self) -> crate::webidl::Context {
|
||||
match self {
|
||||
Self::SetAttribute => crate::webidl::Context::argument("Element setAttribute", 2),
|
||||
Self::SetAttributeNs => crate::webidl::Context::argument("Element setAttributeNS", 3),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum TrustedAttributeSink {
|
||||
Script(String),
|
||||
ScriptUrl(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(in crate::native_bridge::element) enum TrustedHtmlSink {
|
||||
ElementInnerHtml,
|
||||
@@ -126,6 +153,102 @@ pub(in crate::native_bridge::element) fn trusted_script_url_sink_string<'s>(
|
||||
)
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn trusted_attribute_value_string<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
runtime_and_handle: Option<(*mut JsContextHost, DomHandle)>,
|
||||
attribute_namespace: Option<&str>,
|
||||
local_name: &str,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
setter: TrustedAttributeSetter,
|
||||
) -> Option<String> {
|
||||
let sink = runtime_and_handle.and_then(|(runtime_ptr, handle)| {
|
||||
let element = unsafe { &*runtime_ptr }
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.and_then(|node| node.as_element())?;
|
||||
let element_namespace = element.namespace();
|
||||
|
||||
if attribute_namespace.is_none()
|
||||
&& matches!(
|
||||
element_namespace,
|
||||
"http://www.w3.org/1999/xhtml"
|
||||
| "http://www.w3.org/2000/svg"
|
||||
| "http://www.w3.org/1998/Math/MathML"
|
||||
)
|
||||
&& super::event_handlers::is_element_event_handler_content_attribute_name(local_name)
|
||||
{
|
||||
return Some((
|
||||
runtime_ptr,
|
||||
TrustedAttributeSink::Script(format!("Element {local_name}")),
|
||||
));
|
||||
}
|
||||
|
||||
let sink = match (
|
||||
element_namespace,
|
||||
element.local_name(),
|
||||
attribute_namespace,
|
||||
local_name,
|
||||
) {
|
||||
("http://www.w3.org/1999/xhtml", "script", None, "src") => {
|
||||
Some(TrustedAttributeSink::ScriptUrl("HTMLScriptElement src"))
|
||||
}
|
||||
("http://www.w3.org/1999/xhtml", "embed", None, "src") => {
|
||||
Some(TrustedAttributeSink::ScriptUrl("HTMLEmbedElement src"))
|
||||
}
|
||||
("http://www.w3.org/1999/xhtml", "object", None, "data") => {
|
||||
Some(TrustedAttributeSink::ScriptUrl("HTMLObjectElement data"))
|
||||
}
|
||||
("http://www.w3.org/1999/xhtml", "object", None, "codebase") => Some(
|
||||
TrustedAttributeSink::ScriptUrl("HTMLObjectElement codebase"),
|
||||
),
|
||||
(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"script",
|
||||
None | Some("http://www.w3.org/1999/xlink"),
|
||||
"href",
|
||||
) => Some(TrustedAttributeSink::ScriptUrl("SVGScriptElement href")),
|
||||
_ => None,
|
||||
}?;
|
||||
Some((runtime_ptr, sink))
|
||||
});
|
||||
|
||||
if let Some((runtime_ptr, sink)) = sink {
|
||||
let requirements = unsafe { &*runtime_ptr }.trusted_types_for_script_requirements(scope);
|
||||
return match sink {
|
||||
TrustedAttributeSink::Script(sink) => {
|
||||
crate::context_bootstrap::trusted_script_string_or_type_error(
|
||||
scope,
|
||||
value,
|
||||
requirements,
|
||||
&sink,
|
||||
setter.api_name(),
|
||||
)
|
||||
}
|
||||
TrustedAttributeSink::ScriptUrl(sink) => {
|
||||
crate::context_bootstrap::trusted_script_url_string_or_throw(
|
||||
scope,
|
||||
value,
|
||||
requirements,
|
||||
sink,
|
||||
setter.api_name(),
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match crate::webidl::convert::<crate::webidl::DomString>(
|
||||
scope,
|
||||
value,
|
||||
setter.conversion_context(),
|
||||
) {
|
||||
Ok(value) => Some(value.0),
|
||||
Err(error) => {
|
||||
crate::webidl::throw_error(scope, &error);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn trusted_script_source_for_execution(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
|
||||
@@ -958,6 +958,235 @@ fn script_src_enforces_trusted_script_url_and_applies_the_default_policy() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_handler_attribute_writes_enforce_trusted_script_at_the_attribute_boundary() {
|
||||
let mut vm = new_storage_test_vm("https://event-handler-attribute-trusted-types.test/");
|
||||
vm.set_response_content_security_policies(&["require-trusted-types-for 'script'".to_owned()]);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
globalThis.__eventHandlerAttributeRuns = [];
|
||||
const root = document.body ||
|
||||
(document.documentElement || document.appendChild(document.createElement("html")))
|
||||
.appendChild(document.createElement("body"));
|
||||
const policy = trustedTypes.createPolicy("event-handler-script", {
|
||||
createScript: value => value,
|
||||
createHTML: value => value
|
||||
});
|
||||
|
||||
const trusted = document.createElement("button");
|
||||
trusted.setAttribute(
|
||||
"onclick",
|
||||
policy.createScript("globalThis.__eventHandlerAttributeRuns.push('trusted')")
|
||||
);
|
||||
root.appendChild(trusted);
|
||||
trusted.click();
|
||||
|
||||
const trustedNs = document.createElement("button");
|
||||
trustedNs.setAttributeNS(
|
||||
null,
|
||||
"onclick",
|
||||
policy.createScript("globalThis.__eventHandlerAttributeRuns.push('trusted-ns')")
|
||||
);
|
||||
root.appendChild(trustedNs);
|
||||
trustedNs.click();
|
||||
|
||||
const unsuitable = document.createElement("button");
|
||||
let unsuitableRejected = false;
|
||||
try {
|
||||
unsuitable.setAttribute(
|
||||
"onclick",
|
||||
policy.createHTML("globalThis.__eventHandlerAttributeRuns.push('unsuitable')")
|
||||
);
|
||||
} catch (error) {
|
||||
unsuitableRejected = error instanceof TypeError;
|
||||
}
|
||||
|
||||
const plain = document.createElement("button");
|
||||
let plainRejected = false;
|
||||
try {
|
||||
plain.setAttribute(
|
||||
"onclick",
|
||||
"globalThis.__eventHandlerAttributeRuns.push('plain')"
|
||||
);
|
||||
} catch (error) {
|
||||
plainRejected = error instanceof TypeError;
|
||||
}
|
||||
|
||||
const plainNs = document.createElement("button");
|
||||
let plainNsRejected = false;
|
||||
try {
|
||||
plainNs.setAttributeNS(
|
||||
null,
|
||||
"onclick",
|
||||
"globalThis.__eventHandlerAttributeRuns.push('plain-ns')"
|
||||
);
|
||||
} catch (error) {
|
||||
plainNsRejected = error instanceof TypeError;
|
||||
}
|
||||
|
||||
const ordinary = document.createElement("div");
|
||||
ordinary.setAttribute("data-onclick", "plain-data");
|
||||
ordinary.setAttribute("ondoesnotexist", "plain-unknown");
|
||||
ordinary.setAttributeNS("urn:test", "onclick", "plain-namespaced");
|
||||
const uppercaseSvg = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
||||
uppercaseSvg.setAttributeNS(null, "ONCLICK", "plain-uppercase");
|
||||
const elementSpecificRejected = ["onwaitingforkey", "onbegin"].map(name => {
|
||||
try {
|
||||
document.createElement("div").setAttribute(name, "plain-special");
|
||||
return false;
|
||||
} catch (error) {
|
||||
return error instanceof TypeError;
|
||||
}
|
||||
});
|
||||
|
||||
const defaultCalls = [];
|
||||
trustedTypes.createPolicy("default", {
|
||||
createScript: (value, type, sink) => {
|
||||
defaultCalls.push([value, type, sink]);
|
||||
return "globalThis.__eventHandlerAttributeRuns.push('default')";
|
||||
}
|
||||
});
|
||||
const defaulted = document.createElement("button");
|
||||
defaulted.setAttribute("onclick", "default-input");
|
||||
root.appendChild(defaulted);
|
||||
defaulted.click();
|
||||
|
||||
return JSON.stringify({
|
||||
runs: globalThis.__eventHandlerAttributeRuns,
|
||||
unsuitableRejected,
|
||||
unsuitableAttribute: unsuitable.getAttribute("onclick"),
|
||||
plainRejected,
|
||||
plainAttribute: plain.getAttribute("onclick"),
|
||||
plainNsRejected,
|
||||
plainNsAttribute: plainNs.getAttribute("onclick"),
|
||||
ordinary: [
|
||||
ordinary.getAttribute("data-onclick"),
|
||||
ordinary.getAttribute("ondoesnotexist"),
|
||||
ordinary.getAttributeNS("urn:test", "onclick"),
|
||||
uppercaseSvg.getAttribute("ONCLICK")
|
||||
],
|
||||
elementSpecificRejected,
|
||||
defaultedAttribute: defaulted.getAttribute("onclick"),
|
||||
defaultCalls
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("event handler attribute Trusted Types sink probe should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"runs":["trusted","trusted-ns","default"],"unsuitableRejected":true,"unsuitableAttribute":null,"plainRejected":true,"plainAttribute":null,"plainNsRejected":true,"plainNsAttribute":null,"ordinary":["plain-data","plain-unknown","plain-namespaced","plain-uppercase"],"elementSpecificRejected":[true,true],"defaultedAttribute":"globalThis.__eventHandlerAttributeRuns.push('default')","defaultCalls":[["default-input","TrustedScript","Element onclick"]]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_attribute_writes_enforce_the_non_iframe_trusted_script_url_sink_table() {
|
||||
let mut vm = new_storage_test_vm("https://url-attribute-trusted-types.test/");
|
||||
vm.set_response_content_security_policies(&["require-trusted-types-for 'script'".to_owned()]);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const xlink = "http://www.w3.org/1999/xlink";
|
||||
const svg = "http://www.w3.org/2000/svg";
|
||||
const policy = trustedTypes.createPolicy("attribute-url", {
|
||||
createScript: value => value,
|
||||
createScriptURL: value => value
|
||||
});
|
||||
const trusted = value => policy.createScriptURL(value);
|
||||
|
||||
const htmlScript = document.createElement("script");
|
||||
htmlScript.setAttribute("src", trusted("script.js"));
|
||||
const htmlScriptNs = document.createElement("script");
|
||||
htmlScriptNs.setAttributeNS(null, "src", trusted("script-ns.js"));
|
||||
const svgScript = document.createElementNS(svg, "script");
|
||||
svgScript.setAttribute("href", trusted("svg.js"));
|
||||
const svgScriptXlink = document.createElementNS(svg, "script");
|
||||
svgScriptXlink.setAttributeNS(xlink, "xlink:href", trusted("svg-xlink.js"));
|
||||
const embed = document.createElement("embed");
|
||||
embed.setAttribute("src", trusted("embed.js"));
|
||||
const object = document.createElement("object");
|
||||
object.setAttribute("data", trusted("object-data.js"));
|
||||
object.setAttribute("codebase", trusted("object-codebase.js"));
|
||||
|
||||
const rejected = [];
|
||||
for (const [element, setter] of [
|
||||
[document.createElement("script"), element => element.setAttribute("src", "plain")],
|
||||
[document.createElement("script"), element => element.setAttributeNS(null, "src", null)],
|
||||
[document.createElementNS(svg, "script"), element => element.setAttribute("href", "plain")],
|
||||
[document.createElementNS(svg, "script"), element => element.setAttributeNS(xlink, "xlink:href", policy.createScript("wrong"))],
|
||||
[document.createElement("embed"), element => element.setAttribute("src", "plain")],
|
||||
[document.createElement("object"), element => element.setAttribute("data", "plain")],
|
||||
[document.createElement("object"), element => element.setAttribute("codebase", "plain")]
|
||||
]) {
|
||||
try {
|
||||
setter(element);
|
||||
rejected.push(false);
|
||||
} catch (error) {
|
||||
rejected.push(error instanceof TypeError);
|
||||
}
|
||||
}
|
||||
|
||||
const ordinary = document.createElement("script");
|
||||
ordinary.setAttributeNS("urn:test", "src", "namespaced");
|
||||
const uppercaseSvg = document.createElementNS(svg, "script");
|
||||
uppercaseSvg.setAttributeNS(null, "HREF", "uppercase");
|
||||
const div = document.createElement("div");
|
||||
div.setAttribute("src", "plain-div");
|
||||
|
||||
const defaultCalls = [];
|
||||
trustedTypes.createPolicy("default", {
|
||||
createScriptURL: (value, type, sink) => {
|
||||
defaultCalls.push([value, type, sink]);
|
||||
return `safe-${value}`;
|
||||
}
|
||||
});
|
||||
const defaultHtml = document.createElement("script");
|
||||
defaultHtml.setAttribute("src", "default-html");
|
||||
const defaultSvg = document.createElementNS(svg, "script");
|
||||
defaultSvg.setAttributeNS(xlink, "xlink:href", "default-svg");
|
||||
const defaultObject = document.createElement("object");
|
||||
defaultObject.setAttribute("codebase", "default-object");
|
||||
|
||||
return JSON.stringify({
|
||||
trustedValues: [
|
||||
htmlScript.getAttribute("src"),
|
||||
htmlScriptNs.getAttribute("src"),
|
||||
svgScript.getAttribute("href"),
|
||||
svgScriptXlink.getAttributeNS(xlink, "href"),
|
||||
embed.getAttribute("src"),
|
||||
object.getAttribute("data"),
|
||||
object.getAttribute("codebase")
|
||||
],
|
||||
rejected,
|
||||
ordinary: [
|
||||
ordinary.getAttributeNS("urn:test", "src"),
|
||||
uppercaseSvg.getAttribute("HREF"),
|
||||
div.getAttribute("src")
|
||||
],
|
||||
defaultValues: [
|
||||
defaultHtml.getAttribute("src"),
|
||||
defaultSvg.getAttributeNS(xlink, "href"),
|
||||
defaultObject.getAttribute("codebase")
|
||||
],
|
||||
defaultCalls
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("URL attribute Trusted Types sink table probe should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"trustedValues":["script.js","script-ns.js","svg.js","svg-xlink.js","embed.js","object-data.js","object-codebase.js"],"rejected":[true,true,true,true,true,true,true],"ordinary":["namespaced","uppercase","plain-div"],"defaultValues":["safe-default-html","safe-default-svg","safe-default-object"],"defaultCalls":[["default-html","TrustedScriptURL","HTMLScriptElement src"],["default-svg","TrustedScriptURL","SVGScriptElement href"],["default-object","TrustedScriptURL","HTMLObjectElement codebase"]]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_default_policy_reports_each_rejected_element_sink() {
|
||||
let mut vm = new_storage_test_vm("https://empty-default-policy.test/");
|
||||
|
||||
Reference in New Issue
Block a user