Inherit CSP for blob workers (#38033)

Workers created from Blobs inherit their CSP. Now we inherit the CSP and
set the correct base API url. The base API url should be used when
determining the
report-uri endpoint. Otherwise, the blob URL would be used as a base,
which is invalid and the report wouldn't be sent.

Also create a helper method to concatenate two optionals of CSPList,
which was used in several places.

Part of #4577

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe 2025-07-17 10:14:20 +02:00 committed by GitHub
parent 439cb00e31
commit 18d1a62add
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 116 additions and 236 deletions

View file

@ -61,6 +61,7 @@ pub(crate) trait CspReporting {
sink_group: &str,
source: &str,
) -> bool;
fn concatenate(self, new_csp_list: Option<CspList>) -> Option<CspList>;
}
impl CspReporting for Option<CspList> {
@ -196,6 +197,20 @@ impl CspReporting for Option<CspList> {
allowed_by_csp == CheckResult::Blocked
}
fn concatenate(self, new_csp_list: Option<CspList>) -> Option<CspList> {
let Some(new_csp_list) = new_csp_list else {
return self;
};
match self {
None => Some(new_csp_list),
Some(mut old_csp_list) => {
old_csp_list.append(new_csp_list);
Some(old_csp_list)
},
}
}
}
pub(crate) struct SourcePosition {
@ -313,12 +328,7 @@ fn parse_and_potentially_append_to_csp_list(
.to_str()
.ok()
.map(|value| CspList::parse(value, PolicySource::Header, disposition));
if let Some(new_csp_list_value) = new_csp_list {
match csp_list {
None => csp_list = Some(new_csp_list_value),
Some(ref mut csp_list) => csp_list.append(new_csp_list_value),
};
}
csp_list = csp_list.concatenate(new_csp_list);
}
csp_list
}

View file

