mirror of
https://github.com/lexmount/moli.git
synced 2026-10-28 16:00:55 +00:00
fix(trusted-types): honor Function constructor brands
This commit is contained in:
@@ -3645,7 +3645,6 @@ trusted-types/block-string-assignment-to-HTMLElement-generic.html
|
||||
trusted-types/block-string-assignment-to-HTMLIFrameElement-srcdoc.html
|
||||
trusted-types/block-string-assignment-to-attribute-via-attribute-node.html
|
||||
trusted-types/eval-function-constructor-untrusted-arguments-and-applying-default-policy.html
|
||||
trusted-types/eval-function-constructor.html
|
||||
trusted-types/inheriting-csp-for-local-schemes.html
|
||||
trusted-types/legacy-trusted-script-urls.html
|
||||
trusted-types/modify-attributes-in-callback.html
|
||||
|
||||
@@ -8602,6 +8602,7 @@ trusted-types/eval-csp-tt-default-policy-mutate.html
|
||||
trusted-types/eval-csp-tt-default-policy.html
|
||||
trusted-types/eval-csp-tt-no-default-policy.html
|
||||
trusted-types/eval-function-constructor-untrusted-arguments-and-default-policy-throwing.html
|
||||
trusted-types/eval-function-constructor.html
|
||||
trusted-types/eval-no-csp-no-tt-default-policy.html
|
||||
trusted-types/eval-no-csp-no-tt.html
|
||||
trusted-types/eval-with-non-trusted-script-object.html
|
||||
|
||||
@@ -428,8 +428,7 @@ pub(crate) use self::streams::{
|
||||
pub(crate) use self::trusted_types::trusted_types_lazy_state_materialized;
|
||||
pub(crate) use self::trusted_types::{
|
||||
TrustedTypesCodeGenerationCheck, check_javascript_url_trusted_types,
|
||||
install_trusted_types_eval_runtime_state, install_trusted_types_runtime_state,
|
||||
trusted_html_string_or_throw, trusted_html_value_string,
|
||||
install_trusted_types_runtime_state, trusted_html_string_or_throw, trusted_html_value_string,
|
||||
trusted_script_string_for_script_element_execution, trusted_script_string_or_type_error,
|
||||
trusted_script_url_string_or_throw, trusted_types_code_generation_check,
|
||||
trusted_types_code_generation_check_callback,
|
||||
|
||||
@@ -253,13 +253,6 @@ pub(crate) fn install_trusted_types_runtime_state<'s>(
|
||||
realm_state::install_lazy_trusted_types_runtime_state(scope, global)
|
||||
}
|
||||
|
||||
pub(crate) fn install_trusted_types_eval_runtime_state<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
global: v8::Local<'s, v8::Object>,
|
||||
) -> Result<()> {
|
||||
install_function_constructor_wrappers(scope, global)
|
||||
}
|
||||
|
||||
pub(crate) fn trusted_script_url_string_or_throw<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
@@ -726,6 +719,15 @@ fn install_trusted_script_code_like_constructor<'s>(
|
||||
let constructor = template
|
||||
.get_function(scope)
|
||||
.ok_or_else(|| anyhow!("failed to create TrustedScript code-like constructor"))?;
|
||||
let prototype = constructor
|
||||
.get(scope, v8str(scope, "prototype").into())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
.ok_or_else(|| anyhow!("TrustedScript code-like constructor prototype missing"))?;
|
||||
TrustedTypePrototypeDeclaration::default()
|
||||
.initialize(scope, prototype)
|
||||
.map_err(|error| {
|
||||
anyhow!("failed to initialize TrustedScript code-like carrier: {error}")
|
||||
})?;
|
||||
set_private_value(
|
||||
scope,
|
||||
global,
|
||||
@@ -744,6 +746,7 @@ fn build_trusted_type_object<'s>(
|
||||
let prototype = get_private_value(scope, global, kind.prototype_slot())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())?;
|
||||
let value = v8_string(scope, &value)?;
|
||||
let declaration = TrustedTypeObjectDeclaration::new(value);
|
||||
let object = if kind == TrustedTypeKind::Script {
|
||||
get_private_value(scope, global, TRUSTED_SCRIPT_CODE_LIKE_CONSTRUCTOR_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())?
|
||||
@@ -751,7 +754,7 @@ fn build_trusted_type_object<'s>(
|
||||
} else {
|
||||
v8::Object::new(scope)
|
||||
};
|
||||
TrustedTypeObjectDeclaration::new(value)
|
||||
declaration
|
||||
.initialize(scope, object)
|
||||
.expect("TrustedType object declaration should initialize");
|
||||
kind.interface().initialize(scope, object).ok()?;
|
||||
@@ -759,6 +762,22 @@ fn build_trusted_type_object<'s>(
|
||||
Some(object)
|
||||
}
|
||||
|
||||
fn build_trusted_script_code_like_carrier<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: String,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let object = get_private_value(scope, global, TRUSTED_SCRIPT_CODE_LIKE_CONSTRUCTOR_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())?
|
||||
.new_instance(scope, &[])?;
|
||||
let value = v8_string(scope, &value)?;
|
||||
TrustedTypeObjectDeclaration::new(value)
|
||||
.initialize(scope, object)
|
||||
.expect("TrustedScript code-like carrier declaration should initialize");
|
||||
web_api_interfaces::TrustedScript::DESCRIPTOR.initialize(scope, object).ok()?;
|
||||
Some(object)
|
||||
}
|
||||
|
||||
fn trusted_type_illegal_constructor_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
_args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -1002,15 +1021,15 @@ fn trusted_types_is_script_url_callback<'s>(
|
||||
rv.set_bool(trusted_type_string(scope, args.get(0), TrustedTypeKind::ScriptUrl).is_some());
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum TrustedFunctionConstructorKind {
|
||||
#[derive(Clone, Copy)]
|
||||
enum FunctionConstructorKind {
|
||||
Function,
|
||||
AsyncFunction,
|
||||
GeneratorFunction,
|
||||
AsyncGeneratorFunction,
|
||||
}
|
||||
|
||||
impl TrustedFunctionConstructorKind {
|
||||
impl FunctionConstructorKind {
|
||||
fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Function => "Function",
|
||||
@@ -1028,60 +1047,87 @@ impl TrustedFunctionConstructorKind {
|
||||
Self::AsyncGeneratorFunction => "Object.getPrototypeOf(async function*() {})",
|
||||
}
|
||||
}
|
||||
|
||||
fn source_wrapper(self, params: &str, body: &str) -> String {
|
||||
match self {
|
||||
Self::Function => format!("(function anonymous({params}) {{\n{body}\n}})"),
|
||||
Self::AsyncFunction => format!("(async function anonymous({params}) {{\n{body}\n}})"),
|
||||
Self::GeneratorFunction => {
|
||||
format!("(function* anonymous({params}) {{\n{body}\n}})")
|
||||
}
|
||||
Self::AsyncGeneratorFunction => {
|
||||
format!("(async function* anonymous({params}) {{\n{body}\n}})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_policy_source(self, params: &str, body: &str) -> String {
|
||||
let prefix = match self {
|
||||
Self::Function => "function anonymous",
|
||||
Self::AsyncFunction => "async function anonymous",
|
||||
Self::GeneratorFunction => "function* anonymous",
|
||||
Self::AsyncGeneratorFunction => "async function* anonymous",
|
||||
};
|
||||
format!("{prefix}({params}\n) {{\n{body}\n}}")
|
||||
}
|
||||
}
|
||||
|
||||
const TRUSTED_FUNCTION_CONSTRUCTOR_KINDS: [TrustedFunctionConstructorKind; 4] = [
|
||||
TrustedFunctionConstructorKind::Function,
|
||||
TrustedFunctionConstructorKind::AsyncFunction,
|
||||
TrustedFunctionConstructorKind::GeneratorFunction,
|
||||
TrustedFunctionConstructorKind::AsyncGeneratorFunction,
|
||||
const FUNCTION_CONSTRUCTOR_KINDS: [FunctionConstructorKind; 4] = [
|
||||
FunctionConstructorKind::Function,
|
||||
FunctionConstructorKind::AsyncFunction,
|
||||
FunctionConstructorKind::GeneratorFunction,
|
||||
FunctionConstructorKind::AsyncGeneratorFunction,
|
||||
];
|
||||
|
||||
fn install_function_constructor_wrappers<'s>(
|
||||
fn install_function_constructor_brand_guards<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
global: v8::Local<'s, v8::Object>,
|
||||
) -> Result<()> {
|
||||
for (index, kind) in TRUSTED_FUNCTION_CONSTRUCTOR_KINDS.into_iter().enumerate() {
|
||||
let data = crate::util::callback_data_index_value(scope, index);
|
||||
let constructor = v8::Function::builder(trusted_types_function_constructor_callback)
|
||||
.data(data)
|
||||
.length(1)
|
||||
.build(scope)
|
||||
.ok_or_else(|| anyhow!("failed to build {} Trusted Types wrapper", kind.name()))?;
|
||||
constructor.set_name(v8str(scope, kind.name()));
|
||||
if let Some(prototype) = eval_object(scope, kind.prototype_expression()) {
|
||||
let _ = prototype.define_own_property(
|
||||
// V8's Function constructors report one combined source string to the
|
||||
// code-generation callback. Preserve the original argument list here so
|
||||
// an overridden TrustedScript string conversion can still be downgraded
|
||||
// to the default-policy path. The traps always delegate compilation to the
|
||||
// intrinsic constructors; they do not compile source themselves.
|
||||
let reflect_construct = global
|
||||
.get(scope, v8str(scope, "Reflect").into())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
.and_then(|reflect| reflect.get(scope, v8str(scope, "construct").into()))
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
||||
.ok_or_else(|| anyhow!("Reflect.construct missing during Trusted Types bootstrap"))?;
|
||||
let apply = v8::Function::builder(function_constructor_apply_trap_callback)
|
||||
.length(3)
|
||||
.build(scope)
|
||||
.ok_or_else(|| anyhow!("failed to create Function apply brand guard"))?;
|
||||
let construct = v8::Function::builder(function_constructor_construct_trap_callback)
|
||||
.data(reflect_construct.into())
|
||||
.length(3)
|
||||
.build(scope)
|
||||
.ok_or_else(|| anyhow!("failed to create Function construct brand guard"))?;
|
||||
let handler = v8::Object::new(scope);
|
||||
if !handler
|
||||
.define_own_property(
|
||||
scope,
|
||||
v8str(scope, "apply").into(),
|
||||
apply.into(),
|
||||
v8::PropertyAttribute::DONT_ENUM,
|
||||
)
|
||||
.unwrap_or(false)
|
||||
|| !handler
|
||||
.define_own_property(
|
||||
scope,
|
||||
v8str(scope, "construct").into(),
|
||||
construct.into(),
|
||||
v8::PropertyAttribute::DONT_ENUM,
|
||||
)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"failed to initialize Function constructor brand guard"
|
||||
));
|
||||
}
|
||||
|
||||
for kind in FUNCTION_CONSTRUCTOR_KINDS {
|
||||
let prototype = eval_object(scope, kind.prototype_expression())
|
||||
.ok_or_else(|| anyhow!("{} prototype missing", kind.name()))?;
|
||||
let target = prototype
|
||||
.get(scope, v8str(scope, "constructor").into())
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
||||
.ok_or_else(|| anyhow!("{} constructor missing", kind.name()))?;
|
||||
let proxy = v8::Proxy::new(scope, target.into(), handler)
|
||||
.ok_or_else(|| anyhow!("failed to proxy {} constructor", kind.name()))?;
|
||||
if !prototype
|
||||
.define_own_property(
|
||||
scope,
|
||||
v8str(scope, "constructor").into(),
|
||||
constructor.into(),
|
||||
proxy.into(),
|
||||
v8::PropertyAttribute::DONT_ENUM,
|
||||
);
|
||||
)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"failed to install {} constructor brand guard",
|
||||
kind.name()
|
||||
));
|
||||
}
|
||||
if kind == TrustedFunctionConstructorKind::Function {
|
||||
define_global_value(scope, global, "Function", constructor.into())?;
|
||||
if matches!(kind, FunctionConstructorKind::Function) {
|
||||
define_global_value(scope, global, "Function", proxy.into())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -1097,100 +1143,95 @@ fn eval_object<'s>(
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
}
|
||||
|
||||
fn trusted_types_function_constructor_callback<'s>(
|
||||
fn function_constructor_apply_trap_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let Some(kind) = crate::util::callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
&TRUSTED_FUNCTION_CONSTRUCTOR_KINDS,
|
||||
"Trusted Types Function constructor wrappers",
|
||||
) else {
|
||||
let Ok(target) = v8::Local::<v8::Function>::try_from(args.get(0)) else {
|
||||
return;
|
||||
};
|
||||
let Some((params, body)) = function_constructor_source_parts(scope, &args, kind) else {
|
||||
let Ok(arguments) = v8::Local::<v8::Array>::try_from(args.get(2)) else {
|
||||
return;
|
||||
};
|
||||
let source = kind.source_wrapper(¶ms, &body);
|
||||
let Some(source) = v8_string(scope, &source) else {
|
||||
let Some(arguments) = prepare_function_constructor_arguments(scope, arguments) else {
|
||||
return;
|
||||
};
|
||||
let Some(script) = v8::Script::compile(scope, source, None) else {
|
||||
return;
|
||||
};
|
||||
if let Some(function) = crate::script_execution::execute_compiled_script(scope, script) {
|
||||
rv.set(function);
|
||||
if let Some(value) = crate::script_execution::call_function(scope, target, args.get(1), &arguments) {
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn function_constructor_source_parts<'s>(
|
||||
fn function_constructor_construct_trap_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
kind: TrustedFunctionConstructorKind,
|
||||
) -> Option<(String, String)> {
|
||||
let argument_count = args.length();
|
||||
if argument_count == 0 {
|
||||
let empty = v8::String::empty(scope);
|
||||
let body = trusted_script_string_for_function_constructor(scope, empty.into(), "", kind)?;
|
||||
return Some((String::new(), body));
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let Ok(arguments) = v8::Local::<v8::Array>::try_from(args.get(1)) else {
|
||||
return;
|
||||
};
|
||||
let Some(arguments) = prepare_function_constructor_arguments(scope, arguments) else {
|
||||
return;
|
||||
};
|
||||
let Ok(reflect_construct) = v8::Local::<v8::Function>::try_from(args.data()) else {
|
||||
return;
|
||||
};
|
||||
let arguments = v8::Array::new_with_elements(scope, &arguments);
|
||||
let receiver = v8::undefined(scope);
|
||||
let forwarded = [args.get(0), arguments.into(), args.get(2)];
|
||||
if let Some(value) = crate::script_execution::call_function(scope, reflect_construct, receiver.into(), &forwarded) {
|
||||
rv.set(value);
|
||||
}
|
||||
let mut params = Vec::new();
|
||||
for index in 0..argument_count.saturating_sub(1) {
|
||||
params.push(js_value_to_string(scope, args.get(index))?);
|
||||
}
|
||||
let params = params.join(",");
|
||||
let body = trusted_script_string_for_function_constructor(
|
||||
scope,
|
||||
args.get(argument_count.saturating_sub(1)),
|
||||
¶ms,
|
||||
kind,
|
||||
)?;
|
||||
Some((params, body))
|
||||
}
|
||||
|
||||
fn trusted_script_string_for_function_constructor<'s>(
|
||||
fn prepare_function_constructor_arguments<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
params: &str,
|
||||
kind: TrustedFunctionConstructorKind,
|
||||
) -> Option<String> {
|
||||
if let Some(value) = trusted_type_string(scope, value, TrustedTypeKind::Script) {
|
||||
return Some(value);
|
||||
}
|
||||
let original = js_value_to_string(scope, value)?;
|
||||
if trusted_types_eval_is_allowed(scope) {
|
||||
arguments: v8::Local<'s, v8::Array>,
|
||||
) -> Option<Vec<v8::Local<'s, v8::Value>>> {
|
||||
let original = (0..arguments.length())
|
||||
.map(|index| arguments.get_index(scope, index))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
if !trusted_types_for_script_is_required(scope) || trusted_types_eval_is_allowed(scope) {
|
||||
return Some(original);
|
||||
}
|
||||
let violation_sample = function_constructor_violation_sample(params, &original);
|
||||
let default_policy_source = kind.default_policy_source(params, &original);
|
||||
if let Some(default_value) = apply_default_trusted_type_policy(
|
||||
scope,
|
||||
&default_policy_source,
|
||||
TrustedTypeKind::Script,
|
||||
"Function",
|
||||
TrustedTypeErrorKind::Eval,
|
||||
) {
|
||||
if default_value == default_policy_source {
|
||||
|
||||
let mut trusted = Vec::with_capacity(original.len());
|
||||
for value in &original {
|
||||
let Some(value) = trusted_type_string(scope, *value, TrustedTypeKind::Script) else {
|
||||
return Some(original);
|
||||
}
|
||||
dispatch_trusted_types_sink_violation_event(scope, "Function", &violation_sample);
|
||||
throw_eval_error(
|
||||
scope,
|
||||
"Trusted Types default policy must not transform strings passed to Function.",
|
||||
);
|
||||
return None;
|
||||
};
|
||||
trusted.push(value);
|
||||
}
|
||||
dispatch_trusted_types_sink_violation_event(scope, "Function", &violation_sample);
|
||||
throw_trusted_type_error(
|
||||
scope,
|
||||
TrustedTypeErrorKind::Eval,
|
||||
"Function",
|
||||
TrustedTypeKind::Script,
|
||||
"Function",
|
||||
);
|
||||
None
|
||||
let stringified = original
|
||||
.iter()
|
||||
.map(|value| js_value_to_string(scope, *value))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
if stringified != trusted {
|
||||
// Plain strings force V8's combined Function source through the normal
|
||||
// Trusted Types/default-policy check. Reusing the already stringified
|
||||
// values also avoids invoking a page-defined conversion twice.
|
||||
return stringified
|
||||
.into_iter()
|
||||
.map(|value| v8_string(scope, &value).map(Into::into))
|
||||
.collect();
|
||||
}
|
||||
// Private carriers retain V8's code-like brand while using an inaccessible
|
||||
// native string conversion, so the original page objects are converted
|
||||
// exactly once before the intrinsic constructor runs.
|
||||
trusted
|
||||
.into_iter()
|
||||
.map(|value| build_trusted_script_code_like_carrier(scope, value).map(Into::into))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn trusted_types_for_script_is_required(scope: &mut v8::PinScope<'_, '_>) -> bool {
|
||||
if let Some(required) = crate::worker::worker_requires_trusted_types_for_script(scope) {
|
||||
return required;
|
||||
}
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
return false;
|
||||
};
|
||||
unsafe { &*host_ptr }.requires_trusted_types_for_script(scope)
|
||||
}
|
||||
|
||||
fn trusted_types_eval_is_allowed(scope: &mut v8::PinScope<'_, '_>) -> bool {
|
||||
@@ -1203,10 +1244,6 @@ fn trusted_types_eval_is_allowed(scope: &mut v8::PinScope<'_, '_>) -> bool {
|
||||
unsafe { &*host_ptr }.allows_trusted_types_eval(scope)
|
||||
}
|
||||
|
||||
fn function_constructor_violation_sample(params: &str, body: &str) -> String {
|
||||
format!("({params}\n) {{\n{body}\n}}")
|
||||
}
|
||||
|
||||
fn dispatch_trusted_types_sink_violation_event(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
sink: &str,
|
||||
|
||||
@@ -99,6 +99,8 @@ pub(super) fn install_lazy_trusted_types_runtime_state<'s>(
|
||||
TRUSTED_TYPE_POLICY_FACTORY_CONSTRUCTOR_SLOT,
|
||||
policy_factory_constructor.into(),
|
||||
);
|
||||
install_trusted_script_code_like_constructor(scope, global)?;
|
||||
install_function_constructor_brand_guards(scope, global)?;
|
||||
for (property, name) in TrustedTypesGlobalProperty::ALL {
|
||||
let data = v8::Integer::new_from_unsigned(scope, property.callback_data());
|
||||
global
|
||||
|
||||
@@ -970,6 +970,67 @@ fn trusted_script_eval_is_unwrapped_without_trusted_types_enforcement() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_script_code_like_brand_drives_function_constructors() {
|
||||
let mut vm = new_storage_test_vm("https://trusted-script-function-brand.test/");
|
||||
vm.set_response_content_security_policies(&["require-trusted-types-for 'script'".to_owned()]);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const policy = trustedTypes.createPolicy("function-brand", { createScript: value => value });
|
||||
const source = ["a", "b", "c = 5", "return (a + b) * c;"];
|
||||
const constructors = [
|
||||
Function,
|
||||
(async function() {}).constructor,
|
||||
(function*() {}).constructor,
|
||||
(async function*() {}).constructor
|
||||
];
|
||||
const mixedBlocked = constructors.map(Constructor => {
|
||||
let blocked = 0;
|
||||
for (let mask = 0; mask < 15; mask++) {
|
||||
const args = source.map((value, index) =>
|
||||
mask & (2 ** index) ? policy.createScript(value) : value);
|
||||
try {
|
||||
new Constructor(...args);
|
||||
} catch (error) {
|
||||
blocked += error instanceof EvalError;
|
||||
}
|
||||
}
|
||||
return blocked;
|
||||
});
|
||||
const trusted = source.map(value => policy.createScript(value));
|
||||
const functions = constructors.map(Constructor => new Constructor(...trusted));
|
||||
let forgedBlocked = 0;
|
||||
for (let index = 0; index < source.length; index++) {
|
||||
const forged = trusted.slice();
|
||||
forged[index] = Object.assign(policy.createScript(source[index]), {
|
||||
toString: () => ` ${source[index]} `
|
||||
});
|
||||
try {
|
||||
new Function(...forged);
|
||||
} catch (error) {
|
||||
forgedBlocked += error instanceof EvalError;
|
||||
}
|
||||
}
|
||||
return JSON.stringify({
|
||||
mixedBlocked,
|
||||
trustedConstructorTypes: functions.map(value => typeof value),
|
||||
functionValues: [functions[0](1, 2, 3), functions[0](1, 2)],
|
||||
forgedBlocked
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("TrustedScript function constructor brand probe should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"mixedBlocked":[15,15,15,15],"trustedConstructorTypes":["function","function","function","function"],"functionValues":[9,15],"forgedBlocked":4}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_types_default_policy_receives_type_and_eval_sink_arguments() {
|
||||
let mut vm = new_storage_test_vm("https://trusted-types-default-callback-args.test/");
|
||||
|
||||
@@ -179,6 +179,11 @@ const DIRECT_V8_CALL_ALLOWLIST: &[AllowedDirectCallFile] = &[
|
||||
1,
|
||||
DirectCallOwner::BrowserAlgorithm,
|
||||
),
|
||||
allowed(
|
||||
"context_bootstrap/trusted_types.rs",
|
||||
2,
|
||||
DirectCallOwner::NativeForwardingOrScript,
|
||||
),
|
||||
allowed(
|
||||
"context_bootstrap/web_storage/interceptors.rs",
|
||||
1,
|
||||
|
||||
@@ -3017,7 +3017,6 @@ pub(super) fn install_worker_global_scope<'s>(
|
||||
scope
|
||||
.get_current_context()
|
||||
.set_allow_generation_from_strings(false);
|
||||
crate::context_bootstrap::install_trusted_types_eval_runtime_state(scope, global)?;
|
||||
}
|
||||
crate::context_bootstrap::install_webassembly_runtime_state(scope, global)?;
|
||||
if matches!(global_kind, super::thread::WorkerGlobalKind::Service { .. }) {
|
||||
@@ -6245,6 +6244,16 @@ pub(crate) fn worker_allows_trusted_types_eval(scope: &mut v8::PinScope<'_, '_>)
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn worker_requires_trusted_types_for_script(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
) -> Option<bool> {
|
||||
Some(
|
||||
crate::content_security_policy::content_security_policy_requires_trusted_types_for_script(
|
||||
&get_worker_state(scope)?.borrow().content_security_policies,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_worker_trusted_types_sink_violation_event(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
sink: &str,
|
||||
|
||||
@@ -44,9 +44,9 @@ pub(crate) use global_scope::{
|
||||
worker_message_port_event_listener_snapshots, worker_message_port_registry,
|
||||
worker_message_port_wake_sender, worker_message_port_wrapper,
|
||||
worker_notification_permission_state, worker_opfs_directory_iterator_registry,
|
||||
worker_opfs_handle_registry, worker_service_worker_control_state, worker_storage_key,
|
||||
worker_storage_partition_identity, worker_termination_requested,
|
||||
worker_uses_shared_worker_agent_cluster,
|
||||
worker_opfs_handle_registry, worker_requires_trusted_types_for_script,
|
||||
worker_service_worker_control_state, worker_storage_key, worker_storage_partition_identity,
|
||||
worker_termination_requested, worker_uses_shared_worker_agent_cluster,
|
||||
};
|
||||
pub(crate) use handle::WorkerMessage;
|
||||
pub(crate) use handle::{
|
||||
|
||||
@@ -882,6 +882,39 @@ async fn worker_trusted_script_eval_is_unwrapped_with_trusted_types_eval_keyword
|
||||
assert_eq!(expect_post_json(msg), r#"{"trusted":7,"string":9}"#);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_trusted_script_code_like_brand_drives_function_constructor() {
|
||||
ensure_v8();
|
||||
let mut handle = spawn_test_worker_with_options(
|
||||
WorkerSpawnOptions::new(
|
||||
r#"
|
||||
const policy = trustedTypes.createPolicy("function-brand", {
|
||||
createScript: value => value
|
||||
});
|
||||
const source = ["a", "b", "return a + b;"];
|
||||
const trusted = source.map(value => policy.createScript(value));
|
||||
let mixedBlocked = false;
|
||||
try {
|
||||
new Function(trusted[0], "b", trusted[2]);
|
||||
} catch (error) {
|
||||
mixedBlocked = error instanceof EvalError;
|
||||
}
|
||||
const constructed = new Function(...trusted);
|
||||
postMessage({ mixedBlocked, value: constructed(20, 22) });
|
||||
"#
|
||||
.to_owned(),
|
||||
"https://app.test/worker/main.js".to_owned(),
|
||||
)
|
||||
.with_content_security_policies(vec!["require-trusted-types-for 'script'".to_owned()]),
|
||||
);
|
||||
|
||||
let msg = timeout(TIMEOUT, handle.recv())
|
||||
.await
|
||||
.expect("timed out")
|
||||
.expect("channel closed");
|
||||
assert_eq!(expect_post_json(msg), r#"{"mixedBlocked":true,"value":42}"#);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_module_static_imports_resolve_http_dependencies_against_module_url() {
|
||||
ensure_v8();
|
||||
|
||||
Reference in New Issue
Block a user