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 "extensions/browser/extension_navigation_throttle.h"
6 
7 #include <string>
8 
9 #include "components/guest_view/browser/guest_view_base.h"
10 #include "content/public/browser/browser_thread.h"
11 #include "content/public/browser/navigation_handle.h"
12 #include "content/public/browser/render_frame_host.h"
13 #include "content/public/browser/storage_partition_config.h"
14 #include "content/public/browser/web_contents.h"
15 #include "content/public/common/url_constants.h"
16 #include "extensions/browser/app_window/app_window.h"
17 #include "extensions/browser/app_window/app_window_registry.h"
18 #include "extensions/browser/extension_host.h"
19 #include "extensions/browser/extension_registry.h"
20 #include "extensions/browser/extensions_browser_client.h"
21 #include "extensions/browser/guest_view/app_view/app_view_guest.h"
22 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.h"
23 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
24 #include "extensions/browser/process_manager.h"
25 #include "extensions/browser/url_request_util.h"
26 #include "extensions/browser/view_type_utils.h"
27 #include "extensions/common/constants.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/extension_set.h"
30 #include "extensions/common/identifiability_metrics.h"
31 #include "extensions/common/manifest_handlers/icons_handler.h"
32 #include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
33 #include "extensions/common/manifest_handlers/webview_info.h"
34 #include "extensions/common/permissions/api_permission.h"
35 #include "extensions/common/permissions/permissions_data.h"
36 #include "services/metrics/public/cpp/ukm_source_id.h"
37 #include "services/network/public/cpp/web_sandbox_flags.h"
38 #include "ui/base/page_transition_types.h"
39 
40 namespace extensions {
41 
42 namespace {
43 
44 // Whether a navigation to the |platform_app| resource should be blocked in the
45 // given |web_contents|.
ShouldBlockNavigationToPlatformAppResource(const Extension * platform_app,content::WebContents * web_contents)46 bool ShouldBlockNavigationToPlatformAppResource(
47     const Extension* platform_app,
48     content::WebContents* web_contents) {
49   ViewType view_type = GetViewType(web_contents);
50   DCHECK_NE(VIEW_TYPE_INVALID, view_type);
51 
52   // Navigation to platform app's background page.
53   if (view_type == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE)
54     return false;
55 
56   // Navigation within an extension dialog, e.g. this is used by ChromeOS file
57   // manager.
58   if (view_type == VIEW_TYPE_EXTENSION_DIALOG)
59     return false;
60 
61   // Navigation within an app window. The app window must belong to the
62   // |platform_app|.
63   if (view_type == VIEW_TYPE_APP_WINDOW) {
64     AppWindowRegistry* registry =
65         AppWindowRegistry::Get(web_contents->GetBrowserContext());
66     DCHECK(registry);
67     AppWindow* app_window = registry->GetAppWindowForWebContents(web_contents);
68     DCHECK(app_window);
69     return app_window->extension_id() != platform_app->id();
70   }
71 
72   // Navigation within a guest web contents.
73   if (view_type == VIEW_TYPE_EXTENSION_GUEST) {
74     // Platform apps can be embedded by other platform apps using an <appview>
75     // tag.
76     AppViewGuest* app_view = AppViewGuest::FromWebContents(web_contents);
77     if (app_view)
78       return false;
79 
80     // Webviews owned by the platform app can embed platform app resources via
81     // "accessible_resources".
82     WebViewGuest* web_view_guest = WebViewGuest::FromWebContents(web_contents);
83     if (web_view_guest)
84       return web_view_guest->owner_host() != platform_app->id();
85 
86     // Otherwise, it's a guest view that's neither a webview nor an appview
87     // (such as an extensionoptions view). Disallow.
88     return true;
89   }
90 
91   DCHECK(view_type == VIEW_TYPE_BACKGROUND_CONTENTS ||
92          view_type == VIEW_TYPE_COMPONENT ||
93          view_type == VIEW_TYPE_EXTENSION_POPUP ||
94          view_type == VIEW_TYPE_TAB_CONTENTS)
95       << "Unhandled view type: " << view_type;
96 
97   return true;
98 }
99 
100 }  // namespace
101 
ExtensionNavigationThrottle(content::NavigationHandle * navigation_handle)102 ExtensionNavigationThrottle::ExtensionNavigationThrottle(
103     content::NavigationHandle* navigation_handle)
104     : content::NavigationThrottle(navigation_handle) {}
105 
~ExtensionNavigationThrottle()106 ExtensionNavigationThrottle::~ExtensionNavigationThrottle() {}
107 
108 content::NavigationThrottle::ThrottleCheckResult
WillStartOrRedirectRequest()109 ExtensionNavigationThrottle::WillStartOrRedirectRequest() {
110   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
111   content::WebContents* web_contents = navigation_handle()->GetWebContents();
112   content::BrowserContext* browser_context = web_contents->GetBrowserContext();
113 
114   // Prevent the extension's background page from being navigated away. See
115   // crbug.com/1130083.
116   if (navigation_handle()->IsInMainFrame()) {
117     ProcessManager* process_manager = ProcessManager::Get(browser_context);
118     DCHECK(process_manager);
119     ExtensionHost* host = process_manager->GetExtensionHostForRenderFrameHost(
120         web_contents->GetMainFrame());
121 
122     // Navigation throttles don't intercept same document navigations, hence we
123     // can ignore that case.
124     DCHECK(!navigation_handle()->IsSameDocument());
125 
126     if (host &&
127         host->extension_host_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE &&
128         host->initial_url() != navigation_handle()->GetURL()) {
129       return content::NavigationThrottle::CANCEL;
130     }
131   }
132 
133   // Is this navigation targeting an extension resource?
134   ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context);
135   const GURL& url = navigation_handle()->GetURL();
136   bool url_has_extension_scheme = url.SchemeIs(kExtensionScheme);
137   url::Origin target_origin = url::Origin::Create(url);
138   const Extension* target_extension = nullptr;
139   if (url_has_extension_scheme) {
140     // "chrome-extension://" URL.
141     target_extension =
142         registry->enabled_extensions().GetExtensionOrAppByURL(url);
143   } else if (target_origin.scheme() == kExtensionScheme) {
144     // "blob:chrome-extension://" or "filesystem:chrome-extension://" URL.
145     DCHECK(url.SchemeIsFileSystem() || url.SchemeIsBlob());
146     target_extension =
147         registry->enabled_extensions().GetByID(target_origin.host());
148   } else {
149     // If the navigation is not to a chrome-extension resource, no need to
150     // perform any more checks; it's outside of the purview of this throttle.
151     return content::NavigationThrottle::PROCEED;
152   }
153 
154   ukm::SourceIdObj source_id = ukm::SourceIdObj::FromInt64(
155       navigation_handle()->GetNextPageUkmSourceId());
156 
157   // If the navigation is to an unknown or disabled extension, block it.
158   if (!target_extension) {
159     RecordExtensionResourceAccessResult(
160         source_id, url, ExtensionResourceAccessResult::kFailure);
161     // TODO(nick): This yields an unsatisfying error page; use a different error
162     // code once that's supported. https://crbug.com/649869
163     return content::NavigationThrottle::BLOCK_REQUEST;
164   }
165 
166   // Hosted apps don't have any associated resources outside of icons, so
167   // block any requests to URLs in their extension origin.
168   if (target_extension->is_hosted_app()) {
169     base::StringPiece resource_root_relative_path =
170         url.path_piece().empty() ? base::StringPiece()
171                                  : url.path_piece().substr(1);
172     if (!IconsInfo::GetIcons(target_extension)
173              .ContainsPath(resource_root_relative_path)) {
174       RecordExtensionResourceAccessResult(
175           source_id, url, ExtensionResourceAccessResult::kFailure);
176       return content::NavigationThrottle::BLOCK_REQUEST;
177     }
178   }
179 
180   // Block all navigations to blob: or filesystem: URLs with extension
181   // origin from non-extension processes.  See https://crbug.com/645028 and
182   // https://crbug.com/836858.
183   bool current_frame_is_extension_process =
184       !!registry->enabled_extensions().GetExtensionOrAppByURL(
185           navigation_handle()->GetStartingSiteInstance()->GetSiteURL());
186 
187   if (!url_has_extension_scheme && !current_frame_is_extension_process) {
188     // Relax this restriction for apps that use <webview>.  See
189     // https://crbug.com/652077.
190     bool has_webview_permission =
191         target_extension->permissions_data()->HasAPIPermission(
192             APIPermission::kWebView);
193     if (!has_webview_permission) {
194       RecordExtensionResourceAccessResult(
195           source_id, url, ExtensionResourceAccessResult::kCancel);
196       return content::NavigationThrottle::CANCEL;
197     }
198   }
199 
200   if (navigation_handle()->IsInMainFrame()) {
201     guest_view::GuestViewBase* guest =
202         guest_view::GuestViewBase::FromWebContents(web_contents);
203     if (url_has_extension_scheme && guest) {
204       // This only handles top-level navigations. For subresources, is is done
205       // in url_request_util::AllowCrossRendererResourceLoad.
206       const std::string& owner_extension_id = guest->owner_host();
207       const Extension* owner_extension =
208           registry->enabled_extensions().GetByID(owner_extension_id);
209 
210       content::StoragePartitionConfig storage_partition_config =
211           content::StoragePartitionConfig::CreateDefault();
212       bool is_guest = WebViewGuest::GetGuestPartitionConfigForSite(
213           navigation_handle()->GetStartingSiteInstance()->GetSiteURL(),
214           &storage_partition_config);
215 
216       bool allowed = true;
217       url_request_util::AllowCrossRendererResourceLoadHelper(
218           is_guest, target_extension, owner_extension,
219           storage_partition_config.partition_name(), url.path(),
220           navigation_handle()->GetPageTransition(), &allowed);
221       if (!allowed) {
222         RecordExtensionResourceAccessResult(
223             source_id, url, ExtensionResourceAccessResult::kFailure);
224         return content::NavigationThrottle::BLOCK_REQUEST;
225       }
226     }
227   }
228 
229   if (target_extension->is_platform_app() &&
230       ShouldBlockNavigationToPlatformAppResource(target_extension,
231                                                  web_contents)) {
232     RecordExtensionResourceAccessResult(
233         source_id, url, ExtensionResourceAccessResult::kFailure);
234     return content::NavigationThrottle::BLOCK_REQUEST;
235   }
236 
237   // Navigations with no initiator (e.g. browser-initiated requests) are always
238   // considered trusted, and thus allowed.
239   //
240   // Note that GuestView navigations initiated by the embedder also count as a
241   // browser-initiated navigation.
242   if (!navigation_handle()->GetInitiatorOrigin().has_value()) {
243     DCHECK(!navigation_handle()->IsRendererInitiated());
244     return content::NavigationThrottle::PROCEED;
245   }
246 
247   // All renderer-initiated navigations must have an initiator.
248   DCHECK(navigation_handle()->GetInitiatorOrigin().has_value());
249   const url::Origin& initiator_origin =
250       navigation_handle()->GetInitiatorOrigin().value();
251 
252   // Navigations from chrome://, devtools:// or chrome-search:// pages need to
253   // be allowed, even if the target |url| is not web-accessible.  See also:
254   // - https://crbug.com/662602
255   // - similar checks in extensions::ResourceRequestPolicy::CanRequestResource
256   if (initiator_origin.scheme() == content::kChromeUIScheme ||
257       initiator_origin.scheme() == content::kChromeDevToolsScheme ||
258       ExtensionsBrowserClient::Get()->ShouldSchemeBypassNavigationChecks(
259           initiator_origin.scheme())) {
260     return content::NavigationThrottle::PROCEED;
261   }
262 
263   // An extension can initiate navigations to any of its resources.
264   if (initiator_origin == target_origin)
265     return content::NavigationThrottle::PROCEED;
266 
267   // Cancel cross-origin-initiator navigations to blob: or filesystem: URLs.
268   if (!url_has_extension_scheme) {
269     RecordExtensionResourceAccessResult(source_id, url,
270                                         ExtensionResourceAccessResult::kCancel);
271     return content::NavigationThrottle::CANCEL;
272   }
273 
274   // Cross-origin-initiator navigations require that the |url| is in the
275   // manifest's "web_accessible_resources" section.
276   if (!WebAccessibleResourcesInfo::IsResourceWebAccessible(target_extension,
277                                                            url.path())) {
278     RecordExtensionResourceAccessResult(
279         source_id, url, ExtensionResourceAccessResult::kFailure);
280     return content::NavigationThrottle::BLOCK_REQUEST;
281   }
282 
283   // A platform app may not be loaded in an <iframe> by another origin.
284   //
285   // In fact, platform apps may not have any cross-origin iframes at all;
286   // for non-extension origins of |url| this is enforced by means of a
287   // Content Security Policy. But CSP is incapable of blocking the
288   // chrome-extension scheme. Thus, this case must be handled specially
289   // here.
290   // TODO(karandeepb): Investigate if this check can be removed.
291   if (target_extension->is_platform_app()) {
292     RecordExtensionResourceAccessResult(source_id, url,
293                                         ExtensionResourceAccessResult::kCancel);
294     return content::NavigationThrottle::CANCEL;
295   }
296 
297   // A platform app may not load another extension in an <iframe>.
298   const Extension* initiator_extension =
299       registry->enabled_extensions().GetExtensionOrAppByURL(
300           initiator_origin.GetURL());
301   if (initiator_extension && initiator_extension->is_platform_app()) {
302     RecordExtensionResourceAccessResult(
303         source_id, url, ExtensionResourceAccessResult::kFailure);
304     return content::NavigationThrottle::BLOCK_REQUEST;
305   }
306 
307   return content::NavigationThrottle::PROCEED;
308 }
309 
310 content::NavigationThrottle::ThrottleCheckResult
WillStartRequest()311 ExtensionNavigationThrottle::WillStartRequest() {
312   return WillStartOrRedirectRequest();
313 }
314 
315 content::NavigationThrottle::ThrottleCheckResult
WillRedirectRequest()316 ExtensionNavigationThrottle::WillRedirectRequest() {
317   return WillStartOrRedirectRequest();
318 }
319 
320 content::NavigationThrottle::ThrottleCheckResult
WillProcessResponse()321 ExtensionNavigationThrottle::WillProcessResponse() {
322   if (navigation_handle()->IsServedFromBackForwardCache() ||
323       (navigation_handle()->SandboxFlagsToCommit() &
324        network::mojom::WebSandboxFlags::kPlugins) ==
325           network::mojom::WebSandboxFlags::kNone) {
326     return PROCEED;
327   }
328 
329   auto* mime_handler_view_embedder =
330       MimeHandlerViewEmbedder::Get(navigation_handle()->GetFrameTreeNodeId());
331   if (!mime_handler_view_embedder)
332     return PROCEED;
333 
334   // If we have a MimeHandlerViewEmbedder, the frame might embed a resource. If
335   // the frame is sandboxed, however, we shouldn't show the embedded resource.
336   // Instead, we should notify the MimeHandlerViewEmbedder (so that it will
337   // delete itself) and commit an error page.
338   // TODO(https://crbug.com/1144913): Currently MimeHandlerViewEmbedder is
339   // created by PluginResponseInterceptorURLLoaderThrottle before the sandbox
340   // flags are ready. This means in some cases we will create it and delete it
341   // soon after that here. We should move MimeHandlerViewEmbedder creation to a
342   // NavigationThrottle instead and check the sandbox flags before creating, so
343   // that we don't have to remove it soon after creation.
344   mime_handler_view_embedder->OnFrameSandboxed();
345   return ThrottleCheckResult(CANCEL, net::ERR_BLOCKED_BY_CLIENT);
346 }
347 
GetNameForLogging()348 const char* ExtensionNavigationThrottle::GetNameForLogging() {
349   return "ExtensionNavigationThrottle";
350 }
351 
352 }  // namespace extensions
353