nsMixedContentBlocker.cpp 35.2 KB
Newer Older
1
2
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
4
5
6
7
8
/* 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 http://mozilla.org/MPL/2.0/. */

#include "nsMixedContentBlocker.h"

9
#include "nsContentPolicyUtils.h"
10
#include "nsCSPContext.h"
11
#include "nsThreadUtils.h"
12
13
#include "nsINode.h"
#include "nsCOMPtr.h"
14
#include "nsDocShell.h"
15
16
#include "nsIWebProgressListener.h"
#include "nsContentUtils.h"
17
#include "mozilla/dom/BrowsingContext.h"
18
#include "mozilla/dom/WindowContext.h"
19
#include "mozilla/dom/Document.h"
20
#include "nsIChannel.h"
21
#include "nsIParentChannel.h"
22
#include "mozilla/Preferences.h"
23
#include "nsIScriptObjectPrincipal.h"
24
25
#include "nsIProtocolHandler.h"
#include "nsCharSeparatedTokenizer.h"
26
#include "nsISecureBrowserUI.h"
27
#include "nsIWebNavigation.h"
28
#include "nsLoadGroup.h"
29
#include "nsIScriptError.h"
30
31
#include "nsIURI.h"
#include "nsIChannelEventSink.h"
32
#include "nsNetUtil.h"
33
34
#include "nsAsyncRedirectVerifyHelper.h"
#include "mozilla/LoadInfo.h"
35
#include "nsISiteSecurityService.h"
36
#include "prnetdb.h"
37
#include "nsQueryObject.h"
38

39
#include "mozilla/BasePrincipal.h"
40
#include "mozilla/Logging.h"
41
#include "mozilla/StaticPrefs_dom.h"
42
#include "mozilla/StaticPrefs_fission.h"
43
#include "mozilla/StaticPrefs_security.h"
44
45
46
#include "mozilla/Telemetry.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/ipc/URIUtils.h"
47
#include "mozilla/net/DNS.h"
48
#include "mozilla/net/DocumentLoadListener.h"
49

50
using namespace mozilla;
51
using namespace mozilla::dom;
52

53
enum nsMixedContentBlockerMessageType { eBlocked = 0x00, eUserOverride = 0x01 };
54

55
56
57
58
59
// Whitelist of hostnames that should be considered secure contexts even when
// served over http:// or ws://
nsCString* nsMixedContentBlocker::sSecurecontextWhitelist = nullptr;
bool nsMixedContentBlocker::sSecurecontextWhitelistCached = false;

60
enum MixedContentHSTSState {
61
  MCB_HSTS_PASSIVE_NO_HSTS = 0,
62
  MCB_HSTS_PASSIVE_WITH_HSTS = 1,
63
64
  MCB_HSTS_ACTIVE_NO_HSTS = 2,
  MCB_HSTS_ACTIVE_WITH_HSTS = 3
65
66
};

67
nsMixedContentBlocker::~nsMixedContentBlocker() = default;
68

69
NS_IMPL_ISUPPORTS(nsMixedContentBlocker, nsIContentPolicy, nsIChannelEventSink)
70

71
72
static void LogMixedContentMessage(
    MixedContentTypes aClassification, nsIURI* aContentLocation,
73
    uint64_t aInnerWindowID, nsMixedContentBlockerMessageType aMessageType,
74
75
    nsIURI* aRequestingLocation,
    const nsACString& aOverruleMessageLookUpKeyWithThis = EmptyCString()) {
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
  nsAutoCString messageCategory;
  uint32_t severityFlag;
  nsAutoCString messageLookupKey;

  if (aMessageType == eBlocked) {
    severityFlag = nsIScriptError::errorFlag;
    messageCategory.AssignLiteral("Mixed Content Blocker");
    if (aClassification == eMixedDisplay) {
      messageLookupKey.AssignLiteral("BlockMixedDisplayContent");
    } else {
      messageLookupKey.AssignLiteral("BlockMixedActiveContent");
    }
  } else {
    severityFlag = nsIScriptError::warningFlag;
    messageCategory.AssignLiteral("Mixed Content Message");
    if (aClassification == eMixedDisplay) {
92
      messageLookupKey.AssignLiteral("LoadingMixedDisplayContent2");
93
    } else {
94
      messageLookupKey.AssignLiteral("LoadingMixedActiveContent2");
95
96
97
    }
  }

98
99
100
101
102
103
104
  // if the callee explicitly wants to use a special message for this
  // console report, then we allow to overrule the default with the
  // explicitly provided one here.
  if (!aOverruleMessageLookUpKeyWithThis.IsEmpty()) {
    messageLookupKey = aOverruleMessageLookUpKeyWithThis;
  }

105
106
  nsAutoString localizedMsg;
  AutoTArray<nsString, 1> params;
107
  CopyUTF8toUTF16(aContentLocation->GetSpecOrDefault(),
108
109
110
111
112
113
114
115
                  *params.AppendElement());
  nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
                                        messageLookupKey.get(), params,
                                        localizedMsg);

  nsContentUtils::ReportToConsoleByWindowID(localizedMsg, severityFlag,
                                            messageCategory, aInnerWindowID,
                                            aRequestingLocation);