@ -11,18 +11,19 @@ use constellation_traits::{WorkerGlobalScopeInit, WorkerScriptLoadOrigin};
use crossbeam_channel::{Receiver, Sender, unbounded};
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, SourceInfo};
use dom_struct::dom_struct;
use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
use ipc_channel::ipc::IpcReceiver;
use ipc_channel::router::ROUTER;
use js::jsapi::{Heap, JS_AddInterruptCallback, JSContext, JSObject};
use js::jsval::UndefinedValue;
use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue};
use net_traits::IpcSend;
use net_traits::image_cache::ImageCache;
use net_traits::policy_container::PolicyContainer;
use net_traits::request::{
CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder,
RequestMode,
};
use net_traits::{IpcSend, Metadata};
use servo_rand::random;
use servo_url::{ImmutableOrigin, ServoUrl};
use style::thread_state::{self, ThreadState};
@ -393,7 +394,7 @@ impl DedicatedWorkerGlobalScope {
.referrer_policy(referrer_policy)
.insecure_requests_policy(insecure_requests_policy)
.has_trustworthy_ancestor_origin(current_global_ancestor_trustworthy)
.policy_container(policy_container)
.policy_container(policy_container.clone())
.origin(origin);
let runtime = unsafe {
@ -481,7 +482,11 @@ impl DedicatedWorkerGlobalScope {
Ok((metadata, bytes)) => (metadata, bytes),
};
scope.set_url(metadata.final_url.clone());
scope.set_csp_list(parse_csp_list_from_metadata(&metadata.headers));
Self::initialize_policy_container_for_worker_global_scope(
scope,
&metadata,
&policy_container,
);
scope.set_endpoints_list(ReportingEndpoint::parse_reporting_endpoints_header(
&metadata.final_url.clone(),
&metadata.headers,
@ -549,6 +554,39 @@ impl DedicatedWorkerGlobalScope {
.expect("Thread spawning failed")
}
/// <https://html.spec.whatwg.org/multipage/#initialize-worker-policy-container> and
/// <https://html.spec.whatwg.org/multipage/#creating-a-policy-container-from-a-fetch-response>
fn initialize_policy_container_for_worker_global_scope(
scope: &WorkerGlobalScope,
metadata: &Metadata,
parent_policy_container: &PolicyContainer,
) {
// Step 1. If workerGlobalScope's url is local but its scheme is not "blob":
//
// Note that we also allow for blob here, as the parent_policy_container is in both cases
// the container that we need to clone.
if metadata.final_url.is_local_scheme() {
// Step 1.2. Set workerGlobalScope's policy container to a clone of workerGlobalScope's
// owner set[0]'s relevant settings object's policy container.
//
// Step 1. If response's URL's scheme is "blob", then return a clone of response's URL's
// blob URL entry's environment's policy container.
scope.set_csp_list(parent_policy_container.csp_list.clone());
scope.set_referrer_policy(parent_policy_container.get_referrer_policy());
return;
}
// Step 3. Set result's CSP list to the result of parsing a response's Content Security Policies given response.
scope.set_csp_list(parse_csp_list_from_metadata(&metadata.headers));
// Step 5. Set result's referrer policy to the result of parsing the `Referrer-Policy`
// header given response. [REFERRERPOLICY]
let referrer_policy = metadata
.headers
.as_ref()
.and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
.into();
scope.set_referrer_policy(referrer_policy);
}
/// The non-None value of the `worker` field can contain a rooted [`TrustedWorkerAddress`]
/// version of the main thread's worker object. This is set while handling messages and then
/// unset otherwise, ensuring that the main thread object can be garbage collected. See

View file

@ -56,7 +56,7 @@ use crate::dom::bindings::settings_stack::is_execution_stack_empty;
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::characterdata::CharacterData;
use crate::dom::comment::Comment;
use crate::dom::csp::{GlobalCspReporting, Violation, parse_csp_list_from_metadata};
use crate::dom::csp::{CspReporting, GlobalCspReporting, Violation, parse_csp_list_from_metadata};
use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument};
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::documenttype::DocumentType;
@ -860,21 +860,14 @@ impl ParserContext {
let Some(policy_container) = policy_container else {
return;
};
let Some(parent_csp_list) = &policy_container.csp_list else {
return;
};
let Some(parser) = self.parser.as_ref().map(|p| p.root()) else {
return;
};
let new_csp_list = match parser.document.get_csp_list() {
None => parent_csp_list.clone(),
Some(original_csp_list) => {
let mut appended_csp_list = original_csp_list.clone();
appended_csp_list.append(parent_csp_list.clone());
appended_csp_list.to_owned()
},
};
parser.document.set_csp_list(Some(new_csp_list));
let new_csp_list = parser
.document
.get_csp_list()
.concatenate(policy_container.csp_list.clone());
parser.document.set_csp_list(new_csp_list);
}
}

View file

@ -20,12 +20,12 @@ use ipc_channel::ipc::IpcSender;
use js::jsval::UndefinedValue;
use js::panic::maybe_resume_unwind;
use js::rust::{HandleValue, MutableHandleValue, ParentRuntime};
use net_traits::IpcSend;
use net_traits::policy_container::PolicyContainer;
use net_traits::request::{
CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata,
RequestBuilder as NetRequestInit,
};
use net_traits::{IpcSend, ReferrerPolicy};
use profile_traits::mem::{ProcessReports, perform_memory_report};
use servo_url::{MutableOrigin, ServoUrl};
use timers::TimerScheduler;
@ -276,6 +276,12 @@ impl WorkerGlobalScope {
self.policy_container.borrow_mut().set_csp_list(csp_list);
}
pub(crate) fn set_referrer_policy(&self, referrer_policy: ReferrerPolicy) {
self.policy_container
.borrow_mut()
.set_referrer_policy(referrer_policy);
}
pub(crate) fn append_reporting_observer(&self, reporting_observer: DomRoot<ReportingObserver>) {
self.reporting_observer_list
.borrow_mut()

View file

@ -19,7 +19,7 @@ use crate::conversions::Convert;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::root::DomRoot;
use crate::dom::csp::{GlobalCspReporting, Violation};
use crate::dom::csp::Violation;
use crate::dom::csppolicyviolationreport::{
CSPReportUriViolationReport, SecurityPolicyViolationReport,
};
@ -99,6 +99,9 @@ impl CSPViolationReportTask {
for token in &report_uri_directive.value {
// Step 3.4.2.1. Let endpoint be the result of executing the URL parser with token as the input,
// and violations url as the base URL.
//
// TODO: Figure out if this should be the URL of the containing document or not in case
// the url points to a blob
let Ok(endpoint) = ServoUrl::parse_with_base(Some(&global.get_url()), token) else {
// Step 3.4.2.2. If endpoint is not a valid URL, skip the remaining substeps.
continue;
@ -224,10 +227,7 @@ impl FetchResponseListener for CSPReportUriFetchListener {
submit_timing(self, CanGc::note())
}
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations, None, None);
}
fn process_csp_violations(&mut self, _request_id: RequestId, _violations: Vec<Violation>) {}
}
impl ResourceTimingListener for CSPReportUriFetchListener {

View file

@ -911260,7 +911260,7 @@
]
],
"dedicated-worker-from-blob-url.window.js": [
"8455285571a357a5e6c46a38dcf465f7bd432b55",
"61a1c06c246a274b642aae4c56974ef15ae4f5fe",
[
"workers/dedicated-worker-from-blob-url.window.html",
{}
@ -912969,7 +912969,7 @@
}
},
"shared-worker-from-blob-url.window.js": [
"98e34cc3a69a17f31cf5b890744e5f9ca52559b5",
"a479767df39f2b91658b543d9f820d9d802143c9",
[
"workers/shared-worker-from-blob-url.window.html",
{}

View file

@ -1,3 +0,0 @@
[worker-from-guid.sub.html]
[Expecting logs: ["violated-directive=connect-src","xhr blocked","TEST COMPLETE"\]]
expected: FAIL

View file

@ -1,15 +1,3 @@
[dedicatedworker-connect-src.html]
[Cross-origin 'fetch()' in blob: with connect-src 'self']
expected: FAIL
[Cross-origin XHR in blob: with connect-src 'self']
expected: FAIL
[Same-origin => cross-origin 'fetch()' in blob: with connect-src 'self']
expected: FAIL
[WebSocket in blob: with connect-src 'self']
expected: FAIL
[Reports match in blob: with connect-src 'self']
expected: FAIL

View file

@ -1,6 +0,0 @@
[referrer-origin-worker.html]
[Request's referrer is origin]
expected: FAIL
[Cross-origin referrer is overridden by client origin]
expected: FAIL

View file

@ -1,36 +0,0 @@
[fetch.http.html]
[Referrer Policy: Expects omitted for fetch to cross-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to cross-http origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to cross-http origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to cross-https origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to cross-https origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to cross-https origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to same-http origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to same-http origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to same-https origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to same-https origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to same-https origin and swap-origin redirection from http context.]
expected: FAIL

View file

@ -1,6 +0,0 @@
[worker-classic.http.html]
[Referrer Policy: Expects omitted for worker-classic to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for worker-classic to same-http origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,6 +0,0 @@
[worker-module.http.html]
[Referrer Policy: Expects omitted for worker-module to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for worker-module to same-http origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,36 +0,0 @@
[xhr.http.html]
[Referrer Policy: Expects omitted for xhr to cross-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to cross-http origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to cross-http origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to cross-https origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to cross-https origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to cross-https origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to same-http origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to same-http origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to same-https origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to same-https origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to same-https origin and swap-origin redirection from http context.]
expected: FAIL

View file

@ -1,6 +0,0 @@
[fetch.http.html]
[Referrer Policy: Expects origin for fetch to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects origin for fetch to same-http origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,6 +0,0 @@
[worker-classic.http.html]
[Referrer Policy: Expects origin for worker-classic to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects origin for worker-classic to same-http origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,6 +0,0 @@
[worker-module.http.html]
[Referrer Policy: Expects origin for worker-module to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects origin for worker-module to same-http origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,6 +0,0 @@
[xhr.http.html]
[Referrer Policy: Expects origin for xhr to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects origin for xhr to same-http origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,25 +0,0 @@
[fetch.http.html]
[Referrer Policy: Expects omitted for fetch to cross-https origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to cross-http origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to same-http origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to cross-https origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to cross-http origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to cross-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to same-https origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for fetch to cross-https origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,25 +0,0 @@
[xhr.http.html]
[Referrer Policy: Expects omitted for xhr to cross-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to same-http origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to same-https origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to cross-https origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to cross-http origin and no-redirect redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to cross-https origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to cross-https origin and swap-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects omitted for xhr to cross-http origin and swap-origin redirection from http context.]
expected: FAIL

View file

@ -1,6 +0,0 @@
[fetch.http.html]
[Referrer Policy: Expects origin for fetch to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects origin for fetch to same-http origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,6 +0,0 @@
[worker-classic.http.html]
[Referrer Policy: Expects origin for worker-classic to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects origin for worker-classic to same-http origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,6 +0,0 @@
[worker-module.http.html]
[Referrer Policy: Expects origin for worker-module to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects origin for worker-module to same-http origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,6 +0,0 @@
[xhr.http.html]
[Referrer Policy: Expects origin for xhr to same-http origin and keep-origin redirection from http context.]
expected: FAIL
[Referrer Policy: Expects origin for xhr to same-http origin and no-redirect redirection from http context.]
expected: FAIL

View file

@ -1,6 +1,3 @@
[workers.html]
[Dedicated worker with local scheme inherits referrer policy from the creating document.]
expected: FAIL
[Shared worker with local scheme inherits referrer policy from the creating document.]
expected: FAIL

View file

@ -7,3 +7,6 @@
[Connecting to a shared worker on a revoked blob URL works.]
expected: FAIL
[Blob URLs should not resolve relative to document base URL.]
expected: FAIL

View file

@ -27,3 +27,21 @@ promise_test(async t => {
const reply = await message_from_port(worker);
assert_equals(reply, run_result);
}, 'Creating a dedicated worker from a blob URL works immediately before revoking.');
promise_test(async t => {
const run_result = false;
const blob_contents = `
let constructedRequest = false;
try {
new Request("./file.js");
constructedRequest = true;
} catch (e) {}
self.postMessage(constructedRequest);
`;
const blob = new Blob([blob_contents]);
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
const reply = await message_from_port(worker);
assert_equals(reply, run_result, "Should not be able to resolve request with relative file path in blob");
}, 'Blob URLs should not resolve relative to document base URL.');

View file

@ -51,3 +51,21 @@ promise_test(async t => {
const reply2 = await message_from_port(worker2.port);
assert_equals(reply2, run_result + '2');
}, 'Connecting to a shared worker on a revoked blob URL works.');
promise_test(async t => {
const run_result = false;
const blob_contents = `
let constructedRequest = false;
try {
new Request("./file.js");
constructedRequest = true;
} catch (e) {}
self.postMessage(constructedRequest);
`;
const blob = new Blob([blob_contents]);
const url = URL.createObjectURL(blob);
const worker = new SharedWorker(url);
const reply = await message_from_port(worker);
assert_equals(reply, run_result, "Should not be able to resolve request with relative file path in blob");
}, 'Blob URLs should not resolve relative to document base URL.');