summaryrefslogtreecommitdiffstats
path: root/ipc/attachment_broker_privileged_mac_unittest.cc
blob: dbecc735c149c190062045732dda05d96b99ad4a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
// Copyright 2015 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 "ipc/attachment_broker_privileged_mac.h"

#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <stddef.h>
#include <stdint.h>

#include <map>

#include "base/command_line.h"
#include "base/mac/mac_util.h"
#include "base/mac/mach_logging.h"
#include "base/mac/mach_port_util.h"
#include "base/mac/scoped_mach_port.h"
#include "base/macros.h"
#include "base/memory/shared_memory.h"
#include "base/process/port_provider_mac.h"
#include "base/process/process_handle.h"
#include "base/sys_info.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "ipc/test_util_mac.h"
#include "testing/multiprocess_func_list.h"

namespace IPC {

namespace {

static const std::string g_service_switch_name = "service_name";

// Sends a uint32_t to a mach port.
void SendUInt32(mach_port_t port, uint32_t message) {
  int message_size = sizeof(uint32_t);
  int total_size = message_size + sizeof(mach_msg_header_t);
  void* buffer = malloc(total_size);
  mach_msg_header_t* header = (mach_msg_header_t*)buffer;
  header->msgh_remote_port = port;
  header->msgh_local_port = MACH_PORT_NULL;
  header->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
  header->msgh_reserved = 0;
  header->msgh_id = 0;
  header->msgh_size = total_size;
  memcpy(static_cast<char*>(buffer) + sizeof(mach_msg_header_t), &message,
         message_size);

  kern_return_t kr;
  kr = mach_msg(static_cast<mach_msg_header_t*>(buffer), MACH_SEND_MSG,
                total_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
                MACH_PORT_NULL);
  MACH_CHECK(kr == KERN_SUCCESS, kr) << "SendUInt32";
  free(buffer);
}

// Receives a uint32_t from a mach port.
uint32_t ReceiveUInt32(mach_port_t listening_port) {
  int message_size = sizeof(uint32_t);
  int total_size =
      message_size + sizeof(mach_msg_header_t) + sizeof(mach_msg_trailer_t);
  int options = MACH_RCV_MSG;
  void* buffer = malloc(total_size);

  int kr =
      mach_msg(static_cast<mach_msg_header_t*>(buffer), options, 0, total_size,
               listening_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
  MACH_CHECK(kr == KERN_SUCCESS, kr) << "ReceiveUInt32";

  uint32_t response;
  memcpy(&response, static_cast<char*>(buffer) + sizeof(mach_msg_header_t),
         message_size);

  free(buffer);
  return response;
}

// Sets up the mach communication ports with the server. Returns a port to which
// the server will send mach objects.
// |original_name_count| is an output variable that describes the number of
// active names in this task before the task port is shared with the server.
base::mac::ScopedMachReceiveRight CommonChildProcessSetUp(
    mach_msg_type_number_t* original_name_count) {
  base::CommandLine cmd_line = *base::CommandLine::ForCurrentProcess();
  std::string service_name =
      cmd_line.GetSwitchValueASCII(g_service_switch_name);
  base::mac::ScopedMachSendRight server_port(
      LookupServer(service_name.c_str()));
  base::mac::ScopedMachReceiveRight client_port(MakeReceivingPort());

  // |server_port| is a newly allocated right which will be deallocated once
  // this method returns.
  *original_name_count = GetActiveNameCount() - 1;

  // Send the port that this process is listening on to the server.
  SendMachPort(server_port.get(), client_port.get(), MACH_MSG_TYPE_MAKE_SEND);

  // Send the task port for this process.
  SendMachPort(server_port.get(), mach_task_self(), MACH_MSG_TYPE_COPY_SEND);
  return client_port;
}

// Creates a new shared memory region populated with 'a'.
scoped_ptr<base::SharedMemory> CreateAndPopulateSharedMemoryHandle(
    size_t size) {
  base::SharedMemoryHandle shm(size);
  scoped_ptr<base::SharedMemory> shared_memory(
      new base::SharedMemory(shm, false));
  shared_memory->Map(size);
  memset(shared_memory->memory(), 'a', size);
  return shared_memory;
}

// Create a shared memory region from a memory object. The returned object takes
// ownership of |memory_object|.
scoped_ptr<base::SharedMemory> MapMemoryObject(mach_port_t memory_object,
                                               size_t size) {
  base::SharedMemoryHandle shm(memory_object, size, base::GetCurrentProcId());
  scoped_ptr<base::SharedMemory> shared_memory(
      new base::SharedMemory(shm, false));
  shared_memory->Map(size);
  return shared_memory;
}

class MockPortProvider : public base::PortProvider {
 public:
  MockPortProvider() {}
  ~MockPortProvider() override {}
  mach_port_t TaskForPid(base::ProcessHandle process) const override {
    return MACH_PORT_NULL;
  }
};

}  // namespace

class AttachmentBrokerPrivilegedMacMultiProcessTest
    : public base::MultiProcessTest {
 public:
  AttachmentBrokerPrivilegedMacMultiProcessTest() {}

  base::CommandLine MakeCmdLine(const std::string& procname) override {
    base::CommandLine command_line = MultiProcessTest::MakeCmdLine(procname);
    // Pass the service name to the child process.
    command_line.AppendSwitchASCII(g_service_switch_name, service_name_);
    return command_line;
  }

  void SetUpChild(const std::string& name) {
    // Make a random service name so that this test doesn't conflict with other
    // similar tests.
    service_name_ = CreateRandomServiceName();
    server_port_.reset(BecomeMachServer(service_name_.c_str()).release());
    child_process_ = SpawnChild(name);
    client_port_.reset(ReceiveMachPort(server_port_.get()).release());
    client_task_port_.reset(ReceiveMachPort(server_port_.get()).release());
  }

  static const int s_memory_size = 99999;

 protected:
  std::string service_name_;

  // A port on which the main process listens for mach messages from the child
  // process.
  base::mac::ScopedMachReceiveRight server_port_;

  // A port on which the child process listens for mach messages from the main
  // process.
  base::mac::ScopedMachSendRight client_port_;

  // Child process's task port.
  base::mac::ScopedMachSendRight client_task_port_;

  // Dummy port provider.
  MockPortProvider port_provider_;

  base::Process child_process_;
  DISALLOW_COPY_AND_ASSIGN(AttachmentBrokerPrivilegedMacMultiProcessTest);
};

// The attachment broker inserts a right for a memory object into the
// destination task.
TEST_F(AttachmentBrokerPrivilegedMacMultiProcessTest, InsertRight) {
  // Mach-based SharedMemory isn't support on OSX 10.6.
  if (base::mac::IsOSSnowLeopard())
    return;

  SetUpChild("InsertRightClient");
  mach_msg_type_number_t original_name_count = GetActiveNameCount();
  IPC::AttachmentBrokerPrivilegedMac broker(&port_provider_);

  // Create some shared memory.
  scoped_ptr<base::SharedMemory> shared_memory =
      CreateAndPopulateSharedMemoryHandle(s_memory_size);
  ASSERT_TRUE(shared_memory->handle().IsValid());

  // Insert the memory object into the destination task, via an intermediate
  // port.
  IncrementMachRefCount(shared_memory->handle().GetMemoryObject(),
                        MACH_PORT_RIGHT_SEND);
  mach_port_name_t inserted_memory_object = base::CreateIntermediateMachPort(
      client_task_port_.get(),
      base::mac::ScopedMachSendRight(shared_memory->handle().GetMemoryObject()),
      nullptr);
  EXPECT_NE(inserted_memory_object,
            static_cast<mach_port_name_t>(MACH_PORT_NULL));
  SendUInt32(client_port_.get(), inserted_memory_object);

  // Check that no names have been leaked.
  shared_memory.reset();
  EXPECT_EQ(original_name_count, GetActiveNameCount());

  int rv = -1;
  ASSERT_TRUE(child_process_.WaitForExitWithTimeout(
      TestTimeouts::action_timeout(), &rv));
  EXPECT_EQ(0, rv);
}

MULTIPROCESS_TEST_MAIN(InsertRightClient) {
  mach_msg_type_number_t original_name_count = 0;
  base::mac::ScopedMachReceiveRight client_port(
      CommonChildProcessSetUp(&original_name_count).release());
  base::mac::ScopedMachReceiveRight inserted_port(
      ReceiveUInt32(client_port.get()));
  base::mac::ScopedMachSendRight memory_object(
      ReceiveMachPort(inserted_port.get()));
  inserted_port.reset();

  // The server should have inserted a right into this process.
  EXPECT_EQ(original_name_count + 1, GetActiveNameCount());

  // Map the memory object and check its contents.
  scoped_ptr<base::SharedMemory> shared_memory(MapMemoryObject(
      memory_object.release(),
      AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size));
  const char* start = static_cast<const char*>(shared_memory->memory());
  for (int i = 0;
       i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) {
    DCHECK_EQ(start[i], 'a');
  }

  // Check that no names have been leaked.
  shared_memory.reset();
  EXPECT_EQ(original_name_count, GetActiveNameCount());

  return 0;
}

// The attachment broker inserts the right for a memory object into the
// destination task twice.
TEST_F(AttachmentBrokerPrivilegedMacMultiProcessTest, InsertSameRightTwice) {
  // Mach-based SharedMemory isn't support on OSX 10.6.
  if (base::mac::IsOSSnowLeopard())
    return;

  SetUpChild("InsertSameRightTwiceClient");
  mach_msg_type_number_t original_name_count = GetActiveNameCount();
  IPC::AttachmentBrokerPrivilegedMac broker(&port_provider_);

  // Create some shared memory.
  scoped_ptr<base::SharedMemory> shared_memory =
      CreateAndPopulateSharedMemoryHandle(s_memory_size);
  ASSERT_TRUE(shared_memory->handle().IsValid());

  // Insert the memory object into the destination task, via an intermediate
  // port, twice.
  for (int i = 0; i < 2; ++i) {
    IncrementMachRefCount(shared_memory->handle().GetMemoryObject(),
                          MACH_PORT_RIGHT_SEND);
    mach_port_name_t inserted_memory_object = base::CreateIntermediateMachPort(
        client_task_port_.get(),
        base::mac::ScopedMachSendRight(
            shared_memory->handle().GetMemoryObject()),
        nullptr);
    EXPECT_NE(inserted_memory_object,
              static_cast<mach_port_name_t>(MACH_PORT_NULL));
    SendUInt32(client_port_.get(), inserted_memory_object);
  }

  // Check that no names have been leaked.
  shared_memory.reset();
  EXPECT_EQ(original_name_count, GetActiveNameCount());

  int rv = -1;
  ASSERT_TRUE(child_process_.WaitForExitWithTimeout(
      TestTimeouts::action_timeout(), &rv));
  EXPECT_EQ(0, rv);
}

MULTIPROCESS_TEST_MAIN(InsertSameRightTwiceClient) {
  mach_msg_type_number_t original_name_count = 0;
  base::mac::ScopedMachReceiveRight client_port(
      CommonChildProcessSetUp(&original_name_count).release());

  // Receive two memory objects.
  base::mac::ScopedMachReceiveRight inserted_port(
      ReceiveUInt32(client_port.get()));
  base::mac::ScopedMachReceiveRight inserted_port2(
      ReceiveUInt32(client_port.get()));
  base::mac::ScopedMachSendRight memory_object(
      ReceiveMachPort(inserted_port.get()));
  base::mac::ScopedMachSendRight memory_object2(
      ReceiveMachPort(inserted_port2.get()));
  inserted_port.reset();
  inserted_port2.reset();

  // Both rights are for the same Mach port, so only one new name should appear.
  EXPECT_EQ(original_name_count + 1, GetActiveNameCount());

  // Map both memory objects and check their contents.
  scoped_ptr<base::SharedMemory> shared_memory(MapMemoryObject(
      memory_object.release(),
      AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size));
  char* start = static_cast<char*>(shared_memory->memory());
  for (int i = 0;
       i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) {
    DCHECK_EQ(start[i], 'a');
  }

  scoped_ptr<base::SharedMemory> shared_memory2(MapMemoryObject(
      memory_object2.release(),
      AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size));
  char* start2 = static_cast<char*>(shared_memory2->memory());
  for (int i = 0;
       i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) {
    DCHECK_EQ(start2[i], 'a');
  }

  // Check that the contents of both regions are shared.
  start[0] = 'b';
  DCHECK_EQ(start2[0], 'b');

  // After releasing one shared memory region, the name count shouldn't change,
  // since another reference exists.
  shared_memory.reset();
  EXPECT_EQ(original_name_count + 1, GetActiveNameCount());

  // After releasing the second shared memory region, the name count should be
  // as if no names were ever inserted
  shared_memory2.reset();
  EXPECT_EQ(original_name_count, GetActiveNameCount());

  return 0;
}

// The attachment broker inserts the rights for two memory objects into the
// destination task.
TEST_F(AttachmentBrokerPrivilegedMacMultiProcessTest, InsertTwoRights) {
  // Mach-based SharedMemory isn't support on OSX 10.6.
  if (base::mac::IsOSSnowLeopard())
    return;

  SetUpChild("InsertTwoRightsClient");
  mach_msg_type_number_t original_name_count = GetActiveNameCount();
  IPC::AttachmentBrokerPrivilegedMac broker(&port_provider_);

  for (int i = 0; i < 2; ++i) {
    // Create some shared memory.
    scoped_ptr<base::SharedMemory> shared_memory =
        CreateAndPopulateSharedMemoryHandle(s_memory_size);
    ASSERT_TRUE(shared_memory->handle().IsValid());

    // Insert the memory object into the destination task, via an intermediate
    // port.
    IncrementMachRefCount(shared_memory->handle().GetMemoryObject(),
                          MACH_PORT_RIGHT_SEND);
    mach_port_name_t inserted_memory_object = base::CreateIntermediateMachPort(
        client_task_port_.get(),
        base::mac::ScopedMachSendRight(
            shared_memory->handle().GetMemoryObject()),
        nullptr);
    EXPECT_NE(inserted_memory_object,
              static_cast<mach_port_name_t>(MACH_PORT_NULL));
    SendUInt32(client_port_.get(), inserted_memory_object);
  }

  // Check that no names have been leaked.
  EXPECT_EQ(original_name_count, GetActiveNameCount());

  int rv = -1;
  ASSERT_TRUE(child_process_.WaitForExitWithTimeout(
      TestTimeouts::action_timeout(), &rv));
  EXPECT_EQ(0, rv);
}

MULTIPROCESS_TEST_MAIN(InsertTwoRightsClient) {
  mach_msg_type_number_t original_name_count = 0;
  base::mac::ScopedMachReceiveRight client_port(
      CommonChildProcessSetUp(&original_name_count).release());

  // Receive two memory objects.
  base::mac::ScopedMachReceiveRight inserted_port(
      ReceiveUInt32(client_port.get()));
  base::mac::ScopedMachReceiveRight inserted_port2(
      ReceiveUInt32(client_port.get()));
  base::mac::ScopedMachSendRight memory_object(
      ReceiveMachPort(inserted_port.get()));
  base::mac::ScopedMachSendRight memory_object2(
      ReceiveMachPort(inserted_port2.get()));
  inserted_port.reset();
  inserted_port2.reset();

  // There should be two new names to reflect the two new shared memory regions.
  EXPECT_EQ(original_name_count + 2, GetActiveNameCount());

  // Map both memory objects and check their contents.
  scoped_ptr<base::SharedMemory> shared_memory(MapMemoryObject(
      memory_object.release(),
      AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size));
  char* start = static_cast<char*>(shared_memory->memory());
  for (int i = 0;
       i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) {
    DCHECK_EQ(start[i], 'a');
  }

  scoped_ptr<base::SharedMemory> shared_memory2(MapMemoryObject(
      memory_object2.release(),
      AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size));
  char* start2 = static_cast<char*>(shared_memory2->memory());
  for (int i = 0;
       i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) {
    DCHECK_EQ(start2[i], 'a');
  }

  // Check that the contents of both regions are not shared.
  start[0] = 'b';
  DCHECK_EQ(start2[0], 'a');

  // After releasing one shared memory region, the name count should decrement.
  shared_memory.reset();
  EXPECT_EQ(original_name_count + 1, GetActiveNameCount());
  shared_memory2.reset();
  EXPECT_EQ(original_name_count, GetActiveNameCount());

  return 0;
}

}  //  namespace IPC