116
117
}

118
119
120
121
122
123
/* nsIChannelEventSink implementation
 * This code is called when a request is redirected.
 * We check the channel associated with the new uri is allowed to load
 * in the current context
 */
NS_IMETHODIMP
124
125
126
nsMixedContentBlocker::AsyncOnChannelRedirect(
    nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
    nsIAsyncVerifyRedirectCallback* aCallback) {
127
  mozilla::net::nsAsyncRedirectAutoCallback autoCallback(aCallback);
128
129
130
131
132
133

  if (!aOldChannel) {
    NS_ERROR("No channel when evaluating mixed content!");
    return NS_ERROR_FAILURE;
  }

134
135
136
137
138
139
  // If we are in the parent process in e10s, we don't have access to the
  // document node, and hence ShouldLoad will fail when we try to get
  // the docShell.  If that's the case, ignore mixed content checks
  // on redirects in the parent.  Let the child check for mixed content.
  nsCOMPtr<nsIParentChannel> is_ipc_channel;
  NS_QueryNotificationCallbacks(aNewChannel, is_ipc_channel);
140
141
142
  RefPtr<net::DocumentLoadListener> docListener =
      do_QueryObject(is_ipc_channel);
  if (is_ipc_channel && !docListener) {
143
144
145
    return NS_OK;
  }

146
147
148
149
150
151
152
153
154
155
  nsresult rv;
  nsCOMPtr<nsIURI> oldUri;
  rv = aOldChannel->GetURI(getter_AddRefs(oldUri));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIURI> newUri;
  rv = aNewChannel->GetURI(getter_AddRefs(newUri));
  NS_ENSURE_SUCCESS(rv, rv);

  // Get the loading Info from the old channel
156
  nsCOMPtr<nsILoadInfo> loadInfo = aOldChannel->LoadInfo();
157
  nsCOMPtr<nsIPrincipal> requestingPrincipal = loadInfo->GetLoadingPrincipal();
158

159
160
161
  // Since we are calling shouldLoad() directly on redirects, we don't go
  // through the code in nsContentPolicyUtils::NS_CheckContentLoadPolicy().
  // Hence, we have to duplicate parts of it here.
162
163
164
  if (requestingPrincipal) {
    // We check to see if the loadingPrincipal is systemPrincipal and return
    // early if it is
165
    if (requestingPrincipal->IsSystemPrincipal()) {
166
167
168
169
170
      return NS_OK;
    }
  }

  int16_t decision = REJECT_REQUEST;
171
172
  rv = ShouldLoad(newUri, loadInfo,
                  EmptyCString(),  // aMimeGuess
173
                  &decision);
174
175
176
177
178
  if (NS_FAILED(rv)) {
    autoCallback.DontCallback();
    aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
    return NS_BINDING_FAILED;
  }
179
180
181
182

  // If the channel is about to load mixed content, abort the channel
  if (!NS_CP_ACCEPTED(decision)) {
    autoCallback.DontCallback();
183
    aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
184
185
186
187
188
189
    return NS_BINDING_FAILED;
  }

  return NS_OK;
}

190
191
192
193
/* This version of ShouldLoad() is non-static and called by the Content Policy
 * API and AsyncOnChannelRedirect().  See nsIContentPolicy::ShouldLoad()
 * for detailed description of the parameters.
 */
194
NS_IMETHODIMP
195
196
nsMixedContentBlocker::ShouldLoad(nsIURI* aContentLocation,
                                  nsILoadInfo* aLoadInfo,
197
                                  const nsACString& aMimeGuess,
198
                                  int16_t* aDecision) {
199
200
201
202
  // We pass in false as the first parameter to ShouldLoad(), because the
  // callers of this method don't know whether the load went through cached
  // image redirects.  This is handled by direct callers of the static
  // ShouldLoad.
203
204
  nsresult rv = ShouldLoad(false,  // aHadInsecureImageRedirect
                           aContentLocation, aLoadInfo, aMimeGuess, aDecision);
205
206
207
208
209
210

  if (*aDecision == nsIContentPolicy::REJECT_REQUEST) {
    NS_SetRequestBlockingReason(aLoadInfo,
                                nsILoadInfo::BLOCKING_REASON_MIXED_BLOCKED);
  }

211
212
213
  return rv;
}

214
215
bool nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
    const nsACString& aAsciiHost) {
216
217
  if (aAsciiHost.EqualsLiteral("::1") ||
      aAsciiHost.EqualsLiteral("localhost")) {
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
    return true;
  }

  PRNetAddr tempAddr;
  memset(&tempAddr, 0, sizeof(PRNetAddr));

  if (PR_StringToNetAddr(PromiseFlatCString(aAsciiHost).get(), &tempAddr) !=
      PR_SUCCESS) {
    return false;
  }

  using namespace mozilla::net;
  NetAddr addr;
  PRNetAddrToNetAddr(&tempAddr, &addr);

  // Step 4 of
  // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy says
  // we should only consider [::1]/128 as a potentially trustworthy IPv6
  // address, whereas for IPv4 127.0.0.1/8 are considered as potentially
237
238
239
  // trustworthy.  We already handled "[::1]" above, so all that's remained to
  // handle here are IPv4 loopback addresses.
  return IsIPAddrV4(&addr) && IsLoopBackAddress(&addr);
240
241
}

