Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/ipc/glue/Shmem.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "Shmem.h"
8
9
#include "ProtocolUtils.h"
10
#include "SharedMemoryBasic.h"
11
12
#include "mozilla/Unused.h"
13
14
15
namespace mozilla {
16
namespace ipc {
17
18
class ShmemCreated : public IPC::Message
19
{
20
private:
21
  typedef Shmem::id_t id_t;
22
23
public:
24
  ShmemCreated(int32_t routingId,
25
               id_t aIPDLId,
26
               size_t aSize,
27
               SharedMemory::SharedMemoryType aType) :
28
    IPC::Message(routingId, SHMEM_CREATED_MESSAGE_TYPE, 0,
29
                 HeaderFlags(NESTED_INSIDE_CPOW))
30
0
  {
31
0
    IPC::WriteParam(this, aIPDLId);
32
0
    IPC::WriteParam(this, aSize);
33
0
    IPC::WriteParam(this, int32_t(aType));
34
0
  }
35
36
  static bool
37
  ReadInfo(const Message* msg, PickleIterator* iter,
38
           id_t* aIPDLId,
39
           size_t* aSize,
40
           SharedMemory::SharedMemoryType* aType)
41
0
  {
42
0
    if (!IPC::ReadParam(msg, iter, aIPDLId) ||
43
0
        !IPC::ReadParam(msg, iter, aSize) ||
44
0
        !IPC::ReadParam(msg, iter, reinterpret_cast<int32_t*>(aType)))
45
0
      return false;
46
0
    return true;
47
0
  }
48
49
  void Log(const std::string& aPrefix,
50
           FILE* aOutf) const
51
0
  {
52
0
    fputs("(special ShmemCreated msg)", aOutf);
53
0
  }
54
};
55
56
class ShmemDestroyed : public IPC::Message
57
{
58
private:
59
  typedef Shmem::id_t id_t;
60
61
public:
62
  ShmemDestroyed(int32_t routingId,
63
                 id_t aIPDLId) :
64
    IPC::Message(routingId, SHMEM_DESTROYED_MESSAGE_TYPE)
65
0
  {
66
0
    IPC::WriteParam(this, aIPDLId);
67
0
  }
68
};
69
70
static SharedMemory*
71
NewSegment(SharedMemory::SharedMemoryType aType)
72
0
{
73
0
  if (SharedMemory::TYPE_BASIC == aType) {
74
0
    return new SharedMemoryBasic;
75
0
  } else {
76
0
    NS_ERROR("unknown Shmem type");
77
0
    return nullptr;
78
0
  }
79
0
}
80
81
static already_AddRefed<SharedMemory>
82
CreateSegment(SharedMemory::SharedMemoryType aType, size_t aNBytes, size_t aExtraSize)
83
0
{
84
0
  RefPtr<SharedMemory> segment = NewSegment(aType);
85
0
  if (!segment) {
86
0
    return nullptr;
87
0
  }
88
0
  size_t size = SharedMemory::PageAlignedSize(aNBytes + aExtraSize);
89
0
  if (!segment->Create(size) || !segment->Map(size)) {
90
0
    return nullptr;
91
0
  }
92
0
  return segment.forget();
93
0
}
94
95
static already_AddRefed<SharedMemory>
96
ReadSegment(const IPC::Message& aDescriptor, Shmem::id_t* aId, size_t* aNBytes, size_t aExtraSize)
97
0
{
98
0
  if (SHMEM_CREATED_MESSAGE_TYPE != aDescriptor.type()) {
99
0
    NS_ERROR("expected 'shmem created' message");
100
0
    return nullptr;
101
0
  }
102
0
  SharedMemory::SharedMemoryType type;
103
0
  PickleIterator iter(aDescriptor);
104
0
  if (!ShmemCreated::ReadInfo(&aDescriptor, &iter, aId, aNBytes, &type)) {
105
0
    return nullptr;
106
0
  }
107
0
  RefPtr<SharedMemory> segment = NewSegment(type);
108
0
  if (!segment) {
109
0
    return nullptr;
110
0
  }
111
0
  if (!segment->ReadHandle(&aDescriptor, &iter)) {
112
0
    NS_ERROR("trying to open invalid handle");
113
0
    return nullptr;
114
0
  }
115
0
  aDescriptor.EndRead(iter);
116
0
  size_t size = SharedMemory::PageAlignedSize(*aNBytes + aExtraSize);
117
0
  if (!segment->Map(size)) {
118
0
    return nullptr;
119
0
  }
120
0
  // close the handle to the segment after it is mapped
121
0
  segment->CloseHandle();
122
0
  return segment.forget();
123
0
}
124
125
static void
126
DestroySegment(SharedMemory* aSegment)
127
0
{
128
0
  // the SharedMemory dtor closes and unmaps the actual OS shmem segment
129
0
  if (aSegment) {
130
0
    aSegment->Release();
131
0
  }
132
0
}
133
134
135
#if defined(DEBUG)
136
137
static const char sMagic[] =
138
    "This little piggy went to market.\n"
139
    "This little piggy stayed at home.\n"
140
    "This little piggy has roast beef,\n"
141
    "This little piggy had none.\n"
142
    "And this little piggy cried \"Wee! Wee! Wee!\" all the way home";
143
144
145
struct Header {
146
  // Don't use size_t or bool here because their size depends on the
147
  // architecture.
148
  uint32_t mSize;
149
  uint32_t mUnsafe;
150
  char mMagic[sizeof(sMagic)];
151
};
152
153
static void
154
GetSections(Shmem::SharedMemory* aSegment,
155
            Header** aHeader,
156
            char** aFrontSentinel,
157
            char** aData,
158
            char** aBackSentinel)
159
{
160
  MOZ_ASSERT(aSegment && aFrontSentinel && aData && aBackSentinel,
161
             "null param(s)");
162
163
  *aFrontSentinel = reinterpret_cast<char*>(aSegment->memory());
164
  MOZ_ASSERT(*aFrontSentinel, "null memory()");
165
166
  *aHeader = reinterpret_cast<Header*>(*aFrontSentinel);
167
168
  size_t pageSize = Shmem::SharedMemory::SystemPageSize();
169
  *aData = *aFrontSentinel + pageSize;
170
171
  *aBackSentinel = *aFrontSentinel + aSegment->Size() - pageSize;
172
}
173
174
static Header*
175
GetHeader(Shmem::SharedMemory* aSegment)
176
{
177
  Header* header;
178
  char* dontcare;
179
  GetSections(aSegment, &header, &dontcare, &dontcare, &dontcare);
180
  return header;
181
}
182
183
static void
184
Protect(SharedMemory* aSegment)
185
{
186
  MOZ_ASSERT(aSegment, "null segment");
187
  aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()),
188
                    aSegment->Size(),
189
                    RightsNone);
190
}
191
192
static void
193
Unprotect(SharedMemory* aSegment)
194
{
195
  MOZ_ASSERT(aSegment, "null segment");
196
  aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()),
