mirror of
https://github.com/servo/servo
synced 2026-04-26 01:25:32 +02:00
script: Use CSP sandboxing flags for <iframe> and pass them to child Documents (#39610)
This change makes it so that `<iframe>` sanboxing is equivalent to the one used for Content Security Policy, which is how the specification is written. In addition, these sandboxing flags are passed through to `<iframe>` `Document`s via `LoadData` and stored as `Document::creation_sandboxing_flag_set`. The flags are used to calculate the final `Document::active_sandboxing_flag_set` when loading a `Document`. This change makes it so that `<iframe>`s actually behave in a sandboxed way, the same way that `Document`s with CSP configurations do. For instance, now scripts and popups are blocked by default in `<iframe>`s with the `sandbox` attribute. Testing: This causes many WPT tests to start to pass or to move from ERROR to TIMEOUT or failing later. Some tests start to fail: - `/html/semantics/embedded-content/the-canvas-element/canvas-descendants-focusability-005.html`: This test uses a combination of `<iframe allow>` and Canvas fallback content, which we do not support. - `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html`: This test is now failing because the iframe is sanboxed but in the ScriptThread now due to `allow-same-origin`. More implementation is needed to add support for the "one permitted sandbox navigator concept." Fixes: This is part of #31973. --------- Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
@@ -6,12 +6,13 @@ use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use base::id::{BrowsingContextId, PipelineId, WebViewId};
|
||||
use bitflags::bitflags;
|
||||
use constellation_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
|
||||
use constellation_traits::{
|
||||
IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, LoadOrigin,
|
||||
NavigationHistoryBehavior, ScriptToConstellationMessage,
|
||||
};
|
||||
use content_security_policy::sandboxing_directive::{
|
||||
SandboxingFlagSet, parse_a_sandboxing_directive,
|
||||
};
|
||||
use dom_struct::dom_struct;
|
||||
use embedder_traits::ViewportDetails;
|
||||
use html5ever::{LocalName, Prefix, local_name, ns};
|
||||
@@ -51,21 +52,6 @@ use crate::script_runtime::CanGc;
|
||||
use crate::script_thread::ScriptThread;
|
||||
use crate::script_window_proxies::ScriptWindowProxies;
|
||||
|
||||
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
|
||||
struct SandboxAllowance(u8);
|
||||
|
||||
bitflags! {
|
||||
impl SandboxAllowance: u8 {
|
||||
const ALLOW_NOTHING = 0x00;
|
||||
const ALLOW_SAME_ORIGIN = 0x01;
|
||||
const ALLOW_TOP_NAVIGATION = 0x02;
|
||||
const ALLOW_FORMS = 0x04;
|
||||
const ALLOW_SCRIPTS = 0x08;
|
||||
const ALLOW_POINTER_LOCK = 0x10;
|
||||
const ALLOW_POPUPS = 0x20;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum PipelineType {
|
||||
InitialAboutBlank,
|
||||
@@ -92,7 +78,8 @@ pub(crate) struct HTMLIFrameElement {
|
||||
#[no_trace]
|
||||
about_blank_pipeline_id: Cell<Option<PipelineId>>,
|
||||
sandbox: MutNullableDom<DOMTokenList>,
|
||||
sandbox_allowance: Cell<Option<SandboxAllowance>>,
|
||||
#[no_trace]
|
||||
sandboxing_flag_set: Cell<Option<SandboxingFlagSet>>,
|
||||
load_blocker: DomRefCell<Option<LoadBlocker>>,
|
||||
throttled: Cell<bool>,
|
||||
#[conditional_malloc_size_of]
|
||||
@@ -100,10 +87,6 @@ pub(crate) struct HTMLIFrameElement {
|
||||
}
|
||||
|
||||
impl HTMLIFrameElement {
|
||||
pub(crate) fn is_sandboxed(&self) -> bool {
|
||||
self.sandbox_allowance.get().is_some()
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#otherwise-steps-for-iframe-or-frame-elements>,
|
||||
/// step 1.
|
||||
fn get_url(&self) -> ServoUrl {
|
||||
@@ -142,12 +125,6 @@ impl HTMLIFrameElement {
|
||||
history_handling: NavigationHistoryBehavior,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
let sandboxed = if self.is_sandboxed() {
|
||||
IFrameSandboxed
|
||||
} else {
|
||||
IFrameUnsandboxed
|
||||
};
|
||||
|
||||
let browsing_context_id = match self.browsing_context_id() {
|
||||
None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
|
||||
Some(id) => id,
|
||||
@@ -223,7 +200,6 @@ impl HTMLIFrameElement {
|
||||
info: load_info,
|
||||
load_data: load_data.clone(),
|
||||
old_pipeline_id,
|
||||
sandbox: sandboxed,
|
||||
viewport_details,
|
||||
theme: window.theme(),
|
||||
};
|
||||
@@ -252,7 +228,6 @@ impl HTMLIFrameElement {
|
||||
info: load_info,
|
||||
load_data,
|
||||
old_pipeline_id,
|
||||
sandbox: sandboxed,
|
||||
viewport_details,
|
||||
theme: window.theme(),
|
||||
};
|
||||
@@ -287,6 +262,7 @@ impl HTMLIFrameElement {
|
||||
Some(window.as_global_scope().is_secure_context()),
|
||||
Some(document.insecure_requests_policy()),
|
||||
document.has_trustworthy_ancestor_or_current_origin(),
|
||||
self.sandboxing_flag_set(),
|
||||
);
|
||||
load_data.destination = Destination::IFrame;
|
||||
load_data.policy_container = Some(window.as_global_scope().policy_container());
|
||||
@@ -381,6 +357,7 @@ impl HTMLIFrameElement {
|
||||
Some(window.as_global_scope().is_secure_context()),
|
||||
Some(document.insecure_requests_policy()),
|
||||
document.has_trustworthy_ancestor_or_current_origin(),
|
||||
self.sandboxing_flag_set(),
|
||||
);
|
||||
load_data.destination = Destination::IFrame;
|
||||
load_data.policy_container = Some(window.as_global_scope().policy_container());
|
||||
@@ -429,6 +406,7 @@ impl HTMLIFrameElement {
|
||||
Some(window.as_global_scope().is_secure_context()),
|
||||
Some(document.insecure_requests_policy()),
|
||||
document.has_trustworthy_ancestor_or_current_origin(),
|
||||
self.sandboxing_flag_set(),
|
||||
);
|
||||
load_data.destination = Destination::IFrame;
|
||||
load_data.policy_container = Some(window.as_global_scope().policy_container());
|
||||
@@ -491,7 +469,7 @@ impl HTMLIFrameElement {
|
||||
pending_pipeline_id: Cell::new(None),
|
||||
about_blank_pipeline_id: Cell::new(None),
|
||||
sandbox: Default::default(),
|
||||
sandbox_allowance: Cell::new(None),
|
||||
sandboxing_flag_set: Cell::new(None),
|
||||
load_blocker: DomRefCell::new(None),
|
||||
throttled: Cell::new(false),
|
||||
script_window_proxies: ScriptThread::window_proxies(),
|
||||
@@ -531,6 +509,13 @@ impl HTMLIFrameElement {
|
||||
self.webview_id.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn sandboxing_flag_set(&self) -> SandboxingFlagSet {
|
||||
self.sandboxing_flag_set
|
||||
.get()
|
||||
.unwrap_or_else(SandboxingFlagSet::empty)
|
||||
}
|
||||
|
||||
pub(crate) fn set_throttled(&self, throttled: bool) {
|
||||
if self.throttled.get() != throttled {
|
||||
self.throttled.set(throttled);
|
||||
@@ -560,6 +545,25 @@ impl HTMLIFrameElement {
|
||||
|
||||
// TODO Step 5 - unset child document `mut iframe load` flag
|
||||
}
|
||||
|
||||
/// Parse the `sandbox` attribute value given the [`Attr`]. This sets the `sandboxing_flag_set`
|
||||
/// property or clears it is the value isn't specified. Notably, an unspecified sandboxing
|
||||
/// attribute (no sandboxing) is different from an empty one (full sandboxing).
|
||||
fn parse_sandbox_attribute(&self) {
|
||||
let attribute = self
|
||||
.upcast::<Element>()
|
||||
.get_attribute(&ns!(), &local_name!("sandbox"));
|
||||
self.sandboxing_flag_set
|
||||
.set(attribute.map(|attribute_value| {
|
||||
let tokens: Vec<_> = attribute_value
|
||||
.value()
|
||||
.as_tokens()
|
||||
.iter()
|
||||
.map(|atom| atom.to_string().to_ascii_lowercase())
|
||||
.collect();
|
||||
parse_a_sandboxing_directive(&tokens)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait HTMLIFrameElementLayoutMethods {
|
||||
@@ -631,19 +635,30 @@ impl HTMLIFrameElementMethods<crate::DomTypeHolder> for HTMLIFrameElement {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox>
|
||||
///
|
||||
/// The supported tokens for sandbox's DOMTokenList are the allowed values defined in the
|
||||
/// sandbox attribute and supported by the user agent. These range of possible values is
|
||||
/// defined here: <https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox>
|
||||
fn Sandbox(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
|
||||
self.sandbox.or_init(|| {
|
||||
DOMTokenList::new(
|
||||
self.upcast::<Element>(),
|
||||
&local_name!("sandbox"),
|
||||
Some(vec![
|
||||
Atom::from("allow-same-origin"),
|
||||
Atom::from("allow-downloads"),
|
||||
Atom::from("allow-forms"),
|
||||
Atom::from("allow-modals"),
|
||||
Atom::from("allow-orientation-lock"),
|
||||
Atom::from("allow-pointer-lock"),
|
||||
Atom::from("allow-popups"),
|
||||
Atom::from("allow-popups-to-escape-sandbox"),
|
||||
Atom::from("allow-presentation"),
|
||||
Atom::from("allow-same-origin"),
|
||||
Atom::from("allow-scripts"),
|
||||
Atom::from("allow-top-navigation"),
|
||||
Atom::from("allow-top-navigation-by-user-activation"),
|
||||
Atom::from("allow-top-navigation-to-custom-protocols"),
|
||||
]),
|
||||
can_gc,
|
||||
)
|
||||
@@ -729,23 +744,18 @@ impl VirtualMethods for HTMLIFrameElement {
|
||||
.unwrap()
|
||||
.attribute_mutated(attr, mutation, can_gc);
|
||||
match *attr.local_name() {
|
||||
local_name!("sandbox") => {
|
||||
self.sandbox_allowance
|
||||
.set(mutation.new_value(attr).map(|value| {
|
||||
let mut modes = SandboxAllowance::ALLOW_NOTHING;
|
||||
for token in value.as_tokens() {
|
||||
modes |= match &*token.to_ascii_lowercase() {
|
||||
"allow-same-origin" => SandboxAllowance::ALLOW_SAME_ORIGIN,
|
||||
"allow-forms" => SandboxAllowance::ALLOW_FORMS,
|
||||
"allow-pointer-lock" => SandboxAllowance::ALLOW_POINTER_LOCK,
|
||||
"allow-popups" => SandboxAllowance::ALLOW_POPUPS,
|
||||
"allow-scripts" => SandboxAllowance::ALLOW_SCRIPTS,
|
||||
"allow-top-navigation" => SandboxAllowance::ALLOW_TOP_NAVIGATION,
|
||||
_ => SandboxAllowance::ALLOW_NOTHING,
|
||||
};
|
||||
}
|
||||
modes
|
||||
}));
|
||||
// From <https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox>:
|
||||
//
|
||||
// > When an iframe element's sandbox attribute is set or changed while
|
||||
// > it has a non-null content navigable, the user agent must parse the
|
||||
// > sandboxing directive given the attribute's value and the iframe
|
||||
// > element's iframe sandboxing flag set.
|
||||
//
|
||||
// > When an iframe element's sandbox attribute is removed while it has
|
||||
// > a non-null content navigable, the user agent must empty the iframe
|
||||
// > element's iframe sandboxing flag set.
|
||||
local_name!("sandbox") if self.browsing_context_id.get().is_some() => {
|
||||
self.parse_sandbox_attribute();
|
||||
},
|
||||
local_name!("srcdoc") => {
|
||||
// https://html.spec.whatwg.org/multipage/#the-iframe-element:the-iframe-element-9
|
||||
@@ -793,22 +803,30 @@ impl VirtualMethods for HTMLIFrameElement {
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#the-iframe-element:html-element-post-connection-steps>
|
||||
fn post_connection_steps(&self) {
|
||||
if let Some(s) = self.super_type() {
|
||||
s.post_connection_steps();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#the-iframe-element
|
||||
// "When an iframe element is inserted into a document that has
|
||||
// a browsing context, the user agent must create a new
|
||||
// browsing context, set the element's nested browsing context
|
||||
// to the newly-created browsing context, and then process the
|
||||
// iframe attributes for the "first time"."
|
||||
if self.upcast::<Node>().is_connected_with_browsing_context() {
|
||||
debug!("iframe bound to browsing context.");
|
||||
self.create_nested_browsing_context(CanGc::note());
|
||||
self.process_the_iframe_attributes(ProcessingMode::FirstTime, CanGc::note());
|
||||
// This isn't mentioned any longer in the specification, but still seems important. This is
|
||||
// likely due to the fact that we have deviated a great deal with it comes to navigables
|
||||
// and browsing contexts.
|
||||
if !self.upcast::<Node>().is_connected_with_browsing_context() {
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("<iframe> running post connection steps");
|
||||
|
||||
// Step 1. Create a new child navigable for insertedNode.
|
||||
self.create_nested_browsing_context(CanGc::note());
|
||||
|
||||
// Step 2: If insertedNode has a sandbox attribute, then parse the sandboxing directive
|
||||
// given the attribute's value and insertedNode's iframe sandboxing flag set.
|
||||
self.parse_sandbox_attribute();
|
||||
|
||||
// Step 3. Process the iframe attributes for insertedNode, with initialInsertion set to true.
|
||||
self.process_the_iframe_attributes(ProcessingMode::FirstTime, CanGc::note());
|
||||
}
|
||||
|
||||
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
|
||||
|
||||
Reference in New Issue
Block a user