Check all ancestor navigable trustworthiness for mixed content (#36157)

Propagate through documents a flag that represents if any of the
ancestor navigables has a potentially trustworthy origin.

The "potentially trustworthy origin" concept appears to have gotten
confused in a couple of places and we were instead testing if a URL had
"potentially trustworthy" properties.

The main test for the ancestor navigables is
[mixed-content/nested-iframes](https://github.com/web-platform-tests/wpt/blob/master/mixed-content/nested-iframes.window.js)

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by
`[X]` when the step is complete, and replace `___` with appropriate
data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #36108 

<!-- Either: -->
- [X] There are tests for these changes

---------

Signed-off-by: Sebastian C <sebsebmc@gmail.com>
This commit is contained in:
Sebastian C 2025-04-05 00:38:24 -05:00 committed by GitHub
parent 478e876f6d
commit 76edcff202
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
84 changed files with 384 additions and 525 deletions

View file

@ -355,6 +355,8 @@ impl DedicatedWorkerGlobalScope {
let referrer = current_global.get_referrer();
let parent = current_global.runtime_handle();
let current_global_https_state = current_global.get_https_state();
let current_global_ancestor_trustworthy = current_global.has_trustworthy_ancestor_origin();
let is_secure_context = current_global.is_secure_context();
thread::Builder::new()
.name(format!("WW:{}", worker_url.debug_compact()))
@ -384,8 +386,8 @@ impl DedicatedWorkerGlobalScope {
.use_url_credentials(true)
.pipeline_id(Some(pipeline_id))
.referrer_policy(referrer_policy)
.referrer_policy(referrer_policy)
.insecure_requests_policy(insecure_requests_policy)
.has_trustworthy_ancestor_origin(current_global_ancestor_trustworthy)
.origin(origin);
let runtime = unsafe {
@ -418,7 +420,12 @@ impl DedicatedWorkerGlobalScope {
// > scope`'s url's scheme is "data", and `inherited origin`
// > otherwise.
if worker_url.scheme() == "data" {
init.origin = ImmutableOrigin::new_opaque();
// Workers created from a data: url are secure if they were created from secure contexts
if is_secure_context {
init.origin = ImmutableOrigin::new_opaque_data_url_worker();
} else {
init.origin = ImmutableOrigin::new_opaque();
}
}
let global = DedicatedWorkerGlobalScope::new(

View file

@ -524,6 +524,8 @@ pub(crate) struct Document {
/// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#insecure-requests-policy>
#[no_trace]
inherited_insecure_requests_policy: Cell<Option<InsecureRequestsPolicy>>,
//// <https://w3c.github.io/webappsec-mixed-content/#categorize-settings-object>
has_trustworthy_ancestor_origin: Cell<bool>,
/// <https://w3c.github.io/IntersectionObserver/#document-intersectionobservertaskqueued>
intersection_observer_task_queued: Cell<bool>,
/// Active intersection observers that should be processed by this document in
@ -2479,7 +2481,9 @@ impl Document {
mut request: RequestBuilder,
listener: Listener,
) {
request = request.insecure_requests_policy(self.insecure_requests_policy());
request = request
.insecure_requests_policy(self.insecure_requests_policy())
.has_trustworthy_ancestor_origin(self.has_trustworthy_ancestor_or_current_origin());
let callback = NetworkListener {
context: std::sync::Arc::new(Mutex::new(listener)),
task_source: self
@ -2498,7 +2502,9 @@ impl Document {
mut request: RequestBuilder,
listener: Listener,
) {
request = request.insecure_requests_policy(self.insecure_requests_policy());
request = request
.insecure_requests_policy(self.insecure_requests_policy())
.has_trustworthy_ancestor_origin(self.has_trustworthy_ancestor_or_current_origin());
let callback = NetworkListener {
context: std::sync::Arc::new(Mutex::new(listener)),
task_source: self
@ -3735,6 +3741,7 @@ impl Document {
is_initial_about_blank: bool,
allow_declarative_shadow_roots: bool,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
has_trustworthy_ancestor_origin: bool,
) -> Document {
let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
@ -3895,6 +3902,7 @@ impl Document {
is_initial_about_blank: Cell::new(is_initial_about_blank),
allow_declarative_shadow_roots: Cell::new(allow_declarative_shadow_roots),
inherited_insecure_requests_policy: Cell::new(inherited_insecure_requests_policy),
has_trustworthy_ancestor_origin: Cell::new(has_trustworthy_ancestor_origin),
intersection_observer_task_queued: Cell::new(false),
intersection_observers: Default::default(),
active_keyboard_modifiers: Cell::new(Modifiers::empty()),
@ -4052,6 +4060,7 @@ impl Document {
is_initial_about_blank: bool,
allow_declarative_shadow_roots: bool,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
has_trustworthy_ancestor_origin: bool,
can_gc: CanGc,
) -> DomRoot<Document> {
Self::new_with_proto(
@ -4072,6 +4081,7 @@ impl Document {
is_initial_about_blank,
allow_declarative_shadow_roots,
inherited_insecure_requests_policy,
has_trustworthy_ancestor_origin,
can_gc,
)
}
@ -4095,6 +4105,7 @@ impl Document {
is_initial_about_blank: bool,
allow_declarative_shadow_roots: bool,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
has_trustworthy_ancestor_origin: bool,
can_gc: CanGc,
) -> DomRoot<Document> {
let document = reflect_dom_object_with_proto(
@ -4115,6 +4126,7 @@ impl Document {
is_initial_about_blank,
allow_declarative_shadow_roots,
inherited_insecure_requests_policy,
has_trustworthy_ancestor_origin,
)),
window,
proto,
@ -4248,6 +4260,7 @@ impl Document {
false,
self.allow_declarative_shadow_roots(),
Some(self.insecure_requests_policy()),
self.has_trustworthy_ancestor_or_current_origin(),
can_gc,
);
new_doc
@ -4795,6 +4808,15 @@ impl Document {
pub fn set_allow_declarative_shadow_roots(&self, value: bool) {
self.allow_declarative_shadow_roots.set(value)
}
pub fn has_trustworthy_ancestor_origin(&self) -> bool {
self.has_trustworthy_ancestor_origin.get()
}
pub fn has_trustworthy_ancestor_or_current_origin(&self) -> bool {
self.has_trustworthy_ancestor_origin.get() ||
self.origin().immutable().is_potentially_trustworthy()
}
}
#[allow(non_snake_case)]
@ -4825,6 +4847,7 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
false,
doc.allow_declarative_shadow_roots(),
Some(doc.insecure_requests_policy()),
doc.has_trustworthy_ancestor_or_current_origin(),
can_gc,
))
}

View file

@ -110,6 +110,7 @@ impl DOMImplementationMethods<crate::DomTypeHolder> for DOMImplementation {
DocumentSource::NotFromParser,
loader,
Some(self.document.insecure_requests_policy()),
self.document.has_trustworthy_ancestor_or_current_origin(),
can_gc,
);
@ -176,6 +177,7 @@ impl DOMImplementationMethods<crate::DomTypeHolder> for DOMImplementation {
false,
self.document.allow_declarative_shadow_roots(),
Some(self.document.insecure_requests_policy()),
self.document.has_trustworthy_ancestor_or_current_origin(),
can_gc,
);

View file

@ -90,6 +90,7 @@ impl DOMParserMethods<crate::DomTypeHolder> for DOMParser {
false,
false,
Some(doc.insecure_requests_policy()),
doc.has_trustworthy_ancestor_or_current_origin(),
can_gc,
);
ServoParser::parse_html_document(&document, Some(s), url, can_gc);
@ -114,6 +115,7 @@ impl DOMParserMethods<crate::DomTypeHolder> for DOMParser {
false,
false,
Some(doc.insecure_requests_policy()),
doc.has_trustworthy_ancestor_or_current_origin(),
can_gc,
);
ServoParser::parse_xml_document(&document, Some(s), url, can_gc);

View file

@ -561,6 +561,7 @@ impl EventSourceMethods<crate::DomTypeHolder> for EventSource {
Some(true),
global.get_referrer(),
global.insecure_requests_policy(),
global.has_trustworthy_ancestor_or_current_origin(),
)
.origin(global.origin().immutable().clone())
.pipeline_id(Some(global.pipeline_id()));

View file

@ -2386,6 +2386,21 @@ impl GlobalScope {
InsecureRequestsPolicy::DoNotUpgrade
}
/// Whether this document has ancestor navigables that are trustworthy
pub(crate) fn has_trustworthy_ancestor_origin(&self) -> bool {
self.downcast::<Window>()
.is_some_and(|window| window.Document().has_trustworthy_ancestor_origin())
}
// Whether this document has a trustworthy origin or has trustowrthy ancestor navigables
pub(crate) fn has_trustworthy_ancestor_or_current_origin(&self) -> bool {
self.downcast::<Window>().is_some_and(|window| {
window
.Document()
.has_trustworthy_ancestor_or_current_origin()
})
}
/// <https://html.spec.whatwg.org/multipage/#report-the-error>
pub(crate) fn report_an_error(&self, error_info: ErrorInfo, value: HandleValue, can_gc: CanGc) {
// Step 1.

View file

@ -867,6 +867,7 @@ impl HTMLFormElement {
target_document.get_referrer_policy(),
Some(target_window.as_global_scope().is_secure_context()),
Some(target_document.insecure_requests_policy()),
target_document.has_trustworthy_ancestor_origin(),
);
// Step 22

View file

@ -271,6 +271,7 @@ impl HTMLIFrameElement {
document.get_referrer_policy(),
Some(window.as_global_scope().is_secure_context()),
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(),
);
let element = self.upcast::<Element>();
load_data.srcdoc = String::from(element.get_string_attribute(&local_name!("srcdoc")));
@ -362,6 +363,7 @@ impl HTMLIFrameElement {
referrer_policy,
Some(window.as_global_scope().is_secure_context()),
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(),
);
let pipeline_id = self.pipeline_id();
@ -407,6 +409,7 @@ impl HTMLIFrameElement {
document.get_referrer_policy(),
Some(window.as_global_scope().is_secure_context()),
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(),
);
let browsing_context_id = BrowsingContextId::new();
let webview_id = window.window_proxy().webview_id();

View file

@ -424,6 +424,7 @@ impl HTMLImageElement {
None,
document.global().get_referrer(),
document.insecure_requests_policy(),
document.has_trustworthy_ancestor_or_current_origin(),
)
.origin(document.origin().immutable().clone())
.pipeline_id(Some(document.global().pipeline_id()))

View file

@ -82,6 +82,7 @@ struct LinkProcessingOptions {
source_set: Option<()>,
base_url: ServoUrl,
insecure_requests_policy: InsecureRequestsPolicy,
has_trustworthy_ancestor_origin: bool,
// Some fields that we don't need yet are missing
}
@ -335,6 +336,7 @@ impl HTMLLinkElement {
source_set: None, // FIXME
base_url: document.borrow().base_url(),
insecure_requests_policy: document.insecure_requests_policy(),
has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(),
};
// Step 3. If el has an href attribute, then set options's href to the value of el's href attribute.
@ -669,6 +671,7 @@ impl LinkProcessingOptions {
None,
Referrer::NoReferrer,
self.insecure_requests_policy,
self.has_trustworthy_ancestor_origin,
)
.integrity_metadata(self.integrity)
.policy_container(self.policy_container)

View file

@ -894,6 +894,7 @@ impl HTMLMediaElement {
None,
self.global().get_referrer(),
document.insecure_requests_policy(),
document.has_trustworthy_ancestor_or_current_origin(),
)
.headers(headers)
.origin(document.origin().immutable().clone())

View file

@ -558,6 +558,7 @@ impl PreInvoke for ClassicContext {}
/// Steps 1-2 of <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script>
// This function is also used to prefetch a script in `script::dom::servoparser::prefetch`.
#[allow(clippy::too_many_arguments)]
pub(crate) fn script_fetch_request(
webview_id: WebViewId,
url: ServoUrl,
@ -566,6 +567,7 @@ pub(crate) fn script_fetch_request(
pipeline_id: PipelineId,
options: ScriptFetchOptions,
insecure_requests_policy: InsecureRequestsPolicy,
has_trustworthy_ancestor_origin: bool,
) -> RequestBuilder {
// We intentionally ignore options' credentials_mode member for classic scripts.
// The mode is initialized by create_a_potential_cors_request.
@ -577,6 +579,7 @@ pub(crate) fn script_fetch_request(
None,
options.referrer,
insecure_requests_policy,
has_trustworthy_ancestor_origin,
)
.origin(origin)
.pipeline_id(Some(pipeline_id))
@ -605,6 +608,7 @@ fn fetch_a_classic_script(
script.global().pipeline_id(),
options.clone(),
doc.insecure_requests_policy(),
doc.has_trustworthy_ancestor_origin(),
);
let request = doc.prepare_request(request);

View file

@ -126,6 +126,7 @@ impl Location {
referrer_policy,
None, // Top navigation doesn't inherit secure context
Some(source_document.insecure_requests_policy()),
source_document.has_trustworthy_ancestor_origin(),
);
self.window
.load_url(history_handling, reload_triggered, load_data, can_gc);

View file

@ -2661,6 +2661,7 @@ impl Node {
false,
document.allow_declarative_shadow_roots(),
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(),
can_gc,
);
DomRoot::upcast::<Node>(document)

View file

@ -820,6 +820,7 @@ impl Notification {
None,
global.get_referrer(),
global.insecure_requests_policy(),
global.has_trustworthy_ancestor_or_current_origin(),
)
.origin(global.origin().immutable().clone())
.pipeline_id(Some(global.pipeline_id()))

View file

@ -113,6 +113,7 @@ fn net_request_from_global(global: &GlobalScope, url: ServoUrl) -> NetTraitsRequ
.pipeline_id(Some(global.pipeline_id()))
.https_state(global.get_https_state())
.insecure_requests_policy(global.insecure_requests_policy())
.has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin())
.build()
}

View file

@ -228,6 +228,7 @@ impl ServoParser {
false,
allow_declarative_shadow_roots,
Some(context_document.insecure_requests_policy()),
context_document.has_trustworthy_ancestor_or_current_origin(),
can_gc,
);

View file

@ -73,6 +73,7 @@ impl Tokenizer {
// block the main parser.
prefetching: Cell::new(false),
insecure_requests_policy: document.insecure_requests_policy(),
has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(),
};
let options = Default::default();
let inner = TraceableTokenizer(HtmlTokenizer::new(sink, options));
@ -105,6 +106,7 @@ struct PrefetchSink {
prefetching: Cell<bool>,
#[no_trace]
insecure_requests_policy: InsecureRequestsPolicy,
has_trustworthy_ancestor_origin: bool,
}
/// The prefetch tokenizer produces trivial results
@ -146,6 +148,7 @@ impl TokenSink for PrefetchSink {
parser_metadata: ParserMetadata::ParserInserted,
},
self.insecure_requests_policy,
self.has_trustworthy_ancestor_origin,
);
let _ = self
.resource_threads
@ -164,6 +167,7 @@ impl TokenSink for PrefetchSink {
None,
self.referrer.clone(),
self.insecure_requests_policy,
self.has_trustworthy_ancestor_origin,
)
.origin(self.origin.clone())
.pipeline_id(Some(self.pipeline_id))
@ -198,6 +202,7 @@ impl TokenSink for PrefetchSink {
None,
self.referrer.clone(),
self.insecure_requests_policy,
self.has_trustworthy_ancestor_origin,
)
.origin(self.origin.clone())
.pipeline_id(Some(self.pipeline_id))

View file

@ -261,6 +261,7 @@ impl WebSocketMethods<crate::DomTypeHolder> for WebSocket {
let request = RequestBuilder::new(global.webview_id(), url_record, Referrer::NoReferrer)
.origin(global.origin().immutable().clone())
.insecure_requests_policy(global.insecure_requests_policy())
.has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin())
.mode(RequestMode::WebSocket { protocols })
.service_workers_mode(ServiceWorkersMode::None)
.credentials_mode(CredentialsMode::Include)

View file

@ -307,6 +307,7 @@ impl WindowProxy {
document.get_referrer_policy(),
None, // Doesn't inherit secure context
None,
false,
);
let load_info = AuxiliaryWebViewCreationRequest {
load_data: load_data.clone(),
@ -485,6 +486,11 @@ impl WindowProxy {
Some(target_document) => target_document,
None => return Ok(None),
};
let has_trustworthy_ancestor_origin = if new {
target_document.has_trustworthy_ancestor_or_current_origin()
} else {
false
};
let target_window = target_document.window();
// Step 13, and 14.4, will have happened elsewhere,
// since we've created a new browsing context and loaded it with about:blank.
@ -517,6 +523,7 @@ impl WindowProxy {
referrer_policy,
Some(secure),
Some(target_document.insecure_requests_policy()),
has_trustworthy_ancestor_origin,
);
let history_handling = if new {
NavigationHistoryBehavior::Replace

View file

@ -296,6 +296,9 @@ impl WorkerGlobalScopeMethods<crate::DomTypeHolder> for WorkerGlobalScope {
.use_url_credentials(true)
.origin(global_scope.origin().immutable().clone())
.insecure_requests_policy(self.insecure_requests_policy())
.has_trustworthy_ancestor_origin(
global_scope.has_trustworthy_ancestor_or_current_origin(),
)
.pipeline_id(Some(self.upcast::<GlobalScope>().pipeline_id()));
let (url, source) = match fetch::load_whole_resource(

View file

@ -43,6 +43,7 @@ impl XMLDocument {
source: DocumentSource,
doc_loader: DocumentLoader,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
has_trustworthy_ancestor_origin: bool,
) -> XMLDocument {
XMLDocument {
document: Document::new_inherited(
@ -62,6 +63,7 @@ impl XMLDocument {
false,
false,
inherited_insecure_requests_policy,
has_trustworthy_ancestor_origin,
),
}
}
@ -79,6 +81,7 @@ impl XMLDocument {
source: DocumentSource,
doc_loader: DocumentLoader,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
has_trustworthy_ancestor_origin: bool,
can_gc: CanGc,
) -> DomRoot<XMLDocument> {
let doc = reflect_dom_object(
@ -94,6 +97,7 @@ impl XMLDocument {
source,
doc_loader,
inherited_insecure_requests_policy,
has_trustworthy_ancestor_origin,
)),
window,
can_gc,

View file

@ -688,6 +688,7 @@ impl XMLHttpRequestMethods<crate::DomTypeHolder> for XMLHttpRequest {
.origin(self.global().origin().immutable().clone())
.referrer_policy(self.referrer_policy)
.insecure_requests_policy(self.global().insecure_requests_policy())
.has_trustworthy_ancestor_origin(self.global().has_trustworthy_ancestor_or_current_origin())
.pipeline_id(Some(self.global().pipeline_id()));
// step 4 (second half)
@ -1515,6 +1516,7 @@ impl XMLHttpRequest {
false,
false,
Some(doc.insecure_requests_policy()),
doc.has_trustworthy_ancestor_origin(),
can_gc,
)
}

View file

@ -123,6 +123,7 @@ fn request_init_from_request(request: NetTraitsRequest) -> RequestBuilder {
initiator: request.initiator,
policy_container: request.policy_container,
insecure_requests_policy: request.insecure_requests_policy,
has_trustworthy_ancestor_origin: request.has_trustworthy_ancestor_origin,
https_state: request.https_state,
response_tainting: request.response_tainting,
crash: None,
@ -374,6 +375,7 @@ pub(crate) fn load_whole_resource(
}
/// <https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request>
#[allow(clippy::too_many_arguments)]
pub(crate) fn create_a_potential_cors_request(
webview_id: Option<WebViewId>,
url: ServoUrl,
@ -382,6 +384,7 @@ pub(crate) fn create_a_potential_cors_request(
same_origin_fallback: Option<bool>,
referrer: Referrer,
insecure_requests_policy: InsecureRequestsPolicy,
has_trustworthy_ancestor_origin: bool,
) -> RequestBuilder {
RequestBuilder::new(webview_id, url, referrer)
// https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request
@ -401,4 +404,5 @@ pub(crate) fn create_a_potential_cors_request(
.destination(destination)
.use_url_credentials(true)
.insecure_requests_policy(insecure_requests_policy)
.has_trustworthy_ancestor_origin(has_trustworthy_ancestor_origin)
}

View file

@ -440,6 +440,7 @@ pub(crate) fn follow_hyperlink(
referrer_policy,
Some(secure),
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_origin(),
);
let target = Trusted::new(target_window);
let task = task!(navigate_follow_hyperlink: move || {

View file

@ -212,6 +212,7 @@ impl InProgressLoad {
.inherited_insecure_requests_policy
.unwrap_or(InsecureRequestsPolicy::DoNotUpgrade),
)
.has_trustworthy_ancestor_origin(self.load_data.has_trustworthy_ancestor_origin)
.headers(self.load_data.headers.clone())
.body(self.load_data.data.clone())
.redirect_mode(RedirectMode::Manual)

View file

@ -3193,6 +3193,7 @@ impl ScriptThread {
is_initial_about_blank,
true,
incomplete.load_data.inherited_insecure_requests_policy,
incomplete.load_data.has_trustworthy_ancestor_origin,
can_gc,
);

View file

@ -321,7 +321,7 @@ impl ServiceWorkerManager {
/// <https://w3c.github.io/ServiceWorker/#register-algorithm>
fn handle_register_job(&mut self, mut job: Job) {
if !job.script_url.is_origin_trustworthy() {
if !job.script_url.origin().is_potentially_trustworthy() {
// Step 1.1
let _ = job
.client

View file

@ -351,6 +351,7 @@ impl StylesheetLoader<'_> {
None,
self.elem.global().get_referrer(),
document.insecure_requests_policy(),
document.has_trustworthy_ancestor_or_current_origin(),
)
.origin(document.origin().immutable().clone())
.pipeline_id(Some(self.elem.global().pipeline_id()))