mirror of
https://github.com/lexmount/moli.git
synced 2026-11-13 16:10:01 +00:00
fix(csp): preserve policy ownership across document input stream resets
This commit is contained in:
@@ -326,13 +326,7 @@ impl DocumentRuntime {
|
||||
self.clear_classic_defer_timer_schedule_ranges();
|
||||
self.pending_parser_blocking_work = None;
|
||||
self.root_document_parser = None;
|
||||
self.delivered_meta_content_security_policies
|
||||
.get_mut()
|
||||
.clear();
|
||||
self.local_worker_policy_sources.get_mut().clear();
|
||||
self.processed_meta_content_security_policy_handles
|
||||
.get_mut()
|
||||
.clear();
|
||||
self.reset_meta_content_security_policy_processing_for_document(self.document_handle());
|
||||
self.document_input_stream_opened = true;
|
||||
self.set_document_ready_state(DocumentReadyState::Loading);
|
||||
}
|
||||
|
||||
@@ -2401,6 +2401,18 @@ pub(crate) fn set_response_content_security_report_only_policies(
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn reset_meta_content_security_policy_processing_for_document(
|
||||
&self,
|
||||
document: DomHandle,
|
||||
) {
|
||||
// document.open() replaces the input stream of this same Document.
|
||||
// Its installed policies and live blob/worker policy source survive;
|
||||
// meta elements inserted into the new stream are processed again.
|
||||
self.processed_meta_content_security_policy_handles
|
||||
.borrow_mut()
|
||||
.retain(|(owner, _)| *owner != document);
|
||||
}
|
||||
|
||||
pub(crate) fn initialize_inherited_meta_content_security_policies(
|
||||
&self,
|
||||
document_handle: DomHandle,
|
||||
@@ -3699,6 +3711,17 @@ mod tests {
|
||||
.is_enforced(),
|
||||
"checking the parent must not discard the child document's delivered policy"
|
||||
);
|
||||
runtime.open_document();
|
||||
assert!(
|
||||
runtime
|
||||
.dom_host_mut()
|
||||
.set_attribute(child_meta, "content", "script-src 'none'")
|
||||
);
|
||||
assert_eq!(
|
||||
runtime.meta_content_security_policy_strings_for_document(child_document),
|
||||
["require-trusted-types-for 'script'"],
|
||||
"reopening the parent must not discard or reprocess another Document's delivered meta"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3783,7 +3806,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_open_does_not_reuse_replaced_document_meta_csp() {
|
||||
fn document_open_preserves_the_same_documents_delivered_meta_csp() {
|
||||
let mut runtime = runtime_for_html(
|
||||
r#"<!doctype html>
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
@@ -3791,12 +3814,14 @@ mod tests {
|
||||
"#,
|
||||
);
|
||||
assert!(runtime.requires_trusted_types_for_script());
|
||||
let document = runtime.document_handle();
|
||||
|
||||
runtime.open_document();
|
||||
|
||||
assert_eq!(runtime.document_handle(), document);
|
||||
assert!(
|
||||
!runtime.requires_trusted_types_for_script(),
|
||||
"the replacement document must not inherit delivered meta policies"
|
||||
runtime.requires_trusted_types_for_script(),
|
||||
"reopening the same Document's input stream must retain its installed policy"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -721,6 +721,7 @@ impl JsContextHost {
|
||||
// document.open() reuses this Document and its mutable origin.
|
||||
entry.clear_script_execution_state();
|
||||
}
|
||||
host.reset_meta_content_security_policy_processing_for_document(document_handle);
|
||||
host.request_child_frame_realm_materialization(child_handle);
|
||||
host.install_empty_child_classic_script_runner_for_current_document(
|
||||
child_handle,
|
||||
|
||||
@@ -196,6 +196,7 @@ impl JsContextHost {
|
||||
// The input stream changes, but the same Document retains its
|
||||
// autofocus processed flag. Retire only its old candidates.
|
||||
host.clear_autofocus_candidates(document_handle);
|
||||
host.reset_meta_content_security_policy_processing_for_document(document_handle);
|
||||
host.set_lightweight_popup_same_document_url(scope, popup_id, url.clone());
|
||||
host.dom_host_mut()
|
||||
.set_html_quirks_mode_for_parser_document(
|
||||
|
||||
@@ -66,7 +66,10 @@ fn policy_owner_dispatch_scope(scope: &mut v8::PinScope<'_, '_>) -> OwnerDispatc
|
||||
if let Some(popup_id) = active_lightweight_popup_id(scope) {
|
||||
return OwnerDispatchScope::LightweightPopup(popup_id);
|
||||
}
|
||||
OwnerDispatchScope::Top
|
||||
// A borrowed popup intrinsic enters its realm without an ambient popup
|
||||
// task marker. Its Document still owns code generation and Trusted Types.
|
||||
let global = scope.get_current_context().global(scope);
|
||||
policy_owner_dispatch_scope_for_global(scope, global)
|
||||
}
|
||||
|
||||
fn policy_owner_dispatch_scope_for_global<'s>(
|
||||
|
||||
+62
@@ -524,6 +524,68 @@ async fn shared_worker_error_commits_child_navigation_before_document_script_rea
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn blob_worker_keeps_its_creator_policy_source_across_document_open() {
|
||||
run_page_vm_async_test(async move {
|
||||
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default()).unwrap();
|
||||
let (mut page_vm, _resource_source, _owner_wake_rx) =
|
||||
page_vm_with_bound_task_sources_and_owner_wake(
|
||||
&loader,
|
||||
Url::parse("https://document-open-blob-policy.test/page.html").unwrap(),
|
||||
);
|
||||
let local_executor = page_vm.local_executor.clone();
|
||||
local_executor
|
||||
.run(async move {
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.document_runtime
|
||||
.dom_host_mut()
|
||||
.reset_html_document_shell();
|
||||
page_vm.vm_mut().eval(
|
||||
r#"
|
||||
globalThis.__documentOpenWorkerResult = null;
|
||||
const meta = document.createElement('meta');
|
||||
meta.setAttribute('http-equiv', 'Content-Security-Policy');
|
||||
meta.setAttribute('content', "script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:");
|
||||
document.head.appendChild(meta);
|
||||
const workerUrl = URL.createObjectURL(new Blob([
|
||||
'try { eval("0"); postMessage("allowed"); } catch (error) { postMessage(error.name); }'
|
||||
], {type: 'text/javascript'}));
|
||||
document.open(); document.close();
|
||||
const added = document.createElement('meta');
|
||||
added.setAttribute('http-equiv', 'Content-Security-Policy');
|
||||
added.setAttribute('content', "script-src 'self' 'unsafe-inline' blob:");
|
||||
document.head.appendChild(added);
|
||||
const worker = new Worker(workerUrl);
|
||||
worker.onmessage = event => {
|
||||
globalThis.__documentOpenWorkerResult = event.data;
|
||||
worker.terminate(); URL.revokeObjectURL(workerUrl);
|
||||
};
|
||||
worker.onerror = event => {
|
||||
event.preventDefault();
|
||||
globalThis.__documentOpenWorkerResult = 'worker-error:' + event.message;
|
||||
worker.terminate(); URL.revokeObjectURL(workerUrl);
|
||||
};
|
||||
"#,
|
||||
)?;
|
||||
drive_websocket_until_done(
|
||||
&mut page_vm,
|
||||
"String(globalThis.__documentOpenWorkerResult !== null)",
|
||||
"blob worker must observe the same Document's later meta policy",
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
page_vm.vm_mut().eval("globalThis.__documentOpenWorkerResult")?,
|
||||
"EvalError",
|
||||
);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.await
|
||||
.expect("document-open blob worker CSP regression must finish on its owner lane");
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn blob_worker_created_in_data_iframe_inherits_opaque_broadcast_channel_owner_key() {
|
||||
run_page_vm_async_test(async move {
|
||||
|
||||
@@ -4,6 +4,49 @@ mod iframe_navigation;
|
||||
mod inline_tasks;
|
||||
mod parser_positions;
|
||||
|
||||
#[test]
|
||||
fn document_open_keeps_policy_and_processes_meta_in_the_new_stream() {
|
||||
for owner in ["window", "frame.contentWindow", "popup"] {
|
||||
let mut vm = new_storage_test_vm("https://document-open-policy.test/");
|
||||
vm.document_runtime
|
||||
.dom_host_mut()
|
||||
.reset_html_document_shell();
|
||||
assert_eq!(
|
||||
vm.eval(&format!(
|
||||
r#"(() => {{
|
||||
const frame = document.createElement('iframe');
|
||||
document.body.appendChild(frame);
|
||||
const popup = open();
|
||||
const owner = {owner};
|
||||
const doc = owner.document;
|
||||
const meta = doc.createElement('meta');
|
||||
meta.setAttribute('http-equiv', 'Content-Security-Policy');
|
||||
meta.setAttribute('content', "script-src 'self' 'unsafe-inline' 'unsafe-eval'");
|
||||
doc.head.appendChild(meta);
|
||||
const evaluate = () => {{
|
||||
try {{ return owner.eval('40 + 2'); }}
|
||||
catch (error) {{ return error.name + ':' + (error instanceof owner.EvalError); }}
|
||||
}};
|
||||
const before = evaluate();
|
||||
doc.open(); doc.close();
|
||||
const after = evaluate();
|
||||
meta.setAttribute('content', "script-src 'self' 'unsafe-inline'");
|
||||
doc.head.appendChild(meta);
|
||||
const tightened = evaluate();
|
||||
doc.open(); doc.close();
|
||||
const retained = evaluate();
|
||||
const sameDocument = doc === owner.document;
|
||||
popup.close(); frame.remove();
|
||||
return JSON.stringify([before, after, tightened, retained, sameDocument]);
|
||||
}})()"#
|
||||
))
|
||||
.unwrap(),
|
||||
r#"[42,42,"EvalError:true","EvalError:true",true]"#,
|
||||
"{owner}: document.open must retain installed CSP while processing reinserted meta in the new stream",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_script_and_handler_csp_accept_base64url_hashes() {
|
||||
for hash in [
|
||||
|
||||
Reference in New Issue
Block a user