Skip to content

Commit b8f3e8b

Browse files
committed
Add simple implementation of content-security-policy on scripts / styles
This needs a lot more hooks before it'll actually be a good implementation, but for a start it can help get some feedback on if this is the right way to go about it. Part of servo#4577
1 parent 6d488f1 commit b8f3e8b

File tree

16 files changed

+175
-41
lines changed

16 files changed

+175
-41
lines changed

Cargo.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/malloc_size_of/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ servo = [
2121
"url",
2222
"webrender_api",
2323
"xml5ever",
24+
"content-security-policy",
2425
]
2526

2627
[dependencies]
2728
app_units = "0.7"
29+
content-security-policy = {version = "0.3.0", features = ["serde"], optional = true}
2830
crossbeam-channel = { version = "0.3", optional = true }
2931
cssparser = "0.25"
3032
euclid = "0.20"

components/malloc_size_of/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
4949
extern crate app_units;
5050
#[cfg(feature = "servo")]
51+
extern crate content_security_policy;
52+
#[cfg(feature = "servo")]
5153
extern crate crossbeam_channel;
5254
extern crate cssparser;
5355
extern crate euclid;
@@ -79,6 +81,8 @@ extern crate webrender_api;
7981
#[cfg(feature = "servo")]
8082
extern crate xml5ever;
8183

84+
#[cfg(feature = "servo")]
85+
use content_security_policy as csp;
8286
#[cfg(feature = "servo")]
8387
use serde_bytes::ByteBuf;
8488
use std::hash::{BuildHasher, Hash};
@@ -833,6 +837,8 @@ malloc_size_of_is_0!(app_units::Au);
833837

834838
malloc_size_of_is_0!(cssparser::RGBA, cssparser::TokenSerializationType);
835839

840+
malloc_size_of_is_0!(csp::Destination);
841+
836842
#[cfg(feature = "url")]
837843
impl MallocSizeOf for url::Host {
838844
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {

components/net/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ doctest = false
1717
base64 = "0.10.1"
1818
brotli = "3"
1919
bytes = "0.4"
20+
content-security-policy = {version = "0.3.0", features = ["serde"]}
2021
cookie_rs = {package = "cookie", version = "0.11"}
2122
crossbeam-channel = "0.3"
2223
devtools_traits = {path = "../devtools_traits"}

components/net/fetch/methods.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::filemanager_thread::{fetch_file_in_chunks, FileManager, FILE_CHUNK_SI
88
use crate::http_loader::{determine_request_referrer, http_fetch, HttpState};
99
use crate::http_loader::{set_default_accept, set_default_accept_language};
1010
use crate::subresource_integrity::is_response_integrity_valid;
11+
use content_security_policy as csp;
1112
use crossbeam_channel::{unbounded, Receiver, Sender};
1213
use devtools_traits::DevtoolsControlMsg;
1314
use headers::{AccessControlExposeHeaders, ContentType, HeaderMapExt, Range};
@@ -138,6 +139,30 @@ pub fn fetch_with_cors_cache(
138139
main_fetch(request, cache, false, false, target, &mut None, &context);
139140
}
140141

142+
/// https://www.w3.org/TR/CSP/#should-block-request
143+
pub fn should_request_be_blocked_by_csp(request: &Request) -> csp::CheckResult {
144+
let origin = match &request.origin {
145+
Origin::Client => return csp::CheckResult::Allowed,
146+
Origin::Origin(origin) => origin,
147+
};
148+
let csp_request = csp::Request {
149+
url: request.url().into_url(),
150+
origin: origin.clone().into_url_origin(),
151+
redirect_count: request.redirect_count,
152+
destination: request.destination,
153+
initiator: csp::Initiator::None,
154+
nonce: String::new(),
155+
integrity_metadata: request.integrity_metadata.clone(),
156+
parser_metadata: csp::ParserMetadata::None,
157+
};
158+
// TODO: Instead of ignoring violations, report them.
159+
request
160+
.csp_list
161+
.as_ref()
162+
.map(|c| c.should_request_be_blocked(&csp_request).0)
163+
.unwrap_or(csp::CheckResult::Allowed)
164+
}
165+
141166
/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
142167
pub fn main_fetch(
143168
request: &mut Request,
@@ -163,8 +188,18 @@ pub fn main_fetch(
163188
}
164189
}
165190

191+
// Step 2.2.
192+
// TODO: Report violations.
193+
194+
// Step 2.4.
195+
if should_request_be_blocked_by_csp(request) == csp::CheckResult::Blocked {
196+
response = Some(Response::network_error(NetworkError::Internal(
197+
"Blocked by Content-Security-Policy".into(),
198+
)))
199+
}
200+
166201
// Step 3.
167-
// TODO: handle content security policy violations.
202+
// TODO: handle request abort.
168203

169204
// Step 4.
170205
// TODO: handle upgrade to a potentially secure URL.

components/net_traits/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ test = false
1313
doctest = false
1414

1515
[dependencies]
16+
content-security-policy = {version = "0.3.0", features = ["serde"]}
1617
cookie = "0.11"
1718
embedder_traits = { path = "../embedder_traits" }
1819
headers = "0.2"

components/net_traits/request.rs

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use crate::ReferrerPolicy;
66
use crate::ResourceTimingType;
7+
use content_security_policy::{self as csp, CspList};
78
use http::HeaderMap;
89
use hyper::Method;
910
use msg::constellation_msg::PipelineId;
@@ -20,37 +21,7 @@ pub enum Initiator {
2021
}
2122

2223
/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination)
23-
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
24-
pub enum Destination {
25-
None,
26-
Audio,
27-
Document,
28-
Embed,
29-
Font,
30-
Image,
31-
Manifest,
32-
Object,
33-
Report,
34-
Script,
35-
ServiceWorker,
36-
SharedWorker,
37-
Style,
38-
Track,
39-
Video,
40-
Worker,
41-
Xslt,
42-
}
43-
44-
impl Destination {
45-
/// https://fetch.spec.whatwg.org/#request-destination-script-like
46-
#[inline]
47-
pub fn is_script_like(&self) -> bool {
48-
*self == Destination::Script ||
49-
*self == Destination::ServiceWorker ||
50-
*self == Destination::SharedWorker ||
51-
*self == Destination::Worker
52-
}
53-
}
24+
pub use csp::Destination;
5425

5526
/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
5627
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
@@ -175,6 +146,11 @@ pub struct RequestBuilder {
175146
pub pipeline_id: Option<PipelineId>,
176147
pub redirect_mode: RedirectMode,
177148
pub integrity_metadata: String,
149+
// This is nominally a part of the client's global object.
150+
// It is copied here to avoid having to reach across the thread
151+
// boundary every time a redirect occurs.
152+
#[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
153+
pub csp_list: Option<CspList>,
178154
// to keep track of redirects
179155
pub url_list: Vec<ServoUrl>,
180156
pub parser_metadata: ParserMetadata,
@@ -206,6 +182,7 @@ impl RequestBuilder {
206182
url_list: vec![],
207183
parser_metadata: ParserMetadata::Default,
208184
initiator: Initiator::None,
185+
csp_list: None,
209186
}
210187
}
211188

@@ -329,6 +306,7 @@ impl RequestBuilder {
329306
request.url_list = url_list;
330307
request.integrity_metadata = self.integrity_metadata;
331308
request.parser_metadata = self.parser_metadata;
309+
request.csp_list = self.csp_list;
332310
request
333311
}
334312
}
@@ -396,6 +374,11 @@ pub struct Request {
396374
pub response_tainting: ResponseTainting,
397375
/// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata>
398376
pub parser_metadata: ParserMetadata,
377+
// This is nominally a part of the client's global object.
378+
// It is copied here to avoid having to reach across the thread
379+
// boundary every time a redirect occurs.
380+
#[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
381+
pub csp_list: Option<CspList>,
399382
}
400383

401384
impl Request {
@@ -428,6 +411,7 @@ impl Request {
428411
parser_metadata: ParserMetadata::Default,
429412
redirect_count: 0,
430413
response_tainting: ResponseTainting::Basic,
414+
csp_list: None,
431415
}
432416
}
433417

components/script/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ bitflags = "1.0"
3838
bluetooth_traits = {path = "../bluetooth_traits"}
3939
canvas_traits = {path = "../canvas_traits"}
4040
caseless = "0.2"
41+
content-security-policy = {version = "0.3.0", features = ["serde"]}
4142
cookie = "0.11"
4243
chrono = "0.4"
4344
crossbeam-channel = "0.3"

components/script/dom/bindings/trace.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ use canvas_traits::webgl::{
5454
use canvas_traits::webgl::{WebGLFramebufferId, WebGLMsgSender, WebGLPipeline, WebGLProgramId};
5555
use canvas_traits::webgl::{WebGLReceiver, WebGLRenderbufferId, WebGLSLVersion, WebGLSender};
5656
use canvas_traits::webgl::{WebGLShaderId, WebGLSyncId, WebGLTextureId, WebGLVersion};
57+
use content_security_policy::CspList;
5758
use crossbeam_channel::{Receiver, Sender};
5859
use cssparser::RGBA;
5960
use devtools_traits::{CSSError, TimelineMarkerType, WorkerId};
@@ -170,6 +171,8 @@ unsafe_no_jsmanaged_fields!(*mut JobQueue);
170171

171172
unsafe_no_jsmanaged_fields!(Cow<'static, str>);
172173

174+
unsafe_no_jsmanaged_fields!(CspList);
175+
173176
/// Trace a `JSVal`.
174177
pub fn trace_jsval(tracer: *mut JSTracer, description: &str, val: &Heap<JSVal>) {
175178
unsafe {

components/script/dom/document.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ use crate::task::TaskBox;
110110
use crate::task_source::{TaskSource, TaskSourceName};
111111
use crate::timers::OneshotTimerCallback;
112112
use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg};
113+
use content_security_policy::{self as csp, CspList};
113114
use cookie::Cookie;
114115
use devtools_traits::ScriptToDevtoolsControlMsg;
115116
use dom_struct::dom_struct;
@@ -137,6 +138,7 @@ use num_traits::ToPrimitive;
137138
use percent_encoding::percent_decode;
138139
use profile_traits::ipc as profile_ipc;
139140
use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType};
141+
use ref_filter_map::ref_filter_map;
140142
use ref_slice::ref_slice;
141143
use script_layout_interface::message::{Msg, ReflowGoal};
142144
use script_traits::{AnimationState, DocumentActivity, MouseButton, MouseEventType};
@@ -148,7 +150,7 @@ use servo_atoms::Atom;
148150
use servo_config::pref;
149151
use servo_media::{ClientContextId, ServoMedia};
150152
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
151-
use std::borrow::ToOwned;
153+
use std::borrow::Cow;
152154
use std::cell::{Cell, Ref, RefMut};
153155
use std::collections::hash_map::Entry::{Occupied, Vacant};
154156
use std::collections::{HashMap, HashSet, VecDeque};
@@ -398,6 +400,9 @@ pub struct Document {
398400
media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>,
399401
/// List of all WebGL context IDs that need flushing.
400402
dirty_webgl_contexts: DomRefCell<HashSet<WebGLContextId>>,
403+
/// https://html.spec.whatwg.org/multipage/#concept-document-csp-list
404+
#[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
405+
csp_list: DomRefCell<Option<CspList>>,
401406
}
402407

403408
#[derive(JSTraceable, MallocSizeOf)]
@@ -1734,9 +1739,10 @@ impl Document {
17341739
pub fn fetch_async(
17351740
&self,
17361741
load: LoadType,
1737-
request: RequestBuilder,
1742+
mut request: RequestBuilder,
17381743
fetch_target: IpcSender<FetchResponseMsg>,
17391744
) {
1745+
request.csp_list = self.get_csp_list().map(|x| x.clone());
17401746
let mut loader = self.loader.borrow_mut();
17411747
loader.fetch_async(load, request, fetch_target);
17421748
}
@@ -2806,9 +2812,39 @@ impl Document {
28062812
shadow_roots_styles_changed: Cell::new(false),
28072813
media_controls: DomRefCell::new(HashMap::new()),
28082814
dirty_webgl_contexts: DomRefCell::new(HashSet::new()),
2815+
csp_list: DomRefCell::new(None),
28092816
}
28102817
}
28112818

2819+
pub fn set_csp_list(&self, csp_list: Option<CspList>) {
2820+
*self.csp_list.borrow_mut() = csp_list;
2821+
}
2822+
2823+
pub fn get_csp_list(&self) -> Option<Ref<CspList>> {
2824+
ref_filter_map(self.csp_list.borrow(), Option::as_ref)
2825+
}
2826+
2827+
/// https://www.w3.org/TR/CSP/#should-block-inline
2828+
pub fn should_elements_inline_type_behavior_be_blocked(
2829+
&self,
2830+
el: &Element,
2831+
type_: csp::InlineCheckType,
2832+
source: &str,
2833+
) -> csp::CheckResult {
2834+
let element = csp::Element {
2835+
nonce: el
2836+
.get_attribute(&ns!(), &local_name!("nonce"))
2837+
.map(|attr| Cow::Owned(attr.value().to_string())),
2838+
};
2839+
// TODO: Instead of ignoring violations, report them.
2840+
self.get_csp_list()
2841+
.map(|c| {
2842+
c.should_elements_inline_type_behavior_be_blocked(&element, type_, source)
2843+
.0
2844+
})
2845+
.unwrap_or(csp::CheckResult::Allowed)
2846+
}
2847+
28122848
/// Prevent any JS or layout from running until the corresponding call to
28132849
/// `remove_script_and_layout_blocker`. Used to isolate periods in which
28142850
/// the DOM is in an unstable state and should not be exposed to arbitrary

components/script/dom/globalscope.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use crate::task_source::websocket::WebsocketTaskSource;
3838
use crate::task_source::TaskSourceName;
3939
use crate::timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle};
4040
use crate::timers::{OneshotTimers, TimerCallback};
41+
use content_security_policy::CspList;
4142
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
4243
use dom_struct::dom_struct;
4344
use ipc_channel::ipc::IpcSender;
@@ -812,6 +813,15 @@ impl GlobalScope {
812813
pub fn get_user_agent(&self) -> Cow<'static, str> {
813814
self.user_agent.clone()
814815
}
816+
817+
/// https://www.w3.org/TR/CSP/#get-csp-of-object
818+
pub fn get_csp_list(&self) -> Option<CspList> {
819+
if let Some(window) = self.downcast::<Window>() {
820+
return window.Document().get_csp_list().map(|c| c.clone());
821+
}
822+
// TODO: Worker and Worklet global scopes.
823+
None
824+
}
815825
}
816826

817827
fn timestamp_in_ms(time: Timespec) -> u64 {

0 commit comments

Comments
 (0)