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

@ -34,7 +34,7 @@ use net_traits::{
use rustls_pki_types::CertificateDer;
use serde::{Deserialize, Serialize};
use servo_arc::Arc as ServoArc;
use servo_url::{Host, ServoUrl};
use servo_url::{Host, ImmutableOrigin, ServoUrl};
use tokio::sync::mpsc::{UnboundedReceiver as TokioReceiver, UnboundedSender as TokioSender};
use super::fetch_params::FetchParams;
@ -278,7 +278,6 @@ pub async fn main_fetch(
// Step 7. If should request be blocked due to a bad port, should fetching request be blocked
// as mixed content, or should request be blocked by Content Security Policy returns blocked,
// then set response to a network error.
// TODO: check "should fetching request be blocked as mixed content"
if should_request_be_blocked_by_csp(request, &policy_container) == csp::CheckResult::Blocked {
warn!("Request blocked by CSP");
response = Some(Response::network_error(NetworkError::Internal(
@ -290,6 +289,11 @@ pub async fn main_fetch(
"Request attempted on bad port".into(),
)));
}
if should_request_be_blocked_as_mixed_content(request) {
response = Some(Response::network_error(NetworkError::Internal(
"Blocked as mixed content".into(),
)));
}
// Step 8: If requests referrer policy is the empty string, then set requests referrer policy
// to requests policy containers referrer policy.
@ -480,6 +484,8 @@ pub async fn main_fetch(
should_be_blocked_due_to_nosniff(request.destination, &response.headers);
let should_replace_with_mime_type_error = !response_is_network_error &&
should_be_blocked_due_to_mime_type(request.destination, &response.headers);
let should_replace_with_mixed_content = !response_is_network_error &&
should_response_be_blocked_as_mixed_content(request, &response);
// Step 15.
let mut network_error_response = response
@ -502,7 +508,7 @@ pub async fn main_fetch(
}
// Step 19. If response is not a network error and any of the following returns blocked
// TODO: * should internalResponse to request be blocked as mixed content
// * should internalResponse to request be blocked as mixed content
// TODO: * should internalResponse to request be blocked by Content Security Policy
// * should internalResponse to request be blocked due to its MIME type
// * should internalResponse to request be blocked due to nosniff
@ -518,6 +524,10 @@ pub async fn main_fetch(
blocked_error_response =
Response::network_error(NetworkError::Internal("Blocked by mime type".into()));
&blocked_error_response
} else if should_replace_with_mixed_content {
blocked_error_response =
Response::network_error(NetworkError::Internal("Blocked as mixed content".into()));
&blocked_error_response
} else {
internal_response
};
@ -525,7 +535,10 @@ pub async fn main_fetch(
// Step 20. If responses type is "opaque", internalResponses status is 206, internalResponses
// range-requested flag is set, and requests header list does not contain `Range`, then set
// response and internalResponse to a network error.
let internal_response = if response_type == ResponseType::Opaque &&
// Also checking if internal response is a network error to prevent crash from attemtping to
// read status of a network error if we blocked the request above.
let internal_response = if !internal_response.is_network_error() &&
response_type == ResponseType::Opaque &&
internal_response.status.code() == StatusCode::PARTIAL_CONTENT &&
internal_response.range_requested &&
!request.headers.contains_key(RANGE)
@ -914,6 +927,66 @@ pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool {
false
}
/// <https://w3c.github.io/webappsec-mixed-content/#should-block-fetch>
pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool {
// Step 1. Return allowed if one or more of the following conditions are met:
// 1.1. Does settings prohibit mixed security contexts?
// returns "Does Not Restrict Mixed Security Contexts" when applied to requests client.
if do_settings_prohibit_mixed_security_contexts(request) ==
MixedSecurityProhibited::NotProhibited
{
return false;
}
// 1.2. requests URL is a potentially trustworthy URL.
if request.url().is_potentially_trustworthy() {
return false;
}
// 1.3. The user agent has been instructed to allow mixed content.
// 1.4. requests destination is "document", and requests target browsing context has
// no parent browsing context.
if request.destination == Destination::Document {
// TODO: request's target browsing context has no parent browsing context
return false;
}
true
}
/// <https://w3c.github.io/webappsec-mixed-content/#should-block-response>
pub fn should_response_be_blocked_as_mixed_content(request: &Request, response: &Response) -> bool {
// Step 1. Return allowed if one or more of the following conditions are met:
// 1.1. Does settings prohibit mixed security contexts? returns Does Not Restrict Mixed Content
// when applied to requests client.
if do_settings_prohibit_mixed_security_contexts(request) ==
MixedSecurityProhibited::NotProhibited
{
return false;
}
// 1.2. responses url is a potentially trustworthy URL.
if response
.actual_response()
.url()
.is_some_and(|response_url| response_url.is_potentially_trustworthy())
{
return false;
}
// 1.3. TODO: The user agent has been instructed to allow mixed content.
// 1.4. requests destination is "document", and requests target browsing context
// has no parent browsing context.
if request.destination == Destination::Document {
// TODO: if requests target browsing context has no parent browsing context
return false;
}
true
}
/// <https://fetch.spec.whatwg.org/#bad-port>
fn is_bad_port(port: u16) -> bool {
static BAD_PORTS: [u16; 78] = [
@ -983,14 +1056,36 @@ fn should_upgrade_request_to_potentially_trustworty(
request.insecure_requests_policy == InsecureRequestsPolicy::Upgrade
}
// TODO : Needs to revisit
#[derive(Debug, PartialEq)]
pub enum MixedSecurityProhibited {
Prohibited,
NotProhibited,
}
/// <https://w3c.github.io/webappsec-mixed-content/#categorize-settings-object>
fn does_settings_prohibit_mixed_security_contexts(url: &ServoUrl) -> bool {
if url.is_origin_trustworthy() {
return true;
fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecurityProhibited {
if let Origin::Origin(ref origin) = request.origin {
// Workers created from a data: url are secure if they were created from secure contexts
let is_origin_data_url_worker = matches!(
*origin,
ImmutableOrigin::Opaque(servo_url::OpaqueOrigin::SecureWorkerFromDataUrl(_))
);
// Step 1. If settings origin is a potentially trustworthy origin,
// then return "Prohibits Mixed Security Contexts".
if origin.is_potentially_trustworthy() || is_origin_data_url_worker {
return MixedSecurityProhibited::Prohibited;
}
}
false
// Step 2.2. For each navigable navigable in documents ancestor navigables:
// Step 2.2.1. If navigables active document's origin is a potentially trustworthy origin,
// then return "Prohibits Mixed Security Contexts".
if request.has_trustworthy_ancestor_origin {
return MixedSecurityProhibited::Prohibited;
}
MixedSecurityProhibited::NotProhibited
}
/// <https://w3c.github.io/webappsec-mixed-content/#upgrade-algorithm>
@ -1008,12 +1103,14 @@ fn should_upgrade_mixed_content_request(request: &Request) -> bool {
}
// Step 1.3
if !does_settings_prohibit_mixed_security_contexts(&url) {
if do_settings_prohibit_mixed_security_contexts(request) ==
MixedSecurityProhibited::NotProhibited
{
return false;
}
// Step 1.4 : requests destination is not "image", "audio", or "video".
if matches!(
if !matches!(
request.destination,
Destination::Audio | Destination::Image | Destination::Video
) {

View file

@ -1010,7 +1010,7 @@ pub async fn http_redirect_fetch(
// Step 8: Increase requests redirect count by 1.
request.redirect_count += 1;
// Step 7
// Step 9
let same_origin = match request.origin {
Origin::Origin(ref origin) => *origin == location_url.origin(),
Origin::Client => panic!(