mirror of
https://github.com/lexmount/moli.git
synced 2026-10-03 08:00:49 +00:00
fix(trusted-types): gate service worker registration URL
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -340,18 +340,30 @@ 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,
|
||||
@@ -360,7 +372,36 @@ 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(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,
|
||||
"service worker document is no longer current",
|
||||
);
|
||||
rv.set(promise.into());
|
||||
return;
|
||||
};
|
||||
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<url::Url> {
|
||||
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<url::Url> {
|
||||
let mut script_url = document_url.join(script).ok()?;
|
||||
script_url.set_fragment(None);
|
||||
Some(script_url)
|
||||
}
|
||||
|
||||
@@ -1373,6 +1373,29 @@ impl DocumentRuntime {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn trusted_types_for_script_requirements_for_document(
|
||||
&self,
|
||||
document_handle: Option<DomHandle>,
|
||||
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()),
|
||||
|
||||
@@ -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<TrustedTypesForScriptRequirements> {
|
||||
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 {
|
||||
|
||||
@@ -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"]]}"#
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user