197
                    aSegment->Size(),
198
                    RightsRead | RightsWrite);
199
}
200
201
//
202
// In debug builds, we specially allocate shmem segments.  The layout
203
// is as follows
204
//
205
//   Page 0: "front sentinel"
206
//     size of mapping
207
//     magic bytes
208
//   Page 1 through n-1:
209
//     user data
210
//   Page n: "back sentinel"
211
//     [nothing]
212
//
213
// The mapping can be in one of the following states, wrt to the
214
// current process.
215
//
216
//   State "unmapped": all pages are mapped with no access rights.
217
//
218
//   State "mapping": all pages are mapped with read/write access.
219
//
220
//   State "mapped": the front and back sentinels are mapped with no
221
//     access rights, and all the other pages are mapped with
222
//     read/write access.
223
//
224
// When a SharedMemory segment is first allocated, it starts out in
225
// the "mapping" state for the process that allocates the segment, and
226
// in the "unmapped" state for the other process.  The allocating
227
// process will then create a Shmem, which takes the segment into the
228
// "mapped" state, where it can be accessed by clients.
229
//
230
// When a Shmem is sent to another process in an IPDL message, the
231
// segment transitions into the "unmapped" state for the sending
232
// process, and into the "mapping" state for the receiving process.
233
// The receiving process will then create a Shmem from the underlying
234
// segment, and take the segment into the "mapped" state.
235
//
236
// In the "mapping" state, we use the front sentinel to verify the
237
// integrity of the shmem segment.  If valid, it has a size_t
238
// containing the number of bytes the user allocated followed by the
239
// magic bytes above.
240
//
241
// In the "mapped" state, the front and back sentinels have no access
242
// rights.  They act as guards against buffer overflows and underflows
243
// in client code; if clients touch a sentinel, they die with SIGSEGV.
244
//
245
// The "unmapped" state is used to enforce single-owner semantics of
246
// the shmem segment.  If a process other than the current owner tries
247
// to touch the segment, it dies with SIGSEGV.
248
//
249
250
Shmem::Shmem(PrivateIPDLCaller,
251
             SharedMemory* aSegment, id_t aId) :
252
    mSegment(aSegment),
253
    mData(nullptr),
254
    mSize(0)
255
{
256
  MOZ_ASSERT(mSegment, "null segment");
257
  MOZ_ASSERT(aId != 0, "invalid ID");
258
259
  Unprotect(mSegment);
260
261
  Header* header;
262
  char* frontSentinel;
263
  char* data;
264
  char* backSentinel;
265
  GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
266
267
  // do a quick validity check to avoid weird-looking crashes in libc
268
  char check = *frontSentinel;
269
  (void)check;
270
271
  MOZ_ASSERT(!strncmp(header->mMagic, sMagic, sizeof(sMagic)),
272
             "invalid segment");
273
  mSize = static_cast<size_t>(header->mSize);
274
275
  size_t pageSize = SharedMemory::SystemPageSize();
276
  // transition into the "mapped" state by protecting the front and
277
  // back sentinels (which guard against buffer under/overflows)
278
  mSegment->Protect(frontSentinel, pageSize, RightsNone);
279
  mSegment->Protect(backSentinel, pageSize, RightsNone);
280
281
  // don't set these until we know they're valid
282
  mData = data;
283
  mId = aId;
284
}
285
286
void
287
Shmem::AssertInvariants() const
288
{
289
  MOZ_ASSERT(mSegment, "null segment");
290
  MOZ_ASSERT(mData, "null data pointer");
291
  MOZ_ASSERT(mSize > 0, "invalid size");
292
  // if the segment isn't owned by the current process, these will
293
  // trigger SIGSEGV
294
  char checkMappingFront = *reinterpret_cast<char*>(mData);
295
  char checkMappingBack = *(reinterpret_cast<char*>(mData) + mSize - 1);
296
297
  // avoid "unused" warnings for these variables:
298
  Unused << checkMappingFront;
299
  Unused << checkMappingBack;
300
}
301
302
void
303
Shmem::RevokeRights(PrivateIPDLCaller)
304
{
305
  AssertInvariants();
306
307
  size_t pageSize = SharedMemory::SystemPageSize();
308
  Header* header = GetHeader(mSegment);
309
310
  // Open this up for reading temporarily
311
  mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsRead);
312
313
  if (!header->mUnsafe) {
314
    Protect(mSegment);
315
  } else {
316
    mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsNone);
317
  }
318
}
319
320
// static
321
already_AddRefed<Shmem::SharedMemory>
322
Shmem::Alloc(PrivateIPDLCaller,
323
             size_t aNBytes,
324
             SharedMemoryType aType,
325
             bool aUnsafe,
326
             bool aProtect)
327
{
328
  NS_ASSERTION(aNBytes <= UINT32_MAX, "Will truncate shmem segment size!");
329
  MOZ_ASSERT(!aProtect || !aUnsafe, "protect => !unsafe");
330
331
  size_t pageSize = SharedMemory::SystemPageSize();
332
  // |2*pageSize| is for the front and back sentinel
333
  RefPtr<SharedMemory> segment = CreateSegment(aType, aNBytes, 2*pageSize);
334
  if (!segment) {
335
    return nullptr;
336
  }
337
338
  Header* header;
339
  char *frontSentinel;
340
  char *data;
341
  char *backSentinel;
342
  GetSections(segment, &header, &frontSentinel, &data, &backSentinel);
343
344
  // initialize the segment with Shmem-internal information
345
346
  // NB: this can't be a static assert because technically pageSize
347
  // isn't known at compile time, event though in practice it's always
348
  // going to be 4KiB
349
  MOZ_ASSERT(sizeof(Header) <= pageSize,
350
             "Shmem::Header has gotten too big");
351
  memcpy(header->mMagic, sMagic, sizeof(sMagic));
352
  header->mSize = static_cast<uint32_t>(aNBytes);
353
  header->mUnsafe = aUnsafe;
354
355
  if (aProtect)
356
    Protect(segment);
357
358
  return segment.forget();
359
}
360
361
// static
362
already_AddRefed<Shmem::SharedMemory>
363
Shmem::OpenExisting(PrivateIPDLCaller,
364
                    const IPC::Message& aDescriptor,
365
                    id_t* aId,
366
                    bool aProtect)
367
{
368
  size_t size;
369
  size_t pageSize = SharedMemory::SystemPageSize();
370
  // |2*pageSize| is for the front and back sentinels
371
  RefPtr<SharedMemory> segment = ReadSegment(aDescriptor, aId, &size, 2*pageSize);
372
  if (!segment) {
373
    return nullptr;
374
  }
375
376
  Header* header = GetHeader(segment);
377
378
  if (size != header->mSize) {
379
    // Deallocation should zero out the header, so check for that.
380
    if (header->mSize || header->mUnsafe || header->mMagic[0] ||
381
        memcmp(header->mMagic, &header->mMagic[1], sizeof(header->mMagic)-1)) {
382
      NS_ERROR("Wrong size for this Shmem!");
383
    } else {
384
      NS_WARNING("Shmem was deallocated");
385
    }
386
    return nullptr;
387
  }
388
389
  // The caller of this function may not know whether the segment is
390
  // unsafe or not
391
  if (!header->mUnsafe && aProtect)
392
    Protect(segment);
393
394
  return segment.forget();
395
}
396
397
// static
398
void
399
Shmem::Dealloc(PrivateIPDLCaller,
400
               SharedMemory* aSegment)
401
{
402
  if (!aSegment)
403
    return;
404
405
  size_t pageSize = SharedMemory::SystemPageSize();
406
  Header* header;
407
  char *frontSentinel;
408
  char *data;
409
  char *backSentinel;
410
  GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
411
412
  aSegment->Protect(frontSentinel, pageSize, RightsWrite | RightsRead);
413
  memset(header->mMagic, 0, sizeof(sMagic));
414
  header->mSize = 0;
415
  header->mUnsafe = false;          // make it "safe" so as to catch errors
416
417
  DestroySegment(aSegment);
418
}
419
420
421
#else  // !defined(DEBUG)
422
423
// static
424
already_AddRefed<Shmem::SharedMemory>
425
Shmem::Alloc(PrivateIPDLCaller,
426
             size_t aNBytes,
427
             SharedMemoryType aType,
428
             bool /*unused*/,
429
             bool /*unused*/)
430
0
{
431
0
  RefPtr<SharedMemory> segment = CreateSegment(aType, aNBytes, sizeof(uint32_t));
432
0
  if (!segment) {
433
0
    return nullptr;
434
0
  }
435
0
436
0
  *PtrToSize(segment) = static_cast<uint32_t>(aNBytes);
437
0
438
0
  return segment.forget();
439
0
}
440
441
// static
442
already_AddRefed<Shmem::SharedMemory>
443
Shmem::OpenExisting(PrivateIPDLCaller,
444
                    const IPC::Message& aDescriptor,
445
                    id_t* aId,
446
                    bool /*unused*/)
447
0
{
448
0
  size_t size;
449
0
  RefPtr<SharedMemory> segment = ReadSegment(aDescriptor, aId, &size, sizeof(uint32_t));
450
0
  if (!segment) {
451
0
    return nullptr;
452
0
  }
453
0
454
0
  // this is the only validity check done in non-DEBUG builds
455
0
  if (size != static_cast<size_t>(*PtrToSize(segment))) {
456
0
    return nullptr;
457
0
  }
458
0
459
0
  return segment.forget();
460
0
}
461
462
// static
463
void
464
Shmem::Dealloc(PrivateIPDLCaller,
465
               SharedMemory* aSegment)
466
0
{
467
0
  DestroySegment(aSegment);
468
0
}
469
470
#endif  // if defined(DEBUG)
471
472
IPC::Message*
473
Shmem::ShareTo(PrivateIPDLCaller,
474
               base::ProcessId aTargetPid,
475
               int32_t routingId)
476
0
{
477
0
  AssertInvariants();
478
0
479
0
  IPC::Message *msg = new ShmemCreated(routingId, mId, mSize, mSegment->Type());
480
0
  if (!mSegment->ShareHandle(aTargetPid, msg)) {
481
0
    return nullptr;
482
0
  }
483
0
  // close the handle to the segment after it is shared
484
0
  mSegment->CloseHandle();
485
0
  return msg;
486
0
}
487
488
IPC::Message*
489
Shmem::UnshareFrom(PrivateIPDLCaller,
490
                   int32_t routingId)
491
0
{
492
0
  AssertInvariants();
493
0
  return new ShmemDestroyed(routingId, mId);
494
0
}
495
496
void
497
IPDLParamTraits<Shmem>::Write(IPC::Message* aMsg, IProtocol* aActor,
498
                              Shmem& aParam)
499
0
{
500
0
  WriteIPDLParam(aMsg, aActor, aParam.mId);
501
0
502
0
  aParam.RevokeRights(
503
0
    Shmem::PrivateIPDLCaller());
504
0
  aParam.forget(
505
0
    Shmem::PrivateIPDLCaller());
506
0
}
507
508
bool
509
IPDLParamTraits<Shmem>::Read(const IPC::Message* aMsg, PickleIterator* aIter,
510
                             IProtocol* aActor, paramType* aResult)
511
0
{
512
0
  paramType::id_t id;
513
0
  if (!ReadIPDLParam(aMsg, aIter, aActor, &id)) {
514
0
    return false;
515
0
  }
516
0
517
0
  Shmem::SharedMemory* rawmem = aActor->LookupSharedMemory(id);
518
0
  if (!rawmem) {
519
0
    return false;
520
0
  }
521
0
  *aResult = Shmem(
522
0
    Shmem::PrivateIPDLCaller(),
523
0
    rawmem, id);
524
0
  return true;
525
0
}
526
527
} // namespace ipc
528
} // namespace mozilla