| // Copyright (c) 2012 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 "build/build_config.h" | 
 |  | 
 | #if defined(OS_POSIX) | 
 | #if defined(OS_MACOSX) | 
 | extern "C" { | 
 | #include <sandbox.h> | 
 | } | 
 | #endif | 
 | #include <fcntl.h> | 
 | #include <sys/socket.h> | 
 | #include <sys/stat.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include <queue> | 
 |  | 
 | #include "base/callback.h" | 
 | #include "base/file_descriptor_posix.h" | 
 | #include "base/location.h" | 
 | #include "base/pickle.h" | 
 | #include "base/posix/eintr_wrapper.h" | 
 | #include "base/single_thread_task_runner.h" | 
 | #include "base/synchronization/waitable_event.h" | 
 | #include "ipc/ipc_message_attachment_set.h" | 
 | #include "ipc/ipc_message_utils.h" | 
 | #include "ipc/ipc_test_base.h" | 
 |  | 
 | #if defined(OS_POSIX) | 
 | #include "base/macros.h" | 
 | #endif | 
 |  | 
 | namespace { | 
 |  | 
 | const unsigned kNumFDsToSend = 7;  // per message | 
 | const unsigned kNumMessages = 20; | 
 | const char* kDevZeroPath = "/dev/zero"; | 
 |  | 
 | #if defined(OS_POSIX) | 
 | static_assert(kNumFDsToSend == | 
 |                   IPC::MessageAttachmentSet::kMaxDescriptorsPerMessage, | 
 |               "The number of FDs to send must be kMaxDescriptorsPerMessage."); | 
 | #endif | 
 |  | 
 | class MyChannelDescriptorListenerBase : public IPC::Listener { | 
 |  public: | 
 |   bool OnMessageReceived(const IPC::Message& message) override { | 
 |     base::PickleIterator iter(message); | 
 |     base::FileDescriptor descriptor; | 
 |     while (IPC::ParamTraits<base::FileDescriptor>::Read( | 
 |                &message, &iter, &descriptor)) { | 
 |       HandleFD(descriptor.fd); | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |  protected: | 
 |   virtual void HandleFD(int fd) = 0; | 
 | }; | 
 |  | 
 | class MyChannelDescriptorListener : public MyChannelDescriptorListenerBase { | 
 |  public: | 
 |   explicit MyChannelDescriptorListener(ino_t expected_inode_num) | 
 |       : MyChannelDescriptorListenerBase(), | 
 |         expected_inode_num_(expected_inode_num), | 
 |         num_fds_received_(0) { | 
 |   } | 
 |  | 
 |   bool GotExpectedNumberOfDescriptors() const { | 
 |     return num_fds_received_ == kNumFDsToSend * kNumMessages; | 
 |   } | 
 |  | 
 |   void OnChannelError() override { | 
 |     base::MessageLoop::current()->QuitWhenIdle(); | 
 |   } | 
 |  | 
 |  protected: | 
 |   void HandleFD(int fd) override { | 
 |     ASSERT_GE(fd, 0); | 
 |     // Check that we can read from the FD. | 
 |     char buf; | 
 |     ssize_t amt_read = read(fd, &buf, 1); | 
 |     ASSERT_EQ(amt_read, 1); | 
 |     ASSERT_EQ(buf, 0);  // /dev/zero always reads 0 bytes. | 
 |  | 
 |     struct stat st; | 
 |     ASSERT_EQ(fstat(fd, &st), 0); | 
 |  | 
 |     ASSERT_EQ(close(fd), 0); | 
 |  | 
 |     // Compare inode numbers to check that the file sent over the wire is | 
 |     // actually the one expected. | 
 |     ASSERT_EQ(expected_inode_num_, st.st_ino); | 
 |  | 
 |     ++num_fds_received_; | 
 |     if (num_fds_received_ == kNumFDsToSend * kNumMessages) | 
 |       base::MessageLoop::current()->QuitWhenIdle(); | 
 |   } | 
 |  | 
 |  private: | 
 |   ino_t expected_inode_num_; | 
 |   unsigned num_fds_received_; | 
 | }; | 
 |  | 
 |  | 
 | class IPCSendFdsTest : public IPCTestBase { | 
 |  protected: | 
 |   void RunServer() { | 
 |     // Set up IPC channel and start client. | 
 |     MyChannelDescriptorListener listener(-1); | 
 |     CreateChannel(&listener); | 
 |     ASSERT_TRUE(ConnectChannel()); | 
 |     ASSERT_TRUE(StartClient()); | 
 |  | 
 |     for (unsigned i = 0; i < kNumMessages; ++i) { | 
 |       IPC::Message* message = | 
 |           new IPC::Message(0, 3, IPC::Message::PRIORITY_NORMAL); | 
 |       for (unsigned j = 0; j < kNumFDsToSend; ++j) { | 
 |         const int fd = open(kDevZeroPath, O_RDONLY); | 
 |         ASSERT_GE(fd, 0); | 
 |         base::FileDescriptor descriptor(fd, true); | 
 |         IPC::ParamTraits<base::FileDescriptor>::Write(message, descriptor); | 
 |       } | 
 |       ASSERT_TRUE(sender()->Send(message)); | 
 |     } | 
 |  | 
 |     // Run message loop. | 
 |     base::MessageLoop::current()->Run(); | 
 |  | 
 |     // Close the channel so the client's OnChannelError() gets fired. | 
 |     channel()->Close(); | 
 |  | 
 |     EXPECT_TRUE(WaitForClientShutdown()); | 
 |     DestroyChannel(); | 
 |   } | 
 | }; | 
 |  | 
 | #if defined(OS_ANDROID) | 
 | #define MAYBE_DescriptorTest DISABLED_DescriptorTest | 
 | #else | 
 | #define MAYBE_DescriptorTest DescriptorTest | 
 | #endif | 
 | TEST_F(IPCSendFdsTest, MAYBE_DescriptorTest) { | 
 |   Init("SendFdsClient"); | 
 |   RunServer(); | 
 | } | 
 |  | 
 | int SendFdsClientCommon(const std::string& test_client_name, | 
 |                         ino_t expected_inode_num) { | 
 |   base::MessageLoopForIO main_message_loop; | 
 |   MyChannelDescriptorListener listener(expected_inode_num); | 
 |  | 
 |   // Set up IPC channel. | 
 |   scoped_ptr<IPC::Channel> channel(IPC::Channel::CreateClient( | 
 |       IPCTestBase::GetChannelName(test_client_name), &listener)); | 
 |   CHECK(channel->Connect()); | 
 |  | 
 |   // Run message loop. | 
 |   base::MessageLoop::current()->Run(); | 
 |  | 
 |   // Verify that the message loop was exited due to getting the correct number | 
 |   // of descriptors, and not because of the channel closing unexpectedly. | 
 |   CHECK(listener.GotExpectedNumberOfDescriptors()); | 
 |  | 
 |   return 0; | 
 | } | 
 |  | 
 | MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsClient) { | 
 |   struct stat st; | 
 |   int fd = open(kDevZeroPath, O_RDONLY); | 
 |   fstat(fd, &st); | 
 |   EXPECT_GE(IGNORE_EINTR(close(fd)), 0); | 
 |   return SendFdsClientCommon("SendFdsClient", st.st_ino); | 
 | } | 
 |  | 
 | #if defined(OS_MACOSX) | 
 | // Test that FDs are correctly sent to a sandboxed process. | 
 | // TODO(port): Make this test cross-platform. | 
 | TEST_F(IPCSendFdsTest, DescriptorTestSandboxed) { | 
 |   Init("SendFdsSandboxedClient"); | 
 |   RunServer(); | 
 | } | 
 |  | 
 | MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsSandboxedClient) { | 
 |   struct stat st; | 
 |   const int fd = open(kDevZeroPath, O_RDONLY); | 
 |   fstat(fd, &st); | 
 |   if (IGNORE_EINTR(close(fd)) < 0) | 
 |     return -1; | 
 |  | 
 |   // Enable the sandbox. | 
 |   char* error_buff = NULL; | 
 |   int error = sandbox_init(kSBXProfilePureComputation, SANDBOX_NAMED, | 
 |                            &error_buff); | 
 |   bool success = (error == 0 && error_buff == NULL); | 
 |   if (!success) | 
 |     return -1; | 
 |  | 
 |   sandbox_free_error(error_buff); | 
 |  | 
 |   // Make sure sandbox is really enabled. | 
 |   if (open(kDevZeroPath, O_RDONLY) != -1) { | 
 |     LOG(ERROR) << "Sandbox wasn't properly enabled"; | 
 |     return -1; | 
 |   } | 
 |  | 
 |   // See if we can receive a file descriptor. | 
 |   return SendFdsClientCommon("SendFdsSandboxedClient", st.st_ino); | 
 | } | 
 | #endif  // defined(OS_MACOSX) | 
 |  | 
 |  | 
 | class MyCBListener : public MyChannelDescriptorListenerBase { | 
 |  public: | 
 |   MyCBListener(base::Callback<void(int)> cb, int fds_to_send) | 
 |       : MyChannelDescriptorListenerBase(), | 
 |         cb_(cb) { | 
 |     } | 
 |  | 
 |  protected: | 
 |   void HandleFD(int fd) override { cb_.Run(fd); } | 
 |  private: | 
 |   base::Callback<void(int)> cb_; | 
 | }; | 
 |  | 
 | std::pair<int, int> make_socket_pair() { | 
 |   int pipe_fds[2]; | 
 |   CHECK_EQ(0, HANDLE_EINTR(socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds))); | 
 |   return std::pair<int, int>(pipe_fds[0], pipe_fds[1]); | 
 | } | 
 |  | 
 | static void null_cb(int unused_fd) { | 
 |   NOTREACHED(); | 
 | } | 
 |  | 
 | class PipeChannelHelper { | 
 |  public: | 
 |   PipeChannelHelper(base::Thread* in_thread, | 
 |                     base::Thread* out_thread, | 
 |                     base::Callback<void(int)> cb, | 
 |                     int fds_to_send) : | 
 |       in_thread_(in_thread), | 
 |       out_thread_(out_thread), | 
 |       cb_listener_(cb, fds_to_send), | 
 |       null_listener_(base::Bind(&null_cb), 0) { | 
 |   } | 
 |  | 
 |   void Init() { | 
 |     IPC::ChannelHandle in_handle("IN"); | 
 |     in = IPC::Channel::CreateServer(in_handle, &null_listener_); | 
 |     IPC::ChannelHandle out_handle( | 
 |         "OUT", base::FileDescriptor(in->TakeClientFileDescriptor())); | 
 |     out = IPC::Channel::CreateClient(out_handle, &cb_listener_); | 
 |     // PostTask the connect calls to make sure the callbacks happens | 
 |     // on the right threads. | 
 |     in_thread_->task_runner()->PostTask( | 
 |         FROM_HERE, base::Bind(&PipeChannelHelper::Connect, in.get())); | 
 |     out_thread_->task_runner()->PostTask( | 
 |         FROM_HERE, base::Bind(&PipeChannelHelper::Connect, out.get())); | 
 |   } | 
 |  | 
 |   static void DestroyChannel(scoped_ptr<IPC::Channel> *c, | 
 |                              base::WaitableEvent *event) { | 
 |     c->reset(0); | 
 |     event->Signal(); | 
 |   } | 
 |  | 
 |   ~PipeChannelHelper() { | 
 |     base::WaitableEvent a(true, false); | 
 |     base::WaitableEvent b(true, false); | 
 |     in_thread_->task_runner()->PostTask( | 
 |         FROM_HERE, base::Bind(&PipeChannelHelper::DestroyChannel, &in, &a)); | 
 |     out_thread_->task_runner()->PostTask( | 
 |         FROM_HERE, base::Bind(&PipeChannelHelper::DestroyChannel, &out, &b)); | 
 |     a.Wait(); | 
 |     b.Wait(); | 
 |   } | 
 |  | 
 |   static void Connect(IPC::Channel *channel) { | 
 |     EXPECT_TRUE(channel->Connect()); | 
 |   } | 
 |  | 
 |   void Send(int fd) { | 
 |     CHECK_EQ(base::MessageLoop::current(), in_thread_->message_loop()); | 
 |  | 
 |     ASSERT_GE(fd, 0); | 
 |     base::FileDescriptor descriptor(fd, true); | 
 |  | 
 |     IPC::Message* message = | 
 |         new IPC::Message(0, 3, IPC::Message::PRIORITY_NORMAL); | 
 |     IPC::ParamTraits<base::FileDescriptor>::Write(message, descriptor); | 
 |     ASSERT_TRUE(in->Send(message)); | 
 |   } | 
 |  | 
 |  private: | 
 |   scoped_ptr<IPC::Channel> in, out; | 
 |   base::Thread* in_thread_; | 
 |   base::Thread* out_thread_; | 
 |   MyCBListener cb_listener_; | 
 |   MyCBListener null_listener_; | 
 | }; | 
 |  | 
 | // This test is meant to provoke a kernel bug on OSX, and to prove | 
 | // that the workaround for it is working. It sets up two pipes and three | 
 | // threads, the producer thread creates socketpairs and sends one of the fds | 
 | // over pipe1 to the middleman thread. The middleman thread simply takes the fd | 
 | // sends it over pipe2 to the consumer thread. The consumer thread writes a byte | 
 | // to each fd it receives and then closes the pipe. The producer thread reads | 
 | // the bytes back from each pair of pipes and make sure that everything worked. | 
 | // This feedback mechanism makes sure that not too many file descriptors are | 
 | // in flight at the same time. For more info on the bug, see: | 
 | // http://crbug.com/298276 | 
 | class IPCMultiSendingFdsTest : public testing::Test { | 
 |  public: | 
 |   IPCMultiSendingFdsTest() : received_(true, false) {} | 
 |  | 
 |   void Producer(PipeChannelHelper* dest, | 
 |                 base::Thread* t, | 
 |                 int pipes_to_send) { | 
 |     for (int i = 0; i < pipes_to_send; i++) { | 
 |       received_.Reset(); | 
 |       std::pair<int, int> pipe_fds = make_socket_pair(); | 
 |       t->task_runner()->PostTask( | 
 |           FROM_HERE, base::Bind(&PipeChannelHelper::Send, | 
 |                                 base::Unretained(dest), pipe_fds.second)); | 
 |       char tmp = 'x'; | 
 |       CHECK_EQ(1, HANDLE_EINTR(write(pipe_fds.first, &tmp, 1))); | 
 |       CHECK_EQ(0, IGNORE_EINTR(close(pipe_fds.first))); | 
 |       received_.Wait(); | 
 |     } | 
 |   } | 
 |  | 
 |   void ConsumerHandleFD(int fd) { | 
 |     char tmp = 'y'; | 
 |     CHECK_EQ(1, HANDLE_EINTR(read(fd, &tmp, 1))); | 
 |     CHECK_EQ(tmp, 'x'); | 
 |     CHECK_EQ(0, IGNORE_EINTR(close(fd))); | 
 |     received_.Signal(); | 
 |   } | 
 |  | 
 |   base::Thread* CreateThread(const char* name) { | 
 |     base::Thread* ret = new base::Thread(name); | 
 |     base::Thread::Options options; | 
 |     options.message_loop_type = base::MessageLoop::TYPE_IO; | 
 |     ret->StartWithOptions(options); | 
 |     return ret; | 
 |   } | 
 |  | 
 |   void Run() { | 
 |     // On my mac, this test fails roughly 35 times per | 
 |     // million sends with low load, but much more with high load. | 
 |     // Unless the workaround is in place. With 10000 sends, we | 
 |     // should see at least a 3% failure rate. | 
 |     const int pipes_to_send = 20000; | 
 |     scoped_ptr<base::Thread> producer(CreateThread("producer")); | 
 |     scoped_ptr<base::Thread> middleman(CreateThread("middleman")); | 
 |     scoped_ptr<base::Thread> consumer(CreateThread("consumer")); | 
 |     PipeChannelHelper pipe1( | 
 |         middleman.get(), | 
 |         consumer.get(), | 
 |         base::Bind(&IPCMultiSendingFdsTest::ConsumerHandleFD, | 
 |                    base::Unretained(this)), | 
 |         pipes_to_send); | 
 |     PipeChannelHelper pipe2( | 
 |         producer.get(), | 
 |         middleman.get(), | 
 |         base::Bind(&PipeChannelHelper::Send, base::Unretained(&pipe1)), | 
 |         pipes_to_send); | 
 |     pipe1.Init(); | 
 |     pipe2.Init(); | 
 |     Producer(&pipe2, producer.get(), pipes_to_send); | 
 |   } | 
 |  | 
 |  private: | 
 |   base::WaitableEvent received_; | 
 | }; | 
 |  | 
 | TEST_F(IPCMultiSendingFdsTest, StressTest) { | 
 |   Run(); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | #endif  // defined(OS_POSIX) |