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

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: Rebase after 3 spin off CLs landed. Created 3 years, 12 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/common/browser_side_navigation_policy.h"
16 #include "content/public/common/content_client.h"
17 #include "content/public/common/origin_util.h"
18 #include "content/public/common/url_constants.h"
19 #include "content/public/common/web_preferences.h"
20 #include "net/base/url_util.h"
21 #include "third_party/WebKit/public/platform/WebMixedContent.h"
22 #include "url/gurl.h"
23 #include "url/origin.h"
24 #include "url/url_constants.h"
25
26 namespace {
27
28 using namespace content;
29
30 // Should return the same value as SchemeRegistry::shouldTreatURLSchemeAsSecure.
31 bool IsSecureScheme(const std::string& scheme) {
32 // TODO(carlosk): find out how to properly handle these secure schemes. Should
33 // statically defined ones in Chrome/embedder come from a shared file? Should
34 // dynamically defined ones from extensions register both with browser and
35 // renderer code? See https://crbug.com/627502.
36 bool result =
37 // Note: schemes statically defined in secureSchemes()
38 scheme == url::kHttpsScheme || scheme == url::kAboutScheme ||
39 scheme == url::kDataScheme || scheme == url::kWssScheme ||
40 // Note: schemes "dynamically" registered with
41 // SchemeRegistry::registerURLSchemeAsSecure.
42 scheme == kChromeUIScheme;
43 return result;
44 }
45
46 // Should return the same value as SchemeRegistry::shouldTreatURLSchemeAsSecure.
47 bool HasPotentiallySecureScheme(const GURL& url) {
48 return IsSecureScheme(url.scheme());
49 }
50
51 // Should return the same value as SecurityOrigin::isLocal and
52 // SchemeRegistry::shouldTreatURLSchemeAsLocal.
53 bool HasLocalScheme(const GURL& url) {
54 // TODO(carlosk): Same registration problem as described in
55 // HasPotentiallySecureScheme applies here. See https://crbug.com/627502.
56 bool result =
57 // Note: schemes statically defined in schemesWithUniqueOrigins()
58 url.SchemeIs(url::kAboutScheme) || url.SchemeIs(url::kJavaScriptScheme) ||
59 url.SchemeIs(url::kDataScheme) ||
60 // Note: schemes "dynamically" registered with
61 // SchemeRegistry::registerURLSchemeAsNoAccess. There's no content/
62 // re-definition of kChromeNativeScheme.
63 url.SchemeIs("chrome-native");
64 return result;
65 }
66
67 // Should return the same value as SecurityOrigin::isSecure.
68 bool SecurityOriginIsSecure(const GURL& url) {
69 return HasPotentiallySecureScheme(url) ||
70 (url.SchemeIsFileSystem() && url.inner_url() &&
71 HasPotentiallySecureScheme(*url.inner_url())) ||
72 (url.SchemeIsBlob() &&
73 HasPotentiallySecureScheme(GURL(url.GetContent()))) ||
74 IsOriginWhiteListedTrustworthy(url);
75 }
76
77 // Should return the same value as the resource URL checks made inside
78 // MixedContentChecker::isMixedContent.
79 bool IsUrlPotentiallySecure(const GURL& url) {
80 // TODO(carlosk): secure origin checks don't match between content and Blink
81 // hence this implementation here instead of a direct call to IsOriginSecure
82 // (in origin_util.cc). See https://crbug.com/629059.
83
84 bool is_secure = SecurityOriginIsSecure(url);
85
86 // blob: and filesystem: URLs never hit the network, and access is restricted
87 // to same-origin contexts, so they are not blocked either.
88 if (url.SchemeIs(url::kBlobScheme) || url.SchemeIs(url::kFileSystemScheme))
89 is_secure |= true;
90
91 // Mimics SecurityOrigin::isPotentiallyTrustworthy without duplicating (much)
92 // of the checks already done previously.
93 if (HasLocalScheme(url) || net::IsLocalhost(url.HostNoBrackets()))
94 is_secure |= true;
95
96 // TODO(mkwst): Remove this once 'localhost' is no longer considered
97 // potentially trustworthy.
98 if (is_secure && url.SchemeIs(url::kHttpScheme) &&
99 net::IsLocalHostname(url.HostNoBrackets(), nullptr)) {
100 is_secure = false;
101 }
102
103 return is_secure;
104 }
105
106 // This method should return the same results as
107 // SchemeRegistry::shouldTreatURLSchemeAsRestrictingMixedContent.
108 bool DoesOriginSchemeRestricsMixedContent(const url::Origin& origin) {
109 return origin.scheme() == url::kHttpsScheme;
110 }
111
112 bool ShouldTreatURLSchemeAsCORSEnabled(const GURL& url) {
113 // TODO(carlosk): for CORS schemes we have the exact same issue as for the
114 // secure schemes above. See callers to and references in
115 // WebSecurityPolicy::registerURLSchemeAsCORSEnabled. See
116 // https://crbug.com/627502.
117 return
118 // Note: CORS schemes statically defined in CORSEnabledSchemes()
119 url.SchemeIsHTTPOrHTTPS() || url.SchemeIs(url::kDataScheme) ||
120 // Note: CORS schemes "dynamically" registered in
121 // RenderThreadImpl::RegisterSchemes.
122 url.SchemeIs(kChromeUIScheme);
123 }
124
125 const char* TypeNameFromContext(RequestContextType context) {
126 // Note: the static_cast below is guaranteed by the static asserts in
127 // content/child/web_url_request_util.cc.
128 return blink::WebMixedContent::requestContextName(
jam 2016/12/28 20:41:00 similarly here, can we just send an IPC to the ren
carlosk 2017/01/05 06:10:08 Done. I split FrameMsg_MixedContentUpdate IPC in
129 static_cast<blink::WebURLRequest::RequestContext>(context));
130 }
131
132 blink::WebMixedContent::ContextType MixedContextTypeFromContext(
133 RequestContextType context,
134 bool block_mixed_plugin_content) {
135 // Note: the static_cast below is guaranteed by the static asserts in
136 // content/child/web_url_request_util.cc.
137 return blink::WebMixedContent::contextTypeFromRequestContext(
jam 2016/12/28 20:41:00 we only include enums/POD from webkit in the brows
carlosk 2017/01/05 06:10:08 This proved a tad more complicated than I assumed.
jam 2017/01/05 17:21:53 The plugin setting is available in the renderer vi
carlosk 2017/01/10 02:10:44 Done. The content::RequestExtraData idea worked.
138 static_cast<blink::WebURLRequest::RequestContext>(context),
139 block_mixed_plugin_content);
140 }
141
142 // Based off of MixedContentChecker::logToConsoleAboutFetch.
143 void LogToConsoleAboutNavigation(FrameTreeNode* mixed_content_node,
144 NavigationHandleImpl* handle_impl,
145 const GURL& url,
146 bool allowed) {
147 FrameTreeNode* tree_node = handle_impl->frame_tree_node();
148 // TODO(carlosk): we should only use the very tree_node here for the form
149 // submission case. (D)CHECK for that when we get there. For now just check
150 // this is not the root node.
151 DCHECK(tree_node->parent());
152 RequestContextType request_context_type = handle_impl->request_context_type();
153
154 std::string message = base::StringPrintf(
155 "Mixed Content: The page at '%s' was loaded over HTTPS, but requested an "
156 "insecure %s '%s'. %s",
157 mixed_content_node->current_url().spec().c_str(),
158 TypeNameFromContext(request_context_type), url.spec().c_str(),
159 allowed ? "This content should also be served over HTTPS."
160 : "This request has been blocked; the content must be served "
161 "over HTTPS.");
162 ConsoleMessageLevel messageLevel =
163 allowed ? ConsoleMessageLevel::CONSOLE_MESSAGE_LEVEL_WARNING
164 : ConsoleMessageLevel::CONSOLE_MESSAGE_LEVEL_ERROR;
165 tree_node->current_frame_host()->AddMessageToConsole(messageLevel, message);
166 }
167
168 } // namespace
169
170 namespace content {
171
172 MixedContentNavigationThrottle::MixedContentNavigationThrottle(
173 NavigationHandle* navigation_handle)
174 : NavigationThrottle(navigation_handle) {
175 DCHECK(IsBrowserSideNavigationEnabled());
176 }
177
178 MixedContentNavigationThrottle::~MixedContentNavigationThrottle() {}
179
180 ThrottleCheckResult MixedContentNavigationThrottle::WillStartRequest() {
181 bool should_block = ShouldBlockNavigation(false);
182 return should_block ? ThrottleCheckResult::CANCEL
183 : ThrottleCheckResult::PROCEED;
184 }
185
186 ThrottleCheckResult MixedContentNavigationThrottle::WillRedirectRequest() {
187 // Upon redirects the same checks are to be executed as for requests.
188 bool should_block = ShouldBlockNavigation(true);
189 return should_block ? ThrottleCheckResult::CANCEL
190 : ThrottleCheckResult::PROCEED;
191 }
192
193 ThrottleCheckResult MixedContentNavigationThrottle::WillProcessResponse() {
194 // TODO(carlosk): at this point we must check the final security level of
195 // the connection! Does it use an outdated protocol? See
196 // MixedContentChecker::handleCertificateError
197 return ThrottleCheckResult::PROCEED;
198 }
199
200 // Based off of MixedContentChecker::shouldBlockFetch.
201 bool MixedContentNavigationThrottle::ShouldBlockNavigation(bool for_redirect) {
202 NavigationHandleImpl* handle_impl =
203 static_cast<NavigationHandleImpl*>(navigation_handle());
204 FrameTreeNode* node = handle_impl->frame_tree_node();
205
206 // Find the parent node with mixed content, if any.
207 FrameTreeNode* mixed_content_node =
208 InWhichFrameIsContentMixed(node, handle_impl->GetURL());
209 if (!mixed_content_node) {
210 MaybeSendRendererUpdate(node->current_frame_host(), for_redirect, false);
211 return false;
212 }
213
214 // From this point on we know this is not a main frame navigation and that
215 // there is mixed-content. Now let's decide if it's OK to proceed with it.
216
217 const WebPreferences& prefs = mixed_content_node->current_frame_host()
218 ->render_view_host()
219 ->GetWebkitPreferences();
220
221 ReportBasicMixedContentFeatures(handle_impl->request_context_type(), prefs);
222
223 // If we're in strict mode, we'll automagically fail everything, and
224 // intentionally skip the client/embedder checks in order to prevent degrading
225 // the site's security UI.
226 bool block_all_mixed_content = !!(
227 mixed_content_node->current_replication_state().insecure_request_policy &
228 blink::kBlockAllMixedContent);
229 bool strictMode =
230 prefs.strict_mixed_content_checking || block_all_mixed_content;
231
232 blink::WebMixedContent::ContextType mixed_context_type =
233 MixedContextTypeFromContext(handle_impl->request_context_type(),
234 prefs.block_mixed_plugin_content);
235
236 if (!ShouldTreatURLSchemeAsCORSEnabled(handle_impl->GetURL())) {
237 mixed_context_type =
238 blink::WebMixedContent::ContextType::OptionallyBlockable;
239 }
240
241 bool allowed = false;
242 ContentBrowserClient* browser_client = GetContentClient()->browser();
243 RenderFrameHostDelegate* frame_host_delegate =
244 node->current_frame_host()->delegate();
245 switch (mixed_context_type) {
246 case blink::WebMixedContent::ContextType::OptionallyBlockable:
247 allowed = !strictMode;
248 if (allowed) {
249 browser_client->PassiveInsecureContentFound(handle_impl->GetURL());
250 frame_host_delegate->DidDisplayInsecureContent();
251 }
252 break;
253
254 case blink::WebMixedContent::ContextType::Blockable: {
255 // Note: from the renderer side implementation it doesn't seem like we
256 // need to care about the UseCounter reporting of
257 // BlockableMixedContentInSubframeBlocked because it is only triggered for
258 // sub-resources which are not handled in the browser.
259 bool shouldAskEmbedder =
260 !strictMode && (!prefs.strictly_block_blockable_mixed_content ||
261 prefs.allow_running_insecure_content);
262 allowed = shouldAskEmbedder &&
263 browser_client->ShouldAllowRunningInsecureContent(
264 prefs.allow_running_insecure_content,
265 mixed_content_node->current_origin(), handle_impl->GetURL(),
266 handle_impl->GetWebContents());
267 if (allowed) {
268 const GURL& origin_url = mixed_content_node->current_url().GetOrigin();
269 frame_host_delegate->DidRunInsecureContent(origin_url,
270 handle_impl->GetURL());
271 browser_client->RecordURLMetric(
272 "ContentSettings.MixedScript.RanMixedScript", origin_url);
273 mixed_content_features_.insert(MixedContentBlockableAllowed);
274 }
275 break;
276 }
277
278 case blink::WebMixedContent::ContextType::ShouldBeBlockable:
279 allowed = !strictMode;
280 if (allowed)
281 frame_host_delegate->DidDisplayInsecureContent();
282 break;
283
284 case blink::WebMixedContent::ContextType::NotMixedContent:
285 NOTREACHED();
286 break;
287 };
288
289 LogToConsoleAboutNavigation(mixed_content_node, handle_impl,
290 handle_impl->GetURL(), allowed);
291 MaybeSendRendererUpdate(node->current_frame_host(), for_redirect, true);
292
293 return !allowed;
294 }
295
296 FrameTreeNode* MixedContentNavigationThrottle::InWhichFrameIsContentMixed(
297 FrameTreeNode* node,
298 const GURL& url) {
299 // Main frame navigations cannot be mixed content.
300 // TODO(carlosk): except for form submissions which will be dealt with later.
301 if (node->IsMainFrame())
302 return nullptr;
303
304 // There's no mixed content if any of these are true:
305 // - The navigated URL is potentially secure.
306 // - The root nor parent frames' origins are secure.
307 FrameTreeNode* mixed_content_node = nullptr;
308 FrameTreeNode* root = node->frame_tree()->root();
309 FrameTreeNode* parent = node->parent();
310 if (!IsUrlPotentiallySecure(url)) {
311 // TODO(carlosk): don't we need to check more than just the immediate parent
312 // and the root? Is it always the case that these two are the only sources
313 // for obtaining the "origin of the security context"?
314 // See https://crbug.com/623486.
315
316 // Checks if the root or immediate parent frame's origin are secure.
317 if (DoesOriginSchemeRestricsMixedContent(root->current_origin()))
318 mixed_content_node = root;
319 else if (DoesOriginSchemeRestricsMixedContent(parent->current_origin()))
320 mixed_content_node = parent;
321 }
322
323 // Note: This code below should behave the same way as as the two calls to
324 // measureStricterVersionOfIsMixedContent from inside
325 // MixedContentChecker::inWhichFrameIs.
326 if (mixed_content_node) {
327 // We're currently only checking for mixed content in `https://*` contexts.
328 // What about other "secure" contexts the SchemeRegistry knows about? We'll
329 // use this method to measure the occurance of non-webby mixed content to
330 // make sure we're not breaking the world without realizing it.
331 // Note: Based off of measureStricterVersionOfIsMixedContent in
332 // MixedContentChecker.cpp.
333 // TODO(carlosk): this will only ever work once we allow registration of new
334 // potentially secure schemes. crbug.com/627502
335 if (mixed_content_node->current_origin().scheme() != url::kHttpsScheme) {
336 mixed_content_features_.insert(
337 MixedContentInNonHTTPSFrameThatRestrictsMixedContent);
338 }
339 } else if (!SecurityOriginIsSecure(url) &&
340 (IsSecureScheme(root->current_origin().scheme()) ||
341 IsSecureScheme(parent->current_origin().scheme()))) {
342 mixed_content_features_.insert(
343 MixedContentInSecureFrameThatDoesNotRestrictMixedContent);
344 }
345 return mixed_content_node;
346 }
347
348 void MixedContentNavigationThrottle::MaybeSendRendererUpdate(
349 RenderFrameHost* rfh,
350 bool for_redirect,
351 bool has_mixed_content) {
352 if (!mixed_content_features_.empty() || has_mixed_content) {
353 rfh->Send(new FrameMsg_MixedContentUpdate(
354 rfh->GetRoutingID(), mixed_content_features_, has_mixed_content,
355 has_mixed_content ? navigation_handle()->GetURL() : GURL(),
356 for_redirect));
357 mixed_content_features_.clear();
358 }
359 }
360
361 // Based off of MixedContentChecker::count.
362 void MixedContentNavigationThrottle::ReportBasicMixedContentFeatures(
363 RequestContextType context,
364 const WebPreferences& prefs) {
365 mixed_content_features_.insert(MixedContentPresent);
366
367 // Report any blockable content.
368 blink::WebMixedContent::ContextType mixed_context_type =
369 MixedContextTypeFromContext(context, prefs.block_mixed_plugin_content);
370 if (mixed_context_type == blink::WebMixedContent::ContextType::Blockable) {
371 mixed_content_features_.insert(MixedContentBlockable);
372 return;
373 }
374
375 // Note: as there's no mixed content checks for sub resources on the browser
376 // there should only be a subset |context| values that could ever be found
377 // here.
378 UseCounterFeature feature;
379 switch (context) {
380 case REQUEST_CONTEXT_TYPE_INTERNAL:
381 feature = MixedContentInternal;
382 break;
383 case REQUEST_CONTEXT_TYPE_PREFETCH:
384 feature = MixedContentPrefetch;
385 break;
386
387 case REQUEST_CONTEXT_TYPE_AUDIO:
388 case REQUEST_CONTEXT_TYPE_DOWNLOAD:
389 case REQUEST_CONTEXT_TYPE_FAVICON:
390 case REQUEST_CONTEXT_TYPE_IMAGE:
391 case REQUEST_CONTEXT_TYPE_PLUGIN:
392 case REQUEST_CONTEXT_TYPE_VIDEO:
393 default:
394 NOTREACHED() << "RequestContextType has value " << context
395 << " and has WebMixedContent::ContextType of "
396 << static_cast<int>(mixed_context_type);
397 return;
398 }
399 mixed_content_features_.insert(feature);
400 }
401
402 // static
403 bool MixedContentNavigationThrottle::IsMixedContentForTesting(
404 const GURL& origin_url,
405 const GURL& url) {
406 const url::Origin origin(origin_url);
407 return !IsUrlPotentiallySecure(url) &&
408 DoesOriginSchemeRestricsMixedContent(origin);
409 }
410
411 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698