aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock18
-rw-r--r--components/malloc_size_of/Cargo.toml2
-rw-r--r--components/malloc_size_of/lib.rs6
-rw-r--r--components/net/Cargo.toml1
-rw-r--r--components/net/fetch/methods.rs37
-rw-r--r--components/net_traits/Cargo.toml1
-rw-r--r--components/net_traits/request.rs46
-rw-r--r--components/script/Cargo.toml1
-rw-r--r--components/script/dom/bindings/trace.rs3
-rw-r--r--components/script/dom/document.rs40
-rw-r--r--components/script/dom/globalscope.rs10
-rw-r--r--components/script/dom/htmlscriptelement.rs12
-rw-r--r--components/script/dom/request.rs4
-rw-r--r--components/script/dom/servoparser/mod.rs28
-rw-r--r--components/script/fetch.rs2
-rw-r--r--tests/wpt/metadata/fetch/api/policies/csp-blocked.html.ini5
16 files changed, 175 insertions, 41 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8705e2f7e9b..2cf04a97760 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -737,6 +737,20 @@ dependencies = [
]
[[package]]
+name = "content-security-policy"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f30ee9967a875968e66f6690e299f06781ed109cb82d10e0d60a126a38d61947"
+dependencies = [
+ "bitflags",
+ "lazy_static",
+ "percent-encoding",
+ "regex",
+ "serde",
+ "url",
+]
+
+[[package]]
name = "cookie"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2835,6 +2849,7 @@ name = "malloc_size_of"
version = "0.0.1"
dependencies = [
"app_units",
+ "content-security-policy",
"crossbeam-channel",
"cssparser",
"euclid",
@@ -3156,6 +3171,7 @@ dependencies = [
"base64",
"brotli",
"bytes",
+ "content-security-policy",
"cookie",
"crossbeam-channel",
"devtools_traits",
@@ -3215,6 +3231,7 @@ dependencies = [
name = "net_traits"
version = "0.0.1"
dependencies = [
+ "content-security-policy",
"cookie",
"embedder_traits",
"headers",
@@ -4122,6 +4139,7 @@ dependencies = [
"canvas_traits",
"caseless",
"chrono",
+ "content-security-policy",
"cookie",
"crossbeam-channel",
"cssparser",
diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml
index 713d2d59fef..e6a33d7e7c4 100644
--- a/components/malloc_size_of/Cargo.toml
+++ b/components/malloc_size_of/Cargo.toml
@@ -21,10 +21,12 @@ servo = [
"url",
"webrender_api",
"xml5ever",
+ "content-security-policy",
]
[dependencies]
app_units = "0.7"
+content-security-policy = {version = "0.3.0", features = ["serde"], optional = true}
crossbeam-channel = { version = "0.3", optional = true }
cssparser = "0.25"
euclid = "0.20"
diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs
index d7e0a6fdf16..af7147d8edb 100644
--- a/components/malloc_size_of/lib.rs
+++ b/components/malloc_size_of/lib.rs
@@ -48,6 +48,8 @@
extern crate app_units;
#[cfg(feature = "servo")]
+extern crate content_security_policy;
+#[cfg(feature = "servo")]
extern crate crossbeam_channel;
extern crate cssparser;
extern crate euclid;
@@ -80,6 +82,8 @@ extern crate webrender_api;
extern crate xml5ever;
#[cfg(feature = "servo")]
+use content_security_policy as csp;
+#[cfg(feature = "servo")]
use serde_bytes::ByteBuf;
use std::hash::{BuildHasher, Hash};
use std::mem::size_of;
@@ -833,6 +837,8 @@ malloc_size_of_is_0!(app_units::Au);
malloc_size_of_is_0!(cssparser::RGBA, cssparser::TokenSerializationType);
+malloc_size_of_is_0!(csp::Destination);
+
#[cfg(feature = "url")]
impl MallocSizeOf for url::Host {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
diff --git a/components/net/Cargo.toml b/components/net/Cargo.toml
index a91cc6d8a97..f05d5b31268 100644
--- a/components/net/Cargo.toml
+++ b/components/net/Cargo.toml
@@ -17,6 +17,7 @@ doctest = false
base64 = "0.10.1"
brotli = "3"
bytes = "0.4"
+content-security-policy = {version = "0.3.0", features = ["serde"]}
cookie_rs = {package = "cookie", version = "0.11"}
crossbeam-channel = "0.3"
devtools_traits = {path = "../devtools_traits"}
diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs
index b60265ce2b8..fd225991568 100644
--- a/components/net/fetch/methods.rs
+++ b/components/net/fetch/methods.rs
@@ -8,6 +8,7 @@ use crate::filemanager_thread::{fetch_file_in_chunks, FileManager, FILE_CHUNK_SI
use crate::http_loader::{determine_request_referrer, http_fetch, HttpState};
use crate::http_loader::{set_default_accept, set_default_accept_language};
use crate::subresource_integrity::is_response_integrity_valid;
+use content_security_policy as csp;
use crossbeam_channel::{unbounded, Receiver, Sender};
use devtools_traits::DevtoolsControlMsg;
use headers::{AccessControlExposeHeaders, ContentType, HeaderMapExt, Range};
@@ -138,6 +139,30 @@ pub fn fetch_with_cors_cache(
main_fetch(request, cache, false, false, target, &mut None, &context);
}
+/// https://www.w3.org/TR/CSP/#should-block-request
+pub fn should_request_be_blocked_by_csp(request: &Request) -> csp::CheckResult {
+ let origin = match &request.origin {
+ Origin::Client => return csp::CheckResult::Allowed,
+ Origin::Origin(origin) => origin,
+ };
+ let csp_request = csp::Request {
+ url: request.url().into_url(),
+ origin: origin.clone().into_url_origin(),
+ redirect_count: request.redirect_count,
+ destination: request.destination,
+ initiator: csp::Initiator::None,
+ nonce: String::new(),
+ integrity_metadata: request.integrity_metadata.clone(),
+ parser_metadata: csp::ParserMetadata::None,
+ };
+ // TODO: Instead of ignoring violations, report them.
+ request
+ .csp_list
+ .as_ref()
+ .map(|c| c.should_request_be_blocked(&csp_request).0)
+ .unwrap_or(csp::CheckResult::Allowed)
+}
+
/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
pub fn main_fetch(
request: &mut Request,
@@ -163,8 +188,18 @@ pub fn main_fetch(
}
}
+ // Step 2.2.
+ // TODO: Report violations.
+
+ // Step 2.4.
+ if should_request_be_blocked_by_csp(request) == csp::CheckResult::Blocked {
+ response = Some(Response::network_error(NetworkError::Internal(
+ "Blocked by Content-Security-Policy".into(),
+ )))
+ }
+
// Step 3.
- // TODO: handle content security policy violations.
+ // TODO: handle request abort.
// Step 4.
// TODO: handle upgrade to a potentially secure URL.
diff --git a/components/net_traits/Cargo.toml b/components/net_traits/Cargo.toml
index 4bda5d8d0cf..caf1f9787fc 100644
--- a/components/net_traits/Cargo.toml
+++ b/components/net_traits/Cargo.toml
@@ -13,6 +13,7 @@ test = false
doctest = false
[dependencies]
+content-security-policy = {version = "0.3.0", features = ["serde"]}
cookie = "0.11"
embedder_traits = { path = "../embedder_traits" }
headers = "0.2"
diff --git a/components/net_traits/request.rs b/components/net_traits/request.rs
index dad0ae8ca35..07ccdf60695 100644
--- a/components/net_traits/request.rs
+++ b/components/net_traits/request.rs
@@ -4,6 +4,7 @@
use crate::ReferrerPolicy;
use crate::ResourceTimingType;
+use content_security_policy::{self as csp, CspList};
use http::HeaderMap;
use hyper::Method;
use msg::constellation_msg::PipelineId;
@@ -20,37 +21,7 @@ pub enum Initiator {
}
/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination)
-#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
-pub enum Destination {
- None,
- Audio,
- Document,
- Embed,
- Font,
- Image,
- Manifest,
- Object,
- Report,
- Script,
- ServiceWorker,
- SharedWorker,
- Style,
- Track,
- Video,
- Worker,
- Xslt,
-}
-
-impl Destination {
- /// https://fetch.spec.whatwg.org/#request-destination-script-like
- #[inline]
- pub fn is_script_like(&self) -> bool {
- *self == Destination::Script ||
- *self == Destination::ServiceWorker ||
- *self == Destination::SharedWorker ||
- *self == Destination::Worker
- }
-}
+pub use csp::Destination;
/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
@@ -175,6 +146,11 @@ pub struct RequestBuilder {
pub pipeline_id: Option<PipelineId>,
pub redirect_mode: RedirectMode,
pub integrity_metadata: String,
+ // This is nominally a part of the client's global object.
+ // It is copied here to avoid having to reach across the thread
+ // boundary every time a redirect occurs.
+ #[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
+ pub csp_list: Option<CspList>,
// to keep track of redirects
pub url_list: Vec<ServoUrl>,
pub parser_metadata: ParserMetadata,
@@ -206,6 +182,7 @@ impl RequestBuilder {
url_list: vec![],
parser_metadata: ParserMetadata::Default,
initiator: Initiator::None,
+ csp_list: None,
}
}
@@ -329,6 +306,7 @@ impl RequestBuilder {
request.url_list = url_list;
request.integrity_metadata = self.integrity_metadata;
request.parser_metadata = self.parser_metadata;
+ request.csp_list = self.csp_list;
request
}
}
@@ -396,6 +374,11 @@ pub struct Request {
pub response_tainting: ResponseTainting,
/// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata>
pub parser_metadata: ParserMetadata,
+ // This is nominally a part of the client's global object.
+ // It is copied here to avoid having to reach across the thread
+ // boundary every time a redirect occurs.
+ #[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
+ pub csp_list: Option<CspList>,
}
impl Request {
@@ -428,6 +411,7 @@ impl Request {
parser_metadata: ParserMetadata::Default,
redirect_count: 0,
response_tainting: ResponseTainting::Basic,
+ csp_list: None,
}
}
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml
index 8cda8242dea..e521235f1c6 100644
--- a/components/script/Cargo.toml
+++ b/components/script/Cargo.toml
@@ -38,6 +38,7 @@ bitflags = "1.0"
bluetooth_traits = {path = "../bluetooth_traits"}
canvas_traits = {path = "../canvas_traits"}
caseless = "0.2"
+content-security-policy = {version = "0.3.0", features = ["serde"]}
cookie = "0.11"
chrono = "0.4"
crossbeam-channel = "0.3"
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs
index e86c96e0a9e..a914b8161a6 100644
--- a/components/script/dom/bindings/trace.rs
+++ b/components/script/dom/bindings/trace.rs
@@ -54,6 +54,7 @@ use canvas_traits::webgl::{
use canvas_traits::webgl::{WebGLFramebufferId, WebGLMsgSender, WebGLPipeline, WebGLProgramId};
use canvas_traits::webgl::{WebGLReceiver, WebGLRenderbufferId, WebGLSLVersion, WebGLSender};
use canvas_traits::webgl::{WebGLShaderId, WebGLSyncId, WebGLTextureId, WebGLVersion};
+use content_security_policy::CspList;
use crossbeam_channel::{Receiver, Sender};
use cssparser::RGBA;
use devtools_traits::{CSSError, TimelineMarkerType, WorkerId};
@@ -170,6 +171,8 @@ unsafe_no_jsmanaged_fields!(*mut JobQueue);
unsafe_no_jsmanaged_fields!(Cow<'static, str>);
+unsafe_no_jsmanaged_fields!(CspList);
+
/// Trace a `JSVal`.
pub fn trace_jsval(tracer: *mut JSTracer, description: &str, val: &Heap<JSVal>) {
unsafe {
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 452212a2b3c..6581dbd54bb 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -110,6 +110,7 @@ use crate::task::TaskBox;
use crate::task_source::{TaskSource, TaskSourceName};
use crate::timers::OneshotTimerCallback;
use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg};
+use content_security_policy::{self as csp, CspList};
use cookie::Cookie;
use devtools_traits::ScriptToDevtoolsControlMsg;
use dom_struct::dom_struct;
@@ -137,6 +138,7 @@ use num_traits::ToPrimitive;
use percent_encoding::percent_decode;
use profile_traits::ipc as profile_ipc;
use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType};
+use ref_filter_map::ref_filter_map;
use ref_slice::ref_slice;
use script_layout_interface::message::{Msg, ReflowGoal};
use script_traits::{AnimationState, DocumentActivity, MouseButton, MouseEventType};
@@ -148,7 +150,7 @@ use servo_atoms::Atom;
use servo_config::pref;
use servo_media::{ClientContextId, ServoMedia};
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
-use std::borrow::ToOwned;
+use std::borrow::Cow;
use std::cell::{Cell, Ref, RefMut};
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::{HashMap, HashSet, VecDeque};
@@ -398,6 +400,9 @@ pub struct Document {
media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>,
/// List of all WebGL context IDs that need flushing.
dirty_webgl_contexts: DomRefCell<HashSet<WebGLContextId>>,
+ /// https://html.spec.whatwg.org/multipage/#concept-document-csp-list
+ #[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
+ csp_list: DomRefCell<Option<CspList>>,
}
#[derive(JSTraceable, MallocSizeOf)]
@@ -1734,9 +1739,10 @@ impl Document {
pub fn fetch_async(
&self,
load: LoadType,
- request: RequestBuilder,
+ mut request: RequestBuilder,
fetch_target: IpcSender<FetchResponseMsg>,
) {
+ request.csp_list = self.get_csp_list().map(|x| x.clone());
let mut loader = self.loader.borrow_mut();
loader.fetch_async(load, request, fetch_target);
}
@@ -2806,9 +2812,39 @@ impl Document {
shadow_roots_styles_changed: Cell::new(false),
media_controls: DomRefCell::new(HashMap::new()),
dirty_webgl_contexts: DomRefCell::new(HashSet::new()),
+ csp_list: DomRefCell::new(None),
}
}
+ pub fn set_csp_list(&self, csp_list: Option<CspList>) {
+ *self.csp_list.borrow_mut() = csp_list;
+ }
+
+ pub fn get_csp_list(&self) -> Option<Ref<CspList>> {
+ ref_filter_map(self.csp_list.borrow(), Option::as_ref)
+ }
+
+ /// https://www.w3.org/TR/CSP/#should-block-inline
+ pub fn should_elements_inline_type_behavior_be_blocked(
+ &self,
+ el: &Element,
+ type_: csp::InlineCheckType,
+ source: &str,
+ ) -> csp::CheckResult {
+ let element = csp::Element {
+ nonce: el
+ .get_attribute(&ns!(), &local_name!("nonce"))
+ .map(|attr| Cow::Owned(attr.value().to_string())),
+ };
+ // TODO: Instead of ignoring violations, report them.
+ self.get_csp_list()
+ .map(|c| {
+ c.should_elements_inline_type_behavior_be_blocked(&element, type_, source)
+ .0
+ })
+ .unwrap_or(csp::CheckResult::Allowed)
+ }
+
/// Prevent any JS or layout from running until the corresponding call to
/// `remove_script_and_layout_blocker`. Used to isolate periods in which
/// the DOM is in an unstable state and should not be exposed to arbitrary
diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs
index 2ae306e25be..c7635e98463 100644
--- a/components/script/dom/globalscope.rs
+++ b/components/script/dom/globalscope.rs
@@ -38,6 +38,7 @@ use crate::task_source::websocket::WebsocketTaskSource;
use crate::task_source::TaskSourceName;
use crate::timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle};
use crate::timers::{OneshotTimers, TimerCallback};
+use content_security_policy::CspList;
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
use dom_struct::dom_struct;
use ipc_channel::ipc::IpcSender;
@@ -812,6 +813,15 @@ impl GlobalScope {
pub fn get_user_agent(&self) -> Cow<'static, str> {
self.user_agent.clone()
}
+
+ /// https://www.w3.org/TR/CSP/#get-csp-of-object
+ pub fn get_csp_list(&self) -> Option<CspList> {
+ if let Some(window) = self.downcast::<Window>() {
+ return window.Document().get_csp_list().map(|c| c.clone());
+ }
+ // TODO: Worker and Worklet global scopes.
+ None
+ }
}
fn timestamp_in_ms(time: Timespec) -> u64 {
diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs
index 1bd0101c03d..dfac55a6f66 100644
--- a/components/script/dom/htmlscriptelement.rs
+++ b/components/script/dom/htmlscriptelement.rs
@@ -27,6 +27,7 @@ use crate::dom::performanceresourcetiming::InitiatorType;
use crate::dom::virtualmethods::VirtualMethods;
use crate::fetch::create_a_potential_CORS_request;
use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener};
+use content_security_policy as csp;
use dom_struct::dom_struct;
use encoding_rs::Encoding;
use html5ever::{LocalName, Prefix};
@@ -428,7 +429,16 @@ impl HTMLScriptElement {
// TODO: Step 12: nomodule content attribute
- // TODO(#4577): Step 13: CSP.
+ // Step 13.
+ if !element.has_attribute(&local_name!("src")) &&
+ doc.should_elements_inline_type_behavior_be_blocked(
+ &element,
+ csp::InlineCheckType::Script,
+ &text,
+ ) == csp::CheckResult::Blocked
+ {
+ return;
+ }
// Step 14.
let for_attribute = element.get_attribute(&ns!(), &local_name!("for"));
diff --git a/components/script/dom/request.rs b/components/script/dom/request.rs
index 5393288c633..d22d4680355 100644
--- a/components/script/dom/request.rs
+++ b/components/script/dom/request.rs
@@ -755,7 +755,9 @@ impl Into<RequestDestination> for NetTraitsRequestDestination {
NetTraitsRequestDestination::Object => RequestDestination::Object,
NetTraitsRequestDestination::Report => RequestDestination::Report,
NetTraitsRequestDestination::Script => RequestDestination::Script,
- NetTraitsRequestDestination::ServiceWorker => {
+ NetTraitsRequestDestination::ServiceWorker |
+ NetTraitsRequestDestination::AudioWorklet |
+ NetTraitsRequestDestination::PaintWorklet => {
panic!("ServiceWorker request destination should not be exposed to DOM")
},
NetTraitsRequestDestination::SharedWorker => RequestDestination::Sharedworker,
diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs
index a05dea2a66d..91b5c9bfce6 100644
--- a/components/script/dom/servoparser/mod.rs
+++ b/components/script/dom/servoparser/mod.rs
@@ -35,6 +35,7 @@ use crate::dom::text::Text;
use crate::dom::virtualmethods::vtable_for;
use crate::network_listener::PreInvoke;
use crate::script_thread::ScriptThread;
+use content_security_policy::{self as csp, CspList};
use dom_struct::dom_struct;
use embedder_traits::resources::{self, Resource};
use encoding_rs::Encoding;
@@ -736,6 +737,31 @@ impl FetchResponseListener for ParserContext {
.and_then(|meta| meta.content_type)
.map(Serde::into_inner)
.map(Into::into);
+
+ // https://www.w3.org/TR/CSP/#initialize-document-csp
+ // TODO: Implement step 1 (local scheme special case)
+ let csp_list = metadata.as_ref().and_then(|m| {
+ let h = m.headers.as_ref()?;
+ let mut csp = h.get_all("content-security-policy").iter();
+ // This silently ignores the CSP if it contains invalid Unicode.
+ // We should probably report an error somewhere.
+ let c = csp.next().and_then(|c| c.to_str().ok())?;
+ let mut csp_list = CspList::parse(
+ c,
+ csp::PolicySource::Header,
+ csp::PolicyDisposition::Enforce,
+ );
+ for c in csp {
+ let c = c.to_str().ok()?;
+ csp_list.append(CspList::parse(
+ c,
+ csp::PolicySource::Header,
+ csp::PolicyDisposition::Enforce,
+ ));
+ }
+ Some(csp_list)
+ });
+
let parser = match ScriptThread::page_headers_available(&self.id, metadata) {
Some(parser) => parser,
None => return,
@@ -744,6 +770,8 @@ impl FetchResponseListener for ParserContext {
return;
}
+ parser.document.set_csp_list(csp_list);
+
self.parser = Some(Trusted::new(&*parser));
match content_type {
diff --git a/components/script/fetch.rs b/components/script/fetch.rs
index 045e9b93eda..68285653a2f 100644
--- a/components/script/fetch.rs
+++ b/components/script/fetch.rs
@@ -127,6 +127,7 @@ fn request_init_from_request(request: NetTraitsRequest) -> RequestBuilder {
url_list: vec![],
parser_metadata: request.parser_metadata,
initiator: request.initiator,
+ csp_list: None,
}
}
@@ -155,6 +156,7 @@ pub fn Fetch(
let timing_type = request.timing_type();
let mut request_init = request_init_from_request(request);
+ request_init.csp_list = global.get_csp_list().clone();
// Step 3
if global.downcast::<ServiceWorkerGlobalScope>().is_some() {
diff --git a/tests/wpt/metadata/fetch/api/policies/csp-blocked.html.ini b/tests/wpt/metadata/fetch/api/policies/csp-blocked.html.ini
deleted file mode 100644
index 72555302f16..00000000000
--- a/tests/wpt/metadata/fetch/api/policies/csp-blocked.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[csp-blocked.html]
- type: testharness
- [Fetch is blocked by CSP, got a TypeError]
- expected: FAIL
-