OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "extensions/browser/extension_function_dispatcher.h" | 5 #include "extensions/browser/extension_function_dispatcher.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/json/json_string_value_serializer.h" | 8 #include "base/json/json_string_value_serializer.h" |
9 #include "base/lazy_instance.h" | 9 #include "base/lazy_instance.h" |
10 #include "base/logging.h" | 10 #include "base/logging.h" |
11 #include "base/memory/ref_counted.h" | 11 #include "base/memory/ref_counted.h" |
12 #include "base/metrics/sparse_histogram.h" | 12 #include "base/metrics/sparse_histogram.h" |
13 #include "base/process/process.h" | 13 #include "base/process/process.h" |
14 #include "base/values.h" | 14 #include "base/values.h" |
15 #include "build/build_config.h" | 15 #include "build/build_config.h" |
16 #include "content/public/browser/browser_thread.h" | 16 #include "content/public/browser/browser_thread.h" |
| 17 #include "content/public/browser/child_process_security_policy.h" |
17 #include "content/public/browser/render_frame_host.h" | 18 #include "content/public/browser/render_frame_host.h" |
18 #include "content/public/browser/render_process_host.h" | 19 #include "content/public/browser/render_process_host.h" |
19 #include "content/public/browser/render_view_host.h" | 20 #include "content/public/browser/render_view_host.h" |
20 #include "content/public/browser/user_metrics.h" | 21 #include "content/public/browser/user_metrics.h" |
21 #include "content/public/browser/web_contents.h" | 22 #include "content/public/browser/web_contents.h" |
22 #include "content/public/browser/web_contents_observer.h" | 23 #include "content/public/browser/web_contents_observer.h" |
23 #include "content/public/common/result_codes.h" | 24 #include "content/public/common/result_codes.h" |
24 #include "extensions/browser/api_activity_monitor.h" | 25 #include "extensions/browser/api_activity_monitor.h" |
25 #include "extensions/browser/extension_function_registry.h" | 26 #include "extensions/browser/extension_function_registry.h" |
26 #include "extensions/browser/extension_message_filter.h" | 27 #include "extensions/browser/extension_message_filter.h" |
(...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
251 function->AsIOThreadExtensionFunction(); | 252 function->AsIOThreadExtensionFunction(); |
252 if (!function_io) { | 253 if (!function_io) { |
253 NOTREACHED(); | 254 NOTREACHED(); |
254 return; | 255 return; |
255 } | 256 } |
256 function_io->set_ipc_sender(ipc_sender, routing_id); | 257 function_io->set_ipc_sender(ipc_sender, routing_id); |
257 function_io->set_extension_info_map(extension_info_map); | 258 function_io->set_extension_info_map(extension_info_map); |
258 function->set_include_incognito( | 259 function->set_include_incognito( |
259 extension_info_map->IsIncognitoEnabled(extension->id())); | 260 extension_info_map->IsIncognitoEnabled(extension->id())); |
260 | 261 |
261 if (!CheckPermissions(function.get(), extension, params, callback)) | 262 if (!CheckPermissions(function.get(), params, callback)) |
262 return; | 263 return; |
263 | 264 |
264 QuotaService* quota = extension_info_map->GetQuotaService(); | 265 QuotaService* quota = extension_info_map->GetQuotaService(); |
265 std::string violation_error = quota->Assess(extension->id(), | 266 std::string violation_error = quota->Assess(extension->id(), |
266 function.get(), | 267 function.get(), |
267 ¶ms.arguments, | 268 ¶ms.arguments, |
268 base::TimeTicks::Now()); | 269 base::TimeTicks::Now()); |
269 if (violation_error.empty()) { | 270 if (violation_error.empty()) { |
270 scoped_ptr<base::ListValue> args(params.arguments.DeepCopy()); | 271 scoped_ptr<base::ListValue> args(params.arguments.DeepCopy()); |
271 NotifyApiFunctionCalled(extension->id(), | 272 NotifyApiFunctionCalled(extension->id(), |
(...skipping 30 matching lines...) Expand all Loading... |
302 ui_thread_response_callback_wrappers_[render_view_host] = callback_wrapper; | 303 ui_thread_response_callback_wrappers_[render_view_host] = callback_wrapper; |
303 } else { | 304 } else { |
304 callback_wrapper = iter->second; | 305 callback_wrapper = iter->second; |
305 } | 306 } |
306 | 307 |
307 DispatchWithCallbackInternal( | 308 DispatchWithCallbackInternal( |
308 params, render_view_host, NULL, | 309 params, render_view_host, NULL, |
309 callback_wrapper->CreateCallback(params.request_id)); | 310 callback_wrapper->CreateCallback(params.request_id)); |
310 } | 311 } |
311 | 312 |
312 void ExtensionFunctionDispatcher::DispatchWithCallback( | |
313 const ExtensionHostMsg_Request_Params& params, | |
314 content::RenderFrameHost* render_frame_host, | |
315 const ExtensionFunction::ResponseCallback& callback) { | |
316 DispatchWithCallbackInternal(params, NULL, render_frame_host, callback); | |
317 } | |
318 | |
319 void ExtensionFunctionDispatcher::DispatchWithCallbackInternal( | 313 void ExtensionFunctionDispatcher::DispatchWithCallbackInternal( |
320 const ExtensionHostMsg_Request_Params& params, | 314 const ExtensionHostMsg_Request_Params& params, |
321 RenderViewHost* render_view_host, | 315 RenderViewHost* render_view_host, |
322 content::RenderFrameHost* render_frame_host, | 316 content::RenderFrameHost* render_frame_host, |
323 const ExtensionFunction::ResponseCallback& callback) { | 317 const ExtensionFunction::ResponseCallback& callback) { |
324 DCHECK(render_view_host || render_frame_host); | 318 DCHECK(render_view_host || render_frame_host); |
325 // TODO(yzshen): There is some shared logic between this method and | 319 // TODO(yzshen): There is some shared logic between this method and |
326 // DispatchOnIOThread(). It is nice to deduplicate. | 320 // DispatchOnIOThread(). It is nice to deduplicate. |
327 ProcessMap* process_map = ProcessMap::Get(browser_context_); | 321 ProcessMap* process_map = ProcessMap::Get(browser_context_); |
328 if (!process_map) | 322 if (!process_map) |
329 return; | 323 return; |
330 | 324 |
331 ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_); | 325 ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_); |
332 const Extension* extension = registry->enabled_extensions().GetByID( | 326 const Extension* extension = |
333 params.extension_id); | 327 registry->enabled_extensions().GetByID(params.extension_id); |
334 if (!extension) { | 328 if (!extension) { |
335 extension = | 329 extension = |
336 registry->enabled_extensions().GetHostedAppByURL(params.source_url); | 330 registry->enabled_extensions().GetHostedAppByURL(params.source_url); |
337 } | 331 } |
338 | 332 |
339 int process_id = render_view_host ? render_view_host->GetProcess()->GetID() : | 333 int process_id = render_view_host ? render_view_host->GetProcess()->GetID() : |
340 render_frame_host->GetProcess()->GetID(); | 334 render_frame_host->GetProcess()->GetID(); |
341 scoped_refptr<ExtensionFunction> function( | 335 scoped_refptr<ExtensionFunction> function( |
342 CreateExtensionFunction(params, | 336 CreateExtensionFunction(params, |
343 extension, | 337 extension, |
(...skipping 11 matching lines...) Expand all Loading... |
355 NOTREACHED(); | 349 NOTREACHED(); |
356 return; | 350 return; |
357 } | 351 } |
358 if (render_view_host) { | 352 if (render_view_host) { |
359 function_ui->SetRenderViewHost(render_view_host); | 353 function_ui->SetRenderViewHost(render_view_host); |
360 } else { | 354 } else { |
361 function_ui->SetRenderFrameHost(render_frame_host); | 355 function_ui->SetRenderFrameHost(render_frame_host); |
362 } | 356 } |
363 function_ui->set_dispatcher(AsWeakPtr()); | 357 function_ui->set_dispatcher(AsWeakPtr()); |
364 function_ui->set_browser_context(browser_context_); | 358 function_ui->set_browser_context(browser_context_); |
365 function->set_include_incognito( | 359 if (extension && |
366 ExtensionsBrowserClient::Get()->CanExtensionCrossIncognito( | 360 ExtensionsBrowserClient::Get()->CanExtensionCrossIncognito( |
367 extension, browser_context_)); | 361 extension, browser_context_)) { |
| 362 function->set_include_incognito(true); |
| 363 } |
368 | 364 |
369 if (!CheckPermissions(function.get(), extension, params, callback)) | 365 if (!CheckPermissions(function.get(), params, callback)) |
370 return; | 366 return; |
371 | 367 |
| 368 if (!extension) { |
| 369 // Skip all of the UMA, quota, event page, activity logging stuff if there |
| 370 // isn't an extension, e.g. if the function call was from WebUI. |
| 371 function->Run()->Execute(); |
| 372 return; |
| 373 } |
| 374 |
372 ExtensionSystem* extension_system = ExtensionSystem::Get(browser_context_); | 375 ExtensionSystem* extension_system = ExtensionSystem::Get(browser_context_); |
373 QuotaService* quota = extension_system->quota_service(); | 376 QuotaService* quota = extension_system->quota_service(); |
374 std::string violation_error = quota->Assess(extension->id(), | 377 std::string violation_error = quota->Assess(extension->id(), |
375 function.get(), | 378 function.get(), |
376 ¶ms.arguments, | 379 ¶ms.arguments, |
377 base::TimeTicks::Now()); | 380 base::TimeTicks::Now()); |
| 381 |
378 if (violation_error.empty()) { | 382 if (violation_error.empty()) { |
379 scoped_ptr<base::ListValue> args(params.arguments.DeepCopy()); | 383 scoped_ptr<base::ListValue> args(params.arguments.DeepCopy()); |
380 | 384 |
381 NotifyApiFunctionCalled( | 385 NotifyApiFunctionCalled( |
382 extension->id(), params.name, args.Pass(), browser_context_); | 386 extension->id(), params.name, args.Pass(), browser_context_); |
383 UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.FunctionCalls", | 387 UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.FunctionCalls", |
384 function->histogram_value()); | 388 function->histogram_value()); |
385 function->Run()->Execute(); | 389 function->Run()->Execute(); |
386 } else { | 390 } else { |
387 function->OnQuotaExceeded(violation_error); | 391 function->OnQuotaExceeded(violation_error); |
388 } | 392 } |
389 | 393 |
390 // Note: do not access |this| after this point. We may have been deleted | 394 // Note: do not access |this| after this point. We may have been deleted |
391 // if function->Run() ended up closing the tab that owns us. | 395 // if function->Run() ended up closing the tab that owns us. |
392 | 396 |
393 // Check if extension was uninstalled by management.uninstall. | 397 // Check if extension was uninstalled by management.uninstall. |
394 if (!registry->enabled_extensions().GetByID(params.extension_id)) | 398 if (!registry->enabled_extensions().GetByID(params.extension_id)) |
395 return; | 399 return; |
396 | 400 |
397 // We only adjust the keepalive count for UIThreadExtensionFunction for | 401 // We only adjust the keepalive count for UIThreadExtensionFunction for |
398 // now, largely for simplicity's sake. This is OK because currently, only | 402 // now, largely for simplicity's sake. This is OK because currently, only |
399 // the webRequest API uses IOThreadExtensionFunction, and that API is not | 403 // the webRequest API uses IOThreadExtensionFunction, and that API is not |
400 // compatible with lazy background pages. | 404 // compatible with lazy background pages. |
401 extension_system->process_manager()->IncrementLazyKeepaliveCount(extension); | 405 extension_system->process_manager()->IncrementLazyKeepaliveCount(extension); |
402 } | 406 } |
403 | 407 |
404 void ExtensionFunctionDispatcher::OnExtensionFunctionCompleted( | 408 void ExtensionFunctionDispatcher::OnExtensionFunctionCompleted( |
405 const Extension* extension) { | 409 const Extension* extension) { |
406 ExtensionSystem::Get(browser_context_)->process_manager()-> | 410 if (extension) { |
407 DecrementLazyKeepaliveCount(extension); | 411 ExtensionSystem::Get(browser_context_) |
| 412 ->process_manager() |
| 413 ->DecrementLazyKeepaliveCount(extension); |
| 414 } |
408 } | 415 } |
409 | 416 |
410 // static | 417 // static |
411 bool ExtensionFunctionDispatcher::CheckPermissions( | 418 bool ExtensionFunctionDispatcher::CheckPermissions( |
412 ExtensionFunction* function, | 419 ExtensionFunction* function, |
413 const Extension* extension, | |
414 const ExtensionHostMsg_Request_Params& params, | 420 const ExtensionHostMsg_Request_Params& params, |
415 const ExtensionFunction::ResponseCallback& callback) { | 421 const ExtensionFunction::ResponseCallback& callback) { |
416 if (!function->HasPermission()) { | 422 if (!function->HasPermission()) { |
417 LOG(ERROR) << "Extension " << extension->id() << " does not have " | 423 LOG(ERROR) << "Permission denied for " << params.name; |
418 << "permission to function: " << params.name; | |
419 SendAccessDenied(callback); | 424 SendAccessDenied(callback); |
420 return false; | 425 return false; |
421 } | 426 } |
422 return true; | 427 return true; |
423 } | 428 } |
424 | 429 |
425 namespace { | 430 namespace { |
426 | 431 |
427 // Only COMPONENT hosted apps may call extension APIs, and they are limited | 432 // Only COMPONENT hosted apps may call extension APIs, and they are limited |
428 // to just the permissions they explicitly request. They should not have access | 433 // to just the permissions they explicitly request. They should not have access |
(...skipping 25 matching lines...) Expand all Loading... |
454 | 459 |
455 // static | 460 // static |
456 ExtensionFunction* ExtensionFunctionDispatcher::CreateExtensionFunction( | 461 ExtensionFunction* ExtensionFunctionDispatcher::CreateExtensionFunction( |
457 const ExtensionHostMsg_Request_Params& params, | 462 const ExtensionHostMsg_Request_Params& params, |
458 const Extension* extension, | 463 const Extension* extension, |
459 int requesting_process_id, | 464 int requesting_process_id, |
460 const ProcessMap& process_map, | 465 const ProcessMap& process_map, |
461 ExtensionAPI* api, | 466 ExtensionAPI* api, |
462 void* profile_id, | 467 void* profile_id, |
463 const ExtensionFunction::ResponseCallback& callback) { | 468 const ExtensionFunction::ResponseCallback& callback) { |
464 if (!extension) { | 469 const char* disallowed_reason = NULL; |
465 LOG(ERROR) << "Specified extension does not exist."; | 470 |
| 471 if (extension) { |
| 472 // Extension is calling this API. |
| 473 if (extension->is_hosted_app() && |
| 474 !AllowHostedAppAPICall(*extension, params.source_url, params.name)) { |
| 475 // Most hosted apps can't call APIs. |
| 476 disallowed_reason = "Hosted apps cannot call privileged APIs"; |
| 477 } else if (!process_map.Contains(extension->id(), requesting_process_id) && |
| 478 !api->IsAvailableInUntrustedContext(params.name, extension)) { |
| 479 // Privileged APIs can only be called from the process the extension |
| 480 // is running in. |
| 481 disallowed_reason = |
| 482 "Privileged APIs cannot be called from untrusted processes"; |
| 483 } |
| 484 } else if (content::ChildProcessSecurityPolicy::GetInstance() |
| 485 ->HasWebUIBindings(requesting_process_id)) { |
| 486 // WebUI is calling this API. |
| 487 if (!api->IsAvailableToWebUI(params.name)) { |
| 488 disallowed_reason = "WebUI can only call webui-enabled APIs"; |
| 489 } |
| 490 } else { |
| 491 // Web page is calling this API. However, the APIs that are available to |
| 492 // web pages (e.g. messaging) don't go through ExtensionFunctionDispatcher, |
| 493 // so this should be impossible. |
| 494 NOTREACHED(); |
| 495 disallowed_reason = "Specified extension does not exist."; |
| 496 } |
| 497 |
| 498 if (disallowed_reason != NULL) { |
| 499 LOG(ERROR) << "Extension API call disallowed - name:" << params.name |
| 500 << ", pid:" << requesting_process_id |
| 501 << ", from URL: " << params.source_url.spec() |
| 502 << ", reason: " << disallowed_reason; |
466 SendAccessDenied(callback); | 503 SendAccessDenied(callback); |
467 return NULL; | 504 return NULL; |
468 } | 505 } |
469 | |
470 // Most hosted apps can't call APIs. | |
471 bool allowed = true; | |
472 if (extension->is_hosted_app()) | |
473 allowed = AllowHostedAppAPICall(*extension, params.source_url, params.name); | |
474 | |
475 // Privileged APIs can only be called from the process the extension | |
476 // is running in. | |
477 if (allowed && !api->IsAvailableInUntrustedContext(params.name, extension)) | |
478 allowed = process_map.Contains(extension->id(), requesting_process_id); | |
479 | |
480 if (!allowed) { | |
481 LOG(ERROR) << "Extension API call disallowed - name:" << params.name | |
482 << " pid:" << requesting_process_id | |
483 << " from URL " << params.source_url.spec(); | |
484 SendAccessDenied(callback); | |
485 return NULL; | |
486 } | |
487 | 506 |
488 ExtensionFunction* function = | 507 ExtensionFunction* function = |
489 ExtensionFunctionRegistry::GetInstance()->NewFunction(params.name); | 508 ExtensionFunctionRegistry::GetInstance()->NewFunction(params.name); |
490 if (!function) { | 509 if (!function) { |
491 LOG(ERROR) << "Unknown Extension API - " << params.name; | 510 LOG(ERROR) << "Unknown Extension API - " << params.name; |
492 SendAccessDenied(callback); | 511 SendAccessDenied(callback); |
493 return NULL; | 512 return NULL; |
494 } | 513 } |
495 | 514 |
496 function->SetArgs(¶ms.arguments); | 515 function->SetArgs(¶ms.arguments); |
(...skipping 11 matching lines...) Expand all Loading... |
508 | 527 |
509 // static | 528 // static |
510 void ExtensionFunctionDispatcher::SendAccessDenied( | 529 void ExtensionFunctionDispatcher::SendAccessDenied( |
511 const ExtensionFunction::ResponseCallback& callback) { | 530 const ExtensionFunction::ResponseCallback& callback) { |
512 base::ListValue empty_list; | 531 base::ListValue empty_list; |
513 callback.Run(ExtensionFunction::FAILED, empty_list, | 532 callback.Run(ExtensionFunction::FAILED, empty_list, |
514 "Access to extension API denied."); | 533 "Access to extension API denied."); |
515 } | 534 } |
516 | 535 |
517 } // namespace extensions | 536 } // namespace extensions |
OLD | NEW |