mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
Check CSP for javascript:
URLs (#36709)
Also update a WPT test to fail-fast if the iframe incorrectly evaluates the `eval`. Before, it would run into a timeout if the implementation is correct. Now we reject the promise when an exception is thrown. Requires servo/rust-content-security-policy#6 Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
parent
b8971e528f
commit
dd63325f50
16 changed files with 70 additions and 57 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1223,7 +1223,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "content-security-policy"
|
name = "content-security-policy"
|
||||||
version = "0.5.4"
|
version = "0.5.4"
|
||||||
source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#81f95254fbfe98dd6e130260fd872cf950de9fcd"
|
source = "git+https://github.com/servo/rust-content-security-policy/?branch=servo-csp#fcd91e99139ca96629e04e1a8010f96374f0370f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
|
|
|
@ -18,11 +18,12 @@ use base::id::{
|
||||||
ServiceWorkerId, ServiceWorkerRegistrationId, WebViewId,
|
ServiceWorkerId, ServiceWorkerRegistrationId, WebViewId,
|
||||||
};
|
};
|
||||||
use constellation_traits::{
|
use constellation_traits::{
|
||||||
BlobData, BlobImpl, BroadcastMsg, FileBlob, MessagePortImpl, MessagePortMsg, PortMessageTask,
|
BlobData, BlobImpl, BroadcastMsg, FileBlob, LoadData, LoadOrigin, MessagePortImpl,
|
||||||
ScriptToConstellationChan, ScriptToConstellationMessage,
|
MessagePortMsg, PortMessageTask, ScriptToConstellationChan, ScriptToConstellationMessage,
|
||||||
};
|
};
|
||||||
use content_security_policy::{
|
use content_security_policy::{
|
||||||
CheckResult, CspList, PolicyDisposition, PolicySource, Violation, ViolationResource,
|
CheckResult, CspList, Destination, Initiator, NavigationCheckType, ParserMetadata,
|
||||||
|
PolicyDisposition, PolicySource, Request, Violation, ViolationResource,
|
||||||
};
|
};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use devtools_traits::{PageError, ScriptToDevtoolsControlMsg};
|
use devtools_traits::{PageError, ScriptToDevtoolsControlMsg};
|
||||||
|
@ -62,6 +63,7 @@ use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_tim
|
||||||
use script_bindings::interfaces::GlobalScopeHelpers;
|
use script_bindings::interfaces::GlobalScopeHelpers;
|
||||||
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
||||||
use timers::{TimerEventId, TimerEventRequest, TimerSource};
|
use timers::{TimerEventId, TimerEventRequest, TimerSource};
|
||||||
|
use url::Origin;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
#[cfg(feature = "webgpu")]
|
#[cfg(feature = "webgpu")]
|
||||||
use webgpu_traits::{DeviceLostReason, WebGPUDevice};
|
use webgpu_traits::{DeviceLostReason, WebGPUDevice};
|
||||||
|
@ -2951,6 +2953,33 @@ impl GlobalScope {
|
||||||
is_js_evaluation_allowed == CheckResult::Allowed
|
is_js_evaluation_allowed == CheckResult::Allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn should_navigation_request_be_blocked(&self, load_data: &LoadData) -> bool {
|
||||||
|
let Some(csp_list) = self.get_csp_list() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let request = Request {
|
||||||
|
url: load_data.url.clone().into_url(),
|
||||||
|
origin: match &load_data.load_origin {
|
||||||
|
LoadOrigin::Script(immutable_origin) => immutable_origin.clone().into_url_origin(),
|
||||||
|
_ => Origin::new_opaque(),
|
||||||
|
},
|
||||||
|
// TODO: populate this field correctly
|
||||||
|
redirect_count: 0,
|
||||||
|
destination: Destination::None,
|
||||||
|
initiator: Initiator::None,
|
||||||
|
nonce: "".to_owned(),
|
||||||
|
integrity_metadata: "".to_owned(),
|
||||||
|
parser_metadata: ParserMetadata::None,
|
||||||
|
};
|
||||||
|
// TODO: set correct navigation check type for form submission if applicable
|
||||||
|
let (result, violations) =
|
||||||
|
csp_list.should_navigation_request_be_blocked(&request, NavigationCheckType::Other);
|
||||||
|
|
||||||
|
self.report_csp_violations(violations);
|
||||||
|
|
||||||
|
result == CheckResult::Blocked
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn create_image_bitmap(
|
pub(crate) fn create_image_bitmap(
|
||||||
&self,
|
&self,
|
||||||
image: ImageBitmapSource,
|
image: ImageBitmapSource,
|
||||||
|
|
|
@ -162,8 +162,13 @@ impl HTMLIFrameElement {
|
||||||
if load_data.url.scheme() == "javascript" {
|
if load_data.url.scheme() == "javascript" {
|
||||||
let window_proxy = self.GetContentWindow();
|
let window_proxy = self.GetContentWindow();
|
||||||
if let Some(window_proxy) = window_proxy {
|
if let Some(window_proxy) = window_proxy {
|
||||||
|
if document
|
||||||
|
.global()
|
||||||
|
.should_navigation_request_be_blocked(&load_data)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Important re security. See https://github.com/servo/servo/issues/23373
|
// Important re security. See https://github.com/servo/servo/issues/23373
|
||||||
// TODO: check according to https://w3c.github.io/webappsec-csp/#should-block-navigation-request
|
|
||||||
if ScriptThread::check_load_origin(&load_data.load_origin, &document.url().origin())
|
if ScriptThread::check_load_origin(&load_data.load_origin, &document.url().origin())
|
||||||
{
|
{
|
||||||
ScriptThread::eval_js_url(&window_proxy.global(), &mut load_data, can_gc);
|
ScriptThread::eval_js_url(&window_proxy.global(), &mut load_data, can_gc);
|
||||||
|
|
|
@ -598,7 +598,7 @@ impl ScriptThread {
|
||||||
with_script_thread(|script_thread| {
|
with_script_thread(|script_thread| {
|
||||||
let is_javascript = load_data.url.scheme() == "javascript";
|
let is_javascript = load_data.url.scheme() == "javascript";
|
||||||
// If resource is a request whose url's scheme is "javascript"
|
// If resource is a request whose url's scheme is "javascript"
|
||||||
// https://html.spec.whatwg.org/multipage/#javascript-protocol
|
// https://html.spec.whatwg.org/multipage/#navigate-to-a-javascript:-url
|
||||||
if is_javascript {
|
if is_javascript {
|
||||||
let window = match script_thread.documents.borrow().find_window(pipeline_id) {
|
let window = match script_thread.documents.borrow().find_window(pipeline_id) {
|
||||||
None => return,
|
None => return,
|
||||||
|
@ -612,8 +612,12 @@ impl ScriptThread {
|
||||||
.clone();
|
.clone();
|
||||||
let task = task!(navigate_javascript: move || {
|
let task = task!(navigate_javascript: move || {
|
||||||
// Important re security. See https://github.com/servo/servo/issues/23373
|
// Important re security. See https://github.com/servo/servo/issues/23373
|
||||||
// TODO: check according to https://w3c.github.io/webappsec-csp/#should-block-navigation-request
|
|
||||||
if let Some(window) = trusted_global.root().downcast::<Window>() {
|
if let Some(window) = trusted_global.root().downcast::<Window>() {
|
||||||
|
// Step 5: If the result of should navigation request of type be blocked by
|
||||||
|
// Content Security Policy? given request and cspNavigationType is "Blocked", then return. [CSP]
|
||||||
|
if trusted_global.root().should_navigation_request_be_blocked(&load_data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ScriptThread::check_load_origin(&load_data.load_origin, &window.get_url().origin()) {
|
if ScriptThread::check_load_origin(&load_data.load_origin, &window.get_url().origin()) {
|
||||||
ScriptThread::eval_js_url(&trusted_global.root(), &mut load_data, CanGc::note());
|
ScriptThread::eval_js_url(&trusted_global.root(), &mut load_data, CanGc::note());
|
||||||
sender
|
sender
|
||||||
|
@ -622,6 +626,7 @@ impl ScriptThread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Step 19 of <https://html.spec.whatwg.org/multipage/#navigate>
|
||||||
global
|
global
|
||||||
.task_manager()
|
.task_manager()
|
||||||
.dom_manipulation_task_source()
|
.dom_manipulation_task_source()
|
||||||
|
|
2
tests/wpt/meta/MANIFEST.json
vendored
2
tests/wpt/meta/MANIFEST.json
vendored
|
@ -571755,7 +571755,7 @@
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"eval-blocked-in-about-blank-iframe.html": [
|
"eval-blocked-in-about-blank-iframe.html": [
|
||||||
"054e75b52749b37530a02bc5ee0119ca5e76a474",
|
"b2286f56a234a515cddec0e02439f9768e3a2905",
|
||||||
[
|
[
|
||||||
null,
|
null,
|
||||||
{}
|
{}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
[to-javascript-parent-initiated-child-csp.html]
|
[to-javascript-parent-initiated-child-csp.html]
|
||||||
expected: TIMEOUT
|
expected: TIMEOUT
|
||||||
[Should not have executed the javascript URL for\n iframe.contentWindow.location.href with child's CSP "script-src 'none'"]
|
|
||||||
expected: TIMEOUT
|
|
||||||
|
|
||||||
[Should not have executed the javascript URL for\n iframe.src with child's CSP "script-src 'none'"]
|
[Should not have executed the javascript URL for\n iframe.src with child's CSP "script-src 'none'"]
|
||||||
expected: NOTRUN
|
expected: TIMEOUT
|
||||||
|
|
||||||
[Should not have executed the javascript URL for\n otherTabWithScriptSrcNone.location.href with child's CSP "script-src 'none'"]
|
[Should not have executed the javascript URL for\n otherTabWithScriptSrcNone.location.href with child's CSP "script-src 'none'"]
|
||||||
expected: NOTRUN
|
expected: NOTRUN
|
||||||
|
|
|
@ -3,9 +3,6 @@
|
||||||
[Should not have executed the javascript url for\n iframe.contentWindow.location.href]
|
[Should not have executed the javascript url for\n iframe.contentWindow.location.href]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Should not have executed the javascript url for\n iframe.src]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Should not have executed the javascript url for\n otherTab.location.href]
|
[Should not have executed the javascript url for\n otherTab.location.href]
|
||||||
expected: TIMEOUT
|
expected: TIMEOUT
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
[script-src-strict_dynamic_javascript_uri.html]
|
|
||||||
expected: TIMEOUT
|
|
||||||
[Script injected via `javascript:` URIs are not allowed with `strict-dynamic`.]
|
|
||||||
expected: TIMEOUT
|
|
|
@ -1,13 +0,0 @@
|
||||||
[script-src-trusted_types_eval_with_require_trusted_types_eval.html]
|
|
||||||
expected: ERROR
|
|
||||||
[Script injected via direct `eval` is allowed with `trusted-types-eval` and `require-trusted-types-for 'script'`.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Script injected via indirect `eval` is allowed with `trusted-types-eval` and `require-trusted-types-for 'script'`.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Script injected via `new Function` is allowed with `trusted-types-eval` and `require-trusted-types-for 'script'`.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Script injected via `setTimeout` is allowed with `trusted-types-eval` and `require-trusted-types-for 'script'`.]
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +1,4 @@
|
||||||
[linenumber.tentative.html]
|
[linenumber.tentative.html]
|
||||||
|
expected: TIMEOUT
|
||||||
[linenumber]
|
[linenumber]
|
||||||
expected: FAIL
|
expected: NOTRUN
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
[eval-blocked-in-about-blank-iframe.html]
|
[eval-blocked-in-about-blank-iframe.html]
|
||||||
expected: ERROR
|
|
||||||
[eval-blocked-in-about-blank-iframe]
|
[eval-blocked-in-about-blank-iframe]
|
||||||
expected: TIMEOUT
|
expected: FAIL
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[javascript_src_denied_missing_unsafe_hashes-href.html]
|
|
||||||
[javascript: navigation using <a href> should be refused due to missing unsafe-hashes]
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[javascript_src_denied_missing_unsafe_hashes-window_location.html]
|
|
||||||
[Test that the javascript: src is not allowed to run]
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[javascript_src_denied_wrong_hash-href.html]
|
|
||||||
[javascript: navigation using <a href> should be refused due to wrong hash]
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[javascript_src_denied_wrong_hash-window_location.html]
|
|
||||||
[Test that the javascript: src is not allowed to run]
|
|
||||||
expected: FAIL
|
|
|
@ -19,19 +19,28 @@
|
||||||
const document_loaded = new Promise(resolve => window.onload = resolve);
|
const document_loaded = new Promise(resolve => window.onload = resolve);
|
||||||
await document_loaded;
|
await document_loaded;
|
||||||
|
|
||||||
const eval_error = new Promise(resolve => {
|
const eval_error = new Promise((resolve, reject) => {
|
||||||
window.addEventListener('message', function(e) {
|
window.addEventListener('message', function(event) {
|
||||||
assert_not_equals(e.data, 'FAIL', 'eval was executed in the frame');
|
try {
|
||||||
if (e.data === 'PASS')
|
assert_not_equals(event.data, 'FAIL', 'eval was executed in the frame');
|
||||||
|
if (event.data === 'PASS') {
|
||||||
resolve();
|
resolve();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const csp_violation_report = new Promise(resolve => {
|
const csp_violation_report = new Promise((resolve, reject) => {
|
||||||
window.addEventListener('message', function(e) {
|
window.addEventListener('message', function(event) {
|
||||||
if (e.data["violated-directive"]) {
|
try {
|
||||||
assert_equals(e.data["violated-directive"], "script-src");
|
if (event.data["violated-directive"]) {
|
||||||
|
assert_equals(event.data["violated-directive"], "script-src");
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue