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