242
bool nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(nsIURI* aURL) {
243
244
  nsAutoCString asciiHost;
  nsresult rv = aURL->GetAsciiHost(asciiHost);
245
  NS_ENSURE_SUCCESS(rv, false);
246
  return IsPotentiallyTrustworthyLoopbackHost(asciiHost);
247
248
}

249
250
251
/* Maybe we have a .onion URL. Treat it as whitelisted as well if
 * `dom.securecontext.whitelist_onions` is `true`.
 */
252
bool nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(nsIURI* aURL) {
253
  if (!StaticPrefs::dom_securecontext_whitelist_onions()) {
254
255
256
257
258
259
260
261
262
    return false;
  }

  nsAutoCString host;
  nsresult rv = aURL->GetHost(host);
  NS_ENSURE_SUCCESS(rv, false);
  return StringEndsWith(host, NS_LITERAL_CSTRING(".onion"));
}

263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// static
void nsMixedContentBlocker::OnPrefChange(const char* aPref, void* aClosure) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!strcmp(aPref, "dom.securecontext.whitelist"));
  Preferences::GetCString("dom.securecontext.whitelist",
                          *sSecurecontextWhitelist);
}

// static
void nsMixedContentBlocker::GetSecureContextWhiteList(nsACString& aList) {
  MOZ_ASSERT(NS_IsMainThread());
  if (!sSecurecontextWhitelistCached) {
    MOZ_ASSERT(!sSecurecontextWhitelist);
    sSecurecontextWhitelistCached = true;
    sSecurecontextWhitelist = new nsCString();
    Preferences::RegisterCallbackAndCall(OnPrefChange,
                                         "dom.securecontext.whitelist");
  }
  aList = *sSecurecontextWhitelist;
}

// static
void nsMixedContentBlocker::Shutdown() {
  if (sSecurecontextWhitelist) {
    delete sSecurecontextWhitelist;
    sSecurecontextWhitelist = nullptr;
  }
}

292
293
294
295
296
297
298
299
300
301
302
bool nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(nsIURI* aURI) {
  // The following implements:
  // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy

  nsAutoCString scheme;
  nsresult rv = aURI->GetScheme(scheme);
  if (NS_FAILED(rv)) {
    return false;
  }

  // Blobs are expected to inherit their principal so we don't expect to have
303
  // a content principal with scheme 'blob' here.  We can't assert that though
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
  // since someone could mess with a non-blob URI to give it that scheme.
  NS_WARNING_ASSERTION(!scheme.EqualsLiteral("blob"),
                       "IsOriginPotentiallyTrustworthy ignoring blob scheme");

  // According to the specification, the user agent may choose to extend the
  // trust to other, vendor-specific URL schemes. We use this for "resource:",
  // which is technically a substituting protocol handler that is not limited to
  // local resource mapping, but in practice is never mapped remotely as this
  // would violate assumptions a lot of code makes.
  // We use nsIProtocolHandler flags to determine which protocols we consider a
  // priori authenticated.
  bool aPrioriAuthenticated = false;
  if (NS_FAILED(NS_URIChainHasFlags(
          aURI, nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY,
          &aPrioriAuthenticated))) {
    return false;
  }

  if (aPrioriAuthenticated) {
    return true;
  }

  nsAutoCString host;
  rv = aURI->GetHost(host);
  if (NS_FAILED(rv)) {
    return false;
  }

  if (IsPotentiallyTrustworthyLoopbackURL(aURI)) {
    return true;
  }

  // If a host is not considered secure according to the default algorithm, then
  // check to see if it has been whitelisted by the user.  We only apply this
  // whitelist for network resources, i.e., those with scheme "http" or "ws".
  // The pref should contain a comma-separated list of hostnames.

  if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("ws")) {
    return false;
  }

  nsAutoCString whitelist;
346
347
348
349
350
351
  GetSecureContextWhiteList(whitelist);
  nsCCharSeparatedTokenizer tokenizer(whitelist, ',');
  while (tokenizer.hasMoreTokens()) {
    const nsACString& allowedHost = tokenizer.nextToken();
    if (host.Equals(allowedHost)) {
      return true;
352
353
    }
  }
354

355
356
357
358
359
360
361
362
  // Maybe we have a .onion URL. Treat it as whitelisted as well if
  // `dom.securecontext.whitelist_onions` is `true`.
  if (nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(aURI)) {
    return true;
  }
  return false;
}

363
364
365
/* Static version of ShouldLoad() that contains all the Mixed Content Blocker
 * logic.  Called from non-static ShouldLoad().
 */
