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/. */
9 #include "ProtocolUtils.h"
10 #include "SharedMemoryBasic.h"
11 #include "ShmemMessageUtils.h"
12 #include "mozilla/Unused.h"
17 class ShmemCreated
: public IPC::Message
{
19 typedef Shmem::id_t id_t
;
22 ShmemCreated(int32_t routingId
, id_t aIPDLId
, size_t aSize
,
23 SharedMemory::SharedMemoryType aType
)
24 : IPC::Message(routingId
, SHMEM_CREATED_MESSAGE_TYPE
, 0,
25 HeaderFlags(NESTED_INSIDE_CPOW
)) {
26 MOZ_RELEASE_ASSERT(aSize
< std::numeric_limits
<uint32_t>::max(),
27 "Tried to create Shmem with size larger than 4GB");
28 IPC::WriteParam(this, aIPDLId
);
29 IPC::WriteParam(this, uint32_t(aSize
));
30 IPC::WriteParam(this, int32_t(aType
));
33 static bool ReadInfo(const Message
* msg
, PickleIterator
* iter
, id_t
* aIPDLId
,
34 size_t* aSize
, SharedMemory::SharedMemoryType
* aType
) {
36 if (!IPC::ReadParam(msg
, iter
, aIPDLId
) ||
37 !IPC::ReadParam(msg
, iter
, &size
) ||
38 !IPC::ReadParam(msg
, iter
, reinterpret_cast<int32_t*>(aType
))) {
45 void Log(const std::string
& aPrefix
, FILE* aOutf
) const {
46 fputs("(special ShmemCreated msg)", aOutf
);
50 class ShmemDestroyed
: public IPC::Message
{
52 typedef Shmem::id_t id_t
;
55 ShmemDestroyed(int32_t routingId
, id_t aIPDLId
)
56 : IPC::Message(routingId
, SHMEM_DESTROYED_MESSAGE_TYPE
) {
57 IPC::WriteParam(this, aIPDLId
);
61 static SharedMemory
* NewSegment(SharedMemory::SharedMemoryType aType
) {
62 if (SharedMemory::TYPE_BASIC
== aType
) {
63 return new SharedMemoryBasic
;
65 NS_ERROR("unknown Shmem type");
70 static already_AddRefed
<SharedMemory
> CreateSegment(
71 SharedMemory::SharedMemoryType aType
, size_t aNBytes
, size_t aExtraSize
) {
72 RefPtr
<SharedMemory
> segment
= NewSegment(aType
);
76 size_t size
= SharedMemory::PageAlignedSize(aNBytes
+ aExtraSize
);
77 if (!segment
->Create(size
) || !segment
->Map(size
)) {
80 return segment
.forget();
83 static already_AddRefed
<SharedMemory
> ReadSegment(
84 const IPC::Message
& aDescriptor
, Shmem::id_t
* aId
, size_t* aNBytes
,
86 if (SHMEM_CREATED_MESSAGE_TYPE
!= aDescriptor
.type()) {
87 NS_ERROR("expected 'shmem created' message");
90 SharedMemory::SharedMemoryType type
;
91 PickleIterator
iter(aDescriptor
);
92 if (!ShmemCreated::ReadInfo(&aDescriptor
, &iter
, aId
, aNBytes
, &type
)) {
95 RefPtr
<SharedMemory
> segment
= NewSegment(type
);
99 if (!segment
->ReadHandle(&aDescriptor
, &iter
)) {
100 NS_ERROR("trying to open invalid handle");
103 aDescriptor
.EndRead(iter
);
104 size_t size
= SharedMemory::PageAlignedSize(*aNBytes
+ aExtraSize
);
105 if (!segment
->Map(size
)) {
108 // close the handle to the segment after it is mapped
109 segment
->CloseHandle();
110 return segment
.forget();
113 static void DestroySegment(SharedMemory
* aSegment
) {
114 // the SharedMemory dtor closes and unmaps the actual OS shmem segment
122 static const char sMagic
[] =
123 "This little piggy went to market.\n"
124 "This little piggy stayed at home.\n"
125 "This little piggy has roast beef,\n"
126 "This little piggy had none.\n"
127 "And this little piggy cried \"Wee! Wee! Wee!\" all the way home";
130 // Don't use size_t or bool here because their size depends on the
134 char mMagic
[sizeof(sMagic
)];
137 static void GetSections(Shmem::SharedMemory
* aSegment
, Header
** aHeader
,
138 char** aFrontSentinel
, char** aData
,
139 char** aBackSentinel
) {
140 MOZ_ASSERT(aSegment
&& aFrontSentinel
&& aData
&& aBackSentinel
,
143 *aFrontSentinel
= reinterpret_cast<char*>(aSegment
->memory());
144 MOZ_ASSERT(*aFrontSentinel
, "null memory()");
146 *aHeader
= reinterpret_cast<Header
*>(*aFrontSentinel
);
148 size_t pageSize
= Shmem::SharedMemory::SystemPageSize();
149 *aData
= *aFrontSentinel
+ pageSize
;
151 *aBackSentinel
= *aFrontSentinel
+ aSegment
->Size() - pageSize
;
154 static Header
* GetHeader(Shmem::SharedMemory
* aSegment
) {
157 GetSections(aSegment
, &header
, &dontcare
, &dontcare
, &dontcare
);
161 static void Protect(SharedMemory
* aSegment
) {
162 MOZ_ASSERT(aSegment
, "null segment");
163 aSegment
->Protect(reinterpret_cast<char*>(aSegment
->memory()),
164 aSegment
->Size(), RightsNone
);
167 static void Unprotect(SharedMemory
* aSegment
) {
168 MOZ_ASSERT(aSegment
, "null segment");
169 aSegment
->Protect(reinterpret_cast<char*>(aSegment
->memory()),
170 aSegment
->Size(), RightsRead
| RightsWrite
);
174 // In debug builds, we specially allocate shmem segments. The layout
177 // Page 0: "front sentinel"
180 // Page 1 through n-1:
182 // Page n: "back sentinel"
185 // The mapping can be in one of the following states, wrt to the
188 // State "unmapped": all pages are mapped with no access rights.
190 // State "mapping": all pages are mapped with read/write access.
192 // State "mapped": the front and back sentinels are mapped with no
193 // access rights, and all the other pages are mapped with
194 // read/write access.
196 // When a SharedMemory segment is first allocated, it starts out in
197 // the "mapping" state for the process that allocates the segment, and
198 // in the "unmapped" state for the other process. The allocating
199 // process will then create a Shmem, which takes the segment into the
200 // "mapped" state, where it can be accessed by clients.
202 // When a Shmem is sent to another process in an IPDL message, the
203 // segment transitions into the "unmapped" state for the sending
204 // process, and into the "mapping" state for the receiving process.
205 // The receiving process will then create a Shmem from the underlying
206 // segment, and take the segment into the "mapped" state.
208 // In the "mapping" state, we use the front sentinel to verify the
209 // integrity of the shmem segment. If valid, it has a size_t
210 // containing the number of bytes the user allocated followed by the
211 // magic bytes above.
213 // In the "mapped" state, the front and back sentinels have no access
214 // rights. They act as guards against buffer overflows and underflows
215 // in client code; if clients touch a sentinel, they die with SIGSEGV.
217 // The "unmapped" state is used to enforce single-owner semantics of
218 // the shmem segment. If a process other than the current owner tries
219 // to touch the segment, it dies with SIGSEGV.
222 Shmem::Shmem(PrivateIPDLCaller
, SharedMemory
* aSegment
, id_t aId
)
223 : mSegment(aSegment
), mData(nullptr), mSize(0) {
224 MOZ_ASSERT(mSegment
, "null segment");
225 MOZ_ASSERT(aId
!= 0, "invalid ID");
233 GetSections(aSegment
, &header
, &frontSentinel
, &data
, &backSentinel
);
235 // do a quick validity check to avoid weird-looking crashes in libc
236 char check
= *frontSentinel
;
239 MOZ_ASSERT(!strncmp(header
->mMagic
, sMagic
, sizeof(sMagic
)),
241 mSize
= static_cast<size_t>(header
->mSize
);
243 size_t pageSize
= SharedMemory::SystemPageSize();
244 // transition into the "mapped" state by protecting the front and
245 // back sentinels (which guard against buffer under/overflows)
246 mSegment
->Protect(frontSentinel
, pageSize
, RightsNone
);
247 mSegment
->Protect(backSentinel
, pageSize
, RightsNone
);
249 // don't set these until we know they're valid
254 void Shmem::AssertInvariants() const {
255 MOZ_ASSERT(mSegment
, "null segment");
256 MOZ_ASSERT(mData
, "null data pointer");
257 MOZ_ASSERT(mSize
> 0, "invalid size");
258 // if the segment isn't owned by the current process, these will
260 char checkMappingFront
= *reinterpret_cast<char*>(mData
);
261 char checkMappingBack
= *(reinterpret_cast<char*>(mData
) + mSize
- 1);
263 // avoid "unused" warnings for these variables:
264 Unused
<< checkMappingFront
;
265 Unused
<< checkMappingBack
;
268 void Shmem::RevokeRights(PrivateIPDLCaller
) {
271 size_t pageSize
= SharedMemory::SystemPageSize();
272 Header
* header
= GetHeader(mSegment
);
274 // Open this up for reading temporarily
275 mSegment
->Protect(reinterpret_cast<char*>(header
), pageSize
, RightsRead
);
277 if (!header
->mUnsafe
) {
280 mSegment
->Protect(reinterpret_cast<char*>(header
), pageSize
, RightsNone
);
285 already_AddRefed
<Shmem::SharedMemory
> Shmem::Alloc(PrivateIPDLCaller
,
287 SharedMemoryType aType
,
290 NS_ASSERTION(aNBytes
<= UINT32_MAX
, "Will truncate shmem segment size!");
291 MOZ_ASSERT(!aProtect
|| !aUnsafe
, "protect => !unsafe");
293 size_t pageSize
= SharedMemory::SystemPageSize();
294 // |2*pageSize| is for the front and back sentinel
295 RefPtr
<SharedMemory
> segment
= CreateSegment(aType
, aNBytes
, 2 * pageSize
);
304 GetSections(segment
, &header
, &frontSentinel
, &data
, &backSentinel
);
306 // initialize the segment with Shmem-internal information
308 // NB: this can't be a static assert because technically pageSize
309 // isn't known at compile time, event though in practice it's always
311 MOZ_ASSERT(sizeof(Header
) <= pageSize
, "Shmem::Header has gotten too big");
312 memcpy(header
->mMagic
, sMagic
, sizeof(sMagic
));
313 header
->mSize
= static_cast<uint32_t>(aNBytes
);
314 header
->mUnsafe
= aUnsafe
;
316 if (aProtect
) Protect(segment
);
318 return segment
.forget();
322 already_AddRefed
<Shmem::SharedMemory
> Shmem::OpenExisting(
323 PrivateIPDLCaller
, const IPC::Message
& aDescriptor
, id_t
* aId
,
326 size_t pageSize
= SharedMemory::SystemPageSize();
327 // |2*pageSize| is for the front and back sentinels
328 RefPtr
<SharedMemory
> segment
=
329 ReadSegment(aDescriptor
, aId
, &size
, 2 * pageSize
);
334 Header
* header
= GetHeader(segment
);
336 if (size
!= header
->mSize
) {
337 // Deallocation should zero out the header, so check for that.
338 if (header
->mSize
|| header
->mUnsafe
|| header
->mMagic
[0] ||
339 memcmp(header
->mMagic
, &header
->mMagic
[1],
340 sizeof(header
->mMagic
) - 1)) {
341 NS_ERROR("Wrong size for this Shmem!");
343 NS_WARNING("Shmem was deallocated");
348 // The caller of this function may not know whether the segment is
350 if (!header
->mUnsafe
&& aProtect
) Protect(segment
);
352 return segment
.forget();
356 void Shmem::Dealloc(PrivateIPDLCaller
, SharedMemory
* aSegment
) {
357 if (!aSegment
) return;
359 size_t pageSize
= SharedMemory::SystemPageSize();
364 GetSections(aSegment
, &header
, &frontSentinel
, &data
, &backSentinel
);
366 aSegment
->Protect(frontSentinel
, pageSize
, RightsWrite
| RightsRead
);
367 memset(header
->mMagic
, 0, sizeof(sMagic
));
369 header
->mUnsafe
= false; // make it "safe" so as to catch errors
371 DestroySegment(aSegment
);
374 #else // !defined(DEBUG)
377 already_AddRefed
<Shmem::SharedMemory
> Shmem::Alloc(PrivateIPDLCaller
,
379 SharedMemoryType aType
,
382 RefPtr
<SharedMemory
> segment
=
383 CreateSegment(aType
, aNBytes
, sizeof(uint32_t));
388 *PtrToSize(segment
) = static_cast<uint32_t>(aNBytes
);
390 return segment
.forget();
394 already_AddRefed
<Shmem::SharedMemory
> Shmem::OpenExisting(
395 PrivateIPDLCaller
, const IPC::Message
& aDescriptor
, id_t
* aId
,
398 RefPtr
<SharedMemory
> segment
=
399 ReadSegment(aDescriptor
, aId
, &size
, sizeof(uint32_t));
404 // this is the only validity check done in non-DEBUG builds
405 if (size
!= static_cast<size_t>(*PtrToSize(segment
))) {
409 return segment
.forget();
413 void Shmem::Dealloc(PrivateIPDLCaller
, SharedMemory
* aSegment
) {
414 DestroySegment(aSegment
);
417 #endif // if defined(DEBUG)
419 UniquePtr
<IPC::Message
> Shmem::MkCreatedMessage(PrivateIPDLCaller
,
423 auto msg
= MakeUnique
<ShmemCreated
>(routingId
, mId
, mSize
, mSegment
->Type());
424 if (!mSegment
->WriteHandle(msg
.get())) {
427 // close the handle to the segment after it is shared
428 mSegment
->CloseHandle();
432 UniquePtr
<IPC::Message
> Shmem::MkDestroyedMessage(PrivateIPDLCaller
,
435 return MakeUnique
<ShmemDestroyed
>(routingId
, mId
);
438 void IPDLParamTraits
<Shmem
>::Write(IPC::Message
* aMsg
, IProtocol
* aActor
,
440 WriteIPDLParam(aMsg
, aActor
, aParam
.mId
);
442 aParam
.RevokeRights(Shmem::PrivateIPDLCaller());
443 aParam
.forget(Shmem::PrivateIPDLCaller());
446 bool IPDLParamTraits
<Shmem
>::Read(const IPC::Message
* aMsg
,
447 PickleIterator
* aIter
, IProtocol
* aActor
,
448 paramType
* aResult
) {
450 if (!ReadIPDLParam(aMsg
, aIter
, aActor
, &id
)) {
454 Shmem::SharedMemory
* rawmem
= aActor
->LookupSharedMemory(id
);
456 *aResult
= Shmem(Shmem::PrivateIPDLCaller(), rawmem
, id
);
464 } // namespace mozilla