diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index 3c87454ab..6fb8c9712 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -3908,7 +3908,6 @@ trusted-types/HTMLElement-generic.html trusted-types/ServiceWorkerContainer-register-from-DedicatedWorker.https.html trusted-types/ServiceWorkerContainer-register-from-ServiceWorker.https.html trusted-types/ServiceWorkerContainer-register-from-SharedWorker.https.html -trusted-types/ServiceWorkerContainer-register.https.html trusted-types/TrustedType-AttributeNodes.html trusted-types/TrustedTypePolicyFactory-constants.html trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none.html diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index b3c90fd45..1d86c99d7 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -7531,6 +7531,7 @@ trusted-types/SVGScriptElement-internal-slot.html trusted-types/ServiceWorker-block-eval-function-constructor.https.html trusted-types/ServiceWorker-eval.https.html trusted-types/ServiceWorker-importScripts.https.html +trusted-types/ServiceWorkerContainer-register.https.html trusted-types/SharedWorker-block-eval-function-constructor.html trusted-types/SharedWorker-eval.html trusted-types/SharedWorker-importScripts.html diff --git a/moli-renderer-v8/src/context_bootstrap/window_runtime/service_worker.rs b/moli-renderer-v8/src/context_bootstrap/window_runtime/service_worker.rs index ede633701..033ceaaab 100644 --- a/moli-renderer-v8/src/context_bootstrap/window_runtime/service_worker.rs +++ b/moli-renderer-v8/src/context_bootstrap/window_runtime/service_worker.rs @@ -340,18 +340,45 @@ pub(in crate::context_bootstrap) fn navigator_service_worker_register_callback<' args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s, v8::Value>, ) { - let Some(resolver) = v8::PromiseResolver::new(scope) else { - return; - }; - let promise = resolver.get_promise(scope); let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else { + let Some(resolver) = v8::PromiseResolver::new(scope) else { + return; + }; + let promise = resolver.get_promise(scope); reject_service_worker_promise(scope, resolver, "failed to get native bridge"); rv.set(promise.into()); return; }; - let host = unsafe { &mut *host_ptr }; + if args.length() == 0 { + throw_type_error( + scope, + "Failed to execute 'register' on 'ServiceWorkerContainer': 1 argument required, but only 0 present.", + ); + return; + } let owner = service_worker_container_owner_scope(scope, args.this()); - let Some(request_context) = host.service_worker_window_request_context(owner) else { + let Some(request_context) = + (unsafe { &mut *host_ptr }).service_worker_window_request_context(owner) + else { + let Some(resolver) = v8::PromiseResolver::new(scope) else { + return; + }; + let promise = resolver.get_promise(scope); + reject_service_worker_promise_with_type_error( + scope, + resolver, + "service worker document is no longer current", + ); + rv.set(promise.into()); + return; + }; + let Some(requirements) = + (unsafe { &*host_ptr }).trusted_types_for_script_requirements_for_owner(owner) + else { + let Some(resolver) = v8::PromiseResolver::new(scope) else { + return; + }; + let promise = resolver.get_promise(scope); reject_service_worker_promise_with_type_error( scope, resolver, @@ -360,7 +387,21 @@ pub(in crate::context_bootstrap) fn navigator_service_worker_register_callback<' rv.set(promise.into()); return; }; - let Some(script_url) = service_worker_script_url(scope, request_context.document_url(), &args) + let Some(script) = crate::context_bootstrap::trusted_script_url_string_or_throw( + scope, + args.get(0), + requirements, + "ServiceWorkerContainer register", + "register", + ) else { + return; + }; + let Some(resolver) = v8::PromiseResolver::new(scope) else { + return; + }; + let promise = resolver.get_promise(scope); + let Some(script_url) = + resolve_service_worker_script_url(request_context.document_url(), &script) else { reject_service_worker_promise( scope, @@ -388,6 +429,7 @@ pub(in crate::context_bootstrap) fn navigator_service_worker_register_callback<' }; let scope_url = service_worker_scope_url(scope, request_context.document_url(), &script_url, &args); + let host = unsafe { &mut *host_ptr }; let Some(request_client) = host .document_resource_loader_for_window_owner(request_context.owner().window_document_owner()) .map(|loader| loader.request_client().clone()) @@ -2036,16 +2078,8 @@ fn reject_service_worker_navigation_preload_state_error( } } -fn service_worker_script_url( - scope: &mut v8::PinScope<'_, '_>, - document_url: &url::Url, - args: &v8::FunctionCallbackArguments<'_>, -) -> Option { - let script = args - .get(0) - .to_string(scope) - .map(|value| value.to_rust_string_lossy(scope))?; - let mut script_url = document_url.join(&script).ok()?; +fn resolve_service_worker_script_url(document_url: &url::Url, script: &str) -> Option { + let mut script_url = document_url.join(script).ok()?; script_url.set_fragment(None); Some(script_url) } diff --git a/moli-renderer-v8/src/document_runtime/security_policy.rs b/moli-renderer-v8/src/document_runtime/security_policy.rs index 616d6624c..6971d3b80 100644 --- a/moli-renderer-v8/src/document_runtime/security_policy.rs +++ b/moli-renderer-v8/src/document_runtime/security_policy.rs @@ -1373,6 +1373,29 @@ impl DocumentRuntime { ) } + pub(crate) fn trusted_types_for_script_requirements_for_document( + &self, + document_handle: Option, + response_policies: &[String], + report_only_policies: &[String], + reporting_endpoints: &ContentSecurityPolicyReportingEndpoints, + ) -> TrustedTypesForScriptRequirements { + let enforced_policies = self + .document_content_security_policy_strings_for_optional_document( + document_handle, + response_policies, + reporting_endpoints, + ); + let report_only_policies = document_response_content_security_policy_strings( + report_only_policies, + reporting_endpoints, + ); + TrustedTypesForScriptRequirements::new( + document_policies_require_trusted_types_for_script(&enforced_policies), + document_policies_require_trusted_types_for_script(&report_only_policies), + ) + } + pub(crate) fn requires_trusted_types_for_script(&self) -> bool { let policies = self.document_content_security_policy_strings_for_optional_document( Some(self.document_handle()), diff --git a/moli-renderer-v8/src/native_bridge/context_host/security_policy.rs b/moli-renderer-v8/src/native_bridge/context_host/security_policy.rs index 8cdb1effa..7d401eaa0 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/security_policy.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/security_policy.rs @@ -72,6 +72,47 @@ impl JsContextHost { Some(crate::document_runtime::DocumentConnectPolicySnapshot::from_policy_container(&policy)) } + pub(crate) fn trusted_types_for_script_requirements_for_owner( + &self, + owner: OwnerDispatchScope, + ) -> Option { + match owner { + OwnerDispatchScope::Top => { + // SAFETY: JsContextHost is owned by the ScriptVm that owns this DocumentRuntime. + Some(unsafe { &*self.runtime }.trusted_types_for_script_requirements()) + } + OwnerDispatchScope::Child(handle) => { + let response_policies = + self.child_effective_response_content_security_policies(handle); + let report_only_policies = + self.child_effective_response_content_security_report_only_policies(handle); + let reporting_endpoints = + self.child_effective_content_security_reporting_endpoints(handle); + // SAFETY: JsContextHost is owned by the ScriptVm that owns this DocumentRuntime. + Some( + unsafe { &*self.runtime }.trusted_types_for_script_requirements_for_document( + self.child_browsing_context_document_handle(handle), + &response_policies, + &report_only_policies, + &reporting_endpoints, + ), + ) + } + OwnerDispatchScope::LightweightPopup(popup_id) => { + let policy = self.lightweight_popup_policy_container(popup_id)?; + // SAFETY: JsContextHost is owned by the ScriptVm that owns this DocumentRuntime. + Some( + unsafe { &*self.runtime }.trusted_types_for_script_requirements_for_document( + self.lightweight_popup_document_handle(popup_id), + &policy.response_content_security_policies, + &policy.response_content_security_report_only_policies, + &policy.content_security_reporting_endpoints, + ), + ) + } + } + } + pub(crate) fn cross_origin_embedder_policy( &self, ) -> crate::cross_origin_isolation::CrossOriginEmbedderPolicy { diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs index ca72c3df3..9da925f4c 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs @@ -842,3 +842,64 @@ fn script_execution_violation_outside_javascript_stack_avoids_v8_frame_probe() { r#"{"blockedURI":"trusted-types-sink","sample":"HTMLScriptElement text|untrusted-source"}"# ); } + +#[test] +fn service_worker_register_gates_script_url_before_url_resolution() { + let mut vm = new_storage_test_vm("https://service-worker-register-trusted-types.test/"); + vm.set_response_content_security_policies(&["require-trusted-types-for 'script'".to_owned()]); + + let result = vm + .eval( + r#" +(() => { + const errorName = callback => { + try { + callback(); + return "none"; + } catch (error) { + return error && error.name; + } + }; + const policy = trustedTypes.createPolicy("service-worker-register", { + createHTML: value => value, + createScriptURL: value => value + }); + const blockedString = errorName(() => navigator.serviceWorker.register("worker.js")); + const blockedWrongType = errorName(() => + navigator.serviceWorker.register(policy.createHTML("worker.js")) + ); + const missing = errorName(() => navigator.serviceWorker.register()); + + const trustedPromise = navigator.serviceWorker.register( + policy.createScriptURL("http://[") + ); + trustedPromise.catch(() => {}); + + const defaultCalls = []; + trustedTypes.createPolicy("default", { + createScriptURL: (value, type, sink) => { + defaultCalls.push([value, type, sink]); + return "http://["; + } + }); + const defaultPromise = navigator.serviceWorker.register("worker.potato"); + defaultPromise.catch(() => {}); + + return JSON.stringify({ + blockedString, + blockedWrongType, + missing, + trustedPromise: trustedPromise instanceof Promise, + defaultPromise: defaultPromise instanceof Promise, + defaultCalls + }); +})() +"#, + ) + .expect("ServiceWorkerContainer.register TrustedScriptURL probe should evaluate"); + + assert_eq!( + result, + r#"{"blockedString":"TypeError","blockedWrongType":"TypeError","missing":"TypeError","trustedPromise":true,"defaultPromise":true,"defaultCalls":[["worker.potato","TrustedScriptURL","ServiceWorkerContainer register"]]}"# + ); +}