Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=8 et :
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "Shmem.h"
10 #include "ProtocolUtils.h"
11 #include "SharedMemoryBasic.h"
12 #include "SharedMemorySysV.h"
14 #include "nsAutoPtr.h"
15 #include "mozilla/unused.h"
18 namespace mozilla {
19 namespace ipc {
21 class ShmemCreated : public IPC::Message
22 {
23 private:
24 typedef Shmem::id_t id_t;
26 public:
27 ShmemCreated(int32_t routingId,
28 const id_t& aIPDLId,
29 const size_t& aSize,
30 const SharedMemoryBasic::Handle& aHandle) :
31 IPC::Message(routingId, SHMEM_CREATED_MESSAGE_TYPE, PRIORITY_NORMAL)
32 {
33 IPC::WriteParam(this, aIPDLId);
34 IPC::WriteParam(this, aSize);
35 IPC::WriteParam(this, int32_t(SharedMemory::TYPE_BASIC)),
36 IPC::WriteParam(this, aHandle);
37 }
39 // Instead of a single Read() function, we have ReadInfo() and
40 // ReadHandle(). The reason is that the handle type is specific to
41 // the shmem type. These functions should only be called in the
42 // order ReadInfo(); ReadHandle();, and only once each.
44 static bool
45 ReadInfo(const Message* msg, void** iter,
46 id_t* aIPDLId,
47 size_t* aSize,
48 SharedMemory::SharedMemoryType* aType)
49 {
50 if (!IPC::ReadParam(msg, iter, aIPDLId) ||
51 !IPC::ReadParam(msg, iter, aSize) ||
52 !IPC::ReadParam(msg, iter, reinterpret_cast<int32_t*>(aType)))
53 return false;
54 return true;
55 }
57 static bool
58 ReadHandle(const Message* msg, void** iter,
59 SharedMemoryBasic::Handle* aHandle)
60 {
61 if (!IPC::ReadParam(msg, iter, aHandle))
62 return false;
63 msg->EndRead(*iter);
64 return true;
65 }
67 #ifdef MOZ_HAVE_SHAREDMEMORYSYSV
68 ShmemCreated(int32_t routingId,
69 const id_t& aIPDLId,
70 const size_t& aSize,
71 const SharedMemorySysV::Handle& aHandle) :
72 IPC::Message(routingId, SHMEM_CREATED_MESSAGE_TYPE, PRIORITY_NORMAL)
73 {
74 IPC::WriteParam(this, aIPDLId);
75 IPC::WriteParam(this, aSize);
76 IPC::WriteParam(this, int32_t(SharedMemory::TYPE_SYSV)),
77 IPC::WriteParam(this, aHandle);
78 }
80 static bool
81 ReadHandle(const Message* msg, void** iter,
82 SharedMemorySysV::Handle* aHandle)
83 {
84 if (!IPC::ReadParam(msg, iter, aHandle))
85 return false;
86 msg->EndRead(*iter);
87 return true;
88 }
89 #endif
91 void Log(const std::string& aPrefix,
92 FILE* aOutf) const
93 {
94 fputs("(special ShmemCreated msg)", aOutf);
95 }
96 };
98 class ShmemDestroyed : public IPC::Message
99 {
100 private:
101 typedef Shmem::id_t id_t;
103 public:
104 ShmemDestroyed(int32_t routingId,
105 const id_t& aIPDLId) :
106 IPC::Message(routingId, SHMEM_DESTROYED_MESSAGE_TYPE, PRIORITY_NORMAL)
107 {
108 IPC::WriteParam(this, aIPDLId);
109 }
110 };
113 #ifdef MOZ_HAVE_SHAREDMEMORYSYSV
114 static Shmem::SharedMemory*
115 CreateSegment(size_t aNBytes, SharedMemorySysV::Handle aHandle)
116 {
117 nsAutoPtr<SharedMemory> segment;
119 if (SharedMemorySysV::IsHandleValid(aHandle)) {
120 segment = new SharedMemorySysV(aHandle);
121 }
122 else {
123 segment = new SharedMemorySysV();
125 if (!segment->Create(aNBytes))
126 return 0;
127 }
128 if (!segment->Map(aNBytes))
129 return 0;
131 segment->AddRef();
132 return segment.forget();
133 }
134 #endif
136 static Shmem::SharedMemory*
137 CreateSegment(size_t aNBytes, SharedMemoryBasic::Handle aHandle)
138 {
139 nsAutoPtr<SharedMemory> segment;
141 if (SharedMemoryBasic::IsHandleValid(aHandle)) {
142 segment = new SharedMemoryBasic(aHandle);
143 }
144 else {
145 segment = new SharedMemoryBasic();
147 if (!segment->Create(aNBytes))
148 return 0;
149 }
150 if (!segment->Map(aNBytes))
151 return 0;
153 segment->AddRef();
154 return segment.forget();
155 }
157 static void
158 DestroySegment(SharedMemory* aSegment)
159 {
160 // the SharedMemory dtor closes and unmaps the actual OS shmem segment
161 if (aSegment)
162 aSegment->Release();
163 }
166 #if defined(DEBUG)
168 static const char sMagic[] =
169 "This little piggy went to market.\n"
170 "This little piggy stayed at home.\n"
171 "This little piggy has roast beef,\n"
172 "This little piggy had none.\n"
173 "And this little piggy cried \"Wee! Wee! Wee!\" all the way home";
176 struct Header {
177 // Don't use size_t or bool here because their size depends on the
178 // architecture.
179 uint32_t mSize;
180 uint32_t mUnsafe;
181 char mMagic[sizeof(sMagic)];
182 };
184 static void
185 GetSections(Shmem::SharedMemory* aSegment,
186 Header** aHeader,
187 char** aFrontSentinel,
188 char** aData,
189 char** aBackSentinel)
190 {
191 NS_ABORT_IF_FALSE(aSegment && aFrontSentinel && aData && aBackSentinel,
192 "NULL param(s)");
194 *aFrontSentinel = reinterpret_cast<char*>(aSegment->memory());
195 NS_ABORT_IF_FALSE(*aFrontSentinel, "NULL memory()");
197 *aHeader = reinterpret_cast<Header*>(*aFrontSentinel);
199 size_t pageSize = Shmem::SharedMemory::SystemPageSize();
200 *aData = *aFrontSentinel + pageSize;
202 *aBackSentinel = *aFrontSentinel + aSegment->Size() - pageSize;
203 }
205 static Header*
206 GetHeader(Shmem::SharedMemory* aSegment)
207 {
208 Header* header;
209 char* dontcare;
210 GetSections(aSegment, &header, &dontcare, &dontcare, &dontcare);
211 return header;
212 }
214 static void
215 Protect(SharedMemory* aSegment)
216 {
217 NS_ABORT_IF_FALSE(aSegment, "NULL segment");
218 aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()),
219 aSegment->Size(),
220 RightsNone);
221 }
223 static void
224 Unprotect(SharedMemory* aSegment)
225 {
226 NS_ABORT_IF_FALSE(aSegment, "NULL segment");
227 aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()),
228 aSegment->Size(),
229 RightsRead | RightsWrite);
230 }
232 //
233 // In debug builds, we specially allocate shmem segments. The layout
234 // is as follows
235 //
236 // Page 0: "front sentinel"
237 // size of mapping
238 // magic bytes
239 // Page 1 through n-1:
240 // user data
241 // Page n: "back sentinel"
242 // [nothing]
243 //
244 // The mapping can be in one of the following states, wrt to the
245 // current process.
246 //
247 // State "unmapped": all pages are mapped with no access rights.
248 //
249 // State "mapping": all pages are mapped with read/write access.
250 //
251 // State "mapped": the front and back sentinels are mapped with no
252 // access rights, and all the other pages are mapped with
253 // read/write access.
254 //
255 // When a SharedMemory segment is first allocated, it starts out in
256 // the "mapping" state for the process that allocates the segment, and
257 // in the "unmapped" state for the other process. The allocating
258 // process will then create a Shmem, which takes the segment into the
259 // "mapped" state, where it can be accessed by clients.
260 //
261 // When a Shmem is sent to another process in an IPDL message, the
262 // segment transitions into the "unmapped" state for the sending
263 // process, and into the "mapping" state for the receiving process.
264 // The receiving process will then create a Shmem from the underlying
265 // segment, and take the segment into the "mapped" state.
266 //
267 // In the "mapping" state, we use the front sentinel to verify the
268 // integrity of the shmem segment. If valid, it has a size_t
269 // containing the number of bytes the user allocated followed by the
270 // magic bytes above.
271 //
272 // In the "mapped" state, the front and back sentinels have no access
273 // rights. They act as guards against buffer overflows and underflows
274 // in client code; if clients touch a sentinel, they die with SIGSEGV.
275 //
276 // The "unmapped" state is used to enforce single-owner semantics of
277 // the shmem segment. If a process other than the current owner tries
278 // to touch the segment, it dies with SIGSEGV.
279 //
281 Shmem::Shmem(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
282 SharedMemory* aSegment, id_t aId) :
283 mSegment(aSegment),
284 mData(0),
285 mSize(0)
286 {
287 NS_ABORT_IF_FALSE(mSegment, "NULL segment");
288 NS_ABORT_IF_FALSE(aId != 0, "invalid ID");
290 Unprotect(mSegment);
292 Header* header;
293 char* frontSentinel;
294 char* data;
295 char* backSentinel;
296 GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
298 // do a quick validity check to avoid weird-looking crashes in libc
299 char check = *frontSentinel;
300 (void)check;
302 NS_ABORT_IF_FALSE(!strncmp(header->mMagic, sMagic, sizeof(sMagic)),
303 "invalid segment");
304 mSize = static_cast<size_t>(header->mSize);
306 size_t pageSize = SharedMemory::SystemPageSize();
307 // transition into the "mapped" state by protecting the front and
308 // back sentinels (which guard against buffer under/overflows)
309 mSegment->Protect(frontSentinel, pageSize, RightsNone);
310 mSegment->Protect(backSentinel, pageSize, RightsNone);
312 // don't set these until we know they're valid
313 mData = data;
314 mId = aId;
315 }
317 void
318 Shmem::AssertInvariants() const
319 {
320 NS_ABORT_IF_FALSE(mSegment, "NULL segment");
321 NS_ABORT_IF_FALSE(mData, "NULL data pointer");
322 NS_ABORT_IF_FALSE(mSize > 0, "invalid size");
323 // if the segment isn't owned by the current process, these will
324 // trigger SIGSEGV
325 char checkMappingFront = *reinterpret_cast<char*>(mData);
326 char checkMappingBack = *(reinterpret_cast<char*>(mData) + mSize - 1);
328 // avoid "unused" warnings for these variables:
329 unused << checkMappingFront;
330 unused << checkMappingBack;
331 }
333 void
334 Shmem::RevokeRights(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead)
335 {
336 AssertInvariants();
338 size_t pageSize = SharedMemory::SystemPageSize();
339 Header* header = GetHeader(mSegment);
341 // Open this up for reading temporarily
342 mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsRead);
344 if (!header->mUnsafe) {
345 Protect(mSegment);
346 } else {
347 mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsNone);
348 }
349 }
351 // static
352 Shmem::SharedMemory*
353 Shmem::Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
354 size_t aNBytes,
355 SharedMemoryType aType,
356 bool aUnsafe,
357 bool aProtect)
358 {
359 NS_ASSERTION(aNBytes <= UINT32_MAX, "Will truncate shmem segment size!");
360 NS_ABORT_IF_FALSE(!aProtect || !aUnsafe, "protect => !unsafe");
362 size_t pageSize = SharedMemory::SystemPageSize();
363 SharedMemory* segment = nullptr;
364 // |2*pageSize| is for the front and back sentinel
365 size_t segmentSize = SharedMemory::PageAlignedSize(aNBytes + 2*pageSize);
367 if (aType == SharedMemory::TYPE_BASIC)
368 segment = CreateSegment(segmentSize, SharedMemoryBasic::NULLHandle());
369 #ifdef MOZ_HAVE_SHAREDMEMORYSYSV
370 else if (aType == SharedMemory::TYPE_SYSV)
371 segment = CreateSegment(segmentSize, SharedMemorySysV::NULLHandle());
372 #endif
373 else {
374 NS_ERROR("unknown shmem type");
375 return nullptr;
376 }
378 if (!segment)
379 return 0;
381 Header* header;
382 char *frontSentinel;
383 char *data;
384 char *backSentinel;
385 GetSections(segment, &header, &frontSentinel, &data, &backSentinel);
387 // initialize the segment with Shmem-internal information
389 // NB: this can't be a static assert because technically pageSize
390 // isn't known at compile time, event though in practice it's always
391 // going to be 4KiB
392 NS_ABORT_IF_FALSE(sizeof(Header) <= pageSize,
393 "Shmem::Header has gotten too big");
394 memcpy(header->mMagic, sMagic, sizeof(sMagic));
395 header->mSize = static_cast<uint32_t>(aNBytes);
396 header->mUnsafe = aUnsafe;
398 if (aProtect)
399 Protect(segment);
401 return segment;
402 }
404 // static
405 Shmem::SharedMemory*
406 Shmem::OpenExisting(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
407 const IPC::Message& aDescriptor,
408 id_t* aId,
409 bool aProtect)
410 {
411 if (SHMEM_CREATED_MESSAGE_TYPE != aDescriptor.type()) {
412 NS_ERROR("expected 'shmem created' message");
413 return nullptr;
414 }
416 void* iter = 0;
417 SharedMemory::SharedMemoryType type;
418 size_t size;
419 if (!ShmemCreated::ReadInfo(&aDescriptor, &iter, aId, &size, &type))
420 return 0;
422 SharedMemory* segment = 0;
423 size_t pageSize = SharedMemory::SystemPageSize();
424 // |2*pageSize| is for the front and back sentinels
425 size_t segmentSize = SharedMemory::PageAlignedSize(size + 2*pageSize);
427 if (SharedMemory::TYPE_BASIC == type) {
428 SharedMemoryBasic::Handle handle;
429 if (!ShmemCreated::ReadHandle(&aDescriptor, &iter, &handle))
430 return 0;
432 if (!SharedMemoryBasic::IsHandleValid(handle)) {
433 NS_ERROR("trying to open invalid handle");
434 return nullptr;
435 }
436 segment = CreateSegment(segmentSize, handle);
437 }
438 #ifdef MOZ_HAVE_SHAREDMEMORYSYSV
439 else if (SharedMemory::TYPE_SYSV == type) {
440 SharedMemorySysV::Handle handle;
441 if (!ShmemCreated::ReadHandle(&aDescriptor, &iter, &handle))
442 return 0;
444 if (!SharedMemorySysV::IsHandleValid(handle)) {
445 NS_ERROR("trying to open invalid handle");
446 return nullptr;
447 }
448 segment = CreateSegment(segmentSize, handle);
449 }
450 #endif
451 else {
452 NS_ERROR("unknown shmem type");
453 return nullptr;
454 }
456 if (!segment)
457 return 0;
459 Header* header = GetHeader(segment);
461 if (size != header->mSize) {
462 NS_ERROR("Wrong size for this Shmem!");
463 delete segment;
464 return nullptr;
465 }
467 // The caller of this function may not know whether the segment is
468 // unsafe or not
469 if (!header->mUnsafe && aProtect)
470 Protect(segment);
472 return segment;
473 }
475 // static
476 void
477 Shmem::Dealloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
478 SharedMemory* aSegment)
479 {
480 if (!aSegment)
481 return;
483 size_t pageSize = SharedMemory::SystemPageSize();
484 Header* header;
485 char *frontSentinel;
486 char *data;
487 char *backSentinel;
488 GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
490 aSegment->Protect(frontSentinel, pageSize, RightsWrite | RightsRead);
491 memset(header->mMagic, 0, sizeof(sMagic));
492 header->mSize = 0;
493 header->mUnsafe = false; // make it "safe" so as to catch errors
495 DestroySegment(aSegment);
496 }
499 #else // !defined(DEBUG)
501 // static
502 Shmem::SharedMemory*
503 Shmem::Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
504 size_t aNBytes,
505 SharedMemoryType aType,
506 bool /*unused*/,
507 bool /*unused*/)
508 {
509 SharedMemory *segment = nullptr;
511 if (aType == SharedMemory::TYPE_BASIC)
512 segment = CreateSegment(SharedMemory::PageAlignedSize(aNBytes + sizeof(uint32_t)),
513 SharedMemoryBasic::NULLHandle());
514 #ifdef MOZ_HAVE_SHAREDMEMORYSYSV
515 else if (aType == SharedMemory::TYPE_SYSV)
516 segment = CreateSegment(SharedMemory::PageAlignedSize(aNBytes + sizeof(uint32_t)),
517 SharedMemorySysV::NULLHandle());
518 #endif
519 else {
520 return nullptr;
521 }
523 if (!segment)
524 return 0;
526 *PtrToSize(segment) = static_cast<uint32_t>(aNBytes);
528 return segment;
529 }
531 // static
532 Shmem::SharedMemory*
533 Shmem::OpenExisting(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
534 const IPC::Message& aDescriptor,
535 id_t* aId,
536 bool /*unused*/)
537 {
538 if (SHMEM_CREATED_MESSAGE_TYPE != aDescriptor.type()) {
539 return nullptr;
540 }
542 SharedMemory::SharedMemoryType type;
543 void* iter = 0;
544 size_t size;
545 if (!ShmemCreated::ReadInfo(&aDescriptor, &iter, aId, &size, &type))
546 return 0;
548 SharedMemory* segment = 0;
549 size_t segmentSize = SharedMemory::PageAlignedSize(size + sizeof(uint32_t));
551 if (SharedMemory::TYPE_BASIC == type) {
552 SharedMemoryBasic::Handle handle;
553 if (!ShmemCreated::ReadHandle(&aDescriptor, &iter, &handle))
554 return 0;
556 if (!SharedMemoryBasic::IsHandleValid(handle)) {
557 return nullptr;
558 }
560 segment = CreateSegment(segmentSize, handle);
561 }
562 #ifdef MOZ_HAVE_SHAREDMEMORYSYSV
563 else if (SharedMemory::TYPE_SYSV == type) {
564 SharedMemorySysV::Handle handle;
565 if (!ShmemCreated::ReadHandle(&aDescriptor, &iter, &handle))
566 return 0;
568 if (!SharedMemorySysV::IsHandleValid(handle)) {
569 return nullptr;
570 }
571 segment = CreateSegment(segmentSize, handle);
572 }
573 #endif
574 else {
575 return nullptr;
576 }
578 if (!segment)
579 return 0;
581 // this is the only validity check done in non-DEBUG builds
582 if (size != static_cast<size_t>(*PtrToSize(segment))) {
583 delete segment;
584 return nullptr;
585 }
587 return segment;
588 }
590 // static
591 void
592 Shmem::Dealloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
593 SharedMemory* aSegment)
594 {
595 DestroySegment(aSegment);
596 }
598 #endif // if defined(DEBUG)
600 int
601 Shmem::GetSysVID() const
602 {
603 #ifdef MOZ_HAVE_SHAREDMEMORYSYSV
604 AssertInvariants();
606 if (mSegment->Type() != SharedMemory::TYPE_SYSV) {
607 NS_ERROR("Can't call GetSysVID() on a non-SysV Shmem!");
608 return -1;
609 }
611 SharedMemorySysV* seg = static_cast<SharedMemorySysV*>(mSegment);
612 return seg->GetHandle();
613 #else
614 NS_ERROR("Can't call GetSysVID() with no support for SysV shared memory!");
615 return -1; // not reached
616 #endif
617 }
619 IPC::Message*
620 Shmem::ShareTo(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
621 base::ProcessHandle aProcess,
622 int32_t routingId)
623 {
624 AssertInvariants();
626 if (SharedMemory::TYPE_BASIC == mSegment->Type()) {
627 SharedMemoryBasic* seg = static_cast<SharedMemoryBasic*>(mSegment);
628 SharedMemoryBasic::Handle handle;
629 if (!seg->ShareToProcess(aProcess, &handle))
630 return 0;
632 return new ShmemCreated(routingId, mId, mSize, handle);
633 }
634 #ifdef MOZ_HAVE_SHAREDMEMORYSYSV
635 else if (SharedMemory::TYPE_SYSV == mSegment->Type()) {
636 SharedMemorySysV* seg = static_cast<SharedMemorySysV*>(mSegment);
637 return new ShmemCreated(routingId, mId, mSize, seg->GetHandle());
638 }
639 #endif
640 else {
641 NS_ABORT_IF_FALSE(false, "unknown shmem type (here?!)");
642 return nullptr;
643 }
645 return 0;
646 }
648 IPC::Message*
649 Shmem::UnshareFrom(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
650 base::ProcessHandle aProcess,
651 int32_t routingId)
652 {
653 AssertInvariants();
654 return new ShmemDestroyed(routingId, mId);
655 }
657 } // namespace ipc
658 } // namespace mozilla