diff options
author | reillyg <reillyg@chromium.org> | 2015-03-13 12:41:44 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-13 19:43:02 +0000 |
commit | bed8fdc7a0995427e0af24e63693ad790920b612 (patch) | |
tree | d5371fd6a4aa2f7cf1a33bc18a6e1eaea34bda41 /chromeos | |
parent | fee295ea8f4d8bac532eae149029362dccfb4800 (diff) | |
download | chromium_src-bed8fdc7a0995427e0af24e63693ad790920b612.zip chromium_src-bed8fdc7a0995427e0af24e63693ad790920b612.tar.gz chromium_src-bed8fdc7a0995427e0af24e63693ad790920b612.tar.bz2 |
Open a firewall hole when a TCP server or UDP socket is bound.
This patch implements firewall hole punching when a Chrome App uses
the chrome.socket, chrome.sockets.tcpServer or chrome.sockets.udp APIs
to open a socket to receive packets from the network. Sockets that only
listen on the local loopback device do not open a port.
A primitive FirewallHole class is added that could be reused by the
Pepper sockets API as well.
BUG=435404
Review URL: https://codereview.chromium.org/965613002
Cr-Commit-Position: refs/heads/master@{#320552}
Diffstat (limited to 'chromeos')
-rw-r--r-- | chromeos/chromeos.gyp | 3 | ||||
-rw-r--r-- | chromeos/chromeos_switches.cc | 3 | ||||
-rw-r--r-- | chromeos/chromeos_switches.h | 1 | ||||
-rw-r--r-- | chromeos/dbus/fake_permission_broker_client.cc | 12 | ||||
-rw-r--r-- | chromeos/network/firewall_hole.cc | 164 | ||||
-rw-r--r-- | chromeos/network/firewall_hole.h | 80 | ||||
-rw-r--r-- | chromeos/network/firewall_hole_unittest.cc | 135 |
7 files changed, 394 insertions, 4 deletions
diff --git a/chromeos/chromeos.gyp b/chromeos/chromeos.gyp index 0a0c259..9deb35b 100644 --- a/chromeos/chromeos.gyp +++ b/chromeos/chromeos.gyp @@ -330,6 +330,8 @@ 'network/managed_network_configuration_handler_impl.h', 'network/managed_state.cc', 'network/managed_state.h', + 'network/firewall_hole.cc', + 'network/firewall_hole.h', 'network/network_activation_handler.cc', 'network/network_activation_handler.h', 'network/network_cert_migrator.cc', @@ -471,6 +473,7 @@ 'login/login_state_unittest.cc', 'network/auto_connect_handler_unittest.cc', 'network/client_cert_resolver_unittest.cc', + 'network/firewall_hole_unittest.cc', 'network/geolocation_handler_unittest.cc', 'network/host_resolver_impl_chromeos_unittest.cc', 'network/managed_network_configuration_handler_unittest.cc', diff --git a/chromeos/chromeos_switches.cc b/chromeos/chromeos_switches.cc index 93d1292..31b01df 100644 --- a/chromeos/chromeos_switches.cc +++ b/chromeos/chromeos_switches.cc @@ -332,6 +332,9 @@ const char kDisableTimeZoneTrackingOption[] = // Disable new GAIA sign-in flow. const char kDisableWebviewSigninFlow[] = "disable-webview-signin-flow"; +// Enable Chrome OS firewall hole-punching for Chrome Apps. +const char kEnableFirewallHolePunching[] = "enable-firewall-hole-punching"; + bool WakeOnWifiEnabled() { return !base::CommandLine::ForCurrentProcess()->HasSwitch(kDisableWakeOnWifi); } diff --git a/chromeos/chromeos_switches.h b/chromeos/chromeos_switches.h index cf1021d..e32c706 100644 --- a/chromeos/chromeos_switches.h +++ b/chromeos/chromeos_switches.h @@ -59,6 +59,7 @@ CHROMEOS_EXPORT extern const char kEnableCarrierSwitching[]; CHROMEOS_EXPORT extern const char kEnableConsumerManagement[]; CHROMEOS_EXPORT extern const char kEnableEmbeddedSignin[]; CHROMEOS_EXPORT extern const char kEnableExtensionAssetsSharing[]; +CHROMEOS_EXPORT extern const char kEnableFirewallHolePunching[]; CHROMEOS_EXPORT extern const char kEnableFirstRunUITransitions[]; CHROMEOS_EXPORT extern const char kEnableKioskMode[]; CHROMEOS_EXPORT extern const char kEnableNetworkPortalNotification[]; diff --git a/chromeos/dbus/fake_permission_broker_client.cc b/chromeos/dbus/fake_permission_broker_client.cc index 17d84ac..c4b265c 100644 --- a/chromeos/dbus/fake_permission_broker_client.cc +++ b/chromeos/dbus/fake_permission_broker_client.cc @@ -5,6 +5,8 @@ #include "chromeos/dbus/fake_permission_broker_client.h" #include "base/callback.h" +#include "base/logging.h" +#include "dbus/file_descriptor.h" namespace chromeos { @@ -26,7 +28,8 @@ void FakePermissionBrokerClient::RequestTcpPortAccess( const std::string& interface, const dbus::FileDescriptor& lifeline_fd, const ResultCallback& callback) { - callback.Run(false); + DCHECK(lifeline_fd.is_valid()); + callback.Run(true); } void FakePermissionBrokerClient::RequestUdpPortAccess( @@ -34,21 +37,22 @@ void FakePermissionBrokerClient::RequestUdpPortAccess( const std::string& interface, const dbus::FileDescriptor& lifeline_fd, const ResultCallback& callback) { - callback.Run(false); + DCHECK(lifeline_fd.is_valid()); + callback.Run(true); } void FakePermissionBrokerClient::ReleaseTcpPort( uint16 port, const std::string& interface, const ResultCallback& callback) { - callback.Run(false); + callback.Run(true); } void FakePermissionBrokerClient::ReleaseUdpPort( uint16 port, const std::string& interface, const ResultCallback& callback) { - callback.Run(false); + callback.Run(true); } } // namespace chromeos diff --git a/chromeos/network/firewall_hole.cc b/chromeos/network/firewall_hole.cc new file mode 100644 index 0000000..425e423 --- /dev/null +++ b/chromeos/network/firewall_hole.cc @@ -0,0 +1,164 @@ +// 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 "chromeos/network/firewall_hole.h" + +#include <fcntl.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/location.h" +#include "base/threading/worker_pool.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/permission_broker_client.h" +#include "dbus/file_descriptor.h" + +namespace chromeos { + +namespace { + +// Creates a pair of file descriptors that form a "lifeline" between Chrome and +// firewalld. If this pipe is closed unexpectedly (i.e. Chrome crashes) then +// firewalld will notice and close the hole in the firewall. +void CreateValidLifeline(dbus::FileDescriptor* lifeline_local, + dbus::FileDescriptor* lifeline_remote) { + int lifeline[2] = {-1, -1}; + if (pipe2(lifeline, O_CLOEXEC) < 0) { + PLOG(ERROR) << "Failed to create a lifeline pipe"; + return; + } + + lifeline_local->PutValue(lifeline[0]); + lifeline_local->CheckValidity(); + + lifeline_remote->PutValue(lifeline[1]); + lifeline_remote->CheckValidity(); +} + +const char* PortTypeToString(FirewallHole::PortType type) { + switch (type) { + case FirewallHole::PortType::TCP: + return "TCP"; + case FirewallHole::PortType::UDP: + return "UDP"; + } + NOTREACHED(); + return nullptr; +} + +void PortReleased(FirewallHole::PortType type, + uint16_t port, + const std::string& interface, + FirewallHole::ScopedFileDescriptor lifeline_fd, + bool success) { + if (!success) { + LOG(WARNING) << "Failed to release firewall hole for " + << PortTypeToString(type) << " port " << port << " on " + << interface << "."; + } +} + +} // namespace + +void CHROMEOS_EXPORT FirewallHole::FileDescriptorDeleter::operator()( + dbus::FileDescriptor* fd) { + base::WorkerPool::PostTask( + FROM_HERE, base::Bind(&base::DeletePointer<dbus::FileDescriptor>, fd), + false); +} + +// static +void FirewallHole::Open(PortType type, + uint16_t port, + const std::string& interface, + const OpenCallback& callback) { + ScopedFileDescriptor lifeline_local(new dbus::FileDescriptor()); + ScopedFileDescriptor lifeline_remote(new dbus::FileDescriptor()); + + // This closure shares pointers with the one below. PostTaskAndReply + // guarantees that it will always be deleted first. + base::Closure create_lifeline_closure = base::Bind( + &CreateValidLifeline, lifeline_local.get(), lifeline_remote.get()); + + base::WorkerPool::PostTaskAndReply( + FROM_HERE, create_lifeline_closure, + base::Bind(&FirewallHole::RequestPortAccess, type, port, interface, + base::Passed(&lifeline_local), base::Passed(&lifeline_remote), + callback), + false); +} + +FirewallHole::~FirewallHole() { + base::Callback<void(bool)> port_released_closure = base::Bind( + &PortReleased, type_, port_, interface_, base::Passed(&lifeline_fd_)); + + PermissionBrokerClient* client = + DBusThreadManager::Get()->GetPermissionBrokerClient(); + DCHECK(client) << "Could not get permission broker client."; + switch (type_) { + case PortType::TCP: + client->ReleaseTcpPort(port_, interface_, port_released_closure); + return; + case PortType::UDP: + client->ReleaseUdpPort(port_, interface_, port_released_closure); + return; + } +} + +void FirewallHole::RequestPortAccess(PortType type, + uint16_t port, + const std::string& interface, + ScopedFileDescriptor lifeline_local, + ScopedFileDescriptor lifeline_remote, + const OpenCallback& callback) { + if (!lifeline_local->is_valid() || !lifeline_remote->is_valid()) { + callback.Run(nullptr); + return; + } + + base::Callback<void(bool)> access_granted_closure = + base::Bind(&FirewallHole::PortAccessGranted, type, port, interface, + base::Passed(&lifeline_local), callback); + + PermissionBrokerClient* client = + DBusThreadManager::Get()->GetPermissionBrokerClient(); + DCHECK(client) << "Could not get permission broker client."; + + switch (type) { + case PortType::TCP: + client->RequestTcpPortAccess(port, interface, *lifeline_remote, + access_granted_closure); + return; + case PortType::UDP: + client->RequestUdpPortAccess(port, interface, *lifeline_remote, + access_granted_closure); + return; + } +} + +void FirewallHole::PortAccessGranted(PortType type, + uint16_t port, + const std::string& interface, + ScopedFileDescriptor lifeline_fd, + const FirewallHole::OpenCallback& callback, + bool success) { + if (success) { + callback.Run(make_scoped_ptr( + new FirewallHole(type, port, interface, lifeline_fd.Pass()))); + } else { + callback.Run(nullptr); + } +} + +FirewallHole::FirewallHole(PortType type, + uint16_t port, + const std::string& interface, + ScopedFileDescriptor lifeline_fd) + : type_(type), + port_(port), + interface_(interface), + lifeline_fd_(lifeline_fd.Pass()) { +} + +} // namespace chromeos diff --git a/chromeos/network/firewall_hole.h b/chromeos/network/firewall_hole.h new file mode 100644 index 0000000..2bd6788 --- /dev/null +++ b/chromeos/network/firewall_hole.h @@ -0,0 +1,80 @@ +// 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 CHROMEOS_NETWORK_FIREWALL_HOLE_H_ +#define CHROMEOS_NETWORK_FIREWALL_HOLE_H_ + +#include <stdint.h> +#include <string> + +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" +#include "chromeos/chromeos_export.h" + +namespace dbus { +class FileDescriptor; +} + +namespace chromeos { + +// This class works with the Chrome OS permission broker to open a port in the +// system firewall. It is closed on destruction. +class CHROMEOS_EXPORT FirewallHole { + public: + enum class PortType { + UDP, + TCP, + }; + + typedef base::Callback<void(scoped_ptr<FirewallHole>)> OpenCallback; + + // This provides a simple way to pass around file descriptors since they must + // be closed on a thread that is allowed to perform I/O. + struct FileDescriptorDeleter { + void CHROMEOS_EXPORT operator()(dbus::FileDescriptor* fd); + }; + typedef scoped_ptr<dbus::FileDescriptor, FileDescriptorDeleter> + ScopedFileDescriptor; + + // Opens a port on the system firewall for the given network interface (or all + // interfaces if |interface| is ""). The hole will be closed when the object + // provided to the callback is destroyed. + static void Open(PortType type, + uint16_t port, + const std::string& interface, + const OpenCallback& callback); + + ~FirewallHole(); + + private: + static void RequestPortAccess(PortType type, + uint16_t port, + const std::string& interface, + ScopedFileDescriptor lifeline_local, + ScopedFileDescriptor lifeline_remote, + const OpenCallback& callback); + + static void PortAccessGranted(PortType type, + uint16_t port, + const std::string& interface, + ScopedFileDescriptor lifeline_fd, + const FirewallHole::OpenCallback& callback, + bool success); + + FirewallHole(PortType type, + uint16_t port, + const std::string& interface, + ScopedFileDescriptor lifeline_fd); + + const PortType type_; + const uint16_t port_; + const std::string interface_; + + // A file descriptor used by firewalld to track the lifetime of this process. + ScopedFileDescriptor lifeline_fd_; +}; + +} // namespace chromeos + +#endif // CHROMEOS_NETWORK_FIREWALL_HOLE_H_ diff --git a/chromeos/network/firewall_hole_unittest.cc b/chromeos/network/firewall_hole_unittest.cc new file mode 100644 index 0000000..ea5f5ec --- /dev/null +++ b/chromeos/network/firewall_hole_unittest.cc @@ -0,0 +1,135 @@ +// 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 "base/bind.h" +#include "base/run_loop.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/fake_permission_broker_client.h" +#include "chromeos/network/firewall_hole.h" +#include "dbus/file_descriptor.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using chromeos::DBusThreadManager; +using chromeos::FirewallHole; +using testing::_; + +namespace { + +ACTION_TEMPLATE(InvokeCallback, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(p1)) { + ::std::tr1::get<k>(args).Run(p1); +} + +class MockPermissionsBrokerClient : public chromeos::PermissionBrokerClient { + public: + MockPermissionsBrokerClient() {} + ~MockPermissionsBrokerClient() override {} + + MOCK_METHOD1(Init, void(dbus::Bus* bus)); + MOCK_METHOD3(RequestPathAccess, + void(const std::string& path, + int interface_id, + const ResultCallback& callback)); + MOCK_METHOD4(RequestTcpPortAccess, + void(uint16 port, + const std::string& interface, + const dbus::FileDescriptor& lifeline_fd, + const ResultCallback& callback)); + MOCK_METHOD4(RequestUdpPortAccess, + void(uint16 port, + const std::string& interface, + const dbus::FileDescriptor& lifeline_fd, + const ResultCallback& callback)); + MOCK_METHOD3(ReleaseTcpPort, + void(uint16 port, + const std::string& interface, + const ResultCallback& callback)); + MOCK_METHOD3(ReleaseUdpPort, + void(uint16 port, + const std::string& interface, + const ResultCallback& callback)); +}; + +} // namespace + +class FirewallHoleTest : public testing::Test { + public: + FirewallHoleTest() {} + ~FirewallHoleTest() override {} + + void SetUp() override { + mock_permissions_broker_client_ = new MockPermissionsBrokerClient(); + DBusThreadManager::GetSetterForTesting()->SetPermissionBrokerClient( + make_scoped_ptr(mock_permissions_broker_client_)); + } + + void TearDown() override { DBusThreadManager::Shutdown(); } + + void AssertOpenSuccess(scoped_ptr<FirewallHole> hole) { + EXPECT_TRUE(hole.get() != nullptr); + run_loop_.Quit(); + } + + void AssertOpenFailure(scoped_ptr<FirewallHole> hole) { + EXPECT_TRUE(hole.get() == nullptr); + run_loop_.Quit(); + } + + private: + base::MessageLoopForUI message_loop_; + + protected: + base::RunLoop run_loop_; + MockPermissionsBrokerClient* mock_permissions_broker_client_ = nullptr; +}; + +TEST_F(FirewallHoleTest, GrantTcpPortAccess) { + EXPECT_CALL(*mock_permissions_broker_client_, + RequestTcpPortAccess(1234, "foo0", _, _)) + .WillOnce(InvokeCallback<3>(true)); + EXPECT_CALL(*mock_permissions_broker_client_, ReleaseTcpPort(1234, "foo0", _)) + .WillOnce(InvokeCallback<2>(true)); + + FirewallHole::Open( + FirewallHole::PortType::TCP, 1234, "foo0", + base::Bind(&FirewallHoleTest::AssertOpenSuccess, base::Unretained(this))); + run_loop_.Run(); +} + +TEST_F(FirewallHoleTest, DenyTcpPortAccess) { + EXPECT_CALL(*mock_permissions_broker_client_, + RequestTcpPortAccess(1234, "foo0", _, _)) + .WillOnce(InvokeCallback<3>(false)); + + FirewallHole::Open( + FirewallHole::PortType::TCP, 1234, "foo0", + base::Bind(&FirewallHoleTest::AssertOpenFailure, base::Unretained(this))); + run_loop_.Run(); +} + +TEST_F(FirewallHoleTest, GrantUdpPortAccess) { + EXPECT_CALL(*mock_permissions_broker_client_, + RequestUdpPortAccess(1234, "foo0", _, _)) + .WillOnce(InvokeCallback<3>(true)); + EXPECT_CALL(*mock_permissions_broker_client_, ReleaseUdpPort(1234, "foo0", _)) + .WillOnce(InvokeCallback<2>(true)); + + FirewallHole::Open( + FirewallHole::PortType::UDP, 1234, "foo0", + base::Bind(&FirewallHoleTest::AssertOpenSuccess, base::Unretained(this))); + run_loop_.Run(); +} + +TEST_F(FirewallHoleTest, DenyUdpPortAccess) { + EXPECT_CALL(*mock_permissions_broker_client_, + RequestUdpPortAccess(1234, "foo0", _, _)) + .WillOnce(InvokeCallback<3>(false)); + + FirewallHole::Open( + FirewallHole::PortType::UDP, 1234, "foo0", + base::Bind(&FirewallHoleTest::AssertOpenFailure, base::Unretained(this))); + run_loop_.Run(); +} |