/src/mozilla-central/dom/security/nsMixedContentBlocker.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "nsMixedContentBlocker.h" |
8 | | |
9 | | #include "nsContentPolicyUtils.h" |
10 | | #include "nsCSPContext.h" |
11 | | #include "nsThreadUtils.h" |
12 | | #include "nsINode.h" |
13 | | #include "nsCOMPtr.h" |
14 | | #include "nsIDocShell.h" |
15 | | #include "nsISecurityEventSink.h" |
16 | | #include "nsIWebProgressListener.h" |
17 | | #include "nsContentUtils.h" |
18 | | #include "nsIRequest.h" |
19 | | #include "nsIDocument.h" |
20 | | #include "nsIContentViewer.h" |
21 | | #include "nsIChannel.h" |
22 | | #include "nsIHttpChannel.h" |
23 | | #include "nsIParentChannel.h" |
24 | | #include "mozilla/Preferences.h" |
25 | | #include "nsIScriptObjectPrincipal.h" |
26 | | #include "nsISecureBrowserUI.h" |
27 | | #include "nsIDocumentLoader.h" |
28 | | #include "nsIWebNavigation.h" |
29 | | #include "nsLoadGroup.h" |
30 | | #include "nsIScriptError.h" |
31 | | #include "nsIURI.h" |
32 | | #include "nsIChannelEventSink.h" |
33 | | #include "nsNetUtil.h" |
34 | | #include "nsAsyncRedirectVerifyHelper.h" |
35 | | #include "mozilla/LoadInfo.h" |
36 | | #include "nsISiteSecurityService.h" |
37 | | #include "prnetdb.h" |
38 | | |
39 | | #include "mozilla/Logging.h" |
40 | | #include "mozilla/Telemetry.h" |
41 | | #include "mozilla/dom/ContentChild.h" |
42 | | #include "mozilla/ipc/URIUtils.h" |
43 | | |
44 | | |
45 | | using namespace mozilla; |
46 | | |
47 | | enum nsMixedContentBlockerMessageType { |
48 | | eBlocked = 0x00, |
49 | | eUserOverride = 0x01 |
50 | | }; |
51 | | |
52 | | // Is mixed script blocking (fonts, plugin content, scripts, stylesheets, |
53 | | // iframes, websockets, XHR) enabled? |
54 | | bool nsMixedContentBlocker::sBlockMixedScript = false; |
55 | | |
56 | | bool nsMixedContentBlocker::sBlockMixedObjectSubrequest = false; |
57 | | |
58 | | // Is mixed display content blocking (images, audio, video) enabled? |
59 | | bool nsMixedContentBlocker::sBlockMixedDisplay = false; |
60 | | |
61 | | // Is mixed display content upgrading (images, audio, video) enabled? |
62 | | bool nsMixedContentBlocker::sUpgradeMixedDisplay = false; |
63 | | |
64 | | enum MixedContentHSTSState { |
65 | | MCB_HSTS_PASSIVE_NO_HSTS = 0, |
66 | | MCB_HSTS_PASSIVE_WITH_HSTS = 1, |
67 | | MCB_HSTS_ACTIVE_NO_HSTS = 2, |
68 | | MCB_HSTS_ACTIVE_WITH_HSTS = 3 |
69 | | }; |
70 | | |
71 | | // Fired at the document that attempted to load mixed content. The UI could |
72 | | // handle this event, for example, by displaying an info bar that offers the |
73 | | // choice to reload the page with mixed content permitted. |
74 | | class nsMixedContentEvent : public Runnable |
75 | | { |
76 | | public: |
77 | | nsMixedContentEvent(nsISupports* aContext, |
78 | | MixedContentTypes aType, |
79 | | bool aRootHasSecureConnection) |
80 | | : mozilla::Runnable("nsMixedContentEvent") |
81 | | , mContext(aContext) |
82 | | , mType(aType) |
83 | | , mRootHasSecureConnection(aRootHasSecureConnection) |
84 | 0 | {} |
85 | | |
86 | | NS_IMETHOD Run() override |
87 | 0 | { |
88 | 0 | NS_ASSERTION(mContext, |
89 | 0 | "You can't call this runnable without a requesting context"); |
90 | 0 |
|
91 | 0 | // To update the security UI in the tab with the blocked mixed content, call |
92 | 0 | // nsISecurityEventSink::OnSecurityChange. You can get to the event sink by |
93 | 0 | // calling NS_CP_GetDocShellFromContext on the context, and QI'ing to |
94 | 0 | // nsISecurityEventSink. |
95 | 0 |
|
96 | 0 |
|
97 | 0 | // Mixed content was allowed and is about to load; get the document and |
98 | 0 | // set the approriate flag to true if we are about to load Mixed Active |
99 | 0 | // Content. |
100 | 0 | nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(mContext); |
101 | 0 | if (!docShell) { |
102 | 0 | return NS_OK; |
103 | 0 | } |
104 | 0 | nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot; |
105 | 0 | docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); |
106 | 0 | NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!"); |
107 | 0 |
|
108 | 0 | // now get the document from sameTypeRoot |
109 | 0 | nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument(); |
110 | 0 | NS_ASSERTION(rootDoc, "No root document from document shell root tree item."); |
111 | 0 |
|
112 | 0 | // Get eventSink and the current security state from the docShell |
113 | 0 | nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell); |
114 | 0 | NS_ASSERTION(eventSink, "No eventSink from docShell."); |
115 | 0 | nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot); |
116 | 0 | NS_ASSERTION(rootShell, "No root docshell from document shell root tree item."); |
117 | 0 | uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN; |
118 | 0 | nsCOMPtr<nsISecureBrowserUI> securityUI; |
119 | 0 | rootShell->GetSecurityUI(getter_AddRefs(securityUI)); |
120 | 0 | // If there is no securityUI, document doesn't have a security state to |
121 | 0 | // update. But we still want to set the document flags, so we don't return |
122 | 0 | // early. |
123 | 0 | nsresult stateRV = NS_ERROR_FAILURE; |
124 | 0 | if (securityUI) { |
125 | 0 | stateRV = securityUI->GetState(&state); |
126 | 0 | } |
127 | 0 |
|
128 | 0 | if (mType == eMixedScript) { |
129 | 0 | // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI. |
130 | 0 | if (rootDoc->GetHasMixedActiveContentLoaded()) { |
131 | 0 | return NS_OK; |
132 | 0 | } |
133 | 0 | rootDoc->SetHasMixedActiveContentLoaded(true); |
134 | 0 |
|
135 | 0 | // Update the security UI in the tab with the allowed mixed active content |
136 | 0 | if (securityUI) { |
137 | 0 | // Bug 1182551 - before changing the security state to broken, check |
138 | 0 | // that the root is actually secure. |
139 | 0 | if (mRootHasSecureConnection) { |
140 | 0 | // reset state security flag |
141 | 0 | state = state >> 4 << 4; |
142 | 0 | // set state security flag to broken, since there is mixed content |
143 | 0 | state |= nsIWebProgressListener::STATE_IS_BROKEN; |
144 | 0 |
|
145 | 0 | // If mixed display content is loaded, make sure to include that in the state. |
146 | 0 | if (rootDoc->GetHasMixedDisplayContentLoaded()) { |
147 | 0 | state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT; |
148 | 0 | } |
149 | 0 |
|
150 | 0 | eventSink->OnSecurityChange(mContext, |
151 | 0 | (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); |
152 | 0 | } else { |
153 | 0 | // root not secure, mixed active content loaded in an https subframe |
154 | 0 | if (NS_SUCCEEDED(stateRV)) { |
155 | 0 | eventSink->OnSecurityChange(mContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); |
156 | 0 | } |
157 | 0 | } |
158 | 0 | } |
159 | 0 |
|
160 | 0 | } else if (mType == eMixedDisplay) { |
161 | 0 | // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI. |
162 | 0 | if (rootDoc->GetHasMixedDisplayContentLoaded()) { |
163 | 0 | return NS_OK; |
164 | 0 | } |
165 | 0 | rootDoc->SetHasMixedDisplayContentLoaded(true); |
166 | 0 |
|
167 | 0 | // Update the security UI in the tab with the allowed mixed display content. |
168 | 0 | if (securityUI) { |
169 | 0 | // Bug 1182551 - before changing the security state to broken, check |
170 | 0 | // that the root is actually secure. |
171 | 0 | if (mRootHasSecureConnection) { |
172 | 0 | // reset state security flag |
173 | 0 | state = state >> 4 << 4; |
174 | 0 | // set state security flag to broken, since there is mixed content |
175 | 0 | state |= nsIWebProgressListener::STATE_IS_BROKEN; |
176 | 0 |
|
177 | 0 | // If mixed active content is loaded, make sure to include that in the state. |
178 | 0 | if (rootDoc->GetHasMixedActiveContentLoaded()) { |
179 | 0 | state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT; |
180 | 0 | } |
181 | 0 |
|
182 | 0 | eventSink->OnSecurityChange(mContext, |
183 | 0 | (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); |
184 | 0 | } else { |
185 | 0 | // root not secure, mixed display content loaded in an https subframe |
186 | 0 | if (NS_SUCCEEDED(stateRV)) { |
187 | 0 | eventSink->OnSecurityChange(mContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); |
188 | 0 | } |
189 | 0 | } |
190 | 0 | } |
191 | 0 | } |
192 | 0 |
|
193 | 0 | return NS_OK; |
194 | 0 | } |
195 | | private: |
196 | | // The requesting context for the content load. Generally, a DOM node from |
197 | | // the document that caused the load. |
198 | | nsCOMPtr<nsISupports> mContext; |
199 | | |
200 | | // The type of mixed content detected, e.g. active or display |
201 | | const MixedContentTypes mType; |
202 | | |
203 | | // Indicates whether the top level load is https or not. |
204 | | bool mRootHasSecureConnection; |
205 | | }; |
206 | | |
207 | | |
208 | | nsMixedContentBlocker::nsMixedContentBlocker() |
209 | 0 | { |
210 | 0 | // Cache the pref for mixed script blocking |
211 | 0 | Preferences::AddBoolVarCache(&sBlockMixedScript, |
212 | 0 | "security.mixed_content.block_active_content"); |
213 | 0 |
|
214 | 0 | Preferences::AddBoolVarCache(&sBlockMixedObjectSubrequest, |
215 | 0 | "security.mixed_content.block_object_subrequest"); |
216 | 0 |
|
217 | 0 | // Cache the pref for mixed display blocking |
218 | 0 | Preferences::AddBoolVarCache(&sBlockMixedDisplay, |
219 | 0 | "security.mixed_content.block_display_content"); |
220 | 0 |
|
221 | 0 | // Cache the pref for mixed display upgrading |
222 | 0 | Preferences::AddBoolVarCache(&sUpgradeMixedDisplay, |
223 | 0 | "security.mixed_content.upgrade_display_content"); |
224 | 0 | } |
225 | | |
226 | | nsMixedContentBlocker::~nsMixedContentBlocker() |
227 | 0 | { |
228 | 0 | } |
229 | | |
230 | | NS_IMPL_ISUPPORTS(nsMixedContentBlocker, nsIContentPolicy, nsIChannelEventSink) |
231 | | |
232 | | static void |
233 | | LogMixedContentMessage(MixedContentTypes aClassification, |
234 | | nsIURI* aContentLocation, |
235 | | nsIDocument* aRootDoc, |
236 | | nsMixedContentBlockerMessageType aMessageType) |
237 | 0 | { |
238 | 0 | nsAutoCString messageCategory; |
239 | 0 | uint32_t severityFlag; |
240 | 0 | nsAutoCString messageLookupKey; |
241 | 0 |
|
242 | 0 | if (aMessageType == eBlocked) { |
243 | 0 | severityFlag = nsIScriptError::errorFlag; |
244 | 0 | messageCategory.AssignLiteral("Mixed Content Blocker"); |
245 | 0 | if (aClassification == eMixedDisplay) { |
246 | 0 | messageLookupKey.AssignLiteral("BlockMixedDisplayContent"); |
247 | 0 | } else { |
248 | 0 | messageLookupKey.AssignLiteral("BlockMixedActiveContent"); |
249 | 0 | } |
250 | 0 | } else { |
251 | 0 | severityFlag = nsIScriptError::warningFlag; |
252 | 0 | messageCategory.AssignLiteral("Mixed Content Message"); |
253 | 0 | if (aClassification == eMixedDisplay) { |
254 | 0 | messageLookupKey.AssignLiteral("LoadingMixedDisplayContent2"); |
255 | 0 | } else { |
256 | 0 | messageLookupKey.AssignLiteral("LoadingMixedActiveContent2"); |
257 | 0 | } |
258 | 0 | } |
259 | 0 |
|
260 | 0 | NS_ConvertUTF8toUTF16 locationSpecUTF16(aContentLocation->GetSpecOrDefault()); |
261 | 0 | const char16_t* strings[] = { locationSpecUTF16.get() }; |
262 | 0 | nsContentUtils::ReportToConsole(severityFlag, messageCategory, aRootDoc, |
263 | 0 | nsContentUtils::eSECURITY_PROPERTIES, |
264 | 0 | messageLookupKey.get(), strings, ArrayLength(strings)); |
265 | 0 | } |
266 | | |
267 | | /* nsIChannelEventSink implementation |
268 | | * This code is called when a request is redirected. |
269 | | * We check the channel associated with the new uri is allowed to load |
270 | | * in the current context |
271 | | */ |
272 | | NS_IMETHODIMP |
273 | | nsMixedContentBlocker::AsyncOnChannelRedirect(nsIChannel* aOldChannel, |
274 | | nsIChannel* aNewChannel, |
275 | | uint32_t aFlags, |
276 | | nsIAsyncVerifyRedirectCallback* aCallback) |
277 | 0 | { |
278 | 0 | mozilla::net::nsAsyncRedirectAutoCallback autoCallback(aCallback); |
279 | 0 |
|
280 | 0 | if (!aOldChannel) { |
281 | 0 | NS_ERROR("No channel when evaluating mixed content!"); |
282 | 0 | return NS_ERROR_FAILURE; |
283 | 0 | } |
284 | 0 |
|
285 | 0 | // If we are in the parent process in e10s, we don't have access to the |
286 | 0 | // document node, and hence ShouldLoad will fail when we try to get |
287 | 0 | // the docShell. If that's the case, ignore mixed content checks |
288 | 0 | // on redirects in the parent. Let the child check for mixed content. |
289 | 0 | nsCOMPtr<nsIParentChannel> is_ipc_channel; |
290 | 0 | NS_QueryNotificationCallbacks(aNewChannel, is_ipc_channel); |
291 | 0 | if (is_ipc_channel) { |
292 | 0 | return NS_OK; |
293 | 0 | } |
294 | 0 | |
295 | 0 | nsresult rv; |
296 | 0 | nsCOMPtr<nsIURI> oldUri; |
297 | 0 | rv = aOldChannel->GetURI(getter_AddRefs(oldUri)); |
298 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
299 | 0 |
|
300 | 0 | nsCOMPtr<nsIURI> newUri; |
301 | 0 | rv = aNewChannel->GetURI(getter_AddRefs(newUri)); |
302 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
303 | 0 |
|
304 | 0 | // Get the loading Info from the old channel |
305 | 0 | nsCOMPtr<nsILoadInfo> loadInfo; |
306 | 0 | rv = aOldChannel->GetLoadInfo(getter_AddRefs(loadInfo)); |
307 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
308 | 0 | if (!loadInfo) { |
309 | 0 | // XXX: We want to have a loadInfo on all channels, but we don't yet. |
310 | 0 | // If an addon creates a channel, they may not set loadinfo. If that |
311 | 0 | // channel redirects from one page to another page, we would get caught |
312 | 0 | // in this code path. Hence, we have to return NS_OK. Once we have more |
313 | 0 | // confidence that all channels have loadinfo, we can change this to |
314 | 0 | // a failure. See bug 1077201. |
315 | 0 | return NS_OK; |
316 | 0 | } |
317 | 0 | |
318 | 0 | nsCOMPtr<nsIPrincipal> requestingPrincipal = loadInfo->LoadingPrincipal(); |
319 | 0 |
|
320 | 0 | // Since we are calling shouldLoad() directly on redirects, we don't go through the code |
321 | 0 | // in nsContentPolicyUtils::NS_CheckContentLoadPolicy(). Hence, we have to |
322 | 0 | // duplicate parts of it here. |
323 | 0 | if (requestingPrincipal) { |
324 | 0 | // We check to see if the loadingPrincipal is systemPrincipal and return |
325 | 0 | // early if it is |
326 | 0 | if (nsContentUtils::IsSystemPrincipal(requestingPrincipal)) { |
327 | 0 | return NS_OK; |
328 | 0 | } |
329 | 0 | } |
330 | 0 | |
331 | 0 | int16_t decision = REJECT_REQUEST; |
332 | 0 | rv = ShouldLoad(newUri, |
333 | 0 | loadInfo, |
334 | 0 | EmptyCString(), // aMimeGuess |
335 | 0 | &decision); |
336 | 0 | if (NS_FAILED(rv)) { |
337 | 0 | autoCallback.DontCallback(); |
338 | 0 | aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI); |
339 | 0 | return NS_BINDING_FAILED; |
340 | 0 | } |
341 | 0 | |
342 | 0 | // If the channel is about to load mixed content, abort the channel |
343 | 0 | if (!NS_CP_ACCEPTED(decision)) { |
344 | 0 | autoCallback.DontCallback(); |
345 | 0 | aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI); |
346 | 0 | return NS_BINDING_FAILED; |
347 | 0 | } |
348 | 0 | |
349 | 0 | return NS_OK; |
350 | 0 | } |
351 | | |
352 | | /* This version of ShouldLoad() is non-static and called by the Content Policy |
353 | | * API and AsyncOnChannelRedirect(). See nsIContentPolicy::ShouldLoad() |
354 | | * for detailed description of the parameters. |
355 | | */ |
356 | | NS_IMETHODIMP |
357 | | nsMixedContentBlocker::ShouldLoad(nsIURI* aContentLocation, |
358 | | nsILoadInfo* aLoadInfo, |
359 | | const nsACString& aMimeGuess, |
360 | | int16_t* aDecision) |
361 | 0 | { |
362 | 0 | uint32_t contentType = aLoadInfo->InternalContentPolicyType(); |
363 | 0 | nsCOMPtr<nsISupports> requestingContext = aLoadInfo->GetLoadingContext(); |
364 | 0 | nsCOMPtr<nsIPrincipal> requestPrincipal = aLoadInfo->TriggeringPrincipal(); |
365 | 0 | nsCOMPtr<nsIURI> requestingLocation; |
366 | 0 | nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadInfo->LoadingPrincipal(); |
367 | 0 | if (loadingPrincipal) { |
368 | 0 | loadingPrincipal->GetURI(getter_AddRefs(requestingLocation)); |
369 | 0 | } |
370 | 0 |
|
371 | 0 | // We pass in false as the first parameter to ShouldLoad(), because the |
372 | 0 | // callers of this method don't know whether the load went through cached |
373 | 0 | // image redirects. This is handled by direct callers of the static |
374 | 0 | // ShouldLoad. |
375 | 0 | nsresult rv = ShouldLoad(false, // aHadInsecureImageRedirect |
376 | 0 | contentType, |
377 | 0 | aContentLocation, |
378 | 0 | requestingLocation, |
379 | 0 | requestingContext, |
380 | 0 | aMimeGuess, |
381 | 0 | requestPrincipal, |
382 | 0 | aDecision); |
383 | 0 | return rv; |
384 | 0 | } |
385 | | |
386 | | bool |
387 | 0 | nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(nsIURI* aURL) { |
388 | 0 | nsAutoCString host; |
389 | 0 | nsresult rv = aURL->GetHost(host); |
390 | 0 | NS_ENSURE_SUCCESS(rv, false); |
391 | 0 |
|
392 | 0 | // We could also allow 'localhost' (if we can guarantee that it resolves |
393 | 0 | // to a loopback address), but Chrome doesn't support it as of writing. For |
394 | 0 | // web compat, lets only allow what Chrome allows. |
395 | 0 | return host.EqualsLiteral("127.0.0.1") || host.EqualsLiteral("::1"); |
396 | 0 | } |
397 | | |
398 | | /* Maybe we have a .onion URL. Treat it as whitelisted as well if |
399 | | * `dom.securecontext.whitelist_onions` is `true`. |
400 | | */ |
401 | | bool |
402 | 0 | nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(nsIURI* aURL) { |
403 | 0 | static bool sInited = false; |
404 | 0 | static bool sWhiteListOnions = false; |
405 | 0 | if (!sInited) { |
406 | 0 | Preferences::AddBoolVarCache(&sWhiteListOnions, |
407 | 0 | "dom.securecontext.whitelist_onions"); |
408 | 0 | sInited = true; |
409 | 0 | } |
410 | 0 | if (!sWhiteListOnions) { |
411 | 0 | return false; |
412 | 0 | } |
413 | 0 | |
414 | 0 | nsAutoCString host; |
415 | 0 | nsresult rv = aURL->GetHost(host); |
416 | 0 | NS_ENSURE_SUCCESS(rv, false); |
417 | 0 | return StringEndsWith(host, NS_LITERAL_CSTRING(".onion")); |
418 | 0 | } |
419 | | |
420 | | /* Static version of ShouldLoad() that contains all the Mixed Content Blocker |
421 | | * logic. Called from non-static ShouldLoad(). |
422 | | */ |
423 | | nsresult |
424 | | nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, |
425 | | uint32_t aContentType, |
426 | | nsIURI* aContentLocation, |
427 | | nsIURI* aRequestingLocation, |
428 | | nsISupports* aRequestingContext, |
429 | | const nsACString& aMimeGuess, |
430 | | nsIPrincipal* aRequestPrincipal, |
431 | | int16_t* aDecision) |
432 | 0 | { |
433 | 0 | // Asserting that we are on the main thread here and hence do not have to lock |
434 | 0 | // and unlock sBlockMixedScript and sBlockMixedDisplay before reading/writing |
435 | 0 | // to them. |
436 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
437 | 0 |
|
438 | 0 | bool isPreload = nsContentUtils::IsPreloadType(aContentType); |
439 | 0 |
|
440 | 0 | // The content policy type that we receive may be an internal type for |
441 | 0 | // scripts. Let's remember if we have seen a worker type, and reset it to the |
442 | 0 | // external type in all cases right now. |
443 | 0 | bool isWorkerType = aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER || |
444 | 0 | aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER || |
445 | 0 | aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER; |
446 | 0 | aContentType = nsContentUtils::InternalContentPolicyTypeToExternal(aContentType); |
447 | 0 |
|
448 | 0 | // Assume active (high risk) content and blocked by default |
449 | 0 | MixedContentTypes classification = eMixedScript; |
450 | 0 | // Make decision to block/reject by default |
451 | 0 | *aDecision = REJECT_REQUEST; |
452 | 0 |
|
453 | 0 | // Notes on non-obvious decisions: |
454 | 0 | // |
455 | 0 | // TYPE_DTD: A DTD can contain entity definitions that expand to scripts. |
456 | 0 | // |
457 | 0 | // TYPE_FONT: The TrueType hinting mechanism is basically a scripting |
458 | 0 | // language that gets interpreted by the operating system's font rasterizer. |
459 | 0 | // Mixed content web fonts are relatively uncommon, and we can can fall back |
460 | 0 | // to built-in fonts with minimal disruption in almost all cases. |
461 | 0 | // |
462 | 0 | // TYPE_OBJECT_SUBREQUEST could actually be either active content (e.g. a |
463 | 0 | // script that a plugin will execute) or display content (e.g. Flash video |
464 | 0 | // content). Until we have a way to determine active vs passive content |
465 | 0 | // from plugin requests (bug 836352), we will treat this as passive content. |
466 | 0 | // This is to prevent false positives from causing users to become |
467 | 0 | // desensitized to the mixed content blocker. |
468 | 0 | // |
469 | 0 | // TYPE_CSP_REPORT: High-risk because they directly leak information about |
470 | 0 | // the content of the page, and because blocking them does not have any |
471 | 0 | // negative effect on the page loading. |
472 | 0 | // |
473 | 0 | // TYPE_PING: Ping requests are POSTS, not GETs like images and media. |
474 | 0 | // Also, PING requests have no bearing on the rendering or operation of |
475 | 0 | // the page when used as designed, so even though they are lower risk than |
476 | 0 | // scripts, blocking them is basically risk-free as far as compatibility is |
477 | 0 | // concerned. |
478 | 0 | // |
479 | 0 | // TYPE_STYLESHEET: XSLT stylesheets can insert scripts. CSS positioning |
480 | 0 | // and other advanced CSS features can possibly be exploited to cause |
481 | 0 | // spoofing attacks (e.g. make a "grant permission" button look like a |
482 | 0 | // "refuse permission" button). |
483 | 0 | // |
484 | 0 | // TYPE_BEACON: Beacon requests are similar to TYPE_PING, and are blocked by |
485 | 0 | // default. |
486 | 0 | // |
487 | 0 | // TYPE_WEBSOCKET: The Websockets API requires browsers to |
488 | 0 | // reject mixed-content websockets: "If secure is false but the origin of |
489 | 0 | // the entry script has a scheme component that is itself a secure protocol, |
490 | 0 | // e.g. HTTPS, then throw a SecurityError exception." We already block mixed |
491 | 0 | // content websockets within the websockets implementation, so we don't need |
492 | 0 | // to do any blocking here, nor do we need to provide a way to undo or |
493 | 0 | // override the blocking. Websockets without TLS are very flaky anyway in the |
494 | 0 | // face of many HTTP-aware proxies. Compared to passive content, there is |
495 | 0 | // additional risk that the script using WebSockets will disclose sensitive |
496 | 0 | // information from the HTTPS page and/or eval (directly or indirectly) |
497 | 0 | // received data. |
498 | 0 | // |
499 | 0 | // TYPE_XMLHTTPREQUEST: XHR requires either same origin or CORS, so most |
500 | 0 | // mixed-content XHR will already be blocked by that check. This will also |
501 | 0 | // block HTTPS-to-HTTP XHR with CORS. The same security concerns mentioned |
502 | 0 | // above for WebSockets apply to XHR, and XHR should have the same security |
503 | 0 | // properties as WebSockets w.r.t. mixed content. XHR's handling of redirects |
504 | 0 | // amplifies these concerns. |
505 | 0 | // |
506 | 0 | // TYPE_SAVEAS_DOWNLOAD: Save-link-as feature is used to download a resource |
507 | 0 | // without involving a docShell. This kind of loading must be always be |
508 | 0 | // allowed. |
509 | 0 |
|
510 | 0 | static_assert(TYPE_DATAREQUEST == TYPE_XMLHTTPREQUEST, |
511 | 0 | "TYPE_DATAREQUEST is not a synonym for " |
512 | 0 | "TYPE_XMLHTTPREQUEST"); |
513 | 0 |
|
514 | 0 | switch (aContentType) { |
515 | 0 | // The top-level document cannot be mixed content by definition |
516 | 0 | case TYPE_DOCUMENT: |
517 | 0 | *aDecision = ACCEPT; |
518 | 0 | return NS_OK; |
519 | 0 | // Creating insecure websocket connections in a secure page is blocked already |
520 | 0 | // in the websocket constructor. We don't need to check the blocking here |
521 | 0 | // and we don't want to un-block |
522 | 0 | case TYPE_WEBSOCKET: |
523 | 0 | *aDecision = ACCEPT; |
524 | 0 | return NS_OK; |
525 | 0 |
|
526 | 0 | // Creating insecure connections for a save-as link download is acceptable. |
527 | 0 | // This download is completely disconnected from the docShell, but still |
528 | 0 | // using the same loading principal. |
529 | 0 | case TYPE_SAVEAS_DOWNLOAD: |
530 | 0 | *aDecision = ACCEPT; |
531 | 0 | return NS_OK; |
532 | 0 |
|
533 | 0 | // Static display content is considered moderate risk for mixed content so |
534 | 0 | // these will be blocked according to the mixed display preference |
535 | 0 | case TYPE_IMAGE: |
536 | 0 | case TYPE_MEDIA: |
537 | 0 | classification = eMixedDisplay; |
538 | 0 | break; |
539 | 0 | case TYPE_OBJECT_SUBREQUEST: |
540 | 0 | if (sBlockMixedObjectSubrequest) { |
541 | 0 | classification = eMixedScript; |
542 | 0 | } else { |
543 | 0 | classification = eMixedDisplay; |
544 | 0 | } |
545 | 0 | break; |
546 | 0 |
|
547 | 0 | // Active content (or content with a low value/risk-of-blocking ratio) |
548 | 0 | // that has been explicitly evaluated; listed here for documentation |
549 | 0 | // purposes and to avoid the assertion and warning for the default case. |
550 | 0 | case TYPE_BEACON: |
551 | 0 | case TYPE_CSP_REPORT: |
552 | 0 | case TYPE_DTD: |
553 | 0 | case TYPE_FETCH: |
554 | 0 | case TYPE_FONT: |
555 | 0 | case TYPE_IMAGESET: |
556 | 0 | case TYPE_OBJECT: |
557 | 0 | case TYPE_SCRIPT: |
558 | 0 | case TYPE_STYLESHEET: |
559 | 0 | case TYPE_SUBDOCUMENT: |
560 | 0 | case TYPE_PING: |
561 | 0 | case TYPE_WEB_MANIFEST: |
562 | 0 | case TYPE_XBL: |
563 | 0 | case TYPE_XMLHTTPREQUEST: |
564 | 0 | case TYPE_XSLT: |
565 | 0 | case TYPE_OTHER: |
566 | 0 | case TYPE_SPECULATIVE: |
567 | 0 | break; |
568 | 0 |
|
569 | 0 |
|
570 | 0 | // This content policy works as a whitelist. |
571 | 0 | default: |
572 | 0 | MOZ_ASSERT(false, "Mixed content of unknown type"); |
573 | 0 | } |
574 | 0 |
|
575 | 0 | // Make sure to get the URI the load started with. No need to check |
576 | 0 | // outer schemes because all the wrapping pseudo protocols inherit the |
577 | 0 | // security properties of the actual network request represented |
578 | 0 | // by the innerMost URL. |
579 | 0 | nsCOMPtr<nsIURI> innerContentLocation = NS_GetInnermostURI(aContentLocation); |
580 | 0 | if (!innerContentLocation) { |
581 | 0 | NS_ERROR("Can't get innerURI from aContentLocation"); |
582 | 0 | *aDecision = REJECT_REQUEST; |
583 | 0 | return NS_OK; |
584 | 0 | } |
585 | 0 |
|
586 | 0 | /* Get the scheme of the sub-document resource to be requested. If it is |
587 | 0 | * a safe to load in an https context then mixed content doesn't apply. |
588 | 0 | * |
589 | 0 | * Check Protocol Flags to determine if scheme is safe to load: |
590 | 0 | * URI_DOES_NOT_RETURN_DATA - e.g. |
591 | 0 | * "mailto" |
592 | 0 | * URI_IS_LOCAL_RESOURCE - e.g. |
593 | 0 | * "data", |
594 | 0 | * "resource", |
595 | 0 | * "moz-icon" |
596 | 0 | * URI_INHERITS_SECURITY_CONTEXT - e.g. |
597 | 0 | * "javascript" |
598 | 0 | * URI_IS_POTENTIALLY_TRUSTWORTHY - e.g. |
599 | 0 | * "https", |
600 | 0 | * "moz-safe-about" |
601 | 0 | * |
602 | 0 | */ |
603 | 0 | bool schemeLocal = false; |
604 | 0 | bool schemeNoReturnData = false; |
605 | 0 | bool schemeInherits = false; |
606 | 0 | bool schemeSecure = false; |
607 | 0 | if (NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE , &schemeLocal)) || |
608 | 0 | NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, &schemeNoReturnData)) || |
609 | 0 | NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &schemeInherits)) || |
610 | 0 | NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY, &schemeSecure))) { |
611 | 0 | *aDecision = REJECT_REQUEST; |
612 | 0 | return NS_ERROR_FAILURE; |
613 | 0 | } |
614 | 0 | // TYPE_IMAGE redirects are cached based on the original URI, not the final |
615 | 0 | // destination and hence cache hits for images may not have the correct |
616 | 0 | // innerContentLocation. Check if the cached hit went through an http redirect, |
617 | 0 | // and if it did, we can't treat this as a secure subresource. |
618 | 0 | if (!aHadInsecureImageRedirect && |
619 | 0 | (schemeLocal || schemeNoReturnData || schemeInherits || schemeSecure)) { |
620 | 0 | *aDecision = ACCEPT; |
621 | 0 | return NS_OK; |
622 | 0 | } |
623 | 0 | |
624 | 0 | // Since there are cases where aRequestingLocation and aRequestPrincipal are |
625 | 0 | // definitely not the owning document, we try to ignore them by extracting the |
626 | 0 | // requestingLocation in the following order: |
627 | 0 | // 1) from the aRequestingContext, either extracting |
628 | 0 | // a) the node's principal, or the |
629 | 0 | // b) script object's principal. |
630 | 0 | // 2) if aRequestingContext yields a principal but no location, we check |
631 | 0 | // if its the system principal. If it is, allow the load. |
632 | 0 | // 3) Special case handling for: |
633 | 0 | // a) speculative loads, where shouldLoad is called twice (bug 839235) |
634 | 0 | // and the first speculative load does not include a context. |
635 | 0 | // In this case we use aRequestingLocation to set requestingLocation. |
636 | 0 | // b) TYPE_CSP_REPORT which does not provide a context. In this case we |
637 | 0 | // use aRequestingLocation to set requestingLocation. |
638 | 0 | // c) content scripts from addon code that do not provide aRequestingContext |
639 | 0 | // or aRequestingLocation, but do provide aRequestPrincipal. |
640 | 0 | // If aRequestPrincipal is an expanded principal, we allow the load. |
641 | 0 | // 4) If we still end up not having a requestingLocation, we reject the load. |
642 | 0 | |
643 | 0 | nsCOMPtr<nsIPrincipal> principal; |
644 | 0 | // 1a) Try to get the principal if aRequestingContext is a node. |
645 | 0 | nsCOMPtr<nsINode> node = do_QueryInterface(aRequestingContext); |
646 | 0 | if (node) { |
647 | 0 | principal = node->NodePrincipal(); |
648 | 0 | } |
649 | 0 |
|
650 | 0 | // 1b) Try using the window's script object principal if it's not a node. |
651 | 0 | if (!principal) { |
652 | 0 | nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(aRequestingContext); |
653 | 0 | if (scriptObjPrin) { |
654 | 0 | principal = scriptObjPrin->GetPrincipal(); |
655 | 0 | } |
656 | 0 | } |
657 | 0 |
|
658 | 0 | nsCOMPtr<nsIURI> requestingLocation; |
659 | 0 | if (principal) { |
660 | 0 | principal->GetURI(getter_AddRefs(requestingLocation)); |
661 | 0 | } |
662 | 0 |
|
663 | 0 | // 2) if aRequestingContext yields a principal but no location, we check if its a system principal. |
664 | 0 | if (principal && !requestingLocation) { |
665 | 0 | if (nsContentUtils::IsSystemPrincipal(principal)) { |
666 | 0 | *aDecision = ACCEPT; |
667 | 0 | return NS_OK; |
668 | 0 | } |
669 | 0 | } |
670 | 0 | |
671 | 0 | // 3a,b) Special case handling for speculative loads and TYPE_CSP_REPORT. In |
672 | 0 | // such cases, aRequestingContext doesn't exist, so we use aRequestingLocation. |
673 | 0 | // Unfortunately we can not distinguish between speculative and normal loads here, |
674 | 0 | // otherwise we could special case this assignment. |
675 | 0 | if (!requestingLocation) { |
676 | 0 | requestingLocation = aRequestingLocation; |
677 | 0 | } |
678 | 0 |
|
679 | 0 | // 3c) Special case handling for content scripts from addons code, which only |
680 | 0 | // provide a aRequestPrincipal; aRequestingContext and aRequestingLocation are |
681 | 0 | // both null; if the aRequestPrincipal is an expandedPrincipal, we allow the load. |
682 | 0 | if (!principal && !requestingLocation && aRequestPrincipal) { |
683 | 0 | nsCOMPtr<nsIExpandedPrincipal> expanded = do_QueryInterface(aRequestPrincipal); |
684 | 0 | if (expanded) { |
685 | 0 | *aDecision = ACCEPT; |
686 | 0 | return NS_OK; |
687 | 0 | } |
688 | 0 | } |
689 | 0 | |
690 | 0 | // 4) Giving up. We still don't have a requesting location, therefore we can't tell |
691 | 0 | // if this is a mixed content load. Deny to be safe. |
692 | 0 | if (!requestingLocation) { |
693 | 0 | *aDecision = REJECT_REQUEST; |
694 | 0 | return NS_OK; |
695 | 0 | } |
696 | 0 | |
697 | 0 | // Check the parent scheme. If it is not an HTTPS page then mixed content |
698 | 0 | // restrictions do not apply. |
699 | 0 | bool parentIsHttps; |
700 | 0 | nsCOMPtr<nsIURI> innerRequestingLocation = NS_GetInnermostURI(requestingLocation); |
701 | 0 | if (!innerRequestingLocation) { |
702 | 0 | NS_ERROR("Can't get innerURI from requestingLocation"); |
703 | 0 | *aDecision = REJECT_REQUEST; |
704 | 0 | return NS_OK; |
705 | 0 | } |
706 | 0 |
|
707 | 0 | nsresult rv = innerRequestingLocation->SchemeIs("https", &parentIsHttps); |
708 | 0 | if (NS_FAILED(rv)) { |
709 | 0 | NS_ERROR("requestingLocation->SchemeIs failed"); |
710 | 0 | *aDecision = REJECT_REQUEST; |
711 | 0 | return NS_OK; |
712 | 0 | } |
713 | 0 | if (!parentIsHttps) { |
714 | 0 | *aDecision = ACCEPT; |
715 | 0 | return NS_OK; |
716 | 0 | } |
717 | 0 | |
718 | 0 | nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(aRequestingContext); |
719 | 0 | NS_ENSURE_TRUE(docShell, NS_OK); |
720 | 0 |
|
721 | 0 | // Disallow mixed content loads for workers, shared workers and service |
722 | 0 | // workers. |
723 | 0 | if (isWorkerType) { |
724 | 0 | // For workers, we can assume that we're mixed content at this point, since |
725 | 0 | // the parent is https, and the protocol associated with innerContentLocation |
726 | 0 | // doesn't map to the secure URI flags checked above. Assert this for |
727 | 0 | // sanity's sake |
728 | | #ifdef DEBUG |
729 | | bool isHttpsScheme = false; |
730 | | rv = innerContentLocation->SchemeIs("https", &isHttpsScheme); |
731 | | NS_ENSURE_SUCCESS(rv, rv); |
732 | | MOZ_ASSERT(!isHttpsScheme); |
733 | | #endif |
734 | | *aDecision = REJECT_REQUEST; |
735 | 0 | return NS_OK; |
736 | 0 | } |
737 | 0 |
|
738 | 0 | bool isHttpScheme = false; |
739 | 0 | rv = innerContentLocation->SchemeIs("http", &isHttpScheme); |
740 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
741 | 0 |
|
742 | 0 | // Loopback origins are not considered mixed content even over HTTP. See: |
743 | 0 | // https://w3c.github.io/webappsec-mixed-content/#should-block-fetch |
744 | 0 | if (isHttpScheme && |
745 | 0 | IsPotentiallyTrustworthyLoopbackURL(innerContentLocation)) { |
746 | 0 | *aDecision = ACCEPT; |
747 | 0 | return NS_OK; |
748 | 0 | } |
749 | 0 | |
750 | 0 | // .onion URLs are encrypted and authenticated. Don't treat them as mixed |
751 | 0 | // content if potentially trustworthy (i.e. whitelisted). |
752 | 0 | if (isHttpScheme && IsPotentiallyTrustworthyOnion(innerContentLocation)) { |
753 | 0 | *aDecision = ACCEPT; |
754 | 0 | return NS_OK; |
755 | 0 | } |
756 | 0 | |
757 | 0 | // The page might have set the CSP directive 'upgrade-insecure-requests'. In such |
758 | 0 | // a case allow the http: load to succeed with the promise that the channel will |
759 | 0 | // get upgraded to https before fetching any data from the netwerk. |
760 | 0 | // Please see: nsHttpChannel::Connect() |
761 | 0 | // |
762 | 0 | // Please note that the CSP directive 'upgrade-insecure-requests' only applies to |
763 | 0 | // http: and ws: (for websockets). Websockets are not subject to mixed content |
764 | 0 | // blocking since insecure websockets are not allowed within secure pages. Hence, |
765 | 0 | // we only have to check against http: here. Skip mixed content blocking if the |
766 | 0 | // subresource load uses http: and the CSP directive 'upgrade-insecure-requests' |
767 | 0 | // is present on the page. |
768 | 0 | nsIDocument* document = docShell->GetDocument(); |
769 | 0 | MOZ_ASSERT(document, "Expected a document"); |
770 | 0 | if (isHttpScheme && document->GetUpgradeInsecureRequests(isPreload)) { |
771 | 0 | *aDecision = ACCEPT; |
772 | 0 | return NS_OK; |
773 | 0 | } |
774 | 0 | |
775 | 0 | |
776 | 0 | // Allow http: mixed content if we are choosing to upgrade them when the |
777 | 0 | // pref "security.mixed_content.upgrade_display_content" is true. |
778 | 0 | // This behaves like GetUpgradeInsecureRequests above in that the channel will |
779 | 0 | // be upgraded to https before fetching any data from the netwerk. |
780 | 0 | bool isUpgradableDisplayType = nsContentUtils::IsUpgradableDisplayType(aContentType) && ShouldUpgradeMixedDisplayContent(); |
781 | 0 | if (isHttpScheme && isUpgradableDisplayType) { |
782 | 0 | *aDecision = ACCEPT; |
783 | 0 | return NS_OK; |
784 | 0 | } |
785 | 0 | |
786 | 0 | // The page might have set the CSP directive 'block-all-mixed-content' which |
787 | 0 | // should block not only active mixed content loads but in fact all mixed content |
788 | 0 | // loads, see https://www.w3.org/TR/mixed-content/#strict-checking |
789 | 0 | // Block all non secure loads in case the CSP directive is present. Please note |
790 | 0 | // that at this point we already know, based on |schemeSecure| that the load is |
791 | 0 | // not secure, so we can bail out early at this point. |
792 | 0 | if (document->GetBlockAllMixedContent(isPreload)) { |
793 | 0 | // log a message to the console before returning. |
794 | 0 | nsAutoCString spec; |
795 | 0 | rv = aContentLocation->GetSpec(spec); |
796 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
797 | 0 | NS_ConvertUTF8toUTF16 reportSpec(spec); |
798 | 0 |
|
799 | 0 | const char16_t* params[] = { reportSpec.get()}; |
800 | 0 | CSP_LogLocalizedStr("blockAllMixedContent", |
801 | 0 | params, ArrayLength(params), |
802 | 0 | EmptyString(), // aSourceFile |
803 | 0 | EmptyString(), // aScriptSample |
804 | 0 | 0, // aLineNumber |
805 | 0 | 0, // aColumnNumber |
806 | 0 | nsIScriptError::errorFlag, |
807 | 0 | NS_LITERAL_CSTRING("blockAllMixedContent"), |
808 | 0 | document->InnerWindowID(), |
809 | 0 | !!document->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId); |
810 | 0 | *aDecision = REJECT_REQUEST; |
811 | 0 | return NS_OK; |
812 | 0 | } |
813 | 0 |
|
814 | 0 | // Determine if the rootDoc is https and if the user decided to allow Mixed Content |
815 | 0 | bool rootHasSecureConnection = false; |
816 | 0 | bool allowMixedContent = false; |
817 | 0 | bool isRootDocShell = false; |
818 | 0 | rv = docShell->GetAllowMixedContentAndConnectionData(&rootHasSecureConnection, &allowMixedContent, &isRootDocShell); |
819 | 0 | if (NS_FAILED(rv)) { |
820 | 0 | *aDecision = REJECT_REQUEST; |
821 | 0 | return rv; |
822 | 0 | } |
823 | 0 | |
824 | 0 | // Get the sameTypeRoot tree item from the docshell |
825 | 0 | nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot; |
826 | 0 | docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); |
827 | 0 | NS_ASSERTION(sameTypeRoot, "No root tree item from docshell!"); |
828 | 0 |
|
829 | 0 | // When navigating an iframe, the iframe may be https |
830 | 0 | // but its parents may not be. Check the parents to see if any of them are https. |
831 | 0 | // If none of the parents are https, allow the load. |
832 | 0 | if (aContentType == TYPE_SUBDOCUMENT && !rootHasSecureConnection) { |
833 | 0 |
|
834 | 0 | bool httpsParentExists = false; |
835 | 0 |
|
836 | 0 | nsCOMPtr<nsIDocShellTreeItem> parentTreeItem; |
837 | 0 | parentTreeItem = docShell; |
838 | 0 |
|
839 | 0 | while(!httpsParentExists && parentTreeItem) { |
840 | 0 | nsCOMPtr<nsIWebNavigation> parentAsNav(do_QueryInterface(parentTreeItem)); |
841 | 0 | NS_ASSERTION(parentAsNav, "No web navigation object from parent's docshell tree item"); |
842 | 0 | nsCOMPtr<nsIURI> parentURI; |
843 | 0 |
|
844 | 0 | parentAsNav->GetCurrentURI(getter_AddRefs(parentURI)); |
845 | 0 | if (!parentURI) { |
846 | 0 | // if getting the URI fails, assume there is a https parent and break. |
847 | 0 | httpsParentExists = true; |
848 | 0 | break; |
849 | 0 | } |
850 | 0 | |
851 | 0 | nsCOMPtr<nsIURI> innerParentURI = NS_GetInnermostURI(parentURI); |
852 | 0 | if (!innerParentURI) { |
853 | 0 | NS_ERROR("Can't get innerURI from parentURI"); |
854 | 0 | *aDecision = REJECT_REQUEST; |
855 | 0 | return NS_OK; |
856 | 0 | } |
857 | 0 |
|
858 | 0 | if (NS_FAILED(innerParentURI->SchemeIs("https", &httpsParentExists))) { |
859 | 0 | // if getting the scheme fails, assume there is a https parent and break. |
860 | 0 | httpsParentExists = true; |
861 | 0 | break; |
862 | 0 | } |
863 | 0 | |
864 | 0 | // When the parent and the root are the same, we have traversed all the way up |
865 | 0 | // the same type docshell tree. Break out of the while loop. |
866 | 0 | if(sameTypeRoot == parentTreeItem) { |
867 | 0 | break; |
868 | 0 | } |
869 | 0 | |
870 | 0 | // update the parent to the grandparent. |
871 | 0 | nsCOMPtr<nsIDocShellTreeItem> newParentTreeItem; |
872 | 0 | parentTreeItem->GetSameTypeParent(getter_AddRefs(newParentTreeItem)); |
873 | 0 | parentTreeItem = newParentTreeItem; |
874 | 0 | } // end while loop. |
875 | 0 |
|
876 | 0 | if (!httpsParentExists) { |
877 | 0 | *aDecision = nsIContentPolicy::ACCEPT; |
878 | 0 | return NS_OK; |
879 | 0 | } |
880 | 0 | } |
881 | 0 | |
882 | 0 | // Get the root document from the sameTypeRoot |
883 | 0 | nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument(); |
884 | 0 | NS_ASSERTION(rootDoc, "No root document from document shell root tree item."); |
885 | 0 |
|
886 | 0 | // Get eventSink and the current security state from the docShell |
887 | 0 | nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell); |
888 | 0 | NS_ASSERTION(eventSink, "No eventSink from docShell."); |
889 | 0 | nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot); |
890 | 0 | NS_ASSERTION(rootShell, "No root docshell from document shell root tree item."); |
891 | 0 | uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN; |
892 | 0 | nsCOMPtr<nsISecureBrowserUI> securityUI; |
893 | 0 | rootShell->GetSecurityUI(getter_AddRefs(securityUI)); |
894 | 0 | // If there is no securityUI, document doesn't have a security state. |
895 | 0 | // Allow load and return early. |
896 | 0 | if (!securityUI) { |
897 | 0 | *aDecision = nsIContentPolicy::ACCEPT; |
898 | 0 | return NS_OK; |
899 | 0 | } |
900 | 0 | nsresult stateRV = securityUI->GetState(&state); |
901 | 0 |
|
902 | 0 | OriginAttributes originAttributes; |
903 | 0 | if (principal) { |
904 | 0 | originAttributes = principal->OriginAttributesRef(); |
905 | 0 | } else if (aRequestPrincipal) { |
906 | 0 | originAttributes = aRequestPrincipal->OriginAttributesRef(); |
907 | 0 | } |
908 | 0 |
|
909 | 0 |
|
910 | 0 | // At this point we know that the request is mixed content, and the only |
911 | 0 | // question is whether we block it. Record telemetry at this point as to |
912 | 0 | // whether HSTS would have fixed things by making the content location |
913 | 0 | // into an HTTPS URL. |
914 | 0 | // |
915 | 0 | // Note that we count this for redirects as well as primary requests. This |
916 | 0 | // will cause some degree of double-counting, especially when mixed content |
917 | 0 | // is not blocked (e.g., for images). For more detail, see: |
918 | 0 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1198572#c19 |
919 | 0 | // |
920 | 0 | // We do not count requests aHadInsecureImageRedirect=true, since these are |
921 | 0 | // just an artifact of the image caching system. |
922 | 0 | bool active = (classification == eMixedScript); |
923 | 0 | if (!aHadInsecureImageRedirect) { |
924 | 0 | if (XRE_IsParentProcess()) { |
925 | 0 | AccumulateMixedContentHSTS(innerContentLocation, active, |
926 | 0 | originAttributes); |
927 | 0 | } else { |
928 | 0 | // Ask the parent process to do the same call |
929 | 0 | mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); |
930 | 0 | if (cc) { |
931 | 0 | mozilla::ipc::URIParams uri; |
932 | 0 | SerializeURI(innerContentLocation, uri); |
933 | 0 | cc->SendAccumulateMixedContentHSTS(uri, active, |
934 | 0 | originAttributes); |
935 | 0 | } |
936 | 0 | } |
937 | 0 | } |
938 | 0 |
|
939 | 0 | // set hasMixedContentObjectSubrequest on this object if necessary |
940 | 0 | if (aContentType == TYPE_OBJECT_SUBREQUEST) { |
941 | 0 | if (!sBlockMixedObjectSubrequest) { |
942 | 0 | rootDoc->WarnOnceAbout(nsIDocument::eMixedDisplayObjectSubrequest); |
943 | 0 | } |
944 | 0 | rootDoc->SetHasMixedContentObjectSubrequest(true); |
945 | 0 | } |
946 | 0 |
|
947 | 0 | // If the content is display content, and the pref says display content should be blocked, block it. |
948 | 0 | if (sBlockMixedDisplay && classification == eMixedDisplay) { |
949 | 0 | if (allowMixedContent) { |
950 | 0 | LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride); |
951 | 0 | *aDecision = nsIContentPolicy::ACCEPT; |
952 | 0 | // See if mixed display content has already loaded on the page or if the state needs to be updated here. |
953 | 0 | // If mixed display hasn't loaded previously, then we need to call OnSecurityChange() to update the UI. |
954 | 0 | if (rootDoc->GetHasMixedDisplayContentLoaded()) { |
955 | 0 | return NS_OK; |
956 | 0 | } |
957 | 0 | rootDoc->SetHasMixedDisplayContentLoaded(true); |
958 | 0 |
|
959 | 0 | if (rootHasSecureConnection) { |
960 | 0 | // reset state security flag |
961 | 0 | state = state >> 4 << 4; |
962 | 0 | // set state security flag to broken, since there is mixed content |
963 | 0 | state |= nsIWebProgressListener::STATE_IS_BROKEN; |
964 | 0 |
|
965 | 0 | // If mixed active content is loaded, make sure to include that in the state. |
966 | 0 | if (rootDoc->GetHasMixedActiveContentLoaded()) { |
967 | 0 | state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT; |
968 | 0 | } |
969 | 0 |
|
970 | 0 | eventSink->OnSecurityChange(aRequestingContext, |
971 | 0 | (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); |
972 | 0 | } else { |
973 | 0 | // User has overriden the pref and the root is not https; |
974 | 0 | // mixed display content was allowed on an https subframe. |
975 | 0 | if (NS_SUCCEEDED(stateRV)) { |
976 | 0 | eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); |
977 | 0 | } |
978 | 0 | } |
979 | 0 | } else { |
980 | 0 | *aDecision = nsIContentPolicy::REJECT_REQUEST; |
981 | 0 | LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked); |
982 | 0 | if (!rootDoc->GetHasMixedDisplayContentBlocked() && NS_SUCCEEDED(stateRV)) { |
983 | 0 | rootDoc->SetHasMixedDisplayContentBlocked(true); |
984 | 0 | eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT)); |
985 | 0 | } |
986 | 0 | } |
987 | 0 | return NS_OK; |
988 | 0 | |
989 | 0 | } else if (sBlockMixedScript && classification == eMixedScript) { |
990 | 0 | // If the content is active content, and the pref says active content should be blocked, block it |
991 | 0 | // unless the user has choosen to override the pref |
992 | 0 | if (allowMixedContent) { |
993 | 0 | LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride); |
994 | 0 | *aDecision = nsIContentPolicy::ACCEPT; |
995 | 0 | // See if the state will change here. If it will, only then do we need to call OnSecurityChange() to update the UI. |
996 | 0 | if (rootDoc->GetHasMixedActiveContentLoaded()) { |
997 | 0 | return NS_OK; |
998 | 0 | } |
999 | 0 | rootDoc->SetHasMixedActiveContentLoaded(true); |
1000 | 0 |
|
1001 | 0 | if (rootHasSecureConnection) { |
1002 | 0 | // reset state security flag |
1003 | 0 | state = state >> 4 << 4; |
1004 | 0 | // set state security flag to broken, since there is mixed content |
1005 | 0 | state |= nsIWebProgressListener::STATE_IS_BROKEN; |
1006 | 0 |
|
1007 | 0 | // If mixed display content is loaded, make sure to include that in the state. |
1008 | 0 | if (rootDoc->GetHasMixedDisplayContentLoaded()) { |
1009 | 0 | state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT; |
1010 | 0 | } |
1011 | 0 |
|
1012 | 0 | eventSink->OnSecurityChange(aRequestingContext, |
1013 | 0 | (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); |
1014 | 0 |
|
1015 | 0 | return NS_OK; |
1016 | 0 | } else { |
1017 | 0 | // User has already overriden the pref and the root is not https; |
1018 | 0 | // mixed active content was allowed on an https subframe. |
1019 | 0 | if (NS_SUCCEEDED(stateRV)) { |
1020 | 0 | eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); |
1021 | 0 | } |
1022 | 0 | return NS_OK; |
1023 | 0 | } |
1024 | 0 | } else { |
1025 | 0 | //User has not overriden the pref by Disabling protection. Reject the request and update the security state. |
1026 | 0 | *aDecision = nsIContentPolicy::REJECT_REQUEST; |
1027 | 0 | LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked); |
1028 | 0 | // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI. |
1029 | 0 | if (rootDoc->GetHasMixedActiveContentBlocked()) { |
1030 | 0 | return NS_OK; |
1031 | 0 | } |
1032 | 0 | rootDoc->SetHasMixedActiveContentBlocked(true); |
1033 | 0 |
|
1034 | 0 | // The user has not overriden the pref, so make sure they still have an option by calling eventSink |
1035 | 0 | // which will invoke the doorhanger |
1036 | 0 | if (NS_SUCCEEDED(stateRV)) { |
1037 | 0 | eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT)); |
1038 | 0 | } |
1039 | 0 | return NS_OK; |
1040 | 0 | } |
1041 | 0 | } else { |
1042 | 0 | // The content is not blocked by the mixed content prefs. |
1043 | 0 |
|
1044 | 0 | // Log a message that we are loading mixed content. |
1045 | 0 | LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride); |
1046 | 0 |
|
1047 | 0 | // Fire the event from a script runner as it is unsafe to run script |
1048 | 0 | // from within ShouldLoad |
1049 | 0 | nsContentUtils::AddScriptRunner( |
1050 | 0 | new nsMixedContentEvent(aRequestingContext, classification, rootHasSecureConnection)); |
1051 | 0 | *aDecision = ACCEPT; |
1052 | 0 | return NS_OK; |
1053 | 0 | } |
1054 | 0 | } |
1055 | | |
1056 | | NS_IMETHODIMP |
1057 | | nsMixedContentBlocker::ShouldProcess(nsIURI* aContentLocation, |
1058 | | nsILoadInfo* aLoadInfo, |
1059 | | const nsACString& aMimeGuess, |
1060 | | int16_t* aDecision) |
1061 | 0 | { |
1062 | 0 | if (!aContentLocation) { |
1063 | 0 | // aContentLocation may be null when a plugin is loading without an associated URI resource |
1064 | 0 | if ( aLoadInfo->GetExternalContentPolicyType() == TYPE_OBJECT) { |
1065 | 0 | *aDecision = ACCEPT; |
1066 | 0 | return NS_OK; |
1067 | 0 | } |
1068 | 0 | |
1069 | 0 | *aDecision = REJECT_REQUEST; |
1070 | 0 | return NS_ERROR_FAILURE; |
1071 | 0 | } |
1072 | 0 | |
1073 | 0 | return ShouldLoad(aContentLocation, aLoadInfo, aMimeGuess, aDecision); |
1074 | 0 | } |
1075 | | |
1076 | | // Record information on when HSTS would have made mixed content not mixed |
1077 | | // content (regardless of whether it was actually blocked) |
1078 | | void |
1079 | | nsMixedContentBlocker::AccumulateMixedContentHSTS( |
1080 | | nsIURI* aURI, bool aActive, const OriginAttributes& aOriginAttributes) |
1081 | 0 | { |
1082 | 0 | // This method must only be called in the parent, because |
1083 | 0 | // nsSiteSecurityService is only available in the parent |
1084 | 0 | if (!XRE_IsParentProcess()) { |
1085 | 0 | MOZ_ASSERT(false); |
1086 | 0 | return; |
1087 | 0 | } |
1088 | 0 |
|
1089 | 0 | bool hsts; |
1090 | 0 | nsresult rv; |
1091 | 0 | nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv); |
1092 | 0 | if (NS_FAILED(rv)) { |
1093 | 0 | return; |
1094 | 0 | } |
1095 | 0 | rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, 0, |
1096 | 0 | aOriginAttributes, nullptr, nullptr, &hsts); |
1097 | 0 | if (NS_FAILED(rv)) { |
1098 | 0 | return; |
1099 | 0 | } |
1100 | 0 | |
1101 | 0 | // states: would upgrade, would prime, hsts info cached |
1102 | 0 | // active, passive |
1103 | 0 | // |
1104 | 0 | if (!aActive) { |
1105 | 0 | if (!hsts) { |
1106 | 0 | Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS, |
1107 | 0 | MCB_HSTS_PASSIVE_NO_HSTS); |
1108 | 0 | } |
1109 | 0 | else { |
1110 | 0 | Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS, |
1111 | 0 | MCB_HSTS_PASSIVE_WITH_HSTS); |
1112 | 0 | } |
1113 | 0 | } else { |
1114 | 0 | if (!hsts) { |
1115 | 0 | Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS, |
1116 | 0 | MCB_HSTS_ACTIVE_NO_HSTS); |
1117 | 0 | } |
1118 | 0 | else { |
1119 | 0 | Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS, |
1120 | 0 | MCB_HSTS_ACTIVE_WITH_HSTS); |
1121 | 0 | } |
1122 | 0 | } |
1123 | 0 | } |
1124 | | |
1125 | | bool |
1126 | | nsMixedContentBlocker::ShouldUpgradeMixedDisplayContent() |
1127 | 0 | { |
1128 | 0 | return sUpgradeMixedDisplay; |
1129 | 0 | } |