From 8d0ba1dfa407f78253c4c676b6e9f81baea2d69f Mon Sep 17 00:00:00 2001 From: reillyg Date: Mon, 23 Mar 2015 17:31:56 -0700 Subject: [PATCH] Bind open firewall ports to visible application windows. To prevent an application from continuing to listen for TCP connections or incoming UDP packets when the user is not aware that it is still running this patch observes the state of its associated AppWindows and opens and closes ports as necessary. The socket itself is left open so that state is not lost. BUG=435404 Review URL: https://codereview.chromium.org/1022663003 Cr-Commit-Position: refs/heads/master@{#321911} --- extensions/BUILD.gn | 3 - .../api/socket/app_firewall_hole_manager.cc | 166 ++++++++++++++++++ .../api/socket/app_firewall_hole_manager.h | 96 ++++++++++ extensions/browser/api/socket/socket.h | 10 +- extensions/browser/api/socket/socket_api.cc | 67 ++++--- extensions/browser/api/socket/socket_api.h | 21 ++- extensions/extensions.gypi | 2 + extensions/extensions_tests.gypi | 1 + 8 files changed, 315 insertions(+), 51 deletions(-) create mode 100644 extensions/browser/api/socket/app_firewall_hole_manager.cc create mode 100644 extensions/browser/api/socket/app_firewall_hole_manager.h diff --git a/extensions/BUILD.gn b/extensions/BUILD.gn index 39bc1b885f3eeb..ad3960a5c44cbd 100644 --- a/extensions/BUILD.gn +++ b/extensions/BUILD.gn @@ -138,9 +138,6 @@ test("extensions_unittests") { # TODO(rockot): DisplayInfoProvider::Create() is only implemented in Chrome # and app_shell. This is wrong. "shell/browser/shell_display_info_provider.cc", - - # TODO(rockot): See above, but the header is in //components. - "shell/browser/shell_web_contents_modal_dialog_manager.cc", ] deps = [ diff --git a/extensions/browser/api/socket/app_firewall_hole_manager.cc b/extensions/browser/api/socket/app_firewall_hole_manager.cc new file mode 100644 index 00000000000000..bfb2b1ac145f13 --- /dev/null +++ b/extensions/browser/api/socket/app_firewall_hole_manager.cc @@ -0,0 +1,166 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/socket/app_firewall_hole_manager.h" + +#include + +#include "base/bind.h" +#include "base/stl_util.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "content/public/browser/browser_context.h" +#include "extensions/browser/app_window/app_window.h" + +using chromeos::FirewallHole; +using content::BrowserContext; + +namespace extensions { + +namespace { + +class AppFirewallHoleManagerFactory : public BrowserContextKeyedServiceFactory { + public: + static AppFirewallHoleManager* GetForBrowserContext(BrowserContext* context, + bool create) { + return static_cast( + GetInstance()->GetServiceForBrowserContext(context, create)); + } + + static AppFirewallHoleManagerFactory* GetInstance() { + return Singleton::get(); + } + + AppFirewallHoleManagerFactory() + : BrowserContextKeyedServiceFactory( + "AppFirewallHoleManager", + BrowserContextDependencyManager::GetInstance()) { + DependsOn(AppWindowRegistry::Factory::GetInstance()); + } + + ~AppFirewallHoleManagerFactory() override {} + + private: + // BrowserContextKeyedServiceFactory + KeyedService* BuildServiceInstanceFor( + BrowserContext* context) const override { + return new AppFirewallHoleManager(context); + } + + BrowserContext* GetBrowserContextToUse( + BrowserContext* context) const override { + return context; + } +}; + +bool HasVisibleAppWindows(BrowserContext* context, + const std::string& extension_id) { + AppWindowRegistry* registry = AppWindowRegistry::Get(context); + + for (const AppWindow* window : registry->GetAppWindowsForApp(extension_id)) { + if (!window->is_hidden()) { + return true; + } + } + + return false; +} + +} // namespace + +AppFirewallHole::~AppFirewallHole() { + manager_->Close(this); +} + +AppFirewallHole::AppFirewallHole(AppFirewallHoleManager* manager, + PortType type, + uint16_t port, + const std::string& extension_id) + : type_(type), + port_(port), + extension_id_(extension_id), + manager_(manager), + weak_factory_(this) { +} + +void AppFirewallHole::SetVisible(bool app_visible) { + app_visible_ = app_visible; + if (app_visible_) { + if (!firewall_hole_) { + FirewallHole::Open(type_, port_, "" /* all interfaces */, + base::Bind(&AppFirewallHole::OnFirewallHoleOpened, + weak_factory_.GetWeakPtr())); + } + } else { + firewall_hole_.reset(nullptr); + } +} + +void AppFirewallHole::OnFirewallHoleOpened( + scoped_ptr firewall_hole) { + if (app_visible_) { + DCHECK(!firewall_hole_); + firewall_hole_ = firewall_hole.Pass(); + } +} + +AppFirewallHoleManager::AppFirewallHoleManager(BrowserContext* context) + : context_(context), observer_(this) { + observer_.Add(AppWindowRegistry::Get(context)); +} + +AppFirewallHoleManager::~AppFirewallHoleManager() { + STLDeleteValues(&tracked_holes_); +} + +AppFirewallHoleManager* AppFirewallHoleManager::Get(BrowserContext* context) { + return AppFirewallHoleManagerFactory::GetForBrowserContext(context, true); +} + +scoped_ptr AppFirewallHoleManager::Open( + AppFirewallHole::PortType type, + uint16_t port, + const std::string& extension_id) { + scoped_ptr hole( + new AppFirewallHole(this, type, port, extension_id)); + tracked_holes_.insert(std::make_pair(extension_id, hole.get())); + if (HasVisibleAppWindows(context_, extension_id)) { + hole->SetVisible(true); + } + return hole.Pass(); +} + +void AppFirewallHoleManager::Close(AppFirewallHole* hole) { + auto range = tracked_holes_.equal_range(hole->extension_id()); + for (auto iter = range.first; iter != range.second; ++iter) { + if (iter->second == hole) { + tracked_holes_.erase(iter); + return; + } + } + NOTREACHED(); +} + +void AppFirewallHoleManager::OnAppWindowRemoved(AppWindow* app_window) { + OnAppWindowHidden(app_window); +} + +void AppFirewallHoleManager::OnAppWindowHidden(AppWindow* app_window) { + DCHECK(context_ == app_window->browser_context()); + if (!HasVisibleAppWindows(context_, app_window->extension_id())) { + const auto& range = tracked_holes_.equal_range(app_window->extension_id()); + for (auto iter = range.first; iter != range.second; ++iter) { + iter->second->SetVisible(false); + } + } +} + +void AppFirewallHoleManager::OnAppWindowShown(AppWindow* app_window, + bool was_hidden) { + const auto& range = tracked_holes_.equal_range(app_window->extension_id()); + for (auto iter = range.first; iter != range.second; ++iter) { + iter->second->SetVisible(true); + } +} + +} // namespace extensions diff --git a/extensions/browser/api/socket/app_firewall_hole_manager.h b/extensions/browser/api/socket/app_firewall_hole_manager.h new file mode 100644 index 00000000000000..6fa3870dc5dd5f --- /dev/null +++ b/extensions/browser/api/socket/app_firewall_hole_manager.h @@ -0,0 +1,96 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_SOCKET_APP_FIREWALL_HOLE_MANAGER_H_ +#define EXTENSIONS_BROWSER_API_SOCKET_APP_FIREWALL_HOLE_MANAGER_H_ + +#include + +#include "base/scoped_observer.h" +#include "chromeos/network/firewall_hole.h" +#include "extensions/browser/app_window/app_window_registry.h" + +namespace content { +class BrowserContext; +} + +namespace extensions { + +class AppFirewallHoleManager; + +// Represents an open port in the system firewall that will be opened and closed +// automatically when the application has a visible window or not. The hole is +// closed on destruction. +class AppFirewallHole { + public: + typedef chromeos::FirewallHole::PortType PortType; + + ~AppFirewallHole(); + + PortType type() const { return type_; } + uint16_t port() const { return port_; } + const std::string& extension_id() const { return extension_id_; } + + private: + friend class AppFirewallHoleManager; + + AppFirewallHole(AppFirewallHoleManager* manager, + PortType type, + uint16_t port, + const std::string& extension_id); + + void SetVisible(bool app_visible); + void OnFirewallHoleOpened(scoped_ptr firewall_hole); + + PortType type_; + uint16_t port_; + std::string extension_id_; + bool app_visible_ = false; + + // This object is destroyed when the AppFirewallHoleManager that owns it is + // destroyed and so a raw pointer is okay here. + AppFirewallHoleManager* manager_; + + // This will hold the FirewallHole object if one is opened. + scoped_ptr firewall_hole_; + + base::WeakPtrFactory weak_factory_; +}; + +// Tracks ports in the system firewall opened by an application so that they +// may be automatically opened and closed only when the application has a +// visible window. +class AppFirewallHoleManager : public KeyedService, + public AppWindowRegistry::Observer { + public: + explicit AppFirewallHoleManager(content::BrowserContext* context); + ~AppFirewallHoleManager() override; + + // Returns the instance for a given browser context, or NULL if none. + static AppFirewallHoleManager* Get(content::BrowserContext* context); + + // Takes ownership of the AppFirewallHole and will open a port on the system + // firewall if the associated application is currently visible. + scoped_ptr Open(AppFirewallHole::PortType type, + uint16_t port, + const std::string& extension_id); + + private: + friend class AppFirewallHole; + + void Close(AppFirewallHole* hole); + + // AppWindowRegistry::Observer + void OnAppWindowRemoved(AppWindow* app_window) override; + void OnAppWindowHidden(AppWindow* app_window) override; + void OnAppWindowShown(AppWindow* app_window, bool was_hidden) override; + + content::BrowserContext* context_; + ScopedObserver observer_; + std::multimap tracked_holes_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_SOCKET_APP_FIREWALL_HOLE_MANAGER_H_ diff --git a/extensions/browser/api/socket/socket.h b/extensions/browser/api/socket/socket.h index 59879157bd6f77..7f33b832e66595 100644 --- a/extensions/browser/api/socket/socket.h +++ b/extensions/browser/api/socket/socket.h @@ -19,7 +19,7 @@ #include "net/socket/tcp_client_socket.h" #if defined(OS_CHROMEOS) -#include "chromeos/network/firewall_hole.h" +#include "extensions/browser/api/socket/app_firewall_hole_manager.h" #endif // OS_CHROMEOS namespace net { @@ -60,8 +60,10 @@ class Socket : public ApiResource { void set_hostname(const std::string& hostname) { hostname_ = hostname; } #if defined(OS_CHROMEOS) - void set_firewall_hole(scoped_ptr firewall_hole) { - firewall_hole_.reset(firewall_hole.release()); + void set_firewall_hole( + scoped_ptr + firewall_hole) { + firewall_hole_ = firewall_hole.Pass(); } #endif // OS_CHROMEOS @@ -148,7 +150,7 @@ class Socket : public ApiResource { #if defined(OS_CHROMEOS) // Represents a hole punched in the system firewall for this socket. - scoped_ptr + scoped_ptr firewall_hole_; #endif // OS_CHROMEOS }; diff --git a/extensions/browser/api/socket/socket_api.cc b/extensions/browser/api/socket/socket_api.cc index dae0c5297af1c7..9bbc39f4bb0a93 100644 --- a/extensions/browser/api/socket/socket_api.cc +++ b/extensions/browser/api/socket/socket_api.cc @@ -32,12 +32,12 @@ #if defined(OS_CHROMEOS) #include "base/command_line.h" #include "chromeos/chromeos_switches.h" -#include "chromeos/network/firewall_hole.h" #include "content/public/browser/browser_thread.h" #endif // OS_CHROMEOS namespace extensions { +using content::BrowserThread; using content::SocketPermissionRequest; const char kAddressKey[] = "address"; @@ -117,20 +117,14 @@ void SocketAsyncApiFunction::OpenFirewallHole(const std::string& address, return; } - chromeos::FirewallHole::PortType port_type; - if (socket->GetSocketType() == Socket::TYPE_TCP) { - port_type = chromeos::FirewallHole::PortType::TCP; - } else { - port_type = chromeos::FirewallHole::PortType::UDP; - } + AppFirewallHole::PortType type = socket->GetSocketType() == Socket::TYPE_TCP + ? AppFirewallHole::PortType::TCP + : AppFirewallHole::PortType::UDP; - content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind( - &chromeos::FirewallHole::Open, port_type, local_address.port(), - "" /* all interfaces */, - base::Bind(&SocketAsyncApiFunction::OnFirewallHoleOpenedOnUIThread, - this, socket_id))); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&SocketAsyncApiFunction::OpenFirewallHoleOnUIThread, this, + type, local_address.port(), socket_id)); return; } #endif @@ -139,18 +133,25 @@ void SocketAsyncApiFunction::OpenFirewallHole(const std::string& address, #if defined(OS_CHROMEOS) -void SocketAsyncApiFunction::OnFirewallHoleOpenedOnUIThread( - int socket_id, - scoped_ptr hole) { - content::BrowserThread::PostTask( - content::BrowserThread::IO, FROM_HERE, +void SocketAsyncApiFunction::OpenFirewallHoleOnUIThread( + AppFirewallHole::PortType type, + uint16_t port, + int socket_id) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + AppFirewallHoleManager* manager = + AppFirewallHoleManager::Get(browser_context()); + scoped_ptr hole( + manager->Open(type, port, extension_id()).release()); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, base::Bind(&SocketAsyncApiFunction::OnFirewallHoleOpened, this, socket_id, base::Passed(&hole))); } void SocketAsyncApiFunction::OnFirewallHoleOpened( int socket_id, - scoped_ptr hole) { + scoped_ptr hole) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); if (!hole) { error_ = kFirewallFailure; SetResult(new base::FundamentalValue(-1)); @@ -774,9 +775,8 @@ void SocketGetInfoFunction::Work() { } bool SocketGetNetworkListFunction::RunAsync() { - content::BrowserThread::PostTask( - content::BrowserThread::FILE, - FROM_HERE, + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, base::Bind(&SocketGetNetworkListFunction::GetNetworkListOnFileThread, this)); return true; @@ -786,31 +786,28 @@ void SocketGetNetworkListFunction::GetNetworkListOnFileThread() { net::NetworkInterfaceList interface_list; if (GetNetworkList(&interface_list, net::INCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES)) { - content::BrowserThread::PostTask( - content::BrowserThread::UI, - FROM_HERE, - base::Bind(&SocketGetNetworkListFunction::SendResponseOnUIThread, - this, + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&SocketGetNetworkListFunction::SendResponseOnUIThread, this, interface_list)); return; } - content::BrowserThread::PostTask( - content::BrowserThread::UI, - FROM_HERE, + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, base::Bind(&SocketGetNetworkListFunction::HandleGetNetworkListError, this)); } void SocketGetNetworkListFunction::HandleGetNetworkListError() { - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK_CURRENTLY_ON(BrowserThread::UI); error_ = kNetworkListError; SendResponse(false); } void SocketGetNetworkListFunction::SendResponseOnUIThread( const net::NetworkInterfaceList& interface_list) { - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK_CURRENTLY_ON(BrowserThread::UI); std::vector > create_arg; create_arg.reserve(interface_list.size()); @@ -1030,7 +1027,7 @@ SocketSecureFunction::~SocketSecureFunction() { } bool SocketSecureFunction::Prepare() { - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + DCHECK_CURRENTLY_ON(BrowserThread::UI); params_ = core_api::socket::Secure::Params::Create(*args_); EXTENSION_FUNCTION_VALIDATE(params_.get()); url_request_getter_ = browser_context()->GetRequestContext(); @@ -1040,7 +1037,7 @@ bool SocketSecureFunction::Prepare() { // Override the regular implementation, which would call AsyncWorkCompleted // immediately after Work(). void SocketSecureFunction::AsyncWorkStart() { - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + DCHECK_CURRENTLY_ON(BrowserThread::IO); Socket* socket = GetSocket(params_->socket_id); if (!socket) { diff --git a/extensions/browser/api/socket/socket_api.h b/extensions/browser/api/socket/socket_api.h index 2500dd0ae64d36..a3e4e0ead7872f 100644 --- a/extensions/browser/api/socket/socket_api.h +++ b/extensions/browser/api/socket/socket_api.h @@ -17,9 +17,9 @@ #include "net/dns/host_resolver.h" #include "net/socket/tcp_client_socket.h" -namespace chromeos { -class FirewallHole; -} +#if defined(OS_CHROMEOS) +#include "extensions/browser/api/socket/app_firewall_hole_manager.h" +#endif // OS_CHROMEOS namespace content { class BrowserContext; @@ -33,8 +33,8 @@ class SSLClientSocket; } namespace extensions { -class TLSSocket; class Socket; +class TLSSocket; // A simple interface to ApiResourceManager or derived class. The goal // of this interface is to allow Socket API functions to use distinct instances @@ -122,17 +122,20 @@ class SocketAsyncApiFunction : public AsyncApiFunction { void RemoveSocket(int api_resource_id); base::hash_set* GetSocketIds(); - // Only implemented on Chrome OS. + // A no-op outside of Chrome OS. void OpenFirewallHole(const std::string& address, int socket_id, Socket* socket); private: #if defined(OS_CHROMEOS) - void OnFirewallHoleOpenedOnUIThread(int socket_id, - scoped_ptr hole); - void OnFirewallHoleOpened(int socket_id, - scoped_ptr hole); + void OpenFirewallHoleOnUIThread(AppFirewallHole::PortType type, + uint16_t port, + int socket_id); + void OnFirewallHoleOpened( + int socket_id, + scoped_ptr + hole); #endif // OS_CHROMEOS scoped_ptr manager_; diff --git a/extensions/extensions.gypi b/extensions/extensions.gypi index 48e493b46e7dbc..b9b1dd74d5589b 100644 --- a/extensions/extensions.gypi +++ b/extensions/extensions.gypi @@ -787,6 +787,8 @@ 'browser/api/networking_config/networking_config_service.h', 'browser/api/networking_config/networking_config_service_factory.cc', 'browser/api/networking_config/networking_config_service_factory.h', + 'browser/api/socket/app_firewall_hole_manager.cc', + 'browser/api/socket/app_firewall_hole_manager.h', 'browser/api/vpn_provider/vpn_provider_api.cc', 'browser/api/vpn_provider/vpn_provider_api.h', 'browser/api/vpn_provider/vpn_service.cc', diff --git a/extensions/extensions_tests.gypi b/extensions/extensions_tests.gypi index 7fb9dd10f61eff..089c2eb41b9063 100644 --- a/extensions/extensions_tests.gypi +++ b/extensions/extensions_tests.gypi @@ -150,6 +150,7 @@ 'renderer/script_context_set_unittest.cc', 'renderer/script_context_unittest.cc', 'renderer/utils_unittest.cc', + 'shell/browser/shell_web_contents_modal_dialog_manager.cc', 'test/extensions_unittests_main.cc', 'utility/unpacker_unittest.cc', ],