366
367
368
369
370
nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
                                           nsIURI* aContentLocation,
                                           nsILoadInfo* aLoadInfo,
                                           const nsACString& aMimeGuess,
                                           int16_t* aDecision) {
371
  // Asserting that we are on the main thread here and hence do not have to lock
372
373
374
  // and unlock security.mixed_content.block_active_content and
  // security.mixed_content.block_display_content before reading/writing to
  // them.
375
376
  MOZ_ASSERT(NS_IsMainThread());

377
378
379
380
  uint32_t contentType = aLoadInfo->InternalContentPolicyType();
  nsCOMPtr<nsISupports> requestingContext = aLoadInfo->GetLoadingContext();
  nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadInfo->GetLoadingPrincipal();
  nsCOMPtr<nsIPrincipal> triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
381
382
  RefPtr<WindowContext> requestingWindow =
      WindowContext::GetById(aLoadInfo->GetInnerWindowID());
383

384
385
386
  // The content policy type that we receive may be an internal type for
  // scripts.  Let's remember if we have seen a worker type, and reset it to the
  // external type in all cases right now.
387
  bool isWorkerType =
388
389
390
391
392
      contentType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
      contentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
      contentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER;
  contentType =
      nsContentUtils::InternalContentPolicyTypeToExternal(contentType);
393

394
395
  // Assume active (high risk) content and blocked by default
  MixedContentTypes classification = eMixedScript;
396
397
  // Make decision to block/reject by default
  *aDecision = REJECT_REQUEST;
398
399
400
401
402
403
404
405
406
407
408
409

  // Notes on non-obvious decisions:
  //
  // TYPE_DTD: A DTD can contain entity definitions that expand to scripts.
  //
  // TYPE_FONT: The TrueType hinting mechanism is basically a scripting
  // language that gets interpreted by the operating system's font rasterizer.
  // Mixed content web fonts are relatively uncommon, and we can can fall back
  // to built-in fonts with minimal disruption in almost all cases.
  //
  // TYPE_OBJECT_SUBREQUEST could actually be either active content (e.g. a
  // script that a plugin will execute) or display content (e.g. Flash video
410
411
412
413
  // content).  Until we have a way to determine active vs passive content
  // from plugin requests (bug 836352), we will treat this as passive content.
  // This is to prevent false positives from causing users to become
  // desensitized to the mixed content blocker.
414
415
416
417
418
419
420
421
422
  //
  // TYPE_CSP_REPORT: High-risk because they directly leak information about
  // the content of the page, and because blocking them does not have any
  // negative effect on the page loading.
  //
  // TYPE_PING: Ping requests are POSTS, not GETs like images and media.
  // Also, PING requests have no bearing on the rendering or operation of
  // the page when used as designed, so even though they are lower risk than
  // scripts, blocking them is basically risk-free as far as compatibility is
423
  // concerned.
424
425
426
427
428
429
  //
  // TYPE_STYLESHEET: XSLT stylesheets can insert scripts. CSS positioning
  // and other advanced CSS features can possibly be exploited to cause
  // spoofing attacks (e.g. make a "grant permission" button look like a
  // "refuse permission" button).
  //
430
431
  // TYPE_BEACON: Beacon requests are similar to TYPE_PING, and are blocked by
  // default.
432
  //
433
434
435
436
437
438
439
  // TYPE_WEBSOCKET: The Websockets API requires browsers to
  // reject mixed-content websockets: "If secure is false but the origin of
  // the entry script has a scheme component that is itself a secure protocol,
  // e.g. HTTPS, then throw a SecurityError exception." We already block mixed
  // content websockets within the websockets implementation, so we don't need
  // to do any blocking here, nor do we need to provide a way to undo or
  // override the blocking. Websockets without TLS are very flaky anyway in the
Benjamin Peterson's avatar
Benjamin Peterson committed
440
  // face of many HTTP-aware proxies. Compared to passive content, there is
441
442
443
444
445
446
447
448
449
450
  // additional risk that the script using WebSockets will disclose sensitive
  // information from the HTTPS page and/or eval (directly or indirectly)
  // received data.
  //
  // TYPE_XMLHTTPREQUEST: XHR requires either same origin or CORS, so most
  // mixed-content XHR will already be blocked by that check. This will also
  // block HTTPS-to-HTTP XHR with CORS. The same security concerns mentioned
  // above for WebSockets apply to XHR, and XHR should have the same security
  // properties as WebSockets w.r.t. mixed content. XHR's handling of redirects
  // amplifies these concerns.
451
452
453
454
  //
  // TYPE_SAVEAS_DOWNLOAD: Save-link-as feature is used to download a resource
  // without involving a docShell. This kind of loading must be always be
  // allowed.
455

456
457
458
  static_assert(TYPE_DATAREQUEST == TYPE_XMLHTTPREQUEST,
                "TYPE_DATAREQUEST is not a synonym for "
                "TYPE_XMLHTTPREQUEST");
459

460
  switch (contentType) {
461
462
463
464
    // The top-level document cannot be mixed content by definition
    case TYPE_DOCUMENT:
      *aDecision = ACCEPT;
      return NS_OK;
465
466
467
    // Creating insecure websocket connections in a secure page is blocked
    // already in the websocket constructor. We don't need to check the blocking
    // here and we don't want to un-block
468
469
470
    case TYPE_WEBSOCKET:
      *aDecision = ACCEPT;
      return NS_OK;
471

472
473
474
475
476
477
478
    // Creating insecure connections for a save-as link download is acceptable.
    // This download is completely disconnected from the docShell, but still
    // using the same loading principal.
    case TYPE_SAVEAS_DOWNLOAD:
      *aDecision = ACCEPT;
      return NS_OK;

479
480
481
482
483
484
    // Static display content is considered moderate risk for mixed content so
    // these will be blocked according to the mixed display preference
    case TYPE_IMAGE:
    case TYPE_MEDIA:
      classification = eMixedDisplay;
      break;
485
    case TYPE_OBJECT_SUBREQUEST:
486
      if (StaticPrefs::security_mixed_content_block_object_subrequest()) {
487
488
489
490
491
        classification = eMixedScript;
      } else {
        classification = eMixedDisplay;
      }
      break;
492

493
494
495
    // Active content (or content with a low value/risk-of-blocking ratio)
    // that has been explicitly evaluated; listed here for documentation
    // purposes and to avoid the assertion and warning for the default case.
496
    case TYPE_BEACON:
497
498
    case TYPE_CSP_REPORT:
    case TYPE_DTD:
499
    case TYPE_FETCH:
500
    case TYPE_FONT:
501
    case TYPE_IMAGESET:
502
503
504
505
    case TYPE_OBJECT:
    case TYPE_SCRIPT:
    case TYPE_STYLESHEET:
    case TYPE_SUBDOCUMENT:
506
    case TYPE_PING:
507
    case TYPE_WEB_MANIFEST:
508
    case TYPE_XMLHTTPREQUEST:
509
    case TYPE_XSLT:
510
    case TYPE_OTHER:
511
    case TYPE_SPECULATIVE:
512
513
514
515
      break;

    // This content policy works as a whitelist.
    default:
516
      MOZ_ASSERT(false, "Mixed content of unknown type");
517
518
  }

519
520
521
522
523
524
525
526
527
528
529
  // Make sure to get the URI the load started with. No need to check
  // outer schemes because all the wrapping pseudo protocols inherit the
  // security properties of the actual network request represented
  // by the innerMost URL.
  nsCOMPtr<nsIURI> innerContentLocation = NS_GetInnermostURI(aContentLocation);
  if (!innerContentLocation) {
    NS_ERROR("Can't get innerURI from aContentLocation");
    *aDecision = REJECT_REQUEST;
    return NS_OK;
  }

530
531
  // TYPE_IMAGE redirects are cached based on the original URI, not the final
  // destination and hence cache hits for images may not have the correct
532
533
  // innerContentLocation.  Check if the cached hit went through an http
  // redirect, and if it did, we can't treat this as a secure subresource.
534
  if (!aHadInsecureImageRedirect &&
535
      URISafeToBeLoadedInSecureContext(innerContentLocation)) {
536
    *aDecision = ACCEPT;
537
    return NS_OK;
538
539
  }

540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
  /*
   * Most likely aLoadingPrincipal reflects the security context of the owning
   * document for this mixed content check. There are cases where that is not
   * true, hence we have to we process requests in the following order:
   * 1) If the load is triggered by the SystemPrincipal, we allow the load.
   *    Content scripts from addon code do provide aTriggeringPrincipal, which
   *    is an ExpandedPrincipal. If encountered, we allow the load.
   * 2) If aLoadingPrincipal does not yield to a requestingLocation, then we
   *    fall back to querying the requestingLocation from aTriggeringPrincipal.
   * 3) If we still end up not having a requestingLocation, we reject the load.
   */

  // 1) Check if the load was triggered by the system (SystemPrincipal) or
  // a content script from addons code (ExpandedPrincipal) in which case the
  // load is not subject to mixed content blocking.
555
556
  if (triggeringPrincipal) {
    if (triggeringPrincipal->IsSystemPrincipal()) {
557
558
      *aDecision = ACCEPT;
      return NS_OK;
559
    }
560
    nsCOMPtr<nsIExpandedPrincipal> expanded =
561
        do_QueryInterface(triggeringPrincipal);
562
563
564
    if (expanded) {
      *aDecision = ACCEPT;
      return NS_OK;
565
566
    }
  }
567

568
569
570
571
  // 2) If aLoadingPrincipal does not provide a requestingLocation, then
  // we fall back to to querying the requestingLocation from
  // aTriggeringPrincipal.
  nsCOMPtr<nsIURI> requestingLocation;
572
  auto* baseLoadingPrincipal = BasePrincipal::Cast(loadingPrincipal);
573
574
575
576
  if (baseLoadingPrincipal) {
    baseLoadingPrincipal->GetURI(getter_AddRefs(requestingLocation));
  }
  if (!requestingLocation) {
577
    auto* baseTriggeringPrincipal = BasePrincipal::Cast(triggeringPrincipal);
578
579
580
581
582
583
584
    if (baseTriggeringPrincipal) {
      baseTriggeringPrincipal->GetURI(getter_AddRefs(requestingLocation));
    }
  }

  // 3) Giving up. We still don't have a requesting location, therefore we can't
  // tell if this is a mixed content load. Deny to be safe.
585
586
587
588
589
  if (!requestingLocation) {
    *aDecision = REJECT_REQUEST;
    return NS_OK;
  }

590
591
  // Check the parent scheme. If it is not an HTTPS or .onion page then mixed
  // content restrictions do not apply.
592
593
  nsCOMPtr<nsIURI> innerRequestingLocation =
      NS_GetInnermostURI(requestingLocation);
594
  if (!innerRequestingLocation) {
595
596
597
598
599
    NS_ERROR("Can't get innerURI from requestingLocation");
    *aDecision = REJECT_REQUEST;
    return NS_OK;
  }

600
  bool parentIsHttps = innerRequestingLocation->SchemeIs("https");
601
  if (!parentIsHttps) {
602
603
604
605
606
607
608
609
610
611
612
    bool parentIsOnion = IsPotentiallyTrustworthyOnion(innerRequestingLocation);
    if (!parentIsOnion) {
      *aDecision = ACCEPT;
      return NS_OK;
    }
  }

  bool isHttpScheme = innerContentLocation->SchemeIs("http");
  // .onion URLs are encrypted and authenticated. Don't treat them as mixed
  // content if potentially trustworthy (i.e. whitelisted).
  if (isHttpScheme && IsPotentiallyTrustworthyOnion(innerContentLocation)) {
613
614
615
    *aDecision = ACCEPT;
    return NS_OK;
  }
616

617
618
619
620
  // Disallow mixed content loads for workers, shared workers and service
  // workers.
  if (isWorkerType) {
    // For workers, we can assume that we're mixed content at this point, since
621
622
623
    // the parent is https, and the protocol associated with
    // innerContentLocation doesn't map to the secure URI flags checked above.
    // Assert this for sanity's sake
624
#ifdef DEBUG
625
    bool isHttpsScheme = innerContentLocation->SchemeIs("https");
626
627
628
629
630
631
    MOZ_ASSERT(!isHttpsScheme);
#endif
    *aDecision = REJECT_REQUEST;
    return NS_OK;
  }

632
  if (isHttpScheme && IsPotentiallyTrustworthyOrigin(innerContentLocation)) {
633
634
635
636
    *aDecision = ACCEPT;
    return NS_OK;
  }

637
638
639
640
641
642
  // If https-only mode is enabled we'll upgrade this later anyway
  if (StaticPrefs::dom_security_https_only_mode()) {
    *aDecision = ACCEPT;
    return NS_OK;
  }

643
644
645
646
  // The page might have set the CSP directive 'upgrade-insecure-requests'. In
  // such a case allow the http: load to succeed with the promise that the
  // channel will get upgraded to https before fetching any data from the
  // netwerk. Please see: nsHttpChannel::Connect()
647
  //
648
649
650
651
652
653
  // Please note that the CSP directive 'upgrade-insecure-requests' only applies
  // to http: and ws: (for websockets). Websockets are not subject to mixed
  // content blocking since insecure websockets are not allowed within secure
  // pages. Hence, we only have to check against http: here. Skip mixed content
  // blocking if the subresource load uses http: and the CSP directive
  // 'upgrade-insecure-requests' is present on the page.
654
655
656

  // Carve-out: if we're in the parent and we're loading media, e.g. through
  // webbrowserpersist, don't reject it if we can't find a docshell.
657
  if (XRE_IsParentProcess() && !requestingWindow &&
658
      (contentType == TYPE_IMAGE || contentType == TYPE_MEDIA)) {
659
660
661
    *aDecision = ACCEPT;
    return NS_OK;
  }
662
  // Otherwise, we must have a window
663
  NS_ENSURE_TRUE(requestingWindow, NS_OK);
664

665
  if (isHttpScheme && aLoadInfo->GetUpgradeInsecureRequests()) {
666
667
668
669
    *aDecision = ACCEPT;
    return NS_OK;
  }

670
671
672
673
  // Allow http: mixed content if we are choosing to upgrade them when the
  // pref "security.mixed_content.upgrade_display_content" is true.
  // This behaves like GetUpgradeInsecureRequests above in that the channel will
  // be upgraded to https before fetching any data from the netwerk.
674
  bool isUpgradableDisplayType =
675
      nsContentUtils::IsUpgradableDisplayType(contentType) &&
676
      StaticPrefs::security_mixed_content_upgrade_display_content();
677
678
679
680
681
  if (isHttpScheme && isUpgradableDisplayType) {
    *aDecision = ACCEPT;
    return NS_OK;
  }

682
  // The page might have set the CSP directive 'block-all-mixed-content' which
683
684
685
686
687
  // should block not only active mixed content loads but in fact all mixed
  // content loads, see https://www.w3.org/TR/mixed-content/#strict-checking
  // Block all non secure loads in case the CSP directive is present. Please
  // note that at this point we already know, based on |schemeSecure| that the
  // load is not secure, so we can bail out early at this point.
688
  if (aLoadInfo->GetBlockAllMixedContent()) {
689
690
    // log a message to the console before returning.
    nsAutoCString spec;
691
    nsresult rv = aContentLocation->GetSpec(spec);
692
693
    NS_ENSURE_SUCCESS(rv, rv);

694
695
696
    AutoTArray<nsString, 1> params;
    CopyUTF8toUTF16(spec, *params.AppendElement());

697
698
699
700
701
702
703
704
705
    CSP_LogLocalizedStr("blockAllMixedContent", params,
                        EmptyString(),  // aSourceFile
                        EmptyString(),  // aScriptSample
                        0,              // aLineNumber
                        0,              // aColumnNumber
                        nsIScriptError::errorFlag,
                        NS_LITERAL_CSTRING("blockAllMixedContent"),
                        requestingWindow->Id(),
                        !!aLoadInfo->GetOriginAttributes().mPrivateBrowsingId);
706
707
708
709
    *aDecision = REJECT_REQUEST;
    return NS_OK;
  }

710
711
  // Determine if the rootDoc is https and if the user decided to allow Mixed
  // Content
712
  WindowContext* topWC = requestingWindow->TopWindowContext();
713
  bool rootHasSecureConnection = topWC->GetIsSecure();
714
  bool allowMixedContent = topWC->GetAllowMixedContent();
715

716
  // When navigating an iframe, the iframe may be https
717
718
  // but its parents may not be.  Check the parents to see if any of them are
  // https. If none of the parents are https, allow the load.
719
  if (contentType == TYPE_SUBDOCUMENT && !rootHasSecureConnection) {
720
721
    bool httpsParentExists = false;

722
723
    RefPtr<WindowContext> curWindow = requestingWindow;
    while (!httpsParentExists && curWindow) {
724
      httpsParentExists = curWindow->GetIsSecure();
725
      curWindow = curWindow->GetParentWindowContext();
726
    }
727

728
729
730
731
732
    if (!httpsParentExists) {
      *aDecision = nsIContentPolicy::ACCEPT;
      return NS_OK;
    }
  }
733

734
  OriginAttributes originAttributes;
735
736
737
738
  if (loadingPrincipal) {
    originAttributes = loadingPrincipal->OriginAttributesRef();
  } else if (triggeringPrincipal) {
    originAttributes = triggeringPrincipal->OriginAttributesRef();
739
740
  }

741
742
743
744
745
746
747
748
749
750
751
752
  // At this point we know that the request is mixed content, and the only
  // question is whether we block it.  Record telemetry at this point as to
  // whether HSTS would have fixed things by making the content location
  // into an HTTPS URL.
  //
  // Note that we count this for redirects as well as primary requests. This
  // will cause some degree of double-counting, especially when mixed content
  // is not blocked (e.g., for images).  For more detail, see:
  //   https://bugzilla.mozilla.org/show_bug.cgi?id=1198572#c19
  //
  // We do not count requests aHadInsecureImageRedirect=true, since these are
  // just an artifact of the image caching system.
753
  bool active = (classification == eMixedScript);
754
755
  if (!aHadInsecureImageRedirect) {
    if (XRE_IsParentProcess()) {
756
      AccumulateMixedContentHSTS(innerContentLocation, active,
757
                                 originAttributes);
758
759
    } else {
      // Ask the parent process to do the same call
760
761
      mozilla::dom::ContentChild* cc =
          mozilla::dom::ContentChild::GetSingleton();
762
      if (cc) {
763
764
        cc->SendAccumulateMixedContentHSTS(innerContentLocation, active,
                                           originAttributes);
765
766
767
768
      }
    }
  }

769
  // set hasMixedContentObjectSubrequest on this object if necessary
770
  if (contentType == TYPE_OBJECT_SUBREQUEST) {
771
    if (!StaticPrefs::security_mixed_content_block_object_subrequest()) {
772
773
      nsAutoCString messageLookUpKey(
          "LoadingMixedDisplayObjectSubrequestDeprecation");
774
      LogMixedContentMessage(classification, aContentLocation, topWC->Id(),
775
776
                             eUserOverride, requestingLocation,
                             messageLookUpKey);
777
    }
778
779
  }

780
  uint32_t newState = 0;
781
782
  // If the content is display content, and the pref says display content should
  // be blocked, block it.
783
784
785
  if (classification == eMixedDisplay) {
    if (!StaticPrefs::security_mixed_content_block_display_content() ||
        allowMixedContent) {
786
      *aDecision = nsIContentPolicy::ACCEPT;
787
788
789
      // User has overriden the pref and the root is not https;
      // mixed display content was allowed on an https subframe.
      newState |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
790
    } else {
791
      *aDecision = nsIContentPolicy::REJECT_REQUEST;
792
      newState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT;
793
    }
794
795
  } else {
    MOZ_ASSERT(classification == eMixedScript);
796
797
    // If the content is active content, and the pref says active content should
    // be blocked, block it unless the user has choosen to override the pref
798
799
    if (!StaticPrefs::security_mixed_content_block_active_content() ||
        allowMixedContent) {
800
      *aDecision = nsIContentPolicy::ACCEPT;
801
802
803
      // User has already overriden the pref and the root is not https;
      // mixed active content was allowed on an https subframe.
      newState |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
804
    } else {
805
806
      // User has not overriden the pref by Disabling protection. Reject the
      // request and update the security state.
807
      *aDecision = nsIContentPolicy::REJECT_REQUEST;
808
      // The user has not overriden the pref, so make sure they still have an
809
      // option by calling nativeDocShell which will invoke the doorhanger
810
811
812
813
      newState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
    }
  }

814
815
816
817
818
819
  LogMixedContentMessage(classification, aContentLocation, topWC->Id(),
                         (*aDecision == nsIContentPolicy::REJECT_REQUEST)
                             ? eBlocked
                             : eUserOverride,
                         requestingLocation);

820
821
822
  // Notify the top WindowContext of the flags we've computed, and it
  // will handle updating any relevant security UI.
  topWC->AddMixedContentSecurityState(newState);
823
  return NS_OK;
824
825
}

826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
bool nsMixedContentBlocker::URISafeToBeLoadedInSecureContext(nsIURI* aURI) {
  /* Returns a bool if the URI can be loaded as a sub resource safely.
   *
   * Check Protocol Flags to determine if scheme is safe to load:
   * URI_DOES_NOT_RETURN_DATA - e.g.
   *   "mailto"
   * URI_IS_LOCAL_RESOURCE - e.g.
   *   "data",
   *   "resource",
   *   "moz-icon"
   * URI_INHERITS_SECURITY_CONTEXT - e.g.
   *   "javascript"
   * URI_IS_POTENTIALLY_TRUSTWORTHY - e.g.
   *   "https",
   *   "moz-safe-about"
   *
   */
  bool schemeLocal = false;
  bool schemeNoReturnData = false;
  bool schemeInherits = false;
  bool schemeSecure = false;
  if (NS_FAILED(NS_URIChainHasFlags(
          aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) ||
      NS_FAILED(NS_URIChainHasFlags(
          aURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
          &schemeNoReturnData)) ||
      NS_FAILED(NS_URIChainHasFlags(
          aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
          &schemeInherits)) ||
      NS_FAILED(NS_URIChainHasFlags(
          aURI, nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY,
          &schemeSecure))) {
    return false;
  }
  return (schemeLocal || schemeNoReturnData || schemeInherits || schemeSecure);
}

863
NS_IMETHODIMP
864
865
nsMixedContentBlocker::ShouldProcess(nsIURI* aContentLocation,
                                     nsILoadInfo* aLoadInfo,
866
                                     const nsACString& aMimeGuess,
867
                                     int16_t* aDecision) {
868
  if (!aContentLocation) {
869
870
871
872
873
    // aContentLocation may be null when a plugin is loading without an
    // associated URI resource
    if (aLoadInfo->GetExternalContentPolicyType() == TYPE_OBJECT) {
      *aDecision = ACCEPT;
      return NS_OK;
874
    }
875

876
877
    NS_SetRequestBlockingReason(aLoadInfo,
                                nsILoadInfo::BLOCKING_REASON_MIXED_BLOCKED);
878
879
    *aDecision = REJECT_REQUEST;
    return NS_ERROR_FAILURE;
880
881
  }

882
  return ShouldLoad(aContentLocation, aLoadInfo, aMimeGuess, aDecision);
883
}
884
885
886

// Record information on when HSTS would have made mixed content not mixed
// content (regardless of whether it was actually blocked)
887
888
void nsMixedContentBlocker::AccumulateMixedContentHSTS(
    nsIURI* aURI, bool aActive, const OriginAttributes& aOriginAttributes) {
889
890
891
892
893
894
895
896
897
  // This method must only be called in the parent, because
  // nsSiteSecurityService is only available in the parent
  if (!XRE_IsParentProcess()) {
    MOZ_ASSERT(false);
    return;
  }

  bool hsts;
  nsresult rv;
898
899
  nsCOMPtr<nsISiteSecurityService> sss =
      do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
900
901
902
  if (NS_FAILED(rv)) {
    return;
  }
903
  rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, 0,
904
                        aOriginAttributes, nullptr, nullptr, &hsts);
905
906
907
908
  if (NS_FAILED(rv)) {
    return;
  }

909
910
911
  // states: would upgrade, would prime, hsts info cached
  // active, passive
  //
912
913
914
915
  if (!aActive) {
    if (!hsts) {
      Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
                            MCB_HSTS_PASSIVE_NO_HSTS);
916
    } else {
917
918
919
920
921
922
923
      Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
                            MCB_HSTS_PASSIVE_WITH_HSTS);
    }
  } else {
    if (!hsts) {
      Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
                            MCB_HSTS_ACTIVE_NO_HSTS);
924
    } else {
925
926
927
928
929
      Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
                            MCB_HSTS_ACTIVE_WITH_HSTS);
    }
  }
}