8000 Implement Trusted Type eval checks (#37834) · servo/servo@82ca2b9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 82ca2b9

Browse files
authored
Implement Trusted Type eval checks (#37834)
It implements the new codeForEvalGets callback to retrieve the value for a trusted script object. Additionally, it implements the new logic in can-compile-strings to call the policy factory if required. Note that parameter and argument checks aren't implemented yet, as they require updates to binding generation (see TODO in script_runtime). Part of #36258 Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
1 parent 4c05758 commit 82ca2b9

26 files changed

+159
-345
lines changed

components/script/dom/htmlscriptelement.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -606,8 +606,7 @@ impl HTMLScriptElement {
606606
*self.script_text.borrow_mut() = TrustedScript::get_trusted_script_compliant_string(
607607
&self.owner_global(),
608608
self.Text(),
609-
"HTMLScriptElement",
610-
"text",
609+
"HTMLScriptElement text",
611610
can_gc,
612611
)?;
613612
}
@@ -1475,8 +1474,7 @@ impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
14751474
let value = TrustedScript::get_trusted_script_compliant_string(
14761475
&self.owner_global(),
14771476
input,
1478-
"HTMLScriptElement",
1479-
"innerText",
1477+
"HTMLScriptElement innerText",
14801478
can_gc,
14811479
)?;
14821480
*self.script_text.borrow_mut() = value.clone();
@@ -1497,8 +1495,7 @@ impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
14971495
let value = TrustedScript::get_trusted_script_compliant_string(
14981496
&self.owner_global(),
14991497
value,
1500-
"HTMLScriptElement",
1501-
"text",
1498+
"HTMLScriptElement text",
15021499
can_gc,
15031500
)?;
15041501
// Step 2: Set this's script text value to the given value.
@@ -1523,8 +1520,7 @@ impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
15231520
let value = TrustedScript::get_trusted_script_compliant_string(
15241521
&self.owner_global(),
15251522
value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1526-
"HTMLScriptElement",
1527-
"textContent",
1523+
"HTMLScriptElement textContent",
15281524
can_gc,
15291525
)?;
15301526
// Step 2: Set this's script text value to value.

components/script/dom/trustedscript.rs

Lines changed: 83 additions & 5 deletions
< 1D65 /tr>
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@
44
use std::fmt;
55

66
use dom_struct::dom_struct;
7+
use js::jsapi::CompilationType;
8+
use js::rust::HandleValue;
79

810
use crate::dom::bindings::codegen::Bindings::TrustedScriptBinding::TrustedScriptMethods;
911
use crate::dom::bindings::codegen::UnionTypes::TrustedScriptOrString;
1012
use crate::dom::bindings::error::Fallible;
1113
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
1214
use crate::dom::bindings::root::DomRoot;
1315
use crate::dom::bindings::str::DOMString;
16+
use crate::dom::csp::CspReporting;
1417
use crate::dom::globalscope::GlobalScope;
1518
use crate::dom::trustedtypepolicy::TrustedType;
1619
use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory;
17-
use crate::script_runtime::CanGc;
20+
use crate::script_runtime::{CanGc, JSContext};
1821

1922
#[dom_struct]
2023
pub struct TrustedScript {
@@ -39,18 +42,16 @@ impl TrustedScript {
3942
pub(crate) fn get_trusted_script_compliant_string(
4043
global: &GlobalScope,
4144
value: TrustedScriptOrString,
42-
containing_class: &str,
43-
field: &str,
45+
sink: &str,
4446
can_gc: CanGc,
4547
) -> Fallible<DOMString> {
4648
match value {
4749
TrustedScriptOrString::String(value) => {
48-
let sink = format!("{} {}", containing_class, field);
4950
TrustedTypePolicyFactory::get_trusted_type_compliant_string(
5051
TrustedType::TrustedScript,
5152
global,
5253
value,
53-
&sink,
54+
sink,
5455
"'script'",
5556
can_gc,
5657
)
@@ -59,6 +60,83 @@ impl TrustedScript {
5960
TrustedScriptOrString::TrustedScript(trusted_script) => Ok(trusted_script.data.clone()),
6061
}
6162
}
63+
64+
pub(crate) fn data(&self) -> DOMString {
65+
self.data.clone()
66+
}
67+
68+
/// <https://www.w3.org/TR/CSP/#can-compile-strings>
69+
#[allow(clippy::too_many_arguments)]
70+
pub(crate) fn can_compile_string_with_trusted_type(
71+
cx: JSContext,
72+
global: &GlobalScope,
73+
code_string: DOMString,
74+
compilation_type: CompilationType,
75+
_parameter_strings: u8, //FIXME in bindings generation
76+
body_string: DOMString,
77+
_parameter_args: u8, //FIXME in bindings generation
78+
body_arg: HandleValue,
79+
can_gc: CanGc,
80+
) -> bool {
81+
// Step 2.1. Let compilationSink be "Function" if compilationType is "FUNCTION",
82+
// and "eval" otherwise.
83+
let compilation_sink = if compilation_type == CompilationType::Function {
84+
"Function"
85+
} else {
86+
"eval"
87+
};
88+
// Step 2.2. Let isTrusted be true if bodyArg implements TrustedScript,
89+
// and false otherwise.
90+
let is_trusted = match TrustedTypePolicyFactory::is_trusted_script(cx, body_arg) {
91+
// Step 2.3. If isTrusted is true then:
92+
Ok(trusted_script) => {
93+
// Step 2.3.1. If bodyString is not equal to bodyArg’s data, set isTrusted to false.
94+
body_string == trusted_script.data
95+
},
96+
_ => false,
97+
};
98+
// Step 2.4. If isTrusted is true, then:
99+
// Step 2.4.1. Assert: parameterArgs’ [list/size=] is equal to [parameterStrings]' size.
100+
// Step 2.4.2. For each index of the range 0 to |parameterArgs]' [list/size=]:
101+
// Step 2.4.2.1. Let arg be parameterArgs[index].
102+
// Step 2.4.2.2. If arg implements TrustedScript, then:
103+
// Step 2.4.2.2.1. if parameterStrings[index] is not equal to arg’s data,
104+
// set isTrusted to false.
105+
// Step 2.4.2.3. Otherwise, set isTrusted to false.
106+
// Step 2.5. Let sourceToValidate be a new TrustedScript object created in realm
107+
// whose data is set to codeString if isTrusted is true, and codeString otherwise.
108+
let source_string = if is_trusted {
109+
// We don't need to call the compliant string algorithm, as it would immediately
110+
// unroll the type as allowed by copying the data. This allows us to skip creating
111+
// the DOM object.
112+
code_string
113+
} else {
114+
// Step 2.6. Let sourceString be the result of executing the
115+
// Get Trusted Type compliant string algorithm, with TrustedScript, realm,
116+
// sourceToValidate, compilationSink, and 'script'.
117+
match TrustedScript::get_trusted_script_compliant_string(
118+
global,
119+
TrustedScriptOrString::String(code_string.clone()),
120+
compilation_sink,
121+
can_gc,
122+
) {
123+
// Step 2.7. If the algorithm throws an error, throw an EvalError.
124+
Err(_) => {
125+
return false;
126+
},
127+
Ok(source_string) => {
128+
// Step 2.8. If sourceString is not equal to codeString, throw an EvalError.
129+
if source_string != code_string {
130+
return false;
131+
}
132+
source_string
133+
},
134+
}
135+
};
136+
global
137+
.get_csp_list()
138+
.is_js_evaluation_allowed(global, &source_string)
139+
}
62140
}
63141

64142
impl fmt::Display for TrustedScript {

components/script/dom/trustedtypepolicyfactory.rs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use script_bindings::conversions::SafeToJSValConvertible;
1212
use crate::dom::bindings::codegen::Bindings::TrustedTypePolicyFactoryBinding::{
1313
TrustedTypePolicyFactoryMethods, TrustedTypePolicyOptions,
1414
};
15-
use crate::dom::bindings::conversions::root_from_object;
15+
use crate::dom::bindings::conversions::root_from_handlevalue;
1616
use crate::dom::bindings::error::{Error, Fallible};
1717
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
1818
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
@@ -236,6 +236,15 @@ impl TrustedTypePolicyFactory {
236236
// Step 7: Assert: convertedInput is an instance of expectedType.
237237
// TODO(https://github.com/w3c/trusted-types/issues/566): Implement when spec is resolved
238238
}
239+
240+
/// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-isscript>
241+
#[allow(unsafe_code)]
242+
pub(crate) fn is_trusted_script(
243+
cx: JSContext,
244+
value: HandleValue,
245+
) -> Result<DomRoot<TrustedScript>, ()> {
246+
unsafe { root_from_handlevalue::<TrustedScript>(value, *cx) }
247+
}
239248
}
240249

241250
impl TrustedTypePolicyFactoryMethods<crate::DomTypeHolder> for TrustedTypePolicyFactory {
@@ -251,29 +260,17 @@ impl TrustedTypePolicyFactoryMethods<crate::DomTypeHolder> for TrustedTypePolicy
251260
/// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-ishtml>
252261
#[allow(unsafe_code)]
253262
fn IsHTML(&self, cx: JSContext, value: HandleValue) -> bool {
254-
if !value.get().is_object() {
255-
return false;
256-
}
257-
rooted!(in(*cx) let object = value.to_object());
258-
unsafe { root_from_object::<TrustedHTML>(object.get(), *cx).is_ok() }
263+
unsafe { root_from_handlevalue::<TrustedHTML>(value, *cx).is_ok() }
259264
}
260265
/// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-isscript>
261266
#[allow(unsafe_code)]
262267
fn IsScript(&self, cx: JSContext, value: HandleValue) -> bool {
263-
if !value.get().is_object() {
264-
return false;
265-
}
266-
rooted!(in(*cx) let object = value.to_object());
267-
unsafe { root_from_object::<TrustedScript>(object.get(), *cx).is_ok() }
268+
TrustedTypePolicyFactory::is_trusted_script(cx, value).is_ok()
268269
}
269270
/// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-isscripturl>
270271
#[allow(unsafe_code)]
271272
fn IsScriptURL(&self, cx: JSContext, value: HandleValue) -> bool {
272-
if !value.get().is_object() {
273-
return false;
274-
}
275-
rooted!(in(*cx) let object = value.to_object());
276-
unsafe { root_from_object::<TrustedScriptURL>(object.get(), *cx).is_ok() }
273+
unsafe { root_from_handlevalue::<TrustedScriptURL>(value, *cx).is_ok() }
277274
}
278275
/// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-emptyhtml>
279276
fn EmptyHTML(&self, can_gc: CanGc) -> DomRoot<TrustedHTML> {

components/script/script_runtime.rs

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ use js::glue::{
2828
use js::jsapi::{
2929
AsmJSOption, BuildIdCharVector, CompilationType, ContextOptionsRef, Dispatchable as JSRunnable,
3030
Dispatchable_MaybeShuttingDown, GCDescription, GCOptions, GCProgress, GCReason,
31-
GetPromiseUserInputEventHandlingState, HandleObject, HandleString, HandleValue, Heap,
32-
InitConsumeStreamCallback, InitDispatchToEventLoop, JS_AddExtraGCRootsTracer,
33-
JS_InitDestroyPrincipalsCallback, JS_InitReadPrincipalsCallback, JS_NewObject,
34-
JS_SetGCCallback, JS_SetGCParameter, JS_SetGlobalJitCompilerOption,
35-
JS_SetOffthreadIonCompilationEnabled, JS_SetParallelParsingEnabled, JS_SetReservedSlot,
36-
JS_SetSecurityCallbacks, JSCLASS_RESERVED_SLOTS_MASK, JSCLASS_RESERVED_SLOTS_SHIFT, JSClass,
37-
JSClassOps, JSContext as RawJSContext, JSGCParamKey, JSGCStatus, JSJitCompilerOption, JSObject,
38-
JSSecurityCallbacks, JSTracer, JobQueue, MimeType, MutableHandleObject,
31+
GetPromiseUserInputEventHandlingState, HandleObject, HandleString,
32+
HandleValue as RawHandleValue, Heap, InitConsumeStreamCallback, InitDispatchToEventLoop,
33+
JS_AddExtraGCRootsTracer, JS_InitDestroyPrincipalsCallback, JS_InitReadPrincipalsCallback,
34+
JS_NewObject, JS_NewStringCopyN, JS_SetGCCallback, JS_SetGCParameter,
35+
JS_SetGlobalJitCompilerOption, JS_SetOffthreadIonCompilationEnabled,
36+
JS_SetParallelParsingEnabled, JS_SetReservedSlot, JS_SetSecurityCallbacks,
37+
JSCLASS_RESERVED_SLOTS_MASK, JSCLASS_RESERVED_SLOTS_SHIFT, JSClass, JSClassOps,
38+
JSContext as RawJSContext, JSGCParamKey, JSGCStatus, JSJitCompilerOption, JSObject,
39+
JSSecurityCallbacks, JSTracer, JobQueue, MimeType, MutableHandleObject, MutableHandleString,
3940
PromiseRejectionHandlingState, PromiseUserInputEventHandlingState, RuntimeCode,
4041
SetDOMCallbacks, SetGCSliceCallback, SetJobQueue, SetPreserveWrapperCallbacks,
4142
SetProcessBuildIdOp, SetPromiseRejectionTrackerCallback, StreamConsumer as JSStreamConsumer,
@@ -45,8 +46,8 @@ use js::panic::wrap_panic;
4546
pub(crate) use js::rust::ThreadSafeJSContext;
4647
use js::rust::wrappers::{GetPromiseIsHandled, JS_GetPromiseResult};
4748
use js::rust::{
48-
Handle, HandleObject as RustHandleObject, IntoHandle, JSEngine, JSEngineHandle, < 4861 span class="pl-v x x-last">ParentRuntime,
49-
Runtime as RustRuntime,
49+
Handle, HandleObject as RustHandleObject, HandleValue, IntoHandle, JSEngine, JSEngineHandle,
50+
ParentRuntime, Runtime as RustRuntime,
5051
};
5152
use malloc_size_of::MallocSizeOfOps;
5253
use malloc_size_of_derive::MallocSizeOf;
@@ -62,7 +63,7 @@ use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
6263
use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
6364
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
6465
use crate::dom::bindings::conversions::{
65-
get_dom_class, private_from_object, root_from_handleobject,
66+ get_dom_class, private_from_object, root_from_handleobject, root_from_object,
6667
};
6768
use crate::dom::bindings::error::{Error, throw_dom_exception};
6869
use crate::dom::bindings::inheritance::Castable;
@@ -71,6 +72,7 @@ use crate::dom::bindings::refcounted::{
7172
};
7273
use crate::dom::bindings::reflector::{DomGlobal, DomObject};
7374
use crate::dom::bindings::root::trace_roots;
75+
use crate::dom::bindings::str::DOMString;
7476
use crate::dom::bindings::utils::DOM_CALLBACKS;
7577
use crate::dom::bindings::{principals, settings_stack};
7678
use crate::dom::csp::CspReporting;
@@ -80,6 +82,7 @@ use crate::dom::globalscope::GlobalScope;
8082
use crate::dom::promise::Promise;
8183
use crate::dom::promiserejectionevent::PromiseRejectionEvent;
8284
use crate::dom::response::Response;
85+
use crate::dom::trustedscript::TrustedScript;
8386
use crate::microtask::{EnqueuedPromiseCallback, Microtask, MicrotaskQueue};
8487
use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
8588
use crate::script_module::EnsureModuleHooksInitialized;
@@ -98,7 +101,7 @@ static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps {
98101

99102
static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks {
100103
contentSecurityPolicyAllows: Some(content_security_policy_allows),
101-
codeForEvalGets: None, //TODO
104+
codeForEvalGets: Some(code_for_eval_gets),
102105
subsumes: Some(principals::subsumes),
103106
};
104107

@@ -468,16 +471,43 @@ unsafe extern "C" fn promise_rejection_tracker(
468471
})
469472
}
470473

474+
#[allow(unsafe_code)]
475+
fn safely_convert_null_to_string(cx: JSContext, str_: HandleString) -> DOMString {
476+
DOMString::from(match std::ptr::NonNull::new(*str_) {
477+
None => "".to_owned(),
478+
Some(str_) => unsafe { jsstr_to_string(*cx, str_) },
479+
})
480+
}
481+
482+
#[allow(unsafe_code)]
483+
unsafe extern "C" fn code_for_eval_gets(
484+
cx: *mut RawJSContext,
485+
code: HandleObject,
486+
code_for_eval: MutableHandleString,
487+
) -> bool {
488+
let cx = JSContext::from_ptr(cx);
489+
if let Ok(trusted_script) = root_from_object::<TrustedScript>(code.get(), *cx) {
490+
let script_string = trusted_script.data();
491+
let new_string = JS_NewStringCopyN(
492+
*cx,
493+
script_string.as_ptr() as *const libc::c_char,
494+
script_string.len(),
495+
);
496+
code_for_eval.set(new_string);
497+
}
498+
true
499+
}
500+
471501
#[allow(unsafe_code)]
472502
unsafe extern "C" fn content_security_policy_allows(
473503
cx: *mut RawJSContext,
474504
runtime_code: RuntimeCode,
475-
sample: HandleString,
476-
_compilation_type: CompilationType,
477-
_parameter_strings: u8, //FIXME in bindings generation
478-
_body_string: HandleString,
479-
_parameter_args: u8, //FIXME in bindings generation
480-
_body_arg: HandleValue,
505+
code_string: HandleString,
506+
compilation_type: CompilationType,
507+
parameter_strings: u8, //FIXME in bindings generation
508+
body_string: HandleString,
509+
parameter_args: u8, //FIXME in bindings generation
510+
body_arg: RawHandleValue,
481511
can_compile_strings: *mut bool,
482512
) -> bool {
483513
let mut allowed = false;
@@ -488,13 +518,17 @@ unsafe extern "C" fn content_security_policy_allows(
488518
let global = &GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
489519

490520
allowed = match runtime_code {
491-
RuntimeCode::JS => {
492-
let source = std::ptr::NonNull::new(*sample)
493-
.map_or_else(String::new, |jsstr| jsstr_to_string(*cx, jsstr));
494-
global
495-
.get_csp_list()
496-
.is_js_evaluation_allowed(global, &source)
497-
},
521+
RuntimeCode::JS => TrustedScript::can_compile_string_with_trusted_type(
522+
cx,
523+
global,
524+
safely_convert_null_to_string(cx, code_string),
525+
compilation_type,
526+
parameter_strings,
527+
safely_convert_null_to_string(cx, body_string),
528+
parameter_args,
529+
HandleValue::from_raw(body_arg),
530+
CanGc::note(),
531+
),
498532
RuntimeCode::WASM => global.get_csp_list().is_wasm_evaluation_allowed(global),
499533
};
500534
});

tests/wpt/meta/content-security-policy/reporting/report-clips-sample.https.html.ini

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
11
[report-clips-sample.https.html]
2-
[Unsafe eval violation sample is clipped to 40 characters.]
3-
expected: FAIL
4-
5-
[Unsafe indirect eval violation sample is clipped to 40 characters.]
6-
expected: FAIL
7-
82
[Function constructor - the other kind of eval - is clipped.]
93
expected: FAIL
104

tests/wpt/meta/trusted-types/DedicatedWorker-block-eval-function-constructor.html.ini

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)
0