Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion moli-benchmark/wpt-cross-current/failed-cases.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions moli-benchmark/wpt-cross-current/passed-cases.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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)
}
Expand Down
23 changes: 23 additions & 0 deletions moli-renderer-v8/src/document_runtime/security_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down
41 changes: 41 additions & 0 deletions moli-renderer-v8/src/native_bridge/context_host/security_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
61 changes: 61 additions & 0 deletions moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]]}"#
);
}
Loading