Skip to content

Commit 8555633

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 c94ca29 commit 8555633

File tree

16 files changed

+200
-36
lines changed

16 files changed

+200
-36
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: 33 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};
@@ -137,6 +138,30 @@ pub fn fetch_with_cors_cache(
137138
main_fetch(request, cache, false, false, target, &mut None, &context);
138139
}
139140

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

190+
// Step 2.2.
191+
if should_request_be_blocked_by_csp(request) == csp::CheckResult::Blocked {
192+
response = Some(Response::network_error(NetworkError::Internal(
193+
"Blocked by Content-Security-Policy".into(),
194+
)))
195+
}
196+
165197
// Step 3.
166-
// TODO: handle content security policy violations.
198+
// TODO: handle request abort.
167199

168200
// Step 4.
169201
// 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,
@@ -204,6 +180,7 @@ impl RequestBuilder {
204180
integrity_metadata: "".to_owned(),
205181
url_list: vec![],
206182
parser_metadata: ParserMetadata::Default,
183+
csp_list: None,
207184
}
208185
}
209186

@@ -321,6 +298,7 @@ impl RequestBuilder {
321298
request.url_list = url_list;
322299
request.integrity_metadata = self.integrity_metadata;
323300
request.parser_metadata = self.parser_metadata;
301+
request.csp_list = self.csp_list;
324302
request
325303
}
326304
}
@@ -388,6 +366,11 @@ pub struct Request {
388366
pub response_tainting: ResponseTainting,
389367
/// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata>
390368
pub parser_metadata: ParserMetadata,
369+
// This is nominally a part of the client's global object.
370+
// It is copied here to avoid having to reach across the thread
371+
// boundary every time a redirect occurs.
372+
#[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
373+
pub csp_list: Option<CspList>,
391374
}
392375

393376
impl Request {
@@ -420,6 +403,7 @@ impl Request {
420403
parser_metadata: ParserMetadata::Default,
421404
redirect_count: 0,
422405
response_tainting: ResponseTainting::Basic,
406+
csp_list: None,
423407
}
424408
}
425409

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/document_loader.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ impl DocumentLoader {
149149
.blocking_loads
150150
.iter()
151151
.position(|unfinished| *unfinished == *load);
152+
warn!("load count {:?}", self.blocking_loads.len());
152153
match idx {
153154
Some(i) => {
154155
self.blocking_loads.remove(i);

components/script/dom/bindings/trace.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ use canvas_traits::webgl::{WebGLBufferId, WebGLChan, WebGLContextShareMode, WebG
5252
use canvas_traits::webgl::{WebGLFramebufferId, WebGLMsgSender, WebGLPipeline, WebGLProgramId};
5353
use canvas_traits::webgl::{WebGLReceiver, WebGLRenderbufferId, WebGLSLVersion, WebGLSender};
5454
use canvas_traits::webgl::{WebGLShaderId, WebGLSyncId, WebGLTextureId, WebGLVersion};
55+
use content_security_policy::CspList;
5556
use crossbeam_channel::{Receiver, Sender};
5657
use cssparser::RGBA;
5758
use devtools_traits::{CSSError, TimelineMarkerType, WorkerId};
@@ -167,6 +168,8 @@ unsafe_no_jsmanaged_fields!(*mut JobQueue);
167168

168169
unsafe_no_jsmanaged_fields!(Cow<'static, str>);
169170

171+
unsafe_no_jsmanaged_fields!(CspList);
172+
170173
/// Trace a `JSVal`.
171174
pub fn trace_jsval(tracer: *mut JSTracer, description: &str, val: &Heap<JSVal>) {
172175
unsafe {

components/script/dom/document.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ use crate::stylesheet_set::StylesheetSetRef;
109109
use crate::task::TaskBox;
110110
use crate::task_source::{TaskSource, TaskSourceName};
111111
use crate::timers::OneshotTimerCallback;
112+
use content_security_policy::{self as csp, CspList};
112113
use cookie::Cookie;
113114
use devtools_traits::ScriptToDevtoolsControlMsg;
114115
use dom_struct::dom_struct;
@@ -136,6 +137,7 @@ use num_traits::ToPrimitive;
136137
use percent_encoding::percent_decode;
137138
use profile_traits::ipc as profile_ipc;
138139
use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType};
140+
use ref_filter_map::ref_filter_map;
139141
use ref_slice::ref_slice;
140142
use script_layout_interface::message::{Msg, ReflowGoal};
141143
use script_traits::{AnimationState, DocumentActivity, MouseButton, MouseEventType};
@@ -147,7 +149,7 @@ use servo_atoms::Atom;
147149
use servo_config::pref;
148150
use servo_media::{ClientContextId, ServoMedia};
149151
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
150-
use std::borrow::ToOwned;
152+
use std::borrow::Cow;
151153
use std::cell::{Cell, Ref, RefMut};
152154
use std::collections::hash_map::Entry::{Occupied, Vacant};
153155
use std::collections::{HashMap, HashSet, VecDeque};
@@ -395,6 +397,9 @@ pub struct Document {
395397
/// where `id` needs to match any of the registered ShadowRoots
396398
/// hosting the media controls UI.
397399
media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>,
400+
/// https://html.spec.whatwg.org/multipage/#concept-document-csp-list
401+
#[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
402+
csp_list: DomRefCell<Option<CspList>>,
398403
}
399404

400405
#[derive(JSTraceable, MallocSizeOf)]
@@ -1731,9 +1736,10 @@ impl Document {
17311736
pub fn fetch_async(
17321737
&self,
17331738
load: LoadType,
1734-
request: RequestBuilder,
1739+
mut request: RequestBuilder,
17351740
fetch_target: IpcSender<FetchResponseMsg>,
17361741
) {
1742+
request.csp_list = self.get_csp_list().map(|x| x.clone());
17371743
let mut loader = self.loader.borrow_mut();
17381744
loader.fetch_async(load, request, fetch_target);
17391745
}
@@ -2784,9 +2790,56 @@ impl Document {
27842790
shadow_roots: DomRefCell::new(HashSet::new()),
27852791
shadow_roots_styles_changed: Cell::new(false),
27862792
media_controls: DomRefCell::new(HashMap::new()),
2793+
csp_list: DomRefCell::new(None),
27872794
}
27882795
}
27892796

2797+
pub fn set_csp_list(&self, csp_list: Option<CspList>) {
2798+
*self.csp_list.borrow_mut() = csp_list;
2799+
}
2800+
2801+
pub fn get_csp_list(&self) -> Option<Ref<CspList>> {
2802+
ref_filter_map(self.csp_list.borrow(), Option::as_ref)
2803+
}
2804+
2805+
/// https://www.w3.org/TR/CSP/#should-block-request
2806+
pub fn should_request_be_blocked_by_csp(&self, request: &RequestBuilder) -> csp::CheckResult {
2807+
let request = csp::Request {
2808+
url: request.url.clone().into_url(),
2809+
origin: request.origin.clone().into_url_origin(),
2810+
redirect_count: 0,
2811+
destination: request.destination,
2812+
initiator: csp::Initiator::None,
2813+
nonce: String::new(),
2814+
integrity_metadata: request.integrity_metadata.clone(),
2815+
parser_metadata: csp::ParserMetadata::None,
2816+
};
2817+
// TODO: Instead of ignoring violations, report them.
2818+
self.get_csp_list()
2819+
.map(|c| c.should_request_be_blocked(&request).0)
2820+
.unwrap_or(csp::CheckResult::Allowed)
2821+
}
2822+
2823+
pub fn should_elements_inline_type_behavior_be_blocked(
2824+
&self,
2825+
el: &Element,
2826+
type_: csp::InlineCheckType,
2827+
source: &str,
2828+
) -> csp::CheckResult {
2829+
let element = csp::Element {
2830+
nonce: el
2831+
.get_attribute(&ns!(), &local_name!("nonce"))
2832+
.map(|attr| Cow::Owned(attr.value().to_string())),
2833+
};
2834+
// TODO: Instead of ignoring violations, report them.
2835+
self.get_csp_list()
2836+
.map(|c| {
2837+
c.should_elements_inline_type_behavior_be_blocked(&element, type_, source)
2838+
.0
2839+
})
2840+
.unwrap_or(csp::CheckResult::Allowed)
2841+
}
2842+
27902843
/// Prevent any JS or layout from running until the corresponding call to
27912844
/// `remove_script_and_layout_blocker`. Used to isolate periods in which
27922845
/// the DOM is in an unstable state and should not be exposed to arbitrary

0 commit comments

Comments
 (0)