ipc_send_fds_test.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "build/build_config.h"
6
7#if defined(OS_POSIX)
8#if defined(OS_MACOSX)
9extern "C" {
10#include <sandbox.h>
11}
12#endif
13#include <fcntl.h>
14#include <sys/socket.h>
15#include <sys/stat.h>
16#include <unistd.h>
17
18#include <queue>
19
20#include "base/callback.h"
21#include "base/file_descriptor_posix.h"
22#include "base/message_loop/message_loop.h"
23#include "base/pickle.h"
24#include "base/posix/eintr_wrapper.h"
25#include "base/synchronization/waitable_event.h"
26#include "ipc/ipc_message_utils.h"
27#include "ipc/ipc_test_base.h"
28
29namespace {
30
31const unsigned kNumFDsToSend = 20;
32const char* kDevZeroPath = "/dev/zero";
33
34class MyChannelDescriptorListenerBase : public IPC::Listener {
35 public:
36  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
37    PickleIterator iter(message);
38
39    base::FileDescriptor descriptor;
40
41    IPC::ParamTraits<base::FileDescriptor>::Read(&message, &iter, &descriptor);
42
43    HandleFD(descriptor.fd);
44    return true;
45  }
46
47 protected:
48  virtual void HandleFD(int fd) = 0;
49};
50
51class MyChannelDescriptorListener : public MyChannelDescriptorListenerBase {
52 public:
53  explicit MyChannelDescriptorListener(ino_t expected_inode_num)
54      : MyChannelDescriptorListenerBase(),
55        expected_inode_num_(expected_inode_num),
56        num_fds_received_(0) {
57  }
58
59  bool GotExpectedNumberOfDescriptors() const {
60    return num_fds_received_ == kNumFDsToSend;
61  }
62
63  virtual void OnChannelError() OVERRIDE {
64    base::MessageLoop::current()->Quit();
65  }
66
67 protected:
68  virtual void HandleFD(int fd) OVERRIDE {
69    // Check that we can read from the FD.
70    char buf;
71    ssize_t amt_read = read(fd, &buf, 1);
72    ASSERT_EQ(amt_read, 1);
73    ASSERT_EQ(buf, 0);  // /dev/zero always reads 0 bytes.
74
75    struct stat st;
76    ASSERT_EQ(fstat(fd, &st), 0);
77
78    ASSERT_EQ(close(fd), 0);
79
80    // Compare inode numbers to check that the file sent over the wire is
81    // actually the one expected.
82    ASSERT_EQ(expected_inode_num_, st.st_ino);
83
84    ++num_fds_received_;
85    if (num_fds_received_ == kNumFDsToSend)
86      base::MessageLoop::current()->Quit();
87  }
88
89 private:
90  ino_t expected_inode_num_;
91  unsigned num_fds_received_;
92};
93
94
95class IPCSendFdsTest : public IPCTestBase {
96 protected:
97  void RunServer() {
98    // Set up IPC channel and start client.
99    MyChannelDescriptorListener listener(-1);
100    CreateChannel(&listener);
101    ASSERT_TRUE(ConnectChannel());
102    ASSERT_TRUE(StartClient());
103
104    for (unsigned i = 0; i < kNumFDsToSend; ++i) {
105      const int fd = open(kDevZeroPath, O_RDONLY);
106      ASSERT_GE(fd, 0);
107      base::FileDescriptor descriptor(fd, true);
108
109      IPC::Message* message =
110          new IPC::Message(0, 3, IPC::Message::PRIORITY_NORMAL);
111      IPC::ParamTraits<base::FileDescriptor>::Write(message, descriptor);
112      ASSERT_TRUE(sender()->Send(message));
113    }
114
115    // Run message loop.
116    base::MessageLoop::current()->Run();
117
118    // Close the channel so the client's OnChannelError() gets fired.
119    channel()->Close();
120
121    EXPECT_TRUE(WaitForClientShutdown());
122    DestroyChannel();
123  }
124};
125
126TEST_F(IPCSendFdsTest, DescriptorTest) {
127  Init("SendFdsClient");
128  RunServer();
129}
130
131int SendFdsClientCommon(const std::string& test_client_name,
132                        ino_t expected_inode_num) {
133  base::MessageLoopForIO main_message_loop;
134  MyChannelDescriptorListener listener(expected_inode_num);
135
136  // Set up IPC channel.
137  IPC::Channel channel(IPCTestBase::GetChannelName(test_client_name),
138                       IPC::Channel::MODE_CLIENT,
139                       &listener);
140  CHECK(channel.Connect());
141
142  // Run message loop.
143  base::MessageLoop::current()->Run();
144
145  // Verify that the message loop was exited due to getting the correct number
146  // of descriptors, and not because of the channel closing unexpectedly.
147  CHECK(listener.GotExpectedNumberOfDescriptors());
148
149  return 0;
150}
151
152MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsClient) {
153  struct stat st;
154  int fd = open(kDevZeroPath, O_RDONLY);
155  fstat(fd, &st);
156  EXPECT_GE(HANDLE_EINTR(close(fd)), 0);
157  return SendFdsClientCommon("SendFdsClient", st.st_ino);
158}
159
160#if defined(OS_MACOSX)
161// Test that FDs are correctly sent to a sandboxed process.
162// TODO(port): Make this test cross-platform.
163TEST_F(IPCSendFdsTest, DescriptorTestSandboxed) {
164  Init("SendFdsSandboxedClient");
165  RunServer();
166}
167
168MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsSandboxedClient) {
169  struct stat st;
170  const int fd = open(kDevZeroPath, O_RDONLY);
171  fstat(fd, &st);
172  if (HANDLE_EINTR(close(fd)) < 0)
173    return -1;
174
175  // Enable the sandbox.
176  char* error_buff = NULL;
177  int error = sandbox_init(kSBXProfilePureComputation, SANDBOX_NAMED,
178                           &error_buff);
179  bool success = (error == 0 && error_buff == NULL);
180  if (!success)
181    return -1;
182
183  sandbox_free_error(error_buff);
184
185  // Make sure sandbox is really enabled.
186  if (open(kDevZeroPath, O_RDONLY) != -1) {
187    LOG(ERROR) << "Sandbox wasn't properly enabled";
188    return -1;
189  }
190
191  // See if we can receive a file descriptor.
192  return SendFdsClientCommon("SendFdsSandboxedClient", st.st_ino);
193}
194#endif  // defined(OS_MACOSX)
195
196
197class MyCBListener : public MyChannelDescriptorListenerBase {
198 public:
199  MyCBListener(base::Callback<void(int)> cb, int fds_to_send)
200      : MyChannelDescriptorListenerBase(),
201        cb_(cb) {
202    }
203
204 protected:
205  virtual void HandleFD(int fd) OVERRIDE {
206    cb_.Run(fd);
207  }
208 private:
209  base::Callback<void(int)> cb_;
210};
211
212std::pair<int, int> make_socket_pair() {
213  int pipe_fds[2];
214  CHECK_EQ(0, HANDLE_EINTR(socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds)));
215  return std::pair<int, int>(pipe_fds[0], pipe_fds[1]);
216}
217
218static void null_cb(int unused_fd) {
219  NOTREACHED();
220}
221
222class PipeChannelHelper {
223 public:
224  PipeChannelHelper(base::Thread* in_thread,
225                    base::Thread* out_thread,
226                    base::Callback<void(int)> cb,
227                    int fds_to_send) :
228      in_thread_(in_thread),
229      out_thread_(out_thread),
230      cb_listener_(cb, fds_to_send),
231      null_listener_(base::Bind(&null_cb), 0) {
232  }
233
234  void Init() {
235    IPC::ChannelHandle in_handle("IN");
236    in.reset(new IPC::Channel(in_handle,
237                              IPC::Channel::MODE_SERVER,
238                              &null_listener_));
239    base::FileDescriptor out_fd(in->TakeClientFileDescriptor(), false);
240    IPC::ChannelHandle out_handle("OUT", out_fd);
241    out.reset(new IPC::Channel(out_handle,
242                               IPC::Channel::MODE_CLIENT,
243                               &cb_listener_));
244    // PostTask the connect calls to make sure the callbacks happens
245    // on the right threads.
246    in_thread_->message_loop()->PostTask(
247        FROM_HERE,
248        base::Bind(&PipeChannelHelper::Connect, in.get()));
249    out_thread_->message_loop()->PostTask(
250        FROM_HERE,
251        base::Bind(&PipeChannelHelper::Connect, out.get()));
252  }
253
254  static void DestroyChannel(scoped_ptr<IPC::Channel> *c,
255                             base::WaitableEvent *event) {
256    c->reset(0);
257    event->Signal();
258  }
259
260  ~PipeChannelHelper() {
261    base::WaitableEvent a(true, false);
262    base::WaitableEvent b(true, false);
263    in_thread_->message_loop()->PostTask(
264        FROM_HERE,
265        base::Bind(&PipeChannelHelper::DestroyChannel, &in, &a));
266    out_thread_->message_loop()->PostTask(
267        FROM_HERE,
268        base::Bind(&PipeChannelHelper::DestroyChannel, &out, &b));
269    a.Wait();
270    b.Wait();
271  }
272
273  static void Connect(IPC::Channel *channel) {
274    EXPECT_TRUE(channel->Connect());
275  }
276
277  void Send(int fd) {
278    CHECK_EQ(base::MessageLoop::current(), in_thread_->message_loop());
279
280    ASSERT_GE(fd, 0);
281    base::FileDescriptor descriptor(fd, true);
282
283    IPC::Message* message =
284        new IPC::Message(0, 3, IPC::Message::PRIORITY_NORMAL);
285    IPC::ParamTraits<base::FileDescriptor>::Write(message, descriptor);
286    ASSERT_TRUE(in->Send(message));
287  }
288
289 private:
290  scoped_ptr<IPC::Channel> in, out;
291  base::Thread* in_thread_;
292  base::Thread* out_thread_;
293  MyCBListener cb_listener_;
294  MyCBListener null_listener_;
295};
296
297// This test is meant to provoke a kernel bug on OSX, and to prove
298// that the workaround for it is working. It sets up two pipes and three
299// threads, the producer thread creates socketpairs and sends one of the fds
300// over pipe1 to the middleman thread. The middleman thread simply takes the fd
301// sends it over pipe2 to the consumer thread. The consumer thread writes a byte
302// to each fd it receives and then closes the pipe. The producer thread reads
303// the bytes back from each pair of pipes and make sure that everything worked.
304// This feedback mechanism makes sure that not too many file descriptors are
305// in flight at the same time. For more info on the bug, see:
306// http://crbug.com/298276
307class IPCMultiSendingFdsTest : public testing::Test {
308 public:
309  IPCMultiSendingFdsTest() : received_(true, false) {}
310
311  void Producer(PipeChannelHelper* dest,
312                base::Thread* t,
313                int pipes_to_send) {
314    for (int i = 0; i < pipes_to_send; i++) {
315      received_.Reset();
316      std::pair<int, int> pipe_fds = make_socket_pair();
317      t->message_loop()->PostTask(
318          FROM_HERE,
319          base::Bind(&PipeChannelHelper::Send,
320                     base::Unretained(dest),
321                     pipe_fds.second));
322      char tmp = 'x';
323      CHECK_EQ(1, HANDLE_EINTR(write(pipe_fds.first, &tmp, 1)));
324      CHECK_EQ(0, HANDLE_EINTR(close(pipe_fds.first)));
325      received_.Wait();
326    }
327  }
328
329  void ConsumerHandleFD(int fd) {
330    char tmp = 'y';
331    CHECK_EQ(1, HANDLE_EINTR(read(fd, &tmp, 1)));
332    CHECK_EQ(tmp, 'x');
333    CHECK_EQ(0, HANDLE_EINTR(close(fd)));
334    received_.Signal();
335  }
336
337  base::Thread* CreateThread(const char* name) {
338    base::Thread* ret = new base::Thread(name);
339    base::Thread::Options options;
340    options.message_loop_type = base::MessageLoop::TYPE_IO;
341    ret->StartWithOptions(options);
342    return ret;
343  }
344
345  void Run() {
346    // On my mac, this test fails roughly 35 times per
347    // million sends with low load, but much more with high load.
348    // Unless the workaround is in place. With 10000 sends, we
349    // should see at least a 3% failure rate.
350    const int pipes_to_send = 20000;
351    scoped_ptr<base::Thread> producer(CreateThread("producer"));
352    scoped_ptr<base::Thread> middleman(CreateThread("middleman"));
353    scoped_ptr<base::Thread> consumer(CreateThread("consumer"));
354    PipeChannelHelper pipe1(
355        middleman.get(),
356        consumer.get(),
357        base::Bind(&IPCMultiSendingFdsTest::ConsumerHandleFD,
358                   base::Unretained(this)),
359        pipes_to_send);
360    PipeChannelHelper pipe2(
361        producer.get(),
362        middleman.get(),
363        base::Bind(&PipeChannelHelper::Send, base::Unretained(&pipe1)),
364        pipes_to_send);
365    pipe1.Init();
366    pipe2.Init();
367    Producer(&pipe2, producer.get(), pipes_to_send);
368  }
369
370 private:
371  base::WaitableEvent received_;
372};
373
374TEST_F(IPCMultiSendingFdsTest, StressTest) {
375  Run();
376}
377
378}  // namespace
379
380#endif  // defined(OS_POSIX)
381