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

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

Powered by Google App Engine
This is Rietveld 408576698