• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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)
9 extern "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 
29 namespace {
30 
31 const unsigned kNumFDsToSend = 20;
32 const char* kDevZeroPath = "/dev/zero";
33 
34 class MyChannelDescriptorListenerBase : public IPC::Listener {
35  public:
OnMessageReceived(const IPC::Message & message)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 
51 class MyChannelDescriptorListener : public MyChannelDescriptorListenerBase {
52  public:
MyChannelDescriptorListener(ino_t expected_inode_num)53   explicit MyChannelDescriptorListener(ino_t expected_inode_num)
54       : MyChannelDescriptorListenerBase(),
55         expected_inode_num_(expected_inode_num),
56         num_fds_received_(0) {
57   }
58 
GotExpectedNumberOfDescriptors() const59   bool GotExpectedNumberOfDescriptors() const {
60     return num_fds_received_ == kNumFDsToSend;
61   }
62 
OnChannelError()63   virtual void OnChannelError() OVERRIDE {
64     base::MessageLoop::current()->Quit();
65   }
66 
67  protected:
HandleFD(int fd)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 
95 class IPCSendFdsTest : public IPCTestBase {
96  protected:
RunServer()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 
TEST_F(IPCSendFdsTest,DescriptorTest)126 TEST_F(IPCSendFdsTest, DescriptorTest) {
127   Init("SendFdsClient");
128   RunServer();
129 }
130 
SendFdsClientCommon(const std::string & test_client_name,ino_t expected_inode_num)131 int 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 
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsClient)152 MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsClient) {
153   struct stat st;
154   int fd = open(kDevZeroPath, O_RDONLY);
155   fstat(fd, &st);
156   EXPECT_GE(IGNORE_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.
TEST_F(IPCSendFdsTest,DescriptorTestSandboxed)163 TEST_F(IPCSendFdsTest, DescriptorTestSandboxed) {
164   Init("SendFdsSandboxedClient");
165   RunServer();
166 }
167 
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsSandboxedClient)168 MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsSandboxedClient) {
169   struct stat st;
170   const int fd = open(kDevZeroPath, O_RDONLY);
171   fstat(fd, &st);
172   if (IGNORE_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 
197 class MyCBListener : public MyChannelDescriptorListenerBase {
198  public:
MyCBListener(base::Callback<void (int)> cb,int fds_to_send)199   MyCBListener(base::Callback<void(int)> cb, int fds_to_send)
200       : MyChannelDescriptorListenerBase(),
201         cb_(cb) {
202     }
203 
204  protected:
HandleFD(int fd)205   virtual void HandleFD(int fd) OVERRIDE {
206     cb_.Run(fd);
207   }
208  private:
209   base::Callback<void(int)> cb_;
210 };
211 
make_socket_pair()212 std::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 
null_cb(int unused_fd)218 static void null_cb(int unused_fd) {
219   NOTREACHED();
220 }
221 
222 class PipeChannelHelper {
223  public:
PipeChannelHelper(base::Thread * in_thread,base::Thread * out_thread,base::Callback<void (int)> cb,int fds_to_send)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 
Init()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 
DestroyChannel(scoped_ptr<IPC::Channel> * c,base::WaitableEvent * event)254   static void DestroyChannel(scoped_ptr<IPC::Channel> *c,
255                              base::WaitableEvent *event) {
256     c->reset(0);
257     event->Signal();
258   }
259 
~PipeChannelHelper()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 
Connect(IPC::Channel * channel)273   static void Connect(IPC::Channel *channel) {
274     EXPECT_TRUE(channel->Connect());
275   }
276 
Send(int fd)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
307 class IPCMultiSendingFdsTest : public testing::Test {
308  public:
IPCMultiSendingFdsTest()309   IPCMultiSendingFdsTest() : received_(true, false) {}
310 
Producer(PipeChannelHelper * dest,base::Thread * t,int pipes_to_send)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, IGNORE_EINTR(close(pipe_fds.first)));
325       received_.Wait();
326     }
327   }
328 
ConsumerHandleFD(int fd)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, IGNORE_EINTR(close(fd)));
334     received_.Signal();
335   }
336 
CreateThread(const char * name)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 
Run()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 
TEST_F(IPCMultiSendingFdsTest,StressTest)374 TEST_F(IPCMultiSendingFdsTest, StressTest) {
375   Run();
376 }
377 
378 }  // namespace
379 
380 #endif  // defined(OS_POSIX)
381