Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(694)

Side by Side Diff: content/browser/frame_host/mixed_content_navigation_throttle.cc

Issue 1905033002: PlzNavigate: Move navigation-level mixed content checks to the browser. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@console-security-message
Patch Set: Addressed all jam@ latest comments. Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/frame_host/mixed_content_navigation_throttle.h"
6
7 #include "base/strings/stringprintf.h"
8 #include "content/browser/frame_host/frame_tree.h"
9 #include "content/browser/frame_host/frame_tree_node.h"
10 #include "content/browser/frame_host/navigation_handle_impl.h"
11 #include "content/browser/frame_host/render_frame_host_delegate.h"
12 #include "content/browser/renderer_host/render_view_host_impl.h"
13 #include "content/common/frame_messages.h"
14 #include "content/public/browser/content_browser_client.h"
15 #include "content/public/browser/render_frame_host.h"
16 #include "content/public/common/browser_side_navigation_policy.h"
17 #include "content/public/common/content_client.h"
18 #include "content/public/common/origin_util.h"
19 #include "content/public/common/url_constants.h"
20 #include "content/public/common/web_preferences.h"
21 #include "net/base/url_util.h"
22 #include "third_party/WebKit/public/platform/WebMixedContentContextType.h"
23 #include "url/gurl.h"
24 #include "url/origin.h"
25 #include "url/url_constants.h"
26
27 namespace {
28
29 using namespace content;
30
31 // Should return the same value as SchemeRegistry::shouldTreatURLSchemeAsSecure.
32 // TODO(carlosk): we have to figure out how to share schemes registered within
33 // Blink's SchemeRegistry with content/browser code. Should statically defined
34 // ones in Chrome/embedder come from a shared file? Should dynamically defined
35 // ones from extensions register both with browser and renderer code? See
36 // https://crbug.com/627502.
37 bool IsSecureScheme(const std::string& scheme) {
38 // Note: default schemes for URLSchemesRegistry::secureSchemes.
39 bool result = scheme == url::kHttpsScheme || scheme == url::kAboutScheme ||
40 scheme == url::kDataScheme || scheme == url::kWssScheme;
41
42 // Note: below are the schemes registered through registerURLSchemeAsSecure.
43 // Note: here and for other scheme "registration" code below some
44 // registrations should not happen as they depend on the target being built. I
45 // tried limiting that by using platform IF-DEF-s but it is insufficient.
46
47 // Registered from content/renderer/render_thread_impl.cc.
48 result |= scheme == kChromeUIScheme;
49 // Registered from chrome/common/url_constants.cc.
50 result |= scheme == "chrome-search";
51 #if !defined(OS_ANDROID) && !defined(OS_IOS)
52 // Registered from extensions/renderer/dispatcher.cc.
53 result |= scheme == "chrome-extension";
54 #endif
55 #if defined(OS_ANDROID)
56 // Registered from android_webview/renderer/aw_content_renderer_client.cc.
57 result |= scheme == "android-webview-video-poster";
58 #endif
59 return result;
60 }
61
62 // Should return the same value as SchemeRegistry::shouldTreatURLSchemeAsSecure.
63 bool HasPotentiallySecureScheme(const GURL& url) {
64 return IsSecureScheme(url.scheme());
65 }
66
67 // Should return the same value as SecurityOrigin::isLocal and
68 // SchemeRegistry::shouldTreatURLSchemeAsLocal.
69 // TODO(carlosk): Same registration problem as described in IsSecureScheme
70 // applies here. See https://crbug.com/627502.
71 bool HasLocalScheme(const GURL& url) {
72 // Note: default schemes for URLSchemesRegistry::localSchemes.
73 bool result = url.SchemeIs(url::kFileScheme);
74
75 // Note: below are the schemes registered through registerURLSchemeAsLocal.
76
77 #if defined(OS_CHROMEOS)
78 // Registered from chrome/renderer/chrome_content_renderer_client.cc.
79 result |= url.SchemeIs(content::kExternalFileScheme);
80 #endif
81 #if defined(OS_ANDROID)
82 // Registered from chrome/renderer/chrome_content_renderer_client.cc and
83 // from android_webview/renderer/aw_content_renderer_client.cc.
84 result |= url.SchemeIs(url::kContentScheme);
85 #endif
86
87 return result;
88 }
89
90 // This reflects the result of SecurityOrigin::isUnique considering the logic in
91 // SecurityOrigin::create that will return unique origins for URLs that cause
92 // shouldTreatAsUniqueOrigin to return true. The latter checks the scheme
93 // against shouldTreatURLSchemeAsNoAccess and a few other things that don't seem
94 // applicable here.
95 // TODO(carlosk): Same registration problem as described in IsSecureScheme
96 // applies here. See https://crbug.com/627502.
97 bool IsUniqueScheme(const GURL& url) {
98 // Note: default schemes for URLSchemesRegistry::schemesWithUniqueOrigins.
99 bool result = url.SchemeIs(url::kAboutScheme) ||
100 url.SchemeIs(url::kJavaScriptScheme) ||
101 url.SchemeIs(url::kDataScheme);
102
103 // Note: below are the schemes registered through registerURLSchemeAsNoAccess.
104
105 // Registered from chrome/renderer/chrome_render_thread_observer.cc.
106 result |= url.SchemeIs("chrome-native");
107
108 return result;
109 }
110
111 // TODO(carlosk): Same registration problem as described in IsSecureScheme
112 // applies here. See https://crbug.com/627502.
113 bool ShouldTreatURLSchemeAsCORSEnabled(const GURL& url) {
114 // Note: default schemes for URLSchemesRegistry::CORSEnabledSchemes.
115 bool result = url.SchemeIsHTTPOrHTTPS() || url.SchemeIs(url::kDataScheme);
116
117 // Note: below are the schemes registered through
118 // registerURLSchemeAsCORSEnabled.
119
120 // Registered from content/renderer/render_thread_impl.cc.
121 result |= url.SchemeIs(kChromeUIScheme);
122 #if !defined(OS_ANDROID) && !defined(OS_IOS)
123 // Registered from extensions/renderer/dispatcher.cc.
124 result |= url.SchemeIs("chrome-extension");
125 #endif
126
127 return result;
128 }
129
130 // Should return the same value as SecurityOrigin::isSecure.
131 bool SecurityOriginIsSecure(const GURL& url) {
132 return HasPotentiallySecureScheme(url) ||
133 (url.SchemeIsFileSystem() && url.inner_url() &&
134 HasPotentiallySecureScheme(*url.inner_url())) ||
135 (url.SchemeIsBlob() &&
136 HasPotentiallySecureScheme(GURL(url.GetContent()))) ||
137 IsOriginWhiteListedTrustworthy(url);
138 }
139
140 // Should return the same value as the resource URL checks made inside
141 // MixedContentChecker::isMixedContent.
142 bool IsUrlPotentiallySecure(const GURL& url) {
143 // TODO(carlosk): secure origin checks don't match between content and Blink
144 // hence this implementation here instead of a direct call to IsOriginSecure
145 // (in origin_util.cc). See https://crbug.com/629059.
146
147 bool is_secure = SecurityOriginIsSecure(url);
148
149 // blob: and filesystem: URLs never hit the network, and access is restricted
150 // to same-origin contexts, so they are not blocked either.
151 if (url.SchemeIs(url::kBlobScheme) || url.SchemeIs(url::kFileSystemScheme))
152 is_secure |= true;
153
154 // These next checks mimics the behavior of
155 // SecurityOrigin::isPotentiallyTrustworthy without duplicating much of the
156 // checks already performed previously (hence this not being enclosed in
157 // another method). The logic here will consider a unique scheme secure as if
158 // SecurityOrigin::m_isUniqueOriginPotentiallyTrustworthy was true.
159 if (IsUniqueScheme(url) || HasLocalScheme(url) ||
160 net::IsLocalhost(url.HostNoBrackets()))
161 is_secure |= true;
162
163 // TODO(mkwst): Remove this once 'localhost' is no longer considered
164 // potentially trustworthy.
165 if (is_secure && url.SchemeIs(url::kHttpScheme) &&
166 net::IsLocalHostname(url.HostNoBrackets(), nullptr)) {
167 is_secure = false;
168 }
169
170 return is_secure;
171 }
172
173 // This method should return the same results as
174 // SchemeRegistry::shouldTreatURLSchemeAsRestrictingMixedContent.
175 bool DoesOriginSchemeRestricsMixedContent(const url::Origin& origin) {
176 return origin.scheme() == url::kHttpsScheme;
177 }
178
179 void UpdateRendererOnMixedContentFound(NavigationHandleImpl* handle_impl,
180 const GURL& mixed_content_url,
181 bool was_allowed,
182 bool for_redirect) {
183 // TODO(carlosk): the root node should never be considered mixed content for
184 // now. Once/if the browser starts also checking form submits than this will
185 // happen and this DCHECK should be updated.
186 DCHECK(handle_impl->frame_tree_node()->parent());
187 RenderFrameHost* rfh = handle_impl->frame_tree_node()->current_frame_host();
188 rfh->Send(new FrameMsg_MixedContentFoundByTheBrowser(
189 rfh->GetRoutingID(), mixed_content_url, handle_impl->GetURL(),
190 handle_impl->request_context_type(), was_allowed, for_redirect));
191 }
192
193 } // namespace
194
195 namespace content {
196
197 MixedContentNavigationThrottle::MixedContentNavigationThrottle(
198 NavigationHandle* navigation_handle)
199 : NavigationThrottle(navigation_handle) {
200 DCHECK(IsBrowserSideNavigationEnabled());
201 }
202
203 MixedContentNavigationThrottle::~MixedContentNavigationThrottle() {}
204
205 ThrottleCheckResult MixedContentNavigationThrottle::WillStartRequest() {
206 bool should_block = ShouldBlockNavigation(false);
207 return should_block ? ThrottleCheckResult::CANCEL
208 : ThrottleCheckResult::PROCEED;
209 }
210
211 ThrottleCheckResult MixedContentNavigationThrottle::WillRedirectRequest() {
212 // Upon redirects the same checks are to be executed as for requests.
213 bool should_block = ShouldBlockNavigation(true);
214 return should_block ? ThrottleCheckResult::CANCEL
215 : ThrottleCheckResult::PROCEED;
216 }
217
218 ThrottleCheckResult MixedContentNavigationThrottle::WillProcessResponse() {
219 // TODO(carlosk): at this point we must check the final security level of
220 // the connection! Does it use an outdated protocol? See
221 // MixedContentChecker::handleCertificateError
222 return ThrottleCheckResult::PROCEED;
223 }
224
225 // Based off of MixedContentChecker::shouldBlockFetch.
226 bool MixedContentNavigationThrottle::ShouldBlockNavigation(bool for_redirect) {
227 NavigationHandleImpl* handle_impl =
228 static_cast<NavigationHandleImpl*>(navigation_handle());
229 FrameTreeNode* node = handle_impl->frame_tree_node();
230
231 // Find the parent node with mixed content, if any.
232 FrameTreeNode* mixed_content_node =
233 InWhichFrameIsContentMixed(node, handle_impl->GetURL());
234 if (!mixed_content_node) {
235 MaybeSendBlinkFeatureUsageReport();
236 return false;
237 }
238
239 // From this point on we know this is not a main frame navigation and that
240 // there is mixed-content. Now let's decide if it's OK to proceed with it.
241
242 const WebPreferences& prefs = mixed_content_node->current_frame_host()
243 ->render_view_host()
244 ->GetWebkitPreferences();
245
246 ReportBasicMixedContentFeatures(handle_impl->request_context_type(),
247 handle_impl->mixed_content_context_type(),
248 prefs);
249
250 // If we're in strict mode, we'll automagically fail everything, and
251 // intentionally skip the client/embedder checks in order to prevent degrading
252 // the site's security UI.
253 bool block_all_mixed_content = !!(
254 mixed_content_node->current_replication_state().insecure_request_policy &
255 blink::kBlockAllMixedContent);
256 bool strictMode =
257 prefs.strict_mixed_content_checking || block_all_mixed_content;
258
259 blink::WebMixedContentContextType mixed_context_type =
260 handle_impl->mixed_content_context_type();
261
262 if (!ShouldTreatURLSchemeAsCORSEnabled(handle_impl->GetURL())) {
263 mixed_context_type = blink::WebMixedContentContextType::OptionallyBlockable;
264 }
265
266 bool allowed = false;
267 ContentBrowserClient* browser_client = GetContentClient()->browser();
268 RenderFrameHostDelegate* frame_host_delegate =
269 node->current_frame_host()->delegate();
270 switch (mixed_context_type) {
271 case blink::WebMixedContentContextType::OptionallyBlockable:
272 allowed = !strictMode;
273 if (allowed) {
274 browser_client->PassiveInsecureContentFound(handle_impl->GetURL());
275 frame_host_delegate->DidDisplayInsecureContent();
276 }
277 break;
278
279 case blink::WebMixedContentContextType::Blockable: {
280 // Note: from the renderer side implementation it doesn't seem like we
281 // need to care about the UseCounter reporting of
282 // BlockableMixedContentInSubframeBlocked because it is only triggered for
283 // sub-resources which are not handled in the browser.
284 bool shouldAskEmbedder =
285 !strictMode && (!prefs.strictly_block_blockable_mixed_content ||
286 prefs.allow_running_insecure_content);
287 allowed = shouldAskEmbedder &&
288 browser_client->ShouldAllowRunningInsecureContent(
289 prefs.allow_running_insecure_content,
290 mixed_content_node->current_origin(), handle_impl->GetURL(),
291 handle_impl->GetWebContents());
292 if (allowed) {
293 const GURL& origin_url = mixed_content_node->current_url().GetOrigin();
294 frame_host_delegate->DidRunInsecureContent(origin_url,
295 handle_impl->GetURL());
296 browser_client->RecordURLMetric(
297 "ContentSettings.MixedScript.RanMixedScript", origin_url);
298 mixed_content_features_.insert(MixedContentBlockableAllowed);
299 }
300 break;
301 }
302
303 case blink::WebMixedContentContextType::ShouldBeBlockable:
304 allowed = !strictMode;
305 if (allowed)
306 frame_host_delegate->DidDisplayInsecureContent();
307 break;
308
309 case blink::WebMixedContentContextType::NotMixedContent:
310 NOTREACHED();
311 break;
312 };
313
314 UpdateRendererOnMixedContentFound(
315 handle_impl, mixed_content_node->current_url(), allowed, for_redirect);
316 MaybeSendBlinkFeatureUsageReport();
317
318 return !allowed;
319 }
320
321 FrameTreeNode* MixedContentNavigationThrottle::InWhichFrameIsContentMixed(
322 FrameTreeNode* node,
323 const GURL& url) {
324 // Main frame navigations cannot be mixed content.
325 // TODO(carlosk): except for form submissions which will be dealt with later.
326 if (node->IsMainFrame())
327 return nullptr;
328
329 // There's no mixed content if any of these are true:
330 // - The navigated URL is potentially secure.
331 // - The root nor parent frames' origins are secure.
332 FrameTreeNode* mixed_content_node = nullptr;
333 FrameTreeNode* root = node->frame_tree()->root();
334 FrameTreeNode* parent = node->parent();
335 if (!IsUrlPotentiallySecure(url)) {
336 // TODO(carlosk): don't we need to check more than just the immediate parent
337 // and the root? Is it always the case that these two are the only sources
338 // for obtaining the "origin of the security context"?
339 // See https://crbug.com/623486.
340
341 // Checks if the root or immediate parent frame's origin are secure.
342 if (DoesOriginSchemeRestricsMixedContent(root->current_origin()))
343 mixed_content_node = root;
344 else if (DoesOriginSchemeRestricsMixedContent(parent->current_origin()))
345 mixed_content_node = parent;
346 }
347
348 // Note: This code below should behave the same way as as the two calls to
349 // measureStricterVersionOfIsMixedContent from inside
350 // MixedContentChecker::inWhichFrameIs.
351 if (mixed_content_node) {
352 // We're currently only checking for mixed content in `https://*` contexts.
353 // What about other "secure" contexts the SchemeRegistry knows about? We'll
354 // use this method to measure the occurance of non-webby mixed content to
355 // make sure we're not breaking the world without realizing it.
356 // Note: Based off of measureStricterVersionOfIsMixedContent in
357 // MixedContentChecker.cpp.
358 // TODO(carlosk): this will only ever work once we allow registration of new
359 // potentially secure schemes. crbug.com/627502
360 if (mixed_content_node->current_origin().scheme() != url::kHttpsScheme) {
361 mixed_content_features_.insert(
362 MixedContentInNonHTTPSFrameThatRestrictsMixedContent);
363 }
364 } else if (!SecurityOriginIsSecure(url) &&
365 (IsSecureScheme(root->current_origin().scheme()) ||
366 IsSecureScheme(parent->current_origin().scheme()))) {
367 mixed_content_features_.insert(
368 MixedContentInSecureFrameThatDoesNotRestrictMixedContent);
369 }
370 return mixed_content_node;
371 }
372
373 void MixedContentNavigationThrottle::MaybeSendBlinkFeatureUsageReport() {
374 if (!mixed_content_features_.empty()) {
375 NavigationHandleImpl* handle_impl =
376 static_cast<NavigationHandleImpl*>(navigation_handle());
377 RenderFrameHost* rfh = handle_impl->frame_tree_node()->current_frame_host();
378 rfh->Send(new FrameMsg_BlinkFeatureUsageReport(rfh->GetRoutingID(),
379 mixed_content_features_));
380 mixed_content_features_.clear();
381 }
382 }
383
384 // Based off of MixedContentChecker::count.
385 void MixedContentNavigationThrottle::ReportBasicMixedContentFeatures(
386 RequestContextType request_context_type,
387 blink::WebMixedContentContextType mixed_content_context_type,
388 const WebPreferences& prefs) {
389 mixed_content_features_.insert(MixedContentPresent);
390
391 // Report any blockable content.
392 if (mixed_content_context_type ==
393 blink::WebMixedContentContextType::Blockable) {
394 mixed_content_features_.insert(MixedContentBlockable);
395 return;
396 }
397
398 // Note: as there's no mixed content checks for sub resources on the browser
399 // there should only be a subset |request_context_type| values that could ever
400 // be found here.
401 UseCounterFeature feature;
402 switch (request_context_type) {
403 case REQUEST_CONTEXT_TYPE_INTERNAL:
404 feature = MixedContentInternal;
405 break;
406 case REQUEST_CONTEXT_TYPE_PREFETCH:
407 feature = MixedContentPrefetch;
408 break;
409
410 case REQUEST_CONTEXT_TYPE_AUDIO:
411 case REQUEST_CONTEXT_TYPE_DOWNLOAD:
412 case REQUEST_CONTEXT_TYPE_FAVICON:
413 case REQUEST_CONTEXT_TYPE_IMAGE:
414 case REQUEST_CONTEXT_TYPE_PLUGIN:
415 case REQUEST_CONTEXT_TYPE_VIDEO:
416 default:
417 NOTREACHED() << "RequestContextType has value " << request_context_type
418 << " and has WebMixedContentContextType of "
419 << static_cast<int>(mixed_content_context_type);
420 return;
421 }
422 mixed_content_features_.insert(feature);
423 }
424
425 // static
426 bool MixedContentNavigationThrottle::IsMixedContentForTesting(
427 const GURL& origin_url,
428 const GURL& url) {
429 const url::Origin origin(origin_url);
430 return !IsUrlPotentiallySecure(url) &&
431 DoesOriginSchemeRestricsMixedContent(origin);
432 }
433
434 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698