0

Implement IPC::ChannelFactory, a class that accept()s on a UNIX socket.

IPC::ChannelFactory listens on a UNIX domain socket and notifies its delegate
when a client connects. The delegate is expected to craft an IPC::Channel from
the handle it is given.

Previously committed:
- https://src.chromium.org/viewvc/chrome?view=rev&revision=186912
- https://src.chromium.org/viewvc/chrome?view=rev&revision=187233
- https://src.chromium.org/viewvc/chrome?view=rev&revision=187554

Review URL: https://codereview.chromium.org/12386010

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@187772 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
jeremya@chromium.org
2013-03-13 04:23:10 +00:00
parent 989ae864ea
commit bdf9bdc258
13 changed files with 631 additions and 185 deletions

@ -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',

@ -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',
],
}],
],

@ -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.

@ -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

57
ipc/ipc_channel_factory.h Normal file

@ -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_

@ -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() {

@ -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_

@ -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());

@ -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

@ -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:

@ -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

@ -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_

@ -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