summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipc/ipc.gyp6
-rw-r--r--ipc/ipc.gypi14
-rw-r--r--ipc/ipc_channel.h4
-rw-r--r--ipc/ipc_channel_factory.cc88
-rw-r--r--ipc/ipc_channel_factory.h57
-rw-r--r--ipc/ipc_channel_posix.cc189
-rw-r--r--ipc/ipc_channel_posix.h8
-rw-r--r--ipc/ipc_channel_posix_unittest.cc5
-rw-r--r--ipc/ipc_channel_proxy.cc4
-rw-r--r--ipc/ipc_channel_proxy.h2
-rw-r--r--ipc/unix_domain_socket_util.cc202
-rw-r--r--ipc/unix_domain_socket_util.h64
-rw-r--r--ipc/unix_domain_socket_util_unittest.cc163
13 files changed, 621 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..6aaac49 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,20 @@ 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;
+ }
+ // Verify that the server has the same euid as the client.
+ if (!IPC::IsPeerAuthorized(local_pipe)) {
+ if (HANDLE_EINTR(close(local_pipe)) < 0)
+ PLOG(ERROR) << "close " << pipe_name_;
return false;
}
} else {
@@ -674,33 +546,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 +601,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 +622,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 +1006,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..1cf41d3
--- /dev/null
+++ b/ipc/unix_domain_socket_util_unittest.cc
@@ -0,0 +1,163 @@
+// 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),
+ started_watching_event_(false, false),
+ accepted_event_(false, false) {
+ target_thread->PostTask(FROM_HERE,
+ base::Bind(&SocketAcceptor::StartWatching, base::Unretained(this), fd));
+ }
+
+ virtual ~SocketAcceptor() {
+ watcher_.StopWatchingFileDescriptor();
+ }
+
+ int server_fd() const { return server_fd_; }
+
+ void WaitUntilReady() {
+ started_watching_event_.Wait();
+ }
+ void WaitForAccept() {
+ accepted_event_.Wait();
+ }
+
+ private:
+ void StartWatching(int fd) {
+ MessageLoopForIO::current()->WatchFileDescriptor(
+ fd,
+ true,
+ MessageLoopForIO::WATCH_READ,
+ &watcher_,
+ this);
+ started_watching_event_.Signal();
+ }
+ 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_;
+ 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