| // Copyright (c) 2009 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" |
| |
| #include "ipc/ipc_tests.h" |
| |
| #if defined(OS_MACOSX) |
| extern "C" { |
| #include <sandbox.h> |
| } |
| #endif |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| |
| #include "base/eintr_wrapper.h" |
| #include "base/message_loop.h" |
| #include "ipc/ipc_channel.h" |
| #include "ipc/ipc_message_utils.h" |
| #include "testing/multiprocess_func_list.h" |
| |
| #if defined(OS_POSIX) |
| #include "base/file_descriptor_posix.h" |
| |
| namespace { |
| |
| const unsigned kNumFDsToSend = 20; |
| const char* kDevZeroPath = "/dev/zero"; |
| |
| static void VerifyAndCloseDescriptor(int fd, ino_t inode_num) { |
| // 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 NUL bytes. |
| |
| struct stat st; |
| ASSERT_EQ(fstat(fd, &st), 0); |
| |
| ASSERT_EQ(close(fd), 0); |
| |
| // We compare iNode numbers to check that the file sent over the wire |
| // was actually the same physical file as the one we were expecting. |
| ASSERT_EQ(inode_num, st.st_ino); |
| } |
| |
| class MyChannelDescriptorListener : public IPC::Channel::Listener { |
| public: |
| MyChannelDescriptorListener(ino_t expected_inode_num) |
| : expected_inode_num_(expected_inode_num), |
| num_fds_received_(0) {} |
| |
| virtual bool OnMessageReceived(const IPC::Message& message) { |
| void* iter = NULL; |
| |
| ++num_fds_received_; |
| base::FileDescriptor descriptor; |
| |
| IPC::ParamTraits<base::FileDescriptor>::Read( |
| &message, &iter, &descriptor); |
| |
| VerifyAndCloseDescriptor(descriptor.fd, expected_inode_num_); |
| if (num_fds_received_ == kNumFDsToSend) { |
| MessageLoop::current()->Quit(); |
| } |
| return true; |
| } |
| |
| virtual void OnChannelError() { |
| MessageLoop::current()->Quit(); |
| } |
| |
| bool GotExpectedNumberOfDescriptors() { |
| return kNumFDsToSend == num_fds_received_; |
| } |
| |
| private: |
| ino_t expected_inode_num_; |
| unsigned num_fds_received_; |
| }; |
| |
| void TestDescriptorServer(IPC::Channel &chan, |
| base::ProcessHandle process_handle) { |
| ASSERT_TRUE(process_handle); |
| |
| for (unsigned i = 0; i < kNumFDsToSend; ++i) { |
| base::FileDescriptor descriptor; |
| const int fd = open(kDevZeroPath, O_RDONLY); |
| ASSERT_GE(fd, 0); |
| descriptor.auto_close = true; |
| descriptor.fd = fd; |
| |
| IPC::Message* message = new IPC::Message(0, // routing_id |
| 3, // message type |
| IPC::Message::PRIORITY_NORMAL); |
| IPC::ParamTraits<base::FileDescriptor>::Write(message, descriptor); |
| ASSERT_TRUE(chan.Send(message)); |
| } |
| |
| // Run message loop. |
| MessageLoop::current()->Run(); |
| |
| // Close Channel so client gets its OnChannelError() callback fired. |
| chan.Close(); |
| |
| // Cleanup child process. |
| EXPECT_TRUE(base::WaitForSingleProcess(process_handle, 5000)); |
| } |
| |
| int TestDescriptorClient(ino_t expected_inode_num) { |
| MessageLoopForIO main_message_loop; |
| MyChannelDescriptorListener listener(expected_inode_num); |
| |
| // Setup IPC channel. |
| IPC::Channel chan(kTestClientChannel, IPC::Channel::MODE_CLIENT, |
| &listener); |
| CHECK(chan.Connect()); |
| |
| // Run message loop so IPC Channel can handle message IO. |
| MessageLoop::current()->Run(); |
| |
| // Verify that the message loop was exited due to getting the correct |
| // number of descriptors, and not because the channel closing unexpectedly. |
| CHECK(listener.GotExpectedNumberOfDescriptors()); |
| |
| return 0; |
| } |
| |
| } // namespace |
| |
| // --------------------------------------------------------------------------- |
| #if defined(OS_MACOSX) |
| // TODO(port): Make this test cross-platform. |
| MULTIPROCESS_TEST_MAIN(RunTestDescriptorClientSandboxed) { |
| struct stat st; |
| const int fd = open(kDevZeroPath, O_RDONLY); |
| fstat(fd, &st); |
| if (HANDLE_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 TestDescriptorClient(st.st_ino); |
| } |
| |
| // Test that FDs are correctly sent to a sandboxed process. |
| TEST_F(IPCChannelTest, DescriptorTestSandboxed) { |
| // Setup IPC channel. |
| MyChannelDescriptorListener listener(-1); |
| |
| IPC::Channel chan(kTestClientChannel, IPC::Channel::MODE_SERVER, |
| &listener); |
| ASSERT_TRUE(chan.Connect()); |
| |
| base::ProcessHandle process_handle = SpawnChild( |
| TEST_DESCRIPTOR_CLIENT_SANDBOXED, |
| &chan); |
| TestDescriptorServer(chan, process_handle); |
| } |
| #endif // defined(OS_MACOSX) |
| |
| MULTIPROCESS_TEST_MAIN(RunTestDescriptorClient) { |
| struct stat st; |
| const int fd = open(kDevZeroPath, O_RDONLY); |
| fstat(fd, &st); |
| EXPECT_GE(HANDLE_EINTR(close(fd)), 0); |
| |
| return TestDescriptorClient(st.st_ino); |
| } |
| |
| TEST_F(IPCChannelTest, DescriptorTest) { |
| // Setup IPC channel. |
| MyChannelDescriptorListener listener(-1); |
| |
| IPC::Channel chan(kTestClientChannel, IPC::Channel::MODE_SERVER, |
| &listener); |
| ASSERT_TRUE(chan.Connect()); |
| |
| base::ProcessHandle process_handle = SpawnChild(TEST_DESCRIPTOR_CLIENT, |
| &chan); |
| TestDescriptorServer(chan, process_handle); |
| } |
| |
| #endif // defined(OS_POSIX) |