diff --git a/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/auxiliary_page_identity.rs b/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/auxiliary_page_identity.rs index 1a80a468f5..41c358225f 100644 --- a/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/auxiliary_page_identity.rs +++ b/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/auxiliary_page_identity.rs @@ -844,6 +844,34 @@ async fn explicit_browser_and_noopener_blank_documents_keep_their_committed_hist abort_test_cdp_server(server).await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn auxiliary_initial_domain_updates_follow_the_creator() { + let (fixture_addr, _fixture) = spawn_dedicated_fixture_server( + Router::new().fallback(get(|| async { + axum::response::Html("

domain

") + })), + "auxiliary-domain", + ); + let (addr, server) = spawn_test_protocol_server().await; + let (mut browser, _) = + connect_async(format!("ws://{addr}/devtools/browser/{DEFAULT_BROWSER_ID}")) + .await + .unwrap(); + let opener_id = create_dynamic_target(&mut browser, 1).await; + let mut opener = connect_dynamic_page(addr, &opener_id).await; + send_cdp_command(&mut opener, 1, "Page.enable", None, json!({})).await; + navigate_dynamic_page_and_wait_for_load( + &mut opener, + 2, + &format!("http://{fixture_addr}/parent"), + ) + .await; + let (_, _child) = open_auxiliary(addr, &mut opener, "").await; + assert_eq!(evaluate_window_name_probe(&mut opener,3, + "document.domain='127.0.0.1';[p.document.domain,p.document.body.ownerDocument===p.document]").await,json!(["127.0.0.1",true])); + abort_test_cdp_server(server).await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn auxiliary_blank_reload_preserves_origin_storage_and_fallback_base() { let (fixture_addr, _fixture) = spawn_dedicated_fixture_server( diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_frame_runtime/window.rs b/moli-renderer-v8/src/native_bridge/context_host/child_frame_runtime/window.rs index fc6c99f6e3..79f966e160 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_frame_runtime/window.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_frame_runtime/window.rs @@ -660,7 +660,11 @@ fn contexts_can_script_access<'s>( }); }; if accessing_host_ptr != accessed_host_ptr { - return false; + return host.window_execution_context_can_access_other_host( + accessing_identity, + unsafe { &*accessed_host_ptr }, + accessed_identity, + ); } host.window_execution_context_can_access(accessing_identity, accessed_identity) diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_frames.rs b/moli-renderer-v8/src/native_bridge/context_host/child_frames.rs index 0a7fa822ea..102ac09f7f 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_frames.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_frames.rs @@ -19,7 +19,7 @@ use moli_page_types::{ apply_child_browsing_context_navigation_to_entry_seed as apply_child_navigation_to_seed, replace_child_browsing_context_navigation_in_entry_seed as replace_child_navigation_in_seed, }; -use std::collections::HashSet; +use std::{cell::RefCell, collections::HashSet, rc::Rc}; use url::Url; mod classic_scripts; @@ -58,7 +58,7 @@ pub(super) struct ChildBrowsingContextEntry { performance_time_origin: ChildPerformanceTimeOrigin, pending_document_load_id: Option, classic_script_document_state: classic_scripts::ChildClassicScriptDocumentState, - document_domain_override: Option, + document_domain_override: Rc>>, credentialless: bool, service_worker_client_id: Option, pending_service_worker_client_id: Option, @@ -117,15 +117,19 @@ impl ChildBrowsingContextEntry { } pub(super) fn document_domain_override(&self) -> Option { + self.document_domain_override.borrow().clone() + } + + pub(super) fn shared_document_domain_override(&self) -> Rc>> { self.document_domain_override.clone() } pub(super) fn set_document_domain_override(&mut self, domain: String) { - self.document_domain_override = Some(domain); + *self.document_domain_override.borrow_mut() = Some(domain); } pub(super) fn clear_document_domain_override(&mut self) { - self.document_domain_override = None; + self.document_domain_override = Rc::new(RefCell::new(None)); } pub(super) fn window_name(&self) -> &str { diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_frames/registry.rs b/moli-renderer-v8/src/native_bridge/context_host/child_frames/registry.rs index 328fb3fc5e..23b1bc606d 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_frames/registry.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_frames/registry.rs @@ -502,7 +502,8 @@ impl JsContextHost { // Document keeps its mutable origin until replacement commits. document_domain_override: existing .as_ref() - .and_then(|entry| entry.document_domain_override()), + .map(|entry| entry.document_domain_override.clone()) + .unwrap_or_default(), credentialless, service_worker_client_id: existing .as_ref() diff --git a/moli-renderer-v8/src/native_bridge/context_host/core.rs b/moli-renderer-v8/src/native_bridge/context_host/core.rs index 751b2d3912..bdfba522a7 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/core.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/core.rs @@ -47,9 +47,11 @@ impl JsContextHost { &self, origin: &str, inherited_opaque_origin_matches: bool, + inherited_document_domain: Option<&str>, policy: &crate::document_runtime::DocumentPolicyContainer, ) -> crate::frame_owner_model::FrameDocumentLocalWindowTransition { - let same_origin = self.document_domain_override.is_none() + let same_origin = self.document_domain_override.borrow().as_deref() + == inherited_document_domain && (inherited_opaque_origin_matches || moli_url::WebOrigin::from_serialized(&self.main_document_security_origin()) .same_origin(&moli_url::WebOrigin::from_serialized(origin))); @@ -389,7 +391,7 @@ impl JsContextHost { #[cfg(test)] force_child_default_context_preflight_failure: false, child_browsing_context_document_handles: HashMap::new(), - document_domain_override: None, + document_domain_override: Rc::new(RefCell::new(None)), next_child_browsing_context_id: 1, next_child_document_load_id: 0, next_child_classic_script_load_id: 0, diff --git a/moli-renderer-v8/src/native_bridge/context_host/document_domain.rs b/moli-renderer-v8/src/native_bridge/context_host/document_domain.rs index f0db959a29..197db536b2 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/document_domain.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/document_domain.rs @@ -4,6 +4,22 @@ use std::net::IpAddr; use url::{Host, Url}; impl JsContextHost { + pub(crate) fn inherit_document_domain_override(&mut self, domain: Rc>>) { + self.document_domain_override = domain; + } + + pub(crate) fn inherited_document_domain( + &self, + child: Option, + ) -> Rc>> { + match child.and_then(|handle| self.child_browsing_context_security_origin_owner(handle)) { + Some(ChildSecurityOriginOwner::Child(owner)) => { + self.child_browsing_contexts[&owner].shared_document_domain_override() + } + Some(ChildSecurityOriginOwner::Main) | None => self.document_domain_override.clone(), + } + } + pub(crate) fn document_domain_value_for_document_handle( &self, document_handle: DomHandle, @@ -11,6 +27,7 @@ impl JsContextHost { if document_handle == self.document_handle() { return self .document_domain_override + .borrow() .clone() .unwrap_or_else(|| self.main_document_domain_host().unwrap_or_default()); } @@ -48,7 +65,7 @@ impl JsContextHost { if !document_domain_is_allowed_for_host(¤t_host, &domain) { return false; } - self.document_domain_override = Some(domain); + *self.document_domain_override.borrow_mut() = Some(domain); return true; } let Some(child_handle) = @@ -68,7 +85,7 @@ impl JsContextHost { handle: DomHandle, ) -> Option { match self.child_browsing_context_security_origin_owner(handle)? { - ChildSecurityOriginOwner::Main => self.document_domain_override.clone(), + ChildSecurityOriginOwner::Main => self.document_domain_override.borrow().clone(), ChildSecurityOriginOwner::Child(owner) => self .child_browsing_contexts .get(&owner) @@ -115,7 +132,9 @@ impl JsContextHost { return false; }; match origin_owner { - ChildSecurityOriginOwner::Main => self.document_domain_override = Some(domain), + ChildSecurityOriginOwner::Main => { + *self.document_domain_override.borrow_mut() = Some(domain); + } ChildSecurityOriginOwner::Child(owner) => { let Some(entry) = self.child_browsing_contexts.get_mut(&owner) else { return false; diff --git a/moli-renderer-v8/src/native_bridge/context_host/mod.rs b/moli-renderer-v8/src/native_bridge/context_host/mod.rs index 732c2f80de..0e81db50d0 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/mod.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/mod.rs @@ -867,7 +867,7 @@ pub(crate) struct JsContextHost { #[cfg(test)] force_child_default_context_preflight_failure: bool, child_browsing_context_document_handles: HashMap, - document_domain_override: Option, + document_domain_override: Rc>>, next_child_browsing_context_id: u64, next_child_document_load_id: u64, next_child_classic_script_load_id: u64, diff --git a/moli-renderer-v8/src/native_bridge/context_host/popups/renderer_owned.rs b/moli-renderer-v8/src/native_bridge/context_host/popups/renderer_owned.rs index 5bdc403798..38c0128aee 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/popups/renderer_owned.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/popups/renderer_owned.rs @@ -271,6 +271,12 @@ impl JsContextHost { }; let (pending, window_proxy) = allocator.stage_in_scope(scope, &environment, &self.bridge.bindings, init)?; + if !policy.sandbox.forces_opaque_origin { + let target = crate::util::context_host_ptr_from_window_object(scope, window_proxy) + .expect("a related Window retains its native host"); + unsafe { &mut *target }.document_domain_override = + self.inherited_document_domain(creator_child_handle); + } Ok(OpenedRendererWindow { window_proxy, window: Some(window), diff --git a/moli-renderer-v8/src/native_bridge/context_host/window_execution_context/settings.rs b/moli-renderer-v8/src/native_bridge/context_host/window_execution_context/settings.rs index ba2280913f..2f641b81d8 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/window_execution_context/settings.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/window_execution_context/settings.rs @@ -105,7 +105,7 @@ impl WindowEnvironmentSettings { } = &mut origin { if self.domain_owner_document == host.document_handle() { - *document_domain = host.document_domain_override.clone(); + *document_domain = host.document_domain_override.borrow().clone(); } else if let Some(handle) = host.child_browsing_context_handle_for_stored_document(self.domain_owner_document) { diff --git a/moli-renderer-v8/src/native_bridge/context_host/window_security_tokens.rs b/moli-renderer-v8/src/native_bridge/context_host/window_security_tokens.rs index ed88d682c3..f57634414f 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/window_security_tokens.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/window_security_tokens.rs @@ -18,7 +18,7 @@ impl JsContextHost { return None; } let origin = self.main_document_security_origin(); - if self.document_domain_override.is_some() { + if self.document_domain_override.borrow().is_some() { return None; } window_security_token_key(origin) @@ -47,7 +47,7 @@ impl JsContextHost { } window_isolated_world_security_token_key( self.main_document_security_origin(), - self.document_domain_override.is_some(), + self.document_domain_override.borrow().is_some(), ) } @@ -107,6 +107,39 @@ impl JsContextHost { } } self.refresh_child_window_access_surfaces_after_origin_mutation(scope); + if let Some(environment) = self.page_script_environment() { + for related in environment.related_document_hosts() { + let related_ptr = related.as_ptr(); + if std::ptr::eq(related_ptr, self) { + continue; + } + let related_host = unsafe { &*related_ptr }; + if !std::rc::Rc::ptr_eq( + &self.document_domain_override, + &related_host.document_domain_override, + ) { + continue; + } + let dispatch_scope = OwnerDispatchScope::Top; + let Some(owner) = + related_host.current_window_execution_context_owner(dispatch_scope) + else { + continue; + }; + if let Some((_, context)) = + related_host.window_execution_context(scope, owner, dispatch_scope) + && set_window_security_token( + scope, + context, + related_host + .main_default_world_security_token_key() + .as_deref(), + ) + { + updated += 1; + } + } + } updated } @@ -179,6 +212,39 @@ impl JsContextHost { ) } + pub(crate) fn window_execution_context_can_access_other_host( + &self, + accessing: WindowExecutionContextIdentity, + target_host: &JsContextHost, + accessed: WindowExecutionContextIdentity, + ) -> bool { + if !self.window_execution_context_identity_is_current(accessing) + || !target_host.window_execution_context_identity_is_current(accessed) + { + return false; + } + if accessing.grants_universal_access() { + return true; + } + let Some(origin) = self.window_access_origin(accessing) else { + return false; + }; + let Some(target) = + target_host.window_access_origin_for_dispatch_scope(accessed.dispatch_scope()) + else { + return false; + }; + // Opaque owner identifiers are local to each host. Shared opaque + // origins are authorized by their V8 security token before this path. + matches!( + (&origin, &target), + ( + WindowAccessOrigin::Tuple { .. }, + WindowAccessOrigin::Tuple { .. } + ) + ) && origin.can_access(&target) + } + /// Checks Window access before the target realm is entered or materialized. /// /// WebIDL operations such as a borrowed `fetch()` must authorize the @@ -262,7 +328,7 @@ impl JsContextHost { } WindowAccessOrigin::from_serialized_origin( serialized_origin, - self.document_domain_override.clone(), + self.document_domain_override.borrow().clone(), ) } diff --git a/moli-renderer-v8/src/script_vm.rs b/moli-renderer-v8/src/script_vm.rs index 129f5796a4..0ef7a5b5a9 100644 --- a/moli-renderer-v8/src/script_vm.rs +++ b/moli-renderer-v8/src/script_vm.rs @@ -1953,6 +1953,7 @@ impl ScriptVm { pub(crate) struct ScriptVmInitialDocumentEnvironment { security_token: Option>, + document_domain_override: Rc>>, origin: String, pub(crate) policy_container: crate::document_runtime::DocumentPolicyContainer, fallback_base_url: Option, @@ -2052,6 +2053,9 @@ impl ScriptVmPageRealmBootstrap { let (page_context_cancel_tx, page_context_cancel_rx) = renderer_page_context_cancel_channel(); + let inherited_document_domain = initial_environment + .as_ref() + .map(|environment| environment.document_domain_override.clone()); let inherited_security_token = initial_environment.and_then(|environment| environment.security_token); let RendererDocumentIsolateBootstrap { @@ -2106,6 +2110,12 @@ impl ScriptVmPageRealmBootstrap { top_level_storage_key, reserved_service_worker_client_id, ))); + if let Some(domain) = inherited_document_domain { + context_host + .borrow_mut() + .inherit_document_domain_override(domain); + } + context_host .borrow_mut() .bind_deferred_context_host_release_queue( diff --git a/moli-renderer-v8/src/script_vm/document_environment.rs b/moli-renderer-v8/src/script_vm/document_environment.rs index 3dabdf28ee..9b82d25189 100644 --- a/moli-renderer-v8/src/script_vm/document_environment.rs +++ b/moli-renderer-v8/src/script_vm/document_environment.rs @@ -71,8 +71,23 @@ impl ScriptVmInitialDocumentEnvironment { .ok_or_else(|| anyhow!("navigation initiator has no live creation context"))?; let security_token = (!policy_container.sandbox.forces_opaque_origin) .then(|| v8::Global::new(scope, context.get_security_token(scope))); + let document_domain_override = (!policy_container.sandbox.forces_opaque_origin) + .then(|| crate::util::context_host_ptr_from_context_slot(context)) + .flatten() + .map(|host_ptr| { + let host = unsafe { &*host_ptr }; + let child = host + .window_execution_context_identity_for_access_check(context) + .and_then(|identity| match identity.dispatch_scope() { + crate::native_bridge::OwnerDispatchScope::Child(handle) => Some(handle), + _ => None, + }); + host.inherited_document_domain(child) + }) + .unwrap_or_default(); Ok(Self { security_token, + document_domain_override, origin, policy_container, fallback_base_url: Some(base_url), @@ -435,6 +450,7 @@ impl ScriptVm { let token = scope.get_current_context().get_security_token(scope); Ok(ScriptVmInitialDocumentEnvironment { security_token: Some(v8::Global::new(scope, token)), + document_domain_override: host.inherited_document_domain(None), origin, policy_container, fallback_base_url, diff --git a/moli-renderer-v8/src/script_vm/document_isolate.rs b/moli-renderer-v8/src/script_vm/document_isolate.rs index 27e360b77e..060879e278 100644 --- a/moli-renderer-v8/src/script_vm/document_isolate.rs +++ b/moli-renderer-v8/src/script_vm/document_isolate.rs @@ -495,6 +495,17 @@ impl RendererPageScriptEnvironment { .find_map(|inner| matches(&inner).map(|()| Self { inner })) } + pub(crate) fn related_document_hosts(&self) -> Vec>> { + self.inner + .renderer_document_isolate + .related_pages + .borrow() + .values() + .filter_map(Weak::upgrade) + .filter_map(|page| page.document_host.borrow().upgrade()) + .collect() + } + pub(crate) fn window_identity( &self, ) -> Option<( diff --git a/moli-renderer-v8/src/script_vm/initial_window_document.rs b/moli-renderer-v8/src/script_vm/initial_window_document.rs index 48a587456b..bc7fdcc2f3 100644 --- a/moli-renderer-v8/src/script_vm/initial_window_document.rs +++ b/moli-renderer-v8/src/script_vm/initial_window_document.rs @@ -25,12 +25,17 @@ impl ScriptVm { } else { false }; + let inherited_domain = + inherited.map(|environment| environment.document_domain_override.borrow()); Ok(self ._context_host .borrow() .main_document_local_window_transition_for_commit( origin, inherited_opaque_origin_matches, + inherited_domain + .as_ref() + .and_then(|domain| domain.as_deref()), policy, )) } @@ -111,6 +116,11 @@ impl ScriptVm { vm.page_runtime_wake_tx = page_task_tx.page_runtime_wake_sender(); { let mut host = vm._context_host.borrow_mut(); + if let Some(environment) = inherited.as_ref() { + host.inherit_document_domain_override( + environment.document_domain_override.clone(), + ); + } host.rebind_initial_main_document_senders( resource_completion, page_task_tx.top_level_navigation_handoff_sender(), diff --git a/moli-renderer-v8/src/script_vm/related_page.rs b/moli-renderer-v8/src/script_vm/related_page.rs index 6321e33719..ddae6de294 100644 --- a/moli-renderer-v8/src/script_vm/related_page.rs +++ b/moli-renderer-v8/src/script_vm/related_page.rs @@ -38,6 +38,7 @@ impl ScriptVmDefaultWorldBootstrap { None, Some(ScriptVmInitialDocumentEnvironment { security_token: None, + document_domain_override: Rc::default(), fallback_base_url: None, storage_key: None, origin: inherited_origin.clone(),