diff options
-rw-r--r-- | ipc/ipc.gyp | 6 | ||||
-rw-r--r-- | ipc/ipc.gypi | 14 | ||||
-rw-r--r-- | ipc/ipc_channel.h | 4 | ||||
-rw-r--r-- | ipc/ipc_channel_factory.cc | 88 | ||||
-rw-r--r-- | ipc/ipc_channel_factory.h | 57 | ||||
-rw-r--r-- | ipc/ipc_channel_posix.cc | 183 | ||||
-rw-r--r-- | ipc/ipc_channel_posix.h | 8 | ||||
-rw-r--r-- | ipc/ipc_channel_posix_unittest.cc | 5 | ||||
-rw-r--r-- | ipc/ipc_channel_proxy.cc | 4 | ||||
-rw-r--r-- | ipc/ipc_channel_proxy.h | 2 | ||||
-rw-r--r-- | ipc/unix_domain_socket_util.cc | 202 | ||||
-rw-r--r-- | ipc/unix_domain_socket_util.h | 64 | ||||
-rw-r--r-- | ipc/unix_domain_socket_util_unittest.cc | 179 |
13 files changed, 631 insertions, 185 deletions
diff --git a/ipc/ipc.gyp b/ipc/ipc.gyp index 5e2f3a7..a247651 100644 --- a/ipc/ipc.gyp +++ b/ipc/ipc.gyp @@ -58,6 +58,7 @@ 'ipc_test_base.cc', 'ipc_test_base.h', 'sync_socket_unittest.cc', + 'unix_domain_socket_util_unittest.cc', ], 'conditions': [ ['toolkit_uses_gtk == 1', { @@ -65,6 +66,11 @@ '../build/linux/system.gyp:gtk', ], }], + ['OS == "win" or OS == "ios"', { + 'sources!': [ + 'unix_domain_socket_util_unittest.cc', + ], + }], ['OS == "android" and gtest_target_type == "shared_library"', { 'dependencies': [ '../testing/android/native_test.gyp:native_test_native_code', diff --git a/ipc/ipc.gypi b/ipc/ipc.gypi index ef986a7..ba80e42 100644 --- a/ipc/ipc.gypi +++ b/ipc/ipc.gypi @@ -13,8 +13,10 @@ 'sources': [ 'file_descriptor_set_posix.cc', 'file_descriptor_set_posix.h', - 'ipc_channel.h', 'ipc_channel.cc', + 'ipc_channel.h', + 'ipc_channel_factory.cc', + 'ipc_channel_factory.h', 'ipc_channel_handle.h', 'ipc_channel_nacl.cc', 'ipc_channel_nacl.h', @@ -57,6 +59,8 @@ 'param_traits_write_macros.h', 'struct_constructor_macros.h', 'struct_destructor_macros.h', + 'unix_domain_socket_util.cc', + 'unix_domain_socket_util.h', ], 'defines': [ 'IPC_IMPLEMENTATION', @@ -68,7 +72,15 @@ ['>(nacl_untrusted_build)==1', { 'sources!': [ 'ipc_channel.cc', + 'ipc_channel_factory.cc', 'ipc_channel_posix.cc', + 'unix_domain_socket_util.cc', + ], + }], + ['OS == "win" or OS == "ios"', { + 'sources!': [ + 'ipc_channel_factory.cc', + 'unix_domain_socket_util.cc', ], }], ], diff --git a/ipc/ipc_channel.h b/ipc/ipc_channel.h index 29caec3..35ead53 100644 --- a/ipc/ipc_channel.h +++ b/ipc/ipc_channel.h @@ -167,8 +167,8 @@ class IPC_EXPORT Channel : public Sender { bool HasAcceptedConnection() const; // Returns true if the peer process' effective user id can be determined, in - // which case the supplied client_euid is updated with it. - bool GetClientEuid(uid_t* client_euid) const; + // which case the supplied peer_euid is updated with it. + bool GetPeerEuid(uid_t* peer_euid) const; // Closes any currently connected socket, and returns to a listening state // for more connections. diff --git a/ipc/ipc_channel_factory.cc b/ipc/ipc_channel_factory.cc new file mode 100644 index 0000000..d355328 --- /dev/null +++ b/ipc/ipc_channel_factory.cc @@ -0,0 +1,88 @@ +// Copyright 2013 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 "ipc/ipc_channel_factory.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "ipc/unix_domain_socket_util.h" + +namespace IPC { + +ChannelFactory::ChannelFactory(const base::FilePath& path, Delegate* delegate) + : path_(path), delegate_(delegate), listen_fd_(-1) { + DCHECK(delegate_); + CreateSocket(); +} + +ChannelFactory::~ChannelFactory() { + Close(); +} + +bool ChannelFactory::CreateSocket() { + DCHECK(listen_fd_ < 0); + + // Create the socket. + return CreateServerUnixDomainSocket(path_, &listen_fd_); +} + +bool ChannelFactory::Listen() { + if (listen_fd_ < 0) + return false; + + // Watch the fd for connections, and turn any connections into + // active sockets. + MessageLoopForIO::current()->WatchFileDescriptor( + listen_fd_, + true, + MessageLoopForIO::WATCH_READ, + &server_listen_connection_watcher_, + this); + return true; +} + +// Called by libevent when we can read from the fd without blocking. +void ChannelFactory::OnFileCanReadWithoutBlocking(int fd) { + DCHECK(fd == listen_fd_); + int new_fd = -1; + if (!ServerAcceptConnection(listen_fd_, &new_fd)) { + Close(); + delegate_->OnListenError(); + return; + } + + if (new_fd < 0) { + // The accept() failed, but not in such a way that the factory needs to be + // shut down. + return; + } + + file_util::ScopedFD scoped_fd(&new_fd); + + // Verify that the IPC channel peer is running as the same user. + if (!IsPeerAuthorized(new_fd)) + return; + + ChannelHandle handle("", base::FileDescriptor(*scoped_fd.release(), true)); + delegate_->OnClientConnected(handle); +} + +void ChannelFactory::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED() << "Listen fd should never be writable."; +} + +void ChannelFactory::Close() { + if (listen_fd_ < 0) + return; + if (HANDLE_EINTR(close(listen_fd_)) < 0) + PLOG(ERROR) << "close"; + listen_fd_ = -1; + if (unlink(path_.value().c_str()) < 0) + PLOG(ERROR) << "unlink"; + + // Unregister libevent for the listening socket and close it. + server_listen_connection_watcher_.StopWatchingFileDescriptor(); +} + +} // namespace IPC diff --git a/ipc/ipc_channel_factory.h b/ipc/ipc_channel_factory.h new file mode 100644 index 0000000..a1e1d85 --- /dev/null +++ b/ipc/ipc_channel_factory.h @@ -0,0 +1,57 @@ +// Copyright 2013 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 IPC_IPC_CHANNEL_FACTORY_H_ +#define IPC_IPC_CHANNEL_FACTORY_H_ + +#include "base/files/file_path.h" +#include "base/message_loop.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_export.h" + +namespace IPC { + +// A ChannelFactory listens on a UNIX domain socket. When a client connects to +// the socket, it accept()s the connection and passes the new FD to the +// delegate. The delegate is then responsible for creating a new IPC::Channel +// for the FD. +class IPC_EXPORT ChannelFactory : public MessageLoopForIO::Watcher { + public: + class Delegate { + public: + // Called when a client connects to the factory. It is the delegate's + // responsibility to create an IPC::Channel for the handle, or else close + // the file descriptor contained therein. + virtual void OnClientConnected(const ChannelHandle& handle) = 0; + + // Called when an error occurs and the channel is closed. + virtual void OnListenError() = 0; + }; + + ChannelFactory(const base::FilePath& path, Delegate* delegate); + + virtual ~ChannelFactory(); + + // Call this to start listening on the socket. + bool Listen(); + + // Close and unlink the socket, and stop accepting connections. + void Close(); + + private: + bool CreateSocket(); + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + MessageLoopForIO::FileDescriptorWatcher server_listen_connection_watcher_; + base::FilePath path_; + Delegate* delegate_; + int listen_fd_; + + DISALLOW_COPY_AND_ASSIGN(ChannelFactory); +}; + +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_FACTORY_H_ diff --git a/ipc/ipc_channel_posix.cc b/ipc/ipc_channel_posix.cc index 17b3641..7f38a48 100644 --- a/ipc/ipc_channel_posix.cc +++ b/ipc/ipc_channel_posix.cc @@ -40,6 +40,7 @@ #include "ipc/ipc_logging.h" #include "ipc/ipc_message_utils.h" #include "ipc/ipc_switches.h" +#include "ipc/unix_domain_socket_util.h" namespace IPC { @@ -137,143 +138,6 @@ class PipeMap { }; //------------------------------------------------------------------------------ -// Verify that kMaxPipeNameLength is a decent size. -COMPILE_ASSERT(sizeof(((sockaddr_un*)0)->sun_path) >= kMaxPipeNameLength, - BAD_SUN_PATH_LENGTH); - -// Creates a unix domain socket bound to the specified name that is listening -// for connections. -bool CreateServerUnixDomainSocket(const std::string& pipe_name, - int* server_listen_fd) { - DCHECK(server_listen_fd); - - if (pipe_name.length() == 0 || pipe_name.length() >= kMaxPipeNameLength) { - DLOG(ERROR) << "pipe_name.length() == " << pipe_name.length(); - return false; - } - - // Create socket. - int fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) { - return false; - } - - // Make socket non-blocking - if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { - PLOG(ERROR) << "fcntl(O_NONBLOCK) " << pipe_name; - if (HANDLE_EINTR(close(fd)) < 0) - PLOG(ERROR) << "close " << pipe_name; - return false; - } - - // Delete any old FS instances. - unlink(pipe_name.c_str()); - - // Make sure the path we need exists. - base::FilePath path(pipe_name); - base::FilePath dir_path = path.DirName(); - if (!file_util::CreateDirectory(dir_path)) { - if (HANDLE_EINTR(close(fd)) < 0) - PLOG(ERROR) << "close " << pipe_name; - return false; - } - - // Create unix_addr structure. - struct sockaddr_un unix_addr; - memset(&unix_addr, 0, sizeof(unix_addr)); - unix_addr.sun_family = AF_UNIX; - int path_len = snprintf(unix_addr.sun_path, IPC::kMaxPipeNameLength, - "%s", pipe_name.c_str()); - DCHECK_EQ(static_cast<int>(pipe_name.length()), path_len); - size_t unix_addr_len = offsetof(struct sockaddr_un, - sun_path) + path_len + 1; - - // Bind the socket. - if (bind(fd, reinterpret_cast<const sockaddr*>(&unix_addr), - unix_addr_len) != 0) { - PLOG(ERROR) << "bind " << pipe_name; - if (HANDLE_EINTR(close(fd)) < 0) - PLOG(ERROR) << "close " << pipe_name; - return false; - } - - // Start listening on the socket. - const int listen_queue_length = 1; - if (listen(fd, listen_queue_length) != 0) { - PLOG(ERROR) << "listen " << pipe_name; - if (HANDLE_EINTR(close(fd)) < 0) - PLOG(ERROR) << "close " << pipe_name; - return false; - } - - *server_listen_fd = fd; - return true; -} - -// Accept a connection on a socket we are listening to. -bool ServerAcceptConnection(int server_listen_fd, int* server_socket) { - DCHECK(server_socket); - - int accept_fd = HANDLE_EINTR(accept(server_listen_fd, NULL, 0)); - if (accept_fd < 0) - return false; - if (fcntl(accept_fd, F_SETFL, O_NONBLOCK) == -1) { - PLOG(ERROR) << "fcntl(O_NONBLOCK) " << accept_fd; - if (HANDLE_EINTR(close(accept_fd)) < 0) - PLOG(ERROR) << "close " << accept_fd; - return false; - } - - *server_socket = accept_fd; - return true; -} - -bool CreateClientUnixDomainSocket(const std::string& pipe_name, - int* client_socket) { - DCHECK(client_socket); - DCHECK_GT(pipe_name.length(), 0u); - DCHECK_LT(pipe_name.length(), kMaxPipeNameLength); - - if (pipe_name.length() == 0 || pipe_name.length() >= kMaxPipeNameLength) { - return false; - } - - // Create socket. - int fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) { - PLOG(ERROR) << "socket " << pipe_name; - return false; - } - - // Make socket non-blocking - if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { - PLOG(ERROR) << "fcntl(O_NONBLOCK) " << pipe_name; - if (HANDLE_EINTR(close(fd)) < 0) - PLOG(ERROR) << "close " << pipe_name; - return false; - } - - // Create server side of socket. - struct sockaddr_un server_unix_addr; - memset(&server_unix_addr, 0, sizeof(server_unix_addr)); - server_unix_addr.sun_family = AF_UNIX; - int path_len = snprintf(server_unix_addr.sun_path, IPC::kMaxPipeNameLength, - "%s", pipe_name.c_str()); - DCHECK_EQ(static_cast<int>(pipe_name.length()), path_len); - size_t server_unix_addr_len = offsetof(struct sockaddr_un, - sun_path) + path_len + 1; - - if (HANDLE_EINTR(connect(fd, reinterpret_cast<sockaddr*>(&server_unix_addr), - server_unix_addr_len)) != 0) { - PLOG(ERROR) << "connect " << pipe_name; - if (HANDLE_EINTR(close(fd)) < 0) - PLOG(ERROR) << "close " << pipe_name; - return false; - } - - *client_socket = fd; - return true; -} bool SocketWriteErrorIsRecoverable() { #if defined(OS_MACOSX) @@ -388,12 +252,14 @@ bool Channel::ChannelImpl::CreatePipe( } else if (mode_ & MODE_NAMED_FLAG) { // Case 2 from comment above. if (mode_ & MODE_SERVER_FLAG) { - if (!CreateServerUnixDomainSocket(pipe_name_, &local_pipe)) { + if (!CreateServerUnixDomainSocket(base::FilePath(pipe_name_), + &local_pipe)) { return false; } must_unlink_ = true; } else if (mode_ & MODE_CLIENT_FLAG) { - if (!CreateClientUnixDomainSocket(pipe_name_, &local_pipe)) { + if (!CreateClientUnixDomainSocket(base::FilePath(pipe_name_), + &local_pipe)) { return false; } } else { @@ -674,33 +540,9 @@ bool Channel::ChannelImpl::HasAcceptedConnection() const { return AcceptsConnections() && pipe_ != -1; } -bool Channel::ChannelImpl::GetClientEuid(uid_t* client_euid) const { - DCHECK(HasAcceptedConnection()); -#if defined(OS_MACOSX) || defined(OS_OPENBSD) - uid_t peer_euid; - gid_t peer_gid; - if (getpeereid(pipe_, &peer_euid, &peer_gid) != 0) { - PLOG(ERROR) << "getpeereid " << pipe_; - return false; - } - *client_euid = peer_euid; - return true; -#elif defined(OS_SOLARIS) - return false; -#else - struct ucred cred; - socklen_t cred_len = sizeof(cred); - if (getsockopt(pipe_, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) != 0) { - PLOG(ERROR) << "getsockopt " << pipe_; - return false; - } - if (static_cast<unsigned>(cred_len) < sizeof(cred)) { - NOTREACHED() << "Truncated ucred from SO_PEERCRED?"; - return false; - } - *client_euid = cred.uid; - return true; -#endif +bool Channel::ChannelImpl::GetPeerEuid(uid_t* peer_euid) const { + DCHECK(!(mode_ & MODE_SERVER) || HasAcceptedConnection()); + return IPC::GetPeerEuid(pipe_, peer_euid); } void Channel::ChannelImpl::ResetToAcceptingConnectionState() { @@ -753,7 +595,8 @@ void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { bool send_server_hello_msg = false; if (fd == server_listen_pipe_) { int new_pipe = 0; - if (!ServerAcceptConnection(server_listen_pipe_, &new_pipe)) { + if (!ServerAcceptConnection(server_listen_pipe_, &new_pipe) || + new_pipe < 0) { Close(); listener()->OnChannelListenError(); } @@ -773,7 +616,7 @@ void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { if ((mode_ & MODE_OPEN_ACCESS_FLAG) == 0) { // Verify that the IPC channel peer is running as the same user. uid_t client_euid; - if (!GetClientEuid(&client_euid)) { + if (!GetPeerEuid(&client_euid)) { DLOG(ERROR) << "Unable to query client euid"; ResetToAcceptingConnectionState(); return; @@ -1157,8 +1000,8 @@ bool Channel::HasAcceptedConnection() const { return channel_impl_->HasAcceptedConnection(); } -bool Channel::GetClientEuid(uid_t* client_euid) const { - return channel_impl_->GetClientEuid(client_euid); +bool Channel::GetPeerEuid(uid_t* peer_euid) const { + return channel_impl_->GetPeerEuid(peer_euid); } void Channel::ResetToAcceptingConnectionState() { diff --git a/ipc/ipc_channel_posix.h b/ipc/ipc_channel_posix.h index 38e14ef..6378c33 100644 --- a/ipc/ipc_channel_posix.h +++ b/ipc/ipc_channel_posix.h @@ -63,7 +63,7 @@ class Channel::ChannelImpl : public internal::ChannelReader, void CloseClientFileDescriptor(); bool AcceptsConnections() const; bool HasAcceptedConnection() const; - bool GetClientEuid(uid_t* client_euid) const; + bool GetPeerEuid(uid_t* peer_euid) const; void ResetToAcceptingConnectionState(); base::ProcessId peer_pid() const { return peer_pid_; } static bool IsNamedServerInitialized(const std::string& channel_id); @@ -194,12 +194,6 @@ class Channel::ChannelImpl : public internal::ChannelReader, DISALLOW_IMPLICIT_CONSTRUCTORS(ChannelImpl); }; -// The maximum length of the name of a pipe for MODE_NAMED_SERVER or -// MODE_NAMED_CLIENT if you want to pass in your own socket. -// The standard size on linux is 108, mac is 104. To maintain consistency -// across platforms we standardize on the smaller value. -static const size_t kMaxPipeNameLength = 104; - } // namespace IPC #endif // IPC_IPC_CHANNEL_POSIX_H_ diff --git a/ipc/ipc_channel_posix_unittest.cc b/ipc/ipc_channel_posix_unittest.cc index 448e648..b49b096 100644 --- a/ipc/ipc_channel_posix_unittest.cc +++ b/ipc/ipc_channel_posix_unittest.cc @@ -21,6 +21,7 @@ #include "base/test/multiprocess_test.h" #include "base/test/test_timeouts.h" #include "ipc/ipc_listener.h" +#include "ipc/unix_domain_socket_util.h" #include "testing/multiprocess_func_list.h" namespace { @@ -145,7 +146,7 @@ void IPCChannelPosixTest::SetUpSocket(IPC::ChannelHandle *handle, struct sockaddr_un server_address = { 0 }; memset(&server_address, 0, sizeof(server_address)); server_address.sun_family = AF_UNIX; - int path_len = snprintf(server_address.sun_path, IPC::kMaxPipeNameLength, + int path_len = snprintf(server_address.sun_path, IPC::kMaxSocketNameLength, "%s", name.c_str()); DCHECK_EQ(static_cast<int>(name.length()), path_len); size_t server_address_len = offsetof(struct sockaddr_un, @@ -311,7 +312,7 @@ TEST_F(IPCChannelPosixTest, BadChannelName) { "future-proof_growth_strategies_Continually" "pontificate_proactive_potentialities_before" "leading-edge_processes"; - EXPECT_GE(strlen(kTooLongName), IPC::kMaxPipeNameLength); + EXPECT_GE(strlen(kTooLongName), IPC::kMaxSocketNameLength); IPC::ChannelHandle handle2(kTooLongName); IPC::Channel channel2(handle2, IPC::Channel::MODE_NAMED_SERVER, NULL); EXPECT_FALSE(channel2.Connect()); diff --git a/ipc/ipc_channel_proxy.cc b/ipc/ipc_channel_proxy.cc index 0202e9e..1048310 100644 --- a/ipc/ipc_channel_proxy.cc +++ b/ipc/ipc_channel_proxy.cc @@ -414,13 +414,13 @@ int ChannelProxy::TakeClientFileDescriptor() { return channel->TakeClientFileDescriptor(); } -bool ChannelProxy::GetClientEuid(uid_t* client_euid) const { +bool ChannelProxy::GetPeerEuid(uid_t* peer_euid) const { DCHECK(CalledOnValidThread()); Channel* channel = context_.get()->channel_.get(); // Channel must have been created first. DCHECK(channel) << context_.get()->channel_id_; - return channel->GetClientEuid(client_euid); + return channel->GetPeerEuid(peer_euid); } #endif diff --git a/ipc/ipc_channel_proxy.h b/ipc/ipc_channel_proxy.h index e4cd83a..6fbbc2a 100644 --- a/ipc/ipc_channel_proxy.h +++ b/ipc/ipc_channel_proxy.h @@ -181,7 +181,7 @@ class IPC_EXPORT ChannelProxy : public Sender, public base::NonThreadSafe { // Calls through to the underlying channel's methods. int GetClientFileDescriptor(); int TakeClientFileDescriptor(); - bool GetClientEuid(uid_t* client_euid) const; + bool GetPeerEuid(uid_t* peer_euid) const; #endif // defined(OS_POSIX) protected: diff --git a/ipc/unix_domain_socket_util.cc b/ipc/unix_domain_socket_util.cc new file mode 100644 index 0000000..7f513a3 --- /dev/null +++ b/ipc/unix_domain_socket_util.cc @@ -0,0 +1,202 @@ +// Copyright 2013 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 "ipc/unix_domain_socket_util.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace IPC { + +// Verify that kMaxSocketNameLength is a decent size. +COMPILE_ASSERT(sizeof(((sockaddr_un*)0)->sun_path) >= kMaxSocketNameLength, + BAD_SUN_PATH_LENGTH); + +namespace { + +// Returns fd (>= 0) on success, -1 on failure. If successful, fills in +// |unix_addr| with the appropriate data for the socket, and sets +// |unix_addr_len| to the length of the data therein. +int MakeUnixAddrForPath(const std::string& socket_name, + struct sockaddr_un* unix_addr, + size_t* unix_addr_len) { + DCHECK(unix_addr); + DCHECK(unix_addr_len); + + if (socket_name.length() == 0) { + LOG(ERROR) << "Empty socket name provided for unix socket address."; + return -1; + } + // We reject socket_name.length() == kMaxSocketNameLength to make room for + // the NUL terminator at the end of the string. + if (socket_name.length() >= kMaxSocketNameLength) { + LOG(ERROR) << "Socket name too long: " << socket_name; + return -1; + } + + // Create socket. + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + PLOG(ERROR) << "socket"; + return -1; + } + file_util::ScopedFD scoped_fd(&fd); + + // Make socket non-blocking + if (HANDLE_EINTR(fcntl(fd, F_SETFL, O_NONBLOCK)) < 0) { + PLOG(ERROR) << "fcntl(O_NONBLOCK)"; + return -1; + } + + // Create unix_addr structure. + memset(unix_addr, 0, sizeof(struct sockaddr_un)); + unix_addr->sun_family = AF_UNIX; + strncpy(unix_addr->sun_path, socket_name.c_str(), kMaxSocketNameLength); + *unix_addr_len = + offsetof(struct sockaddr_un, sun_path) + socket_name.length(); + return *scoped_fd.release(); +} + +} // namespace + +bool CreateServerUnixDomainSocket(const base::FilePath& socket_path, + int* server_listen_fd) { + DCHECK(server_listen_fd); + + std::string socket_name = socket_path.value(); + base::FilePath socket_dir = socket_path.DirName(); + + struct sockaddr_un unix_addr; + size_t unix_addr_len; + int fd = MakeUnixAddrForPath(socket_name, &unix_addr, &unix_addr_len); + if (fd < 0) + return false; + file_util::ScopedFD scoped_fd(&fd); + + // Make sure the path we need exists. + if (!file_util::CreateDirectory(socket_dir)) { + LOG(ERROR) << "Couldn't create directory: " << socket_dir.value(); + return false; + } + + // Delete any old FS instances. + if (unlink(socket_name.c_str()) < 0 && errno != ENOENT) { + PLOG(ERROR) << "unlink " << socket_name; + return false; + } + + // Bind the socket. + if (bind(fd, reinterpret_cast<const sockaddr*>(&unix_addr), + unix_addr_len) < 0) { + PLOG(ERROR) << "bind " << socket_path.value(); + return false; + } + + // Start listening on the socket. + if (listen(fd, SOMAXCONN) < 0) { + PLOG(ERROR) << "listen " << socket_path.value(); + unlink(socket_name.c_str()); + return false; + } + + *server_listen_fd = *scoped_fd.release(); + return true; +} + +bool CreateClientUnixDomainSocket(const base::FilePath& socket_path, + int* client_socket) { + DCHECK(client_socket); + + std::string socket_name = socket_path.value(); + base::FilePath socket_dir = socket_path.DirName(); + + struct sockaddr_un unix_addr; + size_t unix_addr_len; + int fd = MakeUnixAddrForPath(socket_name, &unix_addr, &unix_addr_len); + if (fd < 0) + return false; + file_util::ScopedFD scoped_fd(&fd); + + if (HANDLE_EINTR(connect(fd, reinterpret_cast<sockaddr*>(&unix_addr), + unix_addr_len)) < 0) { + PLOG(ERROR) << "connect " << socket_path.value(); + return false; + } + + *client_socket = *scoped_fd.release(); + return true; +} + +bool GetPeerEuid(int fd, uid_t* peer_euid) { + DCHECK(peer_euid); +#if defined(OS_MACOSX) || defined(OS_OPENBSD) || defined(OS_FREEBSD) + uid_t socket_euid; + gid_t socket_gid; + if (getpeereid(fd, &socket_euid, &socket_gid) < 0) { + PLOG(ERROR) << "getpeereid " << fd; + return false; + } + *peer_euid = socket_euid; + return true; +#else + struct ucred cred; + socklen_t cred_len = sizeof(cred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) < 0) { + PLOG(ERROR) << "getsockopt " << fd; + return false; + } + if (static_cast<unsigned>(cred_len) < sizeof(cred)) { + NOTREACHED() << "Truncated ucred from SO_PEERCRED?"; + return false; + } + *peer_euid = cred.uid; + return true; +#endif +} + +bool IsPeerAuthorized(int peer_fd) { + uid_t peer_euid; + if (!GetPeerEuid(peer_fd, &peer_euid)) + return false; + if (peer_euid != geteuid()) { + DLOG(ERROR) << "Client euid is not authorised"; + return false; + } + return true; +} + +bool IsRecoverableError(int err) { + return errno == ECONNABORTED || errno == EMFILE || errno == ENFILE || + errno == ENOMEM || errno == ENOBUFS; +} + +bool ServerAcceptConnection(int server_listen_fd, int* server_socket) { + DCHECK(server_socket); + *server_socket = -1; + + int accept_fd = HANDLE_EINTR(accept(server_listen_fd, NULL, 0)); + if (accept_fd < 0) + return IsRecoverableError(errno); + file_util::ScopedFD scoped_fd(&accept_fd); + if (HANDLE_EINTR(fcntl(accept_fd, F_SETFL, O_NONBLOCK)) < 0) { + PLOG(ERROR) << "fcntl(O_NONBLOCK) " << accept_fd; + // It's safe to keep listening on |server_listen_fd| even if the attempt to + // set O_NONBLOCK failed on the client fd. + return true; + } + + *server_socket = *scoped_fd.release(); + return true; +} + +} // namespace IPC diff --git a/ipc/unix_domain_socket_util.h b/ipc/unix_domain_socket_util.h new file mode 100644 index 0000000..5752364 --- /dev/null +++ b/ipc/unix_domain_socket_util.h @@ -0,0 +1,64 @@ +// Copyright 2013 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 IPC_UNIX_DOMAIN_SOCKET_UTIL_H_ +#define IPC_UNIX_DOMAIN_SOCKET_UTIL_H_ + +#include <sys/types.h> + +#include <string> + +#include "ipc/ipc_export.h" + +namespace base { +class FilePath; +} // namespace base + +namespace IPC { + +// Creates a UNIX-domain socket at |socket_name| and bind()s it, then listen()s +// on it. If successful, |server_listen_fd| will be set to the new file +// descriptor, and the function will return true. Otherwise returns false. +// +// This function also effectively performs `mkdir -p` on the dirname of +// |socket_name| to ensure that all the directories up to |socket_name| exist. +// As a result of which this function must be run on a thread that allows +// blocking I/O, e.g. the FILE thread in Chrome's browser process. +IPC_EXPORT bool CreateServerUnixDomainSocket(const base::FilePath& socket_name, + int* server_listen_fd); + +// Opens a UNIX-domain socket at |socket_name| and connect()s to it. If +// successful, |client_socket| will be set to the new file descriptor, and the +// function will return true. Otherwise returns false. +IPC_EXPORT bool CreateClientUnixDomainSocket(const base::FilePath& socket_name, + int* client_socket); + +// Gets the effective user ID of the other end of the UNIX-domain socket +// specified by |fd|. If successful, sets |peer_euid| to the uid, and returns +// true. Otherwise returns false. +IPC_EXPORT bool GetPeerEuid(int fd, uid_t* peer_euid); + +// Checks that the process on the other end of the UNIX domain socket +// represented by |peer_fd| shares the same EUID as this process. +IPC_EXPORT bool IsPeerAuthorized(int peer_fd); + +// Accepts a client attempting to connect to |server_listen_fd|, storing the +// new file descriptor for the connection in |server_socket|. +// +// Returns false if |server_listen_fd| encounters an unrecoverable error. +// Returns true if it's valid to keep listening on |server_listen_fd|. In this +// case, it's possible that a connection wasn't successfully established; then, +// |server_socket| will be set to -1. +IPC_EXPORT bool ServerAcceptConnection(int server_listen_fd, + int* server_socket); + +// The maximum length of the name of a socket for MODE_NAMED_SERVER or +// MODE_NAMED_CLIENT if you want to pass in your own socket. +// The standard size on linux is 108, mac is 104. To maintain consistency +// across platforms we standardize on the smaller value. +static const size_t kMaxSocketNameLength = 104; + +} // namespace IPC + +#endif // IPC_UNIX_DOMAIN_SOCKET_UTIL_H_ diff --git a/ipc/unix_domain_socket_util_unittest.cc b/ipc/unix_domain_socket_util_unittest.cc new file mode 100644 index 0000000..a00d3aa --- /dev/null +++ b/ipc/unix_domain_socket_util_unittest.cc @@ -0,0 +1,179 @@ +// Copyright 2013 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 <sys/socket.h> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "base/threading/thread_restrictions.h" +#include "ipc/unix_domain_socket_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class SocketAcceptor : public MessageLoopForIO::Watcher { + public: + SocketAcceptor(int fd, base::MessageLoopProxy* target_thread) + : server_fd_(-1), + target_thread_(target_thread), + started_watching_event_(false, false), + accepted_event_(false, false) { + target_thread->PostTask(FROM_HERE, + base::Bind(&SocketAcceptor::StartWatching, base::Unretained(this), fd)); + } + + virtual ~SocketAcceptor() { + Close(); + } + + int server_fd() const { return server_fd_; } + + void WaitUntilReady() { + started_watching_event_.Wait(); + } + + void WaitForAccept() { + accepted_event_.Wait(); + } + + void Close() { + if (watcher_.get()) { + target_thread_->PostTask(FROM_HERE, + base::Bind(&SocketAcceptor::StopWatching, base::Unretained(this), + watcher_.release())); + } + } + + private: + void StartWatching(int fd) { + watcher_.reset(new MessageLoopForIO::FileDescriptorWatcher); + MessageLoopForIO::current()->WatchFileDescriptor( + fd, + true, + MessageLoopForIO::WATCH_READ, + watcher_.get(), + this); + started_watching_event_.Signal(); + } + void StopWatching(MessageLoopForIO::FileDescriptorWatcher* watcher) { + watcher->StopWatchingFileDescriptor(); + delete watcher; + } + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE { + ASSERT_EQ(-1, server_fd_); + IPC::ServerAcceptConnection(fd, &server_fd_); + watcher_->StopWatchingFileDescriptor(); + accepted_event_.Signal(); + } + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {} + + int server_fd_; + base::MessageLoopProxy* target_thread_; + scoped_ptr<MessageLoopForIO::FileDescriptorWatcher> watcher_; + base::WaitableEvent started_watching_event_; + base::WaitableEvent accepted_event_; + + DISALLOW_COPY_AND_ASSIGN(SocketAcceptor); +}; + +const base::FilePath GetChannelDir() { +#if defined(OS_ANDROID) + base::FilePath tmp_dir; + PathService::Get(base::DIR_CACHE, &tmp_dir); + return tmp_dir; +#else + return base::FilePath("/var/tmp"); +#endif +} + +class TestUnixSocketConnection { + public: + TestUnixSocketConnection() + : worker_("WorkerThread"), + server_listen_fd_(-1), + server_fd_(-1), + client_fd_(-1) { + socket_name_ = GetChannelDir().Append("TestSocket"); + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + worker_.StartWithOptions(options); + } + + bool CreateServerSocket() { + IPC::CreateServerUnixDomainSocket(socket_name_, &server_listen_fd_); + if (server_listen_fd_ < 0) + return false; + struct stat socket_stat; + stat(socket_name_.value().c_str(), &socket_stat); + EXPECT_TRUE(S_ISSOCK(socket_stat.st_mode)); + acceptor_.reset(new SocketAcceptor(server_listen_fd_, + worker_.message_loop_proxy())); + acceptor_->WaitUntilReady(); + return true; + } + + bool CreateClientSocket() { + DCHECK(server_listen_fd_ >= 0); + IPC::CreateClientUnixDomainSocket(socket_name_, &client_fd_); + if (client_fd_ < 0) + return false; + acceptor_->WaitForAccept(); + server_fd_ = acceptor_->server_fd(); + return server_fd_ >= 0; + } + + virtual ~TestUnixSocketConnection() { + if (client_fd_ >= 0) + close(client_fd_); + if (server_fd_ >= 0) + close(server_fd_); + if (server_listen_fd_ >= 0) { + close(server_listen_fd_); + unlink(socket_name_.value().c_str()); + } + } + + int client_fd() const { return client_fd_; } + int server_fd() const { return server_fd_; } + + private: + base::Thread worker_; + base::FilePath socket_name_; + int server_listen_fd_; + int server_fd_; + int client_fd_; + scoped_ptr<SocketAcceptor> acceptor_; +}; + +// Ensure that IPC::CreateServerUnixDomainSocket creates a socket that +// IPC::CreateClientUnixDomainSocket can successfully connect to. +TEST(UnixDomainSocketUtil, Connect) { + TestUnixSocketConnection connection; + ASSERT_TRUE(connection.CreateServerSocket()); + ASSERT_TRUE(connection.CreateClientSocket()); +} + +// Ensure that messages can be sent across the resulting socket. +TEST(UnixDomainSocketUtil, SendReceive) { + TestUnixSocketConnection connection; + ASSERT_TRUE(connection.CreateServerSocket()); + ASSERT_TRUE(connection.CreateClientSocket()); + + const char buffer[] = "Hello, server!"; + size_t buf_len = sizeof(buffer); + size_t sent_bytes = + HANDLE_EINTR(send(connection.client_fd(), buffer, buf_len, 0)); + ASSERT_EQ(buf_len, sent_bytes); + char recv_buf[sizeof(buffer)]; + size_t received_bytes = + HANDLE_EINTR(recv(connection.server_fd(), recv_buf, buf_len, 0)); + ASSERT_EQ(buf_len, received_bytes); + ASSERT_EQ(0, memcmp(recv_buf, buffer, buf_len)); +} + +} // namespace |