From 989c0d8994da9783f8ced75b2bf2399b7a12869c Mon Sep 17 00:00:00 2001 From: shanehandley Date: Fri, 5 Sep 2025 15:02:23 +1000 Subject: [PATCH] script: Implement document's active sandboxing flag set (#39079) Implements document's active sandboxing flags. These are currently populated only from CSP-derived sandboxing flags for a new document, when defined in the CSP. Testing: 1 new pass, and some new wpt's are added to test points in the spec where these flags influence behaviour. Signed-off-by: Shane Handley --- components/script/dom/document.rs | 26 +++++++++++++--- components/script/dom/html/htmlformelement.rs | 13 ++++++-- .../script/dom/html/htmlmediaelement.rs | 31 ++++++++++++++++--- components/script/dom/servoparser/mod.rs | 23 ++++++++++++-- tests/wpt/meta/MANIFEST.json | 22 +++++++++++++ .../sandboxed-document_domain.html.ini | 3 -- .../sandbox/autoplay-disabled-by-csp.html | 25 +++++++++++++++ .../autoplay-disabled-by-csp.html.headers | 1 + ...form-submission-blocked-by-sandboxing.html | 26 ++++++++++++++++ ...mission-blocked-by-sandboxing.html.headers | 1 + 10 files changed, 156 insertions(+), 15 deletions(-) delete mode 100644 tests/wpt/meta/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.ini create mode 100644 tests/wpt/tests/content-security-policy/sandbox/autoplay-disabled-by-csp.html create mode 100644 tests/wpt/tests/content-security-policy/sandbox/autoplay-disabled-by-csp.html.headers create mode 100644 tests/wpt/tests/content-security-policy/sandbox/form-submission-blocked-by-sandboxing.html create mode 100644 tests/wpt/tests/content-security-policy/sandbox/form-submission-blocked-by-sandboxing.html.headers diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index a22b177186b..25579678307 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -19,6 +19,7 @@ use canvas_traits::canvas::CanvasId; use canvas_traits::webgl::{WebGLContextId, WebGLMsg}; use chrono::Local; use constellation_traits::{NavigationHistoryBehavior, ScriptToConstellationMessage}; +use content_security_policy::sandboxing_directive::SandboxingFlagSet; use content_security_policy::{CspList, PolicyDisposition}; use cookie::Cookie; use cssparser::match_ignore_ascii_case; @@ -569,6 +570,10 @@ pub(crate) struct Document { /// The global custom element reaction stack for this script thread. #[conditional_malloc_size_of] custom_element_reaction_stack: Rc, + #[no_trace] + #[ignore_malloc_size_of = "type from external crate"] + /// , + active_sandboxing_flag_set: Cell, } #[allow(non_snake_case)] @@ -3439,6 +3444,7 @@ impl Document { waiting_on_canvas_image_updates: Cell::new(false), current_canvas_epoch: RefCell::new(Epoch(0)), custom_element_reaction_stack, + active_sandboxing_flag_set: Cell::new(SandboxingFlagSet::empty()), } } @@ -4416,6 +4422,14 @@ impl Document { pub(crate) fn custom_element_reaction_stack(&self) -> Rc { self.custom_element_reaction_stack.clone() } + + pub(crate) fn has_active_sandboxing_flag(&self, flag: SandboxingFlagSet) -> bool { + self.active_sandboxing_flag_set.get().contains(flag) + } + + pub(crate) fn set_active_sandboxing_flag_set(&self, flags: SandboxingFlagSet) { + self.active_sandboxing_flag_set.set(flags) + } } #[allow(non_snake_case)] @@ -4537,16 +4551,20 @@ impl DocumentMethods for Document { } } - // https://html.spec.whatwg.org/multipage/#dom-document-domain + /// fn SetDomain(&self, value: DOMString) -> ErrorResult { // Step 1. if !self.has_browsing_context { return Err(Error::Security); } - // TODO: Step 2. "If this Document object's active sandboxing - // flag set has its sandboxed document.domain browsing context - // flag set, then throw a "SecurityError" DOMException." + // Step 2. If this Document object's active sandboxing flag set has its sandboxed + // document.domain browsing context flag set, then throw a "SecurityError" DOMException. + if self.has_active_sandboxing_flag( + SandboxingFlagSet::SANDBOXED_DOCUMENT_DOMAIN_BROWSING_CONTEXT_FLAG, + ) { + return Err(Error::Security); + } // Steps 3-4. let effective_domain = match self.origin.effective_domain() { diff --git a/components/script/dom/html/htmlformelement.rs b/components/script/dom/html/htmlformelement.rs index 765d63497cd..d6ad7e46aa1 100644 --- a/components/script/dom/html/htmlformelement.rs +++ b/components/script/dom/html/htmlformelement.rs @@ -6,6 +6,7 @@ use std::borrow::ToOwned; use std::cell::Cell; use constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior}; +use content_security_policy::sandboxing_directive::SandboxingFlagSet; use dom_struct::dom_struct; use encoding_rs::{Encoding, UTF_8}; use headers::{ContentType, HeaderMapExt}; @@ -739,10 +740,18 @@ impl HTMLFormElement { if self.constructing_entry_list.get() { return; } - // Step 3 + // Step 3. Let form document be form's node document. let doc = self.owner_document(); + + // Step 4. If form document's active sandboxing flag set has its sandboxed forms browsing + // context flag set, then return. + if doc.has_active_sandboxing_flag(SandboxingFlagSet::SANDBOXED_FORMS_BROWSING_CONTEXT_FLAG) + { + return; + } + let base = doc.base_url(); - // TODO: Handle browsing contexts (Step 4, 5) + // TODO: Handle browsing contexts (Step 5) // Step 6 if submit_method_flag == SubmittedFrom::NotFromForm { // Step 6.1 diff --git a/components/script/dom/html/htmlmediaelement.rs b/components/script/dom/html/htmlmediaelement.rs index 793d279be33..deb2fc0ba1b 100644 --- a/components/script/dom/html/htmlmediaelement.rs +++ b/components/script/dom/html/htmlmediaelement.rs @@ -10,6 +10,7 @@ use std::time::{Duration, Instant}; use std::{f64, mem}; use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData}; +use content_security_policy::sandboxing_directive::SandboxingFlagSet; use dom_struct::dom_struct; use embedder_traits::{MediaPositionState, MediaSessionEvent, MediaSessionPlaybackState}; use euclid::default::Size2D; @@ -717,11 +718,8 @@ impl HTMLMediaElement { } if ready_state == ReadyState::HaveEnoughData { - // TODO: Check sandboxed automatic features browsing context flag. - // FIXME(nox): I have no idea what this TODO is about. - // FIXME(nox): Review this block. - if self.autoplaying.get() && self.Paused() && self.Autoplay() { + if self.eligible_for_autoplay() { // Step 1 self.paused.set(false); // Step 2 @@ -968,6 +966,31 @@ impl HTMLMediaElement { } } + /// + fn eligible_for_autoplay(&self) -> bool { + // its can autoplay flag is true; + self.autoplaying.get() && + + // its paused attribute is true; + self.Paused() && + + // it has an autoplay attribute specified; + self.Autoplay() && + + // its node document's active sandboxing flag set does not have the sandboxed automatic + // features browsing context flag set; and + { + let document = self.owner_document(); + + !document.has_active_sandboxing_flag( + SandboxingFlagSet::SANDBOXED_AUTOMATIC_FEATURES_BROWSING_CONTEXT_FLAG, + ) + } + + // its node document is allowed to use the "autoplay" feature. + // TODO: Feature policy: https://html.spec.whatwg.org/iframe-embed-object.html#allowed-to-use + } + // https://html.spec.whatwg.org/multipage/#concept-media-load-resource fn resource_fetch_algorithm(&self, resource: Resource) { if let Err(e) = self.setup_media_player(&resource) { diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 27d51f879f5..991ea1662d1 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -10,6 +10,7 @@ use base::cross_process_instant::CrossProcessInstant; use base::id::PipelineId; use base64::Engine as _; use base64::engine::general_purpose; +use content_security_policy::sandboxing_directive::SandboxingFlagSet; use devtools_traits::ScriptToDevtoolsControlMsg; use dom_struct::dom_struct; use embedder_traits::resources::{self, Resource}; @@ -940,6 +941,15 @@ impl FetchResponseListener for ParserContext { let _realm = enter_realm(&*parser.document); + // From Step 23.8.3 of https://html.spec.whatwg.org/multipage/#navigate + // Let finalSandboxFlags be the union of targetSnapshotParams's sandboxing flags and + // policyContainer's CSP list's CSP-derived sandboxing flags. + // TODO: implement targetSnapshotParam's sandboxing flags + let csp_derived_sandboxing_flag_set = csp_list + .as_ref() + .and_then(|csp| csp.get_sandboxing_flag_set_for_document()) + .unwrap_or(SandboxingFlagSet::empty()); + if let Some(endpoints) = endpoints_list { parser.document.window().set_endpoints_list(endpoints); } @@ -961,6 +971,15 @@ impl FetchResponseListener for ParserContext { let Some(media_type) = MimeClassifier::get_media_type(&mime_type) else { return; }; + + // CSP/Sandboxing is applied conditionally based on the content type + let apply_csp_and_sandboxing_flags = || { + parser + .document + .set_active_sandboxing_flag_set(csp_derived_sandboxing_flag_set); + parser.document.set_csp_list(csp_list); + }; + match media_type { // Return the result of loading a media document given navigationParams and type. MediaType::Image | MediaType::AudioVideo => { @@ -1037,10 +1056,10 @@ impl FetchResponseListener for ParserContext { }, Some(_) => {}, // Return the result of loading an HTML document, given navigationParams. - None => parser.document.set_csp_list(csp_list), + None => apply_csp_and_sandboxing_flags(), }, // Return the result of loading an XML document given navigationParams and type. - MediaType::Xml => parser.document.set_csp_list(csp_list), + MediaType::Xml => apply_csp_and_sandboxing_flags(), _ => { // Show warning page for unknown mime types. let page = format!( diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index a1a29c03ac7..7d53f98916a 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -403717,6 +403717,14 @@ ] }, "sandbox": { + "autoplay-disabled-by-csp.html.headers": [ + "32518e57d4584de71845a9260b093c3535fc3074", + [] + ], + "form-submission-blocked-by-sandboxing.html.headers": [ + "1efcf8c226fac074c98d0a5a747856f532e5d84e", + [] + ], "support": { "empty.html": [ "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", @@ -581697,6 +581705,20 @@ ] }, "sandbox": { + "autoplay-disabled-by-csp.html": [ + "d7bd453a34c0e75c98c837f853c0cf492359625a", + [ + null, + {} + ] + ], + "form-submission-blocked-by-sandboxing.html": [ + "4c717a18fd8bfa9d5cb4bc5449b0f25498ccb754", + [ + null, + {} + ] + ], "iframe-inside-csp.sub.html": [ "cd402bdba0198bf763e1733004c2005614b9a542", [ diff --git a/tests/wpt/meta/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.ini b/tests/wpt/meta/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.ini deleted file mode 100644 index a563e9b9d25..00000000000 --- a/tests/wpt/meta/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[sandboxed-document_domain.html] - [Sandboxed document.domain] - expected: FAIL diff --git a/tests/wpt/tests/content-security-policy/sandbox/autoplay-disabled-by-csp.html b/tests/wpt/tests/content-security-policy/sandbox/autoplay-disabled-by-csp.html new file mode 100644 index 00000000000..d7bd453a34c --- /dev/null +++ b/tests/wpt/tests/content-security-policy/sandbox/autoplay-disabled-by-csp.html @@ -0,0 +1,25 @@ + + + + + Test that autoplay is blocked by a document's active sandboxing flags + + + + + + + + + diff --git a/tests/wpt/tests/content-security-policy/sandbox/autoplay-disabled-by-csp.html.headers b/tests/wpt/tests/content-security-policy/sandbox/autoplay-disabled-by-csp.html.headers new file mode 100644 index 00000000000..32518e57d45 --- /dev/null +++ b/tests/wpt/tests/content-security-policy/sandbox/autoplay-disabled-by-csp.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-forms diff --git a/tests/wpt/tests/content-security-policy/sandbox/form-submission-blocked-by-sandboxing.html b/tests/wpt/tests/content-security-policy/sandbox/form-submission-blocked-by-sandboxing.html new file mode 100644 index 00000000000..4c717a18fd8 --- /dev/null +++ b/tests/wpt/tests/content-security-policy/sandbox/form-submission-blocked-by-sandboxing.html @@ -0,0 +1,26 @@ + + + + + Test that form submission is blocked by a document's active sandboxing flags + + + + +
+ +
+ + + diff --git a/tests/wpt/tests/content-security-policy/sandbox/form-submission-blocked-by-sandboxing.html.headers b/tests/wpt/tests/content-security-policy/sandbox/form-submission-blocked-by-sandboxing.html.headers new file mode 100644 index 00000000000..1efcf8c226f --- /dev/null +++ b/tests/wpt/tests/content-security-policy/sandbox/form-submission-blocked-by-sandboxing.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts