// 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 <stddef.h> #include <sys/socket.h> #include <sys/stat.h> #include <unistd.h> #include <memory> #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/run_loop.h" #include "base/single_thread_task_runner.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" #include "base/threading/thread_task_runner_handle.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 #if defined(OS_MACOSX) #include "sandbox/mac/seatbelt.h" #endif namespace { const unsigned kNumFDsToSend = 7; // per message const unsigned kNumMessages = 20; const char* kDevZeroPath = "/dev/zero"; #if defined(OS_POSIX) || defined(OS_FUCHSIA) 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) { } unsigned num_fds_received() const { return num_fds_received_; } void OnChannelError() override { base::RunLoop::QuitCurrentWhenIdleDeprecated(); } 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::RunLoop::QuitCurrentWhenIdleDeprecated(); } private: ino_t expected_inode_num_; unsigned num_fds_received_; }; class IPCSendFdsTest : public IPCChannelMojoTestBase { protected: void RunServer() { // Set up IPC channel and start client. MyChannelDescriptorListener listener(-1); CreateChannel(&listener); ASSERT_TRUE(ConnectChannel()); 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::RunLoop().Run(); // Close the channel so the client's OnChannelError() gets fired. channel()->Close(); EXPECT_TRUE(WaitForClientShutdown()); DestroyChannel(); } }; TEST_F(IPCSendFdsTest, DescriptorTest) { Init("SendFdsClient"); RunServer(); } class SendFdsTestClientFixture : public IpcChannelMojoTestClient { protected: void SendFdsClientCommon(const std::string& test_client_name, ino_t expected_inode_num) { MyChannelDescriptorListener listener(expected_inode_num); // Set up IPC channel. Connect(&listener); // Run message loop. base::RunLoop().Run(); // Verify that the message loop was exited due to getting the correct number // of descriptors, and not because of the channel closing unexpectedly. EXPECT_EQ(kNumFDsToSend * kNumMessages, listener.num_fds_received()); Close(); } }; DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE( SendFdsClient, SendFdsTestClientFixture) { struct stat st; int fd = open(kDevZeroPath, O_RDONLY); fstat(fd, &st); EXPECT_GE(IGNORE_EINTR(close(fd)), 0); 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(); } DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE( SendFdsSandboxedClient, SendFdsTestClientFixture) { struct stat st; const int fd = open(kDevZeroPath, O_RDONLY); fstat(fd, &st); ASSERT_LE(0, IGNORE_EINTR(close(fd))); // Enable the sandbox. char* error_buff = NULL; int error = sandbox::Seatbelt::Init( sandbox::Seatbelt::kProfilePureComputation, SANDBOX_NAMED, &error_buff); ASSERT_EQ(0, error); ASSERT_FALSE(error_buff); sandbox::Seatbelt::FreeError(error_buff); // Make sure sandbox is really enabled. ASSERT_EQ(-1, open(kDevZeroPath, O_RDONLY)) << "Sandbox wasn't properly enabled"; // See if we can receive a file descriptor. SendFdsClientCommon("SendFdsSandboxedClient", st.st_ino); } #endif // defined(OS_MACOSX) } // namespace #endif // defined(OS_POSIX)