enhance: Add support for unsafe-eval and wasm-unsafe-eval (#32893)

Signed-off-by: Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com>
This commit is contained in:
Chocolate Pie 2024-08-02 02:26:44 +09:00 committed by GitHub
parent 2cf207ddc8
commit 92866ab911
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 755 additions and 73 deletions

4
Cargo.lock generated
View file

@ -976,9 +976,9 @@ dependencies = [
[[package]]
name = "content-security-policy"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "754c060c4a3342c5824d14caeba6c588716e9327f50558532685ef56718e0461"
checksum = "bf7225464dae1993d0045c023d0975f44d63337f35f85faddb998ff9abdfcd0f"
dependencies = [
"base64",
"bitflags 2.6.0",

View file

@ -474,6 +474,8 @@ pub struct Document {
fonts: MutNullableDom<FontFaceSet>,
/// <https://html.spec.whatwg.org/multipage/#visibility-state>
visibility_state: Cell<DocumentVisibilityState>,
/// <https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml>
status_code: Option<u16>,
}
#[derive(JSTraceable, MallocSizeOf)]
@ -3001,6 +3003,10 @@ impl Document {
};
global_scope.report_an_error(error_info, HandleValue::null());
}
pub(crate) fn status_code(&self) -> Option<u16> {
self.status_code
}
}
fn is_character_value_key(key: &Key) -> bool {
@ -3156,6 +3162,7 @@ impl Document {
doc_loader: DocumentLoader,
referrer: Option<String>,
referrer_policy: Option<ReferrerPolicy>,
status_code: Option<u16>,
canceller: FetchCanceller,
) -> Document {
let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
@ -3306,6 +3313,7 @@ impl Document {
resize_observers: Default::default(),
fonts: Default::default(),
visibility_state: Cell::new(DocumentVisibilityState::Hidden),
status_code,
}
}
@ -3443,6 +3451,7 @@ impl Document {
docloader,
None,
None,
None,
Default::default(),
))
}
@ -3461,6 +3470,7 @@ impl Document {
doc_loader: DocumentLoader,
referrer: Option<String>,
referrer_policy: Option<ReferrerPolicy>,
status_code: Option<u16>,
canceller: FetchCanceller,
) -> DomRoot<Document> {
Self::new_with_proto(
@ -3477,6 +3487,7 @@ impl Document {
doc_loader,
referrer,
referrer_policy,
status_code,
canceller,
)
}
@ -3496,6 +3507,7 @@ impl Document {
doc_loader: DocumentLoader,
referrer: Option<String>,
referrer_policy: Option<ReferrerPolicy>,
status_code: Option<u16>,
canceller: FetchCanceller,
) -> DomRoot<Document> {
let document = reflect_dom_object_with_proto(
@ -3512,6 +3524,7 @@ impl Document {
doc_loader,
referrer,
referrer_policy,
status_code,
canceller,
)),
window,
@ -3634,6 +3647,7 @@ impl Document {
DocumentLoader::new(&self.loader()),
None,
None,
None,
Default::default(),
);
new_doc

View file

@ -155,6 +155,7 @@ impl DOMImplementationMethods for DOMImplementation {
loader,
None,
None,
None,
Default::default(),
);

View file

@ -78,6 +78,7 @@ impl DOMParserMethods for DOMParser {
loader,
None,
None,
None,
Default::default(),
);
ServoParser::parse_html_document(&document, Some(s), url);
@ -98,6 +99,7 @@ impl DOMParserMethods for DOMParser {
loader,
None,
None,
None,
Default::default(),
);
ServoParser::parse_xml_document(&document, Some(s), url);

View file

@ -18,7 +18,7 @@ use base::id::{
BlobId, BroadcastChannelRouterId, MessagePortId, MessagePortRouterId, PipelineId,
ServiceWorkerId, ServiceWorkerRegistrationId,
};
use content_security_policy::CspList;
use content_security_policy::{CheckResult, CspList, PolicyDisposition};
use crossbeam_channel::Sender;
use devtools_traits::{PageError, ScriptToDevtoolsControlMsg};
use dom_struct::dom_struct;
@ -28,14 +28,15 @@ use ipc_channel::router::ROUTER;
use js::glue::{IsWrapper, UnwrapObjectDynamic};
use js::jsapi::{
Compile1, CurrentGlobalOrNull, GetNonCCWObjectGlobal, HandleObject, Heap,
InstantiateGlobalStencil, InstantiateOptions, JSContext, JSObject, JSScript, SetScriptPrivate,
InstantiateGlobalStencil, InstantiateOptions, JSContext, JSObject, JSScript, RuntimeCode,
SetScriptPrivate,
};
use js::jsval::{JSVal, PrivateValue, UndefinedValue};
use js::panic::maybe_resume_unwind;
use js::rust::wrappers::{JS_ExecuteScript, JS_GetScriptPrivate};
use js::rust::{
get_object_class, transform_str_to_source_text, CompileOptionsWrapper, HandleValue,
MutableHandleValue, ParentRuntime, Runtime,
describe_scripted_caller, get_object_class, transform_str_to_source_text,
CompileOptionsWrapper, HandleValue, MutableHandleValue, ParentRuntime, Runtime,
};
use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
use net_traits::blob_url_store::{get_blob_origin, BlobBuf};
@ -119,6 +120,7 @@ use crate::script_runtime::{
CommonScriptMsg, ContextForRequestInterrupt, JSContext as SafeJSContext, ScriptChan, ScriptPort,
};
use crate::script_thread::{MainThreadScriptChan, ScriptThread};
use crate::security_manager::CSPViolationReporter;
use crate::task::TaskCanceller;
use crate::task_source::dom_manipulation::DOMManipulationTaskSource;
use crate::task_source::file_reading::FileReadingTaskSource;
@ -2773,6 +2775,37 @@ impl GlobalScope {
}))
}
#[allow(unsafe_code)]
pub fn is_js_evaluation_allowed(&self, cx: SafeJSContext) -> bool {
let Some(csp_list) = self.get_csp_list() else {
return true;
};
let scripted_caller = unsafe { describe_scripted_caller(*cx) }.unwrap_or_default();
let is_js_evaluation_allowed = csp_list.is_js_evaluation_allowed() == CheckResult::Allowed;
if !is_js_evaluation_allowed {
// FIXME: Don't fire event if `script-src` and `default-src`
// were not passed.
for policy in csp_list.0 {
let task = CSPViolationReporter::new(
self,
None,
policy.disposition == PolicyDisposition::Report,
RuntimeCode::JS,
scripted_caller.filename.clone(),
scripted_caller.line,
scripted_caller.col,
);
self.dom_manipulation_task_source()
.queue(task, self)
.unwrap();
}
}
is_js_evaluation_allowed
}
pub fn create_image_bitmap(
&self,
image: ImageBitmapSource,
@ -3090,6 +3123,13 @@ impl GlobalScope {
None
}
pub fn status_code(&self) -> Option<u16> {
if let Some(window) = self.downcast::<Window>() {
return window.Document().status_code();
}
None
}
pub fn wgpu_id_hub(&self) -> Arc<Identities> {
self.gpu_id_hub.clone()
}

View file

@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use content_security_policy::{CspList, PolicyDisposition, PolicySource};
use dom_struct::dom_struct;
use html5ever::{local_name, namespace_url, ns, LocalName, Prefix};
use js::rust::HandleObject;
@ -80,6 +81,48 @@ impl HTMLHeadElement {
}
}
}
/// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
pub fn set_content_security_policy(&self) {
let doc = document_from_node(self);
if doc.GetHead().as_deref() != Some(self) {
return;
}
let mut csp_list: Option<CspList> = None;
let node = self.upcast::<Node>();
let candinates = node
.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
.filter(|elem| elem.is::<HTMLMetaElement>())
.filter(|elem| {
elem.get_string_attribute(&local_name!("http-equiv"))
.to_ascii_lowercase() ==
"content-security-policy".to_owned()
})
.filter(|elem| {
elem.get_attribute(&ns!(), &local_name!("content"))
.is_some()
});
for meta in candinates {
if let Some(ref content) = meta.get_attribute(&ns!(), &local_name!("content")) {
let content = content.value();
let content_val = content.trim();
if !content_val.is_empty() {
let policies =
CspList::parse(content_val, PolicySource::Meta, PolicyDisposition::Enforce);
match csp_list {
Some(ref mut csp_list) => csp_list.append(policies),
None => csp_list = Some(policies),
}
}
}
}
doc.set_csp_list(csp_list);
}
}
impl VirtualMethods for HTMLHeadElement {

View file

@ -88,8 +88,10 @@ impl HTMLMetaElement {
// https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv
} else if !self.HttpEquiv().is_empty() {
// TODO: Implement additional http-equiv candidates
if self.HttpEquiv().to_ascii_lowercase().as_str() == "refresh" {
self.declarative_refresh();
match self.HttpEquiv().to_ascii_lowercase().as_str() {
"refresh" => self.declarative_refresh(),
"content-security-policy" => self.apply_csp_list(),
_ => {},
}
}
}
@ -115,6 +117,15 @@ impl HTMLMetaElement {
}
}
/// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
fn apply_csp_list(&self) {
if let Some(parent) = self.upcast::<Node>().GetParentElement() {
if let Some(head) = parent.downcast::<HTMLHeadElement>() {
head.set_content_security_policy();
}
}
}
/// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
fn declarative_refresh(&self) {
// 2

View file

@ -471,6 +471,7 @@ macro_rules! global_event_handlers(
event_handler!(progress, GetOnprogress, SetOnprogress);
event_handler!(ratechange, GetOnratechange, SetOnratechange);
event_handler!(reset, GetOnreset, SetOnreset);
event_handler!(securitypolicyviolation, GetOnsecuritypolicyviolation, SetOnsecuritypolicyviolation);
event_handler!(seeked, GetOnseeked, SetOnseeked);
event_handler!(seeking, GetOnseeking, SetOnseeking);
event_handler!(select, GetOnselect, SetOnselect);

View file

@ -525,6 +525,7 @@ pub(crate) mod rtcrtptransceiver;
pub mod rtcsessiondescription;
pub mod rtctrackevent;
pub mod screen;
pub mod securitypolicyviolationevent;
pub mod selection;
pub mod serviceworker;
pub mod serviceworkercontainer;

View file

@ -2296,6 +2296,7 @@ impl Node {
loader,
None,
None,
document.status_code(),
Default::default(),
);
DomRoot::upcast::<Node>(document)

View file

@ -720,8 +720,11 @@ impl From<RequestDestination> for NetTraitsRequestDestination {
RequestDestination::Document => NetTraitsRequestDestination::Document,
RequestDestination::Embed => NetTraitsRequestDestination::Embed,
RequestDestination::Font => NetTraitsRequestDestination::Font,
RequestDestination::Frame => NetTraitsRequestDestination::Frame,
RequestDestination::Iframe => NetTraitsRequestDestination::IFrame,
RequestDestination::Image => NetTraitsRequestDestination::Image,
RequestDestination::Manifest => NetTraitsRequestDestination::Manifest,
RequestDestination::Json => NetTraitsRequestDestination::Json,
RequestDestination::Object => NetTraitsRequestDestination::Object,
RequestDestination::Report => NetTraitsRequestDestination::Report,
RequestDestination::Script => NetTraitsRequestDestination::Script,
@ -743,8 +746,11 @@ impl From<NetTraitsRequestDestination> for RequestDestination {
NetTraitsRequestDestination::Document => RequestDestination::Document,
NetTraitsRequestDestination::Embed => RequestDestination::Embed,
NetTraitsRequestDestination::Font => RequestDestination::Font,
NetTraitsRequestDestination::Frame => RequestDestination::Frame,
NetTraitsRequestDestination::IFrame => RequestDestination::Iframe,
NetTraitsRequestDestination::Image => RequestDestination::Image,
NetTraitsRequestDestination::Manifest => RequestDestination::Manifest,
NetTraitsRequestDestination::Json => RequestDestination::Json,
NetTraitsRequestDestination::Object => RequestDestination::Object,
NetTraitsRequestDestination::Report => RequestDestination::Report,
NetTraitsRequestDestination::Script => RequestDestination::Script,
@ -759,6 +765,7 @@ impl From<NetTraitsRequestDestination> for RequestDestination {
NetTraitsRequestDestination::Video => RequestDestination::Video,
NetTraitsRequestDestination::Worker => RequestDestination::Worker,
NetTraitsRequestDestination::Xslt => RequestDestination::Xslt,
NetTraitsRequestDestination::WebIdentity => RequestDestination::_empty,
}
}
}

View file

@ -0,0 +1,180 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use servo_atoms::Atom;
use super::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding::{
SecurityPolicyViolationEventDisposition, SecurityPolicyViolationEventInit,
SecurityPolicyViolationEventMethods,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::globalscope::GlobalScope;
// https://w3c.github.io/webappsec-csp/#securitypolicyviolationevent
#[dom_struct]
pub struct SecurityPolicyViolationEvent {
event: Event,
document_uri: USVString,
referrer: USVString,
blocked_uri: USVString,
effective_directive: DOMString,
violated_directive: DOMString,
original_policy: DOMString,
source_file: USVString,
sample: DOMString,
disposition: SecurityPolicyViolationEventDisposition,
status_code: u16,
line_number: u32,
column_number: u32,
}
impl SecurityPolicyViolationEvent {
fn new_inherited(init: &SecurityPolicyViolationEventInit) -> SecurityPolicyViolationEvent {
SecurityPolicyViolationEvent {
event: Event::new_inherited(),
document_uri: init.documentURI.clone(),
referrer: init.referrer.clone(),
blocked_uri: init.blockedURI.clone(),
effective_directive: init.effectiveDirective.clone(),
violated_directive: init.violatedDirective.clone(),
original_policy: init.originalPolicy.clone(),
source_file: init.sourceFile.clone(),
sample: init.sample.clone(),
disposition: init.disposition.clone(),
status_code: init.statusCode,
line_number: init.lineNumber,
column_number: init.columnNumber,
}
}
pub fn new_initialized(
global: &GlobalScope,
init: &SecurityPolicyViolationEventInit,
proto: Option<HandleObject>,
) -> DomRoot<SecurityPolicyViolationEvent> {
reflect_dom_object_with_proto(
Box::new(SecurityPolicyViolationEvent::new_inherited(init)),
global,
proto,
)
}
fn new_with_proto(
global: &GlobalScope,
proto: Option<HandleObject>,
type_: Atom,
bubbles: EventBubbles,
cancelable: EventCancelable,
init: &SecurityPolicyViolationEventInit,
) -> DomRoot<Self> {
let ev = SecurityPolicyViolationEvent::new_initialized(global, init, proto);
{
let event = ev.upcast::<Event>();
event.init_event(type_, bool::from(bubbles), bool::from(cancelable));
}
ev
}
pub fn new(
global: &GlobalScope,
type_: Atom,
bubbles: EventBubbles,
cancelable: EventCancelable,
init: &SecurityPolicyViolationEventInit,
) -> DomRoot<Self> {
Self::new_with_proto(global, None, type_, bubbles, cancelable, init)
}
#[allow(non_snake_case)]
pub fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
type_: DOMString,
init: &SecurityPolicyViolationEventInit,
) -> DomRoot<Self> {
SecurityPolicyViolationEvent::new_with_proto(
global,
proto,
Atom::from(type_),
EventBubbles::from(init.parent.bubbles),
EventCancelable::from(init.parent.cancelable),
init,
)
}
}
#[allow(non_snake_case)]
impl SecurityPolicyViolationEventMethods for SecurityPolicyViolationEvent {
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-documenturi>
fn DocumentURI(&self) -> USVString {
self.document_uri.clone()
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-referrer>
fn Referrer(&self) -> USVString {
self.referrer.clone()
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-blockeduri>
fn BlockedURI(&self) -> USVString {
self.blocked_uri.clone()
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-effectivedirective>
fn EffectiveDirective(&self) -> DOMString {
self.effective_directive.clone()
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-violateddirective>
fn ViolatedDirective(&self) -> DOMString {
self.violated_directive.clone()
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-originalpolicy>
fn OriginalPolicy(&self) -> DOMString {
self.original_policy.clone()
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-sourcefile>
fn SourceFile(&self) -> USVString {
self.source_file.clone()
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-sample>
fn Sample(&self) -> DOMString {
self.sample.clone()
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-disposition>
fn Disposition(&self) -> SecurityPolicyViolationEventDisposition {
self.disposition.clone()
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-statuscode>
fn StatusCode(&self) -> u16 {
self.status_code
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-linenumber>
fn LineNumber(&self) -> u32 {
self.line_number
}
/// <https://w3c.github.io/webappsec-csp/#dom-securitypolicyviolationevent-columnnumber>
fn ColumnNumber(&self) -> u32 {
self.column_number
}
/// <https://dom.spec.whatwg.org/#dom-event-istrusted>
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -214,6 +214,7 @@ impl ServoParser {
loader,
None,
None,
None,
Default::default(),
);

View file

@ -77,6 +77,7 @@ interface mixin GlobalEventHandlers {
attribute EventHandler onreset;
attribute EventHandler onresize;
attribute EventHandler onscroll;
attribute EventHandler onsecuritypolicyviolation;
attribute EventHandler onseeked;
attribute EventHandler onseeking;
attribute EventHandler onselect;

View file

@ -47,7 +47,10 @@ enum RequestDestination {
"document",
"embed",
"font",
"frame",
"iframe",
"image",
"json",
"manifest",
"object",
"report",

View file

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
// https://w3c.github.io/webappsec-csp/#securitypolicyviolationevent
enum SecurityPolicyViolationEventDisposition {
"enforce", "report"
};
[Exposed=(Window,Worker)]
interface SecurityPolicyViolationEvent : Event {
constructor(DOMString type, optional SecurityPolicyViolationEventInit eventInitDict = {});
readonly attribute USVString documentURI;
readonly attribute USVString referrer;
readonly attribute USVString blockedURI;
readonly attribute DOMString effectiveDirective;
readonly attribute DOMString violatedDirective; // historical alias of effectiveDirective
readonly attribute DOMString originalPolicy;
readonly attribute USVString sourceFile;
readonly attribute DOMString sample;
readonly attribute SecurityPolicyViolationEventDisposition disposition;
readonly attribute unsigned short statusCode;
readonly attribute unsigned long lineNumber;
readonly attribute unsigned long columnNumber;
};
dictionary SecurityPolicyViolationEventInit : EventInit {
USVString documentURI = "";
USVString referrer = "";
USVString blockedURI = "";
DOMString violatedDirective = "";
DOMString effectiveDirective = "";
DOMString originalPolicy = "";
USVString sourceFile = "";
DOMString sample = "";
SecurityPolicyViolationEventDisposition disposition = "enforce";
unsigned short statusCode = 0;
unsigned long lineNumber = 0;
unsigned long columnNumber = 0;
};

View file

@ -55,6 +55,7 @@ impl XMLDocument {
doc_loader,
None,
None,
None,
Default::default(),
),
}

View file

@ -1527,6 +1527,7 @@ impl XMLHttpRequest {
docloader,
None,
None,
None,
Default::default(),
)
}

View file

@ -68,6 +68,8 @@ pub mod script_runtime;
#[allow(unsafe_code)]
pub mod script_thread;
#[warn(deprecated)]
pub mod security_manager;
#[warn(deprecated)]
pub mod serviceworker_manager;
#[warn(deprecated)]
mod stylesheet_loader;

View file

@ -19,6 +19,8 @@ use std::time::{Duration, Instant};
use std::{fmt, os, ptr, thread};
use base::id::PipelineId;
use content_security_policy::{CheckResult, PolicyDisposition};
use js::conversions::jsstr_to_string;
use js::glue::{
CollectServoSizes, CreateJobQueue, DeleteJobQueue, DispatchableRun, JobQueueTraps,
RUST_js_GetErrorMessage, SetBuildId, StreamConsumerConsumeChunk,
@ -27,23 +29,23 @@ use js::glue::{
use js::jsapi::{
AsmJSOption, BuildIdCharVector, ContextOptionsRef, DisableIncrementalGC,
Dispatchable as JSRunnable, Dispatchable_MaybeShuttingDown, GCDescription, GCOptions,
GCProgress, GCReason, GetPromiseUserInputEventHandlingState, HandleObject, Heap,
GCProgress, GCReason, GetPromiseUserInputEventHandlingState, HandleObject, HandleString, Heap,
InitConsumeStreamCallback, InitDispatchToEventLoop, JSContext as RawJSContext, JSGCParamKey,
JSGCStatus, JSJitCompilerOption, JSObject, JSSecurityCallbacks, JSTracer,
JS_AddExtraGCRootsTracer, JS_InitDestroyPrincipalsCallback, JS_InitReadPrincipalsCallback,
JS_RequestInterruptCallback, JS_SetGCCallback, JS_SetGCParameter,
JS_SetGlobalJitCompilerOption, JS_SetOffthreadIonCompilationEnabled,
JS_SetParallelParsingEnabled, JS_SetSecurityCallbacks, JobQueue, MimeType,
PromiseRejectionHandlingState, PromiseUserInputEventHandlingState, SetDOMCallbacks,
SetGCSliceCallback, SetJobQueue, SetPreserveWrapperCallbacks, SetProcessBuildIdOp,
SetPromiseRejectionTrackerCallback, StreamConsumer as JSStreamConsumer,
PromiseRejectionHandlingState, PromiseUserInputEventHandlingState, RuntimeCode,
SetDOMCallbacks, SetGCSliceCallback, SetJobQueue, SetPreserveWrapperCallbacks,
SetProcessBuildIdOp, SetPromiseRejectionTrackerCallback, StreamConsumer as JSStreamConsumer,
};
use js::jsval::UndefinedValue;
use js::panic::wrap_panic;
use js::rust::wrappers::{GetPromiseIsHandled, JS_GetPromiseResult};
use js::rust::{
Handle, HandleObject as RustHandleObject, IntoHandle, JSEngine, JSEngineHandle, ParentRuntime,
Runtime as RustRuntime,
describe_scripted_caller, Handle, HandleObject as RustHandleObject, IntoHandle, JSEngine,
JSEngineHandle, ParentRuntime, Runtime as RustRuntime,
};
use lazy_static::lazy_static;
use malloc_size_of::MallocSizeOfOps;
@ -79,6 +81,7 @@ use crate::microtask::{EnqueuedPromiseCallback, Microtask, MicrotaskQueue};
use crate::realms::{AlreadyInRealm, InRealm};
use crate::script_module::EnsureModuleHooksInitialized;
use crate::script_thread::trace_thread;
use crate::security_manager::CSPViolationReporter;
use crate::task::TaskBox;
use crate::task_source::networking::NetworkingTaskSource;
use crate::task_source::{TaskSource, TaskSourceName};
@ -90,8 +93,7 @@ static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps {
};
static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks {
// TODO: Content Security Policy <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>
contentSecurityPolicyAllows: None,
contentSecurityPolicyAllows: Some(content_security_policy_allows),
subsumes: Some(principals::subsumes),
};
@ -311,6 +313,61 @@ unsafe extern "C" fn promise_rejection_tracker(
})
}
#[allow(unsafe_code)]
unsafe extern "C" fn content_security_policy_allows(
cx: *mut RawJSContext,
runtime_code: RuntimeCode,
sample: HandleString,
) -> bool {
let mut allowed = false;
let cx = JSContext::from_ptr(cx);
wrap_panic(&mut || {
// SpiderMonkey provides null pointer when executing webassembly.
let sample = match sample {
sample if !sample.is_null() => Some(jsstr_to_string(*cx, *sample)),
_ => None,
};
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
let global = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
let Some(csp_list) = global.get_csp_list() else {
allowed = true;
return;
};
let is_js_evaluation_allowed = csp_list.is_js_evaluation_allowed() == CheckResult::Allowed;
let is_wasm_evaluation_allowed =
csp_list.is_wasm_evaluation_allowed() == CheckResult::Allowed;
let scripted_caller = describe_scripted_caller(*cx).unwrap_or_default();
allowed = match runtime_code {
RuntimeCode::JS if is_js_evaluation_allowed => true,
RuntimeCode::WASM if is_wasm_evaluation_allowed => true,
_ => false,
};
if !allowed {
// FIXME: Don't fire event if `script-src` and `default-src`
// were not passed.
for policy in csp_list.0 {
let task = CSPViolationReporter::new(
&global,
sample.clone(),
policy.disposition == PolicyDisposition::Report,
runtime_code,
scripted_caller.filename.clone(),
scripted_caller.line,
scripted_caller.col,
);
global
.dom_manipulation_task_source()
.queue(task, &global)
.unwrap();
}
}
});
allowed
}
#[allow(unsafe_code, crown::unrooted_must_root)]
/// <https://html.spec.whatwg.org/multipage/#notify-about-rejected-promises>
pub fn notify_about_rejected_promises(global: &GlobalScope) {

View file

@ -3672,6 +3672,8 @@ impl ScriptThread {
.and_then(|h| h.typed_get::<ReferrerPolicyHeader>())
.map(ReferrerPolicy::from);
let status_code = metadata.status.map(|status| status.0).unwrap_or(200);
let document = Document::new(
&window,
HasBrowsingContext::Yes,
@ -3685,6 +3687,7 @@ impl ScriptThread {
loader,
referrer,
referrer_policy,
Some(status_code),
incomplete.canceller,
);
document.set_ready_state(DocumentReadyState::Loading);

View file

@ -0,0 +1,177 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use js::jsapi::RuntimeCode;
use net_traits::request::Referrer;
use serde::Serialize;
use servo_atoms::Atom;
use servo_url::ServoUrl;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding::{
SecurityPolicyViolationEventDisposition, SecurityPolicyViolationEventInit,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::DomObject;
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent;
use crate::dom::types::GlobalScope;
use crate::task::TaskOnce;
pub struct CSPViolationReporter {
sample: Option<String>,
filename: String,
report_only: bool,
runtime_code: RuntimeCode,
line_number: u32,
column_number: u32,
target: Trusted<EventTarget>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SecurityPolicyViolationReport {
sample: Option<String>,
#[serde(rename = "blockedURL")]
blocked_url: String,
referrer: String,
status_code: u16,
#[serde(rename = "documentURL")]
document_url: String,
source_file: String,
violated_directive: String,
effective_directive: String,
line_number: u32,
column_number: u32,
original_policy: String,
disposition: SecurityPolicyViolationEventDisposition,
}
impl CSPViolationReporter {
pub fn new(
global: &GlobalScope,
sample: Option<String>,
report_only: bool,
runtime_code: RuntimeCode,
filename: String,
line_number: u32,
column_number: u32,
) -> CSPViolationReporter {
CSPViolationReporter {
sample,
filename,
report_only,
runtime_code,
line_number,
column_number,
target: Trusted::new(global.upcast::<EventTarget>()),
}
}
fn get_report(&self, global: &GlobalScope) -> SecurityPolicyViolationReport {
SecurityPolicyViolationReport {
sample: self.sample.clone(),
disposition: match self.report_only {
true => SecurityPolicyViolationEventDisposition::Report,
false => SecurityPolicyViolationEventDisposition::Enforce,
},
// https://w3c.github.io/webappsec-csp/#violation-resource
blocked_url: match self.runtime_code {
RuntimeCode::JS => "eval".to_owned(),
RuntimeCode::WASM => "wasm-eval".to_owned(),
},
// https://w3c.github.io/webappsec-csp/#violation-referrer
referrer: match global.get_referrer() {
Referrer::Client(url) => self.strip_url_for_reports(url),
Referrer::ReferrerUrl(url) => self.strip_url_for_reports(url),
_ => "".to_owned(),
},
status_code: global.status_code().unwrap_or(200),
document_url: self.strip_url_for_reports(global.get_url()),
source_file: self.filename.clone(),
violated_directive: "script-src".to_owned(),
effective_directive: "script-src".to_owned(),
line_number: self.line_number,
column_number: self.column_number,
original_policy: String::default(),
}
}
fn fire_violation_event(&self) {
let target = self.target.root();
let global = &target.global();
let report = self.get_report(global);
let event = SecurityPolicyViolationEvent::new(
global,
Atom::from("securitypolicyviolation"),
EventBubbles::Bubbles,
EventCancelable::Cancelable,
&report.into(),
);
event.upcast::<Event>().fire(&target);
}
/// <https://w3c.github.io/webappsec-csp/#strip-url-for-use-in-reports>
fn strip_url_for_reports(&self, mut url: ServoUrl) -> String {
let scheme = url.scheme();
// > Step 1: If urls scheme is not an HTTP(S) scheme, then return urls scheme.
if scheme != "https" && scheme != "http" {
return scheme.to_owned();
}
// > Step 2: Set urls fragment to the empty string.
url.set_fragment(None);
// > Step 3: Set urls username to the empty string.
let _ = url.set_username("");
// > Step 4: Set urls password to the empty string.
let _ = url.set_password(None);
// > Step 5: Return the result of executing the URL serializer on url.
url.into_string()
}
}
/// Corresponds to the operation in 5.5 Report Violation
/// <https://w3c.github.io/webappsec-csp/#report-violation>
/// > Queue a task to run the following steps:
impl TaskOnce for CSPViolationReporter {
fn run_once(self) {
// > If target implements EventTarget, fire an event named securitypolicyviolation
// > that uses the SecurityPolicyViolationEvent interface
// > at target with its attributes initialized as follows:
self.fire_violation_event();
// TODO: Support `report-to` directive that corresponds to 5.5.3.5.
}
}
impl From<SecurityPolicyViolationReport> for SecurityPolicyViolationEventInit {
fn from(value: SecurityPolicyViolationReport) -> Self {
SecurityPolicyViolationEventInit {
sample: value.sample.unwrap_or_default().into(),
blockedURI: value.blocked_url.into(),
referrer: value.referrer.into(),
statusCode: value.status_code,
documentURI: value.document_url.into(),
sourceFile: value.source_file.into(),
violatedDirective: value.violated_directive.into(),
effectiveDirective: value.effective_directive.into(),
lineNumber: value.line_number,
columnNumber: value.column_number,
originalPolicy: value.original_policy.into(),
disposition: value.disposition.into(),
parent: EventInit::empty(),
}
}
}
impl Serialize for SecurityPolicyViolationEventDisposition {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Report => serializer.serialize_str("report"),
Self::Enforce => serializer.serialize_str("enforce"),
}
}
}

View file

@ -442,7 +442,12 @@ impl JsTimers {
) -> i32 {
let callback = match callback {
TimerCallback::StringTimerCallback(code_str) => {
InternalTimerCallback::StringTimerCallback(code_str)
let cx = GlobalScope::get_cx();
if global.is_js_evaluation_allowed(cx) {
InternalTimerCallback::StringTimerCallback(code_str)
} else {
return 0;
}
},
TimerCallback::FunctionTimerCallback(function) => {
// This is a bit complicated, but this ensures that the vector's

View file

@ -9,6 +9,11 @@ skip: true
skip: false
[samesite]
skip: true
[content-security-policy]
[unsafe-eval]
skip: false
[wasm-unsafe-eval]
skip: false
[cors]
skip: false
[css]

View file

@ -0,0 +1,4 @@
[eval-blocked-in-about-blank-iframe.html]
expected: ERROR
[eval-blocked-in-about-blank-iframe]
expected: TIMEOUT

View file

@ -0,0 +1,12 @@
[default-src-blocks-wasm.any.worker.html]
[default-src-blocks-wasm]
expected: FAIL
[default-src-blocks-wasm.any.html]
[default-src-blocks-wasm.any.serviceworker.html]
expected: ERROR
[default-src-blocks-wasm.any.sharedworker.html]
expected: ERROR

View file

@ -0,0 +1,9 @@
[default-src-unsafe-eval-allows-wasm.any.sharedworker.html]
expected: ERROR
[default-src-unsafe-eval-allows-wasm.any.worker.html]
[default-src-unsafe-eval-allows-wasm.any.html]
[default-src-unsafe-eval-allows-wasm.any.serviceworker.html]
expected: ERROR

View file

@ -0,0 +1,9 @@
[default-src-wasm-unsafe-eval-allows-wasm.any.worker.html]
[default-src-wasm-unsafe-eval-allows-wasm.any.html]
[default-src-wasm-unsafe-eval-allows-wasm.any.sharedworker.html]
expected: ERROR
[default-src-wasm-unsafe-eval-allows-wasm.any.serviceworker.html]
expected: ERROR

View file

@ -0,0 +1,4 @@
[postMessage-wasm-module.html]
expected: ERROR
[Got the expected securitypolicyviolation in the iframe]
expected: TIMEOUT

View file

@ -0,0 +1,12 @@
[script-src-blocks-wasm.any.sharedworker.html]
expected: ERROR
[script-src-blocks-wasm.any.worker.html]
[script-src-blocks-wasm]
expected: FAIL
[script-src-blocks-wasm.any.html]
[script-src-blocks-wasm.any.serviceworker.html]
expected: ERROR

View file

@ -0,0 +1,16 @@
[script-src-spv-asynch.any.sharedworker.html]
expected: ERROR
[script-src-spv-asynch.any.html]
[Securitypolicyviolation event looks like it should]
expected: FAIL
[script-src-spv-asynch.any.worker.html]
expected: TIMEOUT
[Securitypolicyviolation event looks like it should]
expected: TIMEOUT
[script-src-spv-asynch.any.serviceworker.html]
expected: ERROR

View file

@ -0,0 +1,9 @@
[script-src-unsafe-eval-allows-wasm.any.sharedworker.html]
expected: ERROR
[script-src-unsafe-eval-allows-wasm.any.html]
[script-src-unsafe-eval-allows-wasm.any.worker.html]
[script-src-unsafe-eval-allows-wasm.any.serviceworker.html]
expected: ERROR

View file

@ -0,0 +1,9 @@
[script-src-wasm-unsafe-eval-allows-wasm.any.serviceworker.html]
expected: ERROR
[script-src-wasm-unsafe-eval-allows-wasm.any.html]
[script-src-wasm-unsafe-eval-allows-wasm.any.worker.html]
[script-src-wasm-unsafe-eval-allows-wasm.any.sharedworker.html]
expected: ERROR

View file

@ -2002,9 +2002,6 @@
[Document interface: new Document() must inherit property "onwebkitanimationstart" with the proper type]
expected: FAIL
[Document interface: new Document() must inherit property "onsecuritypolicyviolation" with the proper type]
expected: FAIL
[Document interface: calling queryCommandIndeterm(DOMString) on documentWithHandlers with too few arguments must throw TypeError]
expected: FAIL
@ -2017,9 +2014,6 @@
[Window interface: window must inherit property "onwebkitanimationiteration" with the proper type]
expected: FAIL
[Window interface: attribute onsecuritypolicyviolation]
expected: FAIL
[Window interface: window must inherit property "applicationCache" with the proper type]
expected: FAIL
@ -2059,9 +2053,6 @@
[Document interface: documentWithHandlers must inherit property "execCommand(DOMString, optional boolean, optional DOMString)" with the proper type]
expected: FAIL
[Window interface: window must inherit property "onsecuritypolicyviolation" with the proper type]
expected: FAIL
[Window interface: operation print()]
expected: FAIL
@ -2074,9 +2065,6 @@
[Document interface: attribute all]
expected: FAIL
[Document interface: documentWithHandlers must inherit property "onsecuritypolicyviolation" with the proper type]
expected: FAIL
[Window interface: operation focus()]
expected: FAIL
@ -2215,9 +2203,6 @@
[Document interface: operation execCommand(DOMString, optional boolean, optional DOMString)]
expected: FAIL
[Document interface: attribute onsecuritypolicyviolation]
expected: FAIL
[Window interface: attribute menubar]
expected: FAIL
@ -2314,9 +2299,6 @@
[Window interface: internal [[SetPrototypeOf\]\] method of interface prototype object - setting to a new value via Object.setPrototypeOf should throw a TypeError]
expected: FAIL
[Document interface: iframe.contentDocument must inherit property "onsecuritypolicyviolation" with the proper type]
expected: FAIL
[Document interface: new Document() must inherit property "queryCommandValue(DOMString)" with the proper type]
expected: FAIL
@ -2892,9 +2874,6 @@
[HTMLElement interface: document.createElement("noscript") must inherit property "autocapitalize" with the proper type]
expected: FAIL
[HTMLElement interface: attribute onsecuritypolicyviolation]
expected: FAIL
[HTMLTableColElement interface: document.createElement("col") must inherit property "align" with the proper type]
expected: FAIL
@ -4155,9 +4134,6 @@
[HTMLObjectElement interface: document.createElement("object") must inherit property "data" with the proper type]
expected: FAIL
[HTMLElement interface: document.createElement("noscript") must inherit property "onsecuritypolicyviolation" with the proper type]
expected: FAIL
[HTMLFrameElement interface: attribute contentDocument]
expected: FAIL

View file

@ -1,3 +0,0 @@
[http-equiv-enumerated-ascii-case-insensitive.html]
[keyword content-security-policy]
expected: FAIL

View file

@ -0,0 +1,10 @@
[code-cache-nonce.html]
expected: ERROR
[First dynamic import should use nonce=abc]
expected: TIMEOUT
[Second dynamic import should use nonce=def]
expected: NOTRUN
[Third dynamic import should use nonce=ghi]
expected: NOTRUN

View file

@ -1,4 +1,5 @@
[basic.any.html]
expected: TIMEOUT
[basic.any.sharedworker.html]
expected: ERROR

View file

@ -0,0 +1,2 @@
[propagate-nonce-external-classic.html]
expected: TIMEOUT

View file

@ -0,0 +1,2 @@
[propagate-nonce-external-module.html]
expected: TIMEOUT

View file

@ -0,0 +1,2 @@
[propagate-nonce-inline-classic.html]
expected: TIMEOUT

View file

@ -0,0 +1,2 @@
[propagate-nonce-inline-module.html]
expected: TIMEOUT

View file

@ -1,4 +1,5 @@
[string-compilation-nonce-classic.html]
expected: TIMEOUT
[reflected inline event handlers must not inherit the nonce from the triggering script, thus fail]
expected: FAIL

View file

@ -1,4 +1,5 @@
[string-compilation-nonce-module.html]
expected: TIMEOUT
[reflected inline event handlers must not inherit the nonce from the triggering script, thus fail]
expected: FAIL

View file

@ -0,0 +1,31 @@
[v8-code-cache.html]
expected: ERROR
[text/javascript: Run #1]
expected: TIMEOUT
[text/javascript: Run #2]
expected: NOTRUN
[text/javascript: Run #3]
expected: NOTRUN
[text/javascript: Run #4]
expected: NOTRUN
[text/javascript: Run #5]
expected: NOTRUN
[module: Run #1]
expected: NOTRUN
[module: Run #2]
expected: NOTRUN
[module: Run #3]
expected: NOTRUN
[module: Run #4]
expected: NOTRUN
[module: Run #5]
expected: NOTRUN

View file

@ -314,9 +314,6 @@
[onsecuritypolicyviolation: the default value must be null]
expected: FAIL
[onsecuritypolicyviolation: the content attribute must be compiled into a function as the corresponding property]
expected: FAIL
[onseeked: must be on the appropriate locations for GlobalEventHandlers]
expected: FAIL

View file

@ -8,9 +8,6 @@
[not shadowed contextrestored (document.body)]
expected: FAIL
[not shadowed securitypolicyviolation (document.body)]
expected: FAIL
[not shadowed slotchange (document.body)]
expected: FAIL
@ -44,9 +41,6 @@
[not shadowed contextrestored (document.createElement("body"))]
expected: FAIL
[not shadowed securitypolicyviolation (document.createElement("body"))]
expected: FAIL
[not shadowed slotchange (document.createElement("body"))]
expected: FAIL
@ -80,9 +74,6 @@
[not shadowed contextrestored (window)]
expected: FAIL
[not shadowed securitypolicyviolation (window)]
expected: FAIL
[not shadowed slotchange (window)]
expected: FAIL

View file

@ -8,9 +8,6 @@
[not shadowed contextrestored (document.body)]
expected: FAIL
[not shadowed securitypolicyviolation (document.body)]
expected: FAIL
[not shadowed slotchange (document.body)]
expected: FAIL
@ -44,9 +41,6 @@
[not shadowed contextrestored (document.createElement("frameset"))]
expected: FAIL
[not shadowed securitypolicyviolation (document.createElement("frameset"))]
expected: FAIL
[not shadowed slotchange (document.createElement("frameset"))]
expected: FAIL
@ -80,9 +74,6 @@
[not shadowed contextrestored (window)]
expected: FAIL
[not shadowed securitypolicyviolation (window)]
expected: FAIL
[not shadowed slotchange (window)]
expected: FAIL

View file

@ -8,9 +8,6 @@
[contextrestored is unaffected on a windowless body]
expected: FAIL
[securitypolicyviolation is unaffected on a windowless body]
expected: FAIL
[slotchange is unaffected on a windowless body]
expected: FAIL
@ -35,9 +32,6 @@
[contextrestored is unaffected on a windowless frameset]
expected: FAIL
[securitypolicyviolation is unaffected on a windowless frameset]
expected: FAIL
[slotchange is unaffected on a windowless frameset]
expected: FAIL

View file

@ -13434,14 +13434,14 @@
]
],
"interfaces.html": [
"d4daa95cfe84f1ffd77d0b631e67deb778db5fc3",
"2ab9214e53c431e4a599254d4cb498fd75eef4ed",
[
null,
{}
]
],
"interfaces.worker.js": [
"b1c7b7e9c5c2f21eafdcd27eafe589e654260628",
"2782a452ac10b97c4cd4418fb7ba516325a76fab",
[
"mozilla/interfaces.worker.html",
{}

View file

@ -223,6 +223,7 @@ test_interfaces([
"Request",
"Response",
"Screen",
"SecurityPolicyViolationEvent",
"Selection",
"ShadowRoot",
"StaticRange",

View file

@ -55,6 +55,7 @@ test_interfaces([
"ReadableStream",
"Request",
"Response",
"SecurityPolicyViolationEvent",
"TextDecoder",
"TextEncoder",
"URL",