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 "chrome/common/ipc_message_utils.h"
13 #include "mozilla/Unused.h"
18 class ShmemCreated
: public IPC::Message
{
20 typedef Shmem::id_t id_t
;
23 ShmemCreated(int32_t routingId
, id_t aIPDLId
, size_t aSize
)
25 routingId
, SHMEM_CREATED_MESSAGE_TYPE
, 0,
26 HeaderFlags(NESTED_INSIDE_CPOW
, CONTROL_PRIORITY
, COMPRESSION_NONE
,
27 LAZY_SEND
, NOT_CONSTRUCTOR
, ASYNC
, NOT_REPLY
)) {
28 MOZ_RELEASE_ASSERT(aSize
< std::numeric_limits
<uint32_t>::max(),
29 "Tried to create Shmem with size larger than 4GB");
30 IPC::MessageWriter
writer(*this);
31 IPC::WriteParam(&writer
, aIPDLId
);
32 IPC::WriteParam(&writer
, uint32_t(aSize
));
35 static bool ReadInfo(IPC::MessageReader
* aReader
, id_t
* aIPDLId
,
38 if (!IPC::ReadParam(aReader
, aIPDLId
) || !IPC::ReadParam(aReader
, &size
)) {
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
)
57 routingId
, SHMEM_DESTROYED_MESSAGE_TYPE
, 0,
58 HeaderFlags(NOT_NESTED
, NORMAL_PRIORITY
, COMPRESSION_NONE
,
59 LAZY_SEND
, NOT_CONSTRUCTOR
, ASYNC
, NOT_REPLY
)) {
60 IPC::MessageWriter
writer(*this);
61 IPC::WriteParam(&writer
, aIPDLId
);
65 static SharedMemory
* NewSegment() { return new SharedMemoryBasic
; }
67 static already_AddRefed
<SharedMemory
> CreateSegment(size_t aNBytes
,
69 RefPtr
<SharedMemory
> segment
= NewSegment();
73 size_t size
= SharedMemory::PageAlignedSize(aNBytes
+ aExtraSize
);
74 if (!segment
->Create(size
) || !segment
->Map(size
)) {
77 return segment
.forget();
80 static already_AddRefed
<SharedMemory
> ReadSegment(
81 const IPC::Message
& aDescriptor
, Shmem::id_t
* aId
, size_t* aNBytes
,
83 if (SHMEM_CREATED_MESSAGE_TYPE
!= aDescriptor
.type()) {
84 NS_ERROR("expected 'shmem created' message");
87 IPC::MessageReader
reader(aDescriptor
);
88 if (!ShmemCreated::ReadInfo(&reader
, aId
, aNBytes
)) {
91 RefPtr
<SharedMemory
> segment
= NewSegment();
95 if (!segment
->ReadHandle(&reader
)) {
96 NS_ERROR("trying to open invalid handle");
100 size_t size
= SharedMemory::PageAlignedSize(*aNBytes
+ aExtraSize
);
101 if (!segment
->Map(size
)) {
104 // close the handle to the segment after it is mapped
105 segment
->CloseHandle();
106 return segment
.forget();
109 static void DestroySegment(SharedMemory
* aSegment
) {
110 // the SharedMemory dtor closes and unmaps the actual OS shmem segment
118 static const char sMagic
[] =
119 "This little piggy went to market.\n"
120 "This little piggy stayed at home.\n"
121 "This little piggy has roast beef,\n"
122 "This little piggy had none.\n"
123 "And this little piggy cried \"Wee! Wee! Wee!\" all the way home";
126 // Don't use size_t or bool here because their size depends on the
130 char mMagic
[sizeof(sMagic
)];
133 static void GetSections(Shmem::SharedMemory
* aSegment
, Header
** aHeader
,
134 char** aFrontSentinel
, char** aData
,
135 char** aBackSentinel
) {
136 MOZ_ASSERT(aSegment
&& aFrontSentinel
&& aData
&& aBackSentinel
,
139 *aFrontSentinel
= reinterpret_cast<char*>(aSegment
->memory());
140 MOZ_ASSERT(*aFrontSentinel
, "null memory()");
142 *aHeader
= reinterpret_cast<Header
*>(*aFrontSentinel
);
144 size_t pageSize
= Shmem::SharedMemory::SystemPageSize();
145 *aData
= *aFrontSentinel
+ pageSize
;
147 *aBackSentinel
= *aFrontSentinel
+ aSegment
->Size() - pageSize
;
150 static Header
* GetHeader(Shmem::SharedMemory
* aSegment
) {
153 GetSections(aSegment
, &header
, &dontcare
, &dontcare
, &dontcare
);
157 static void Protect(SharedMemory
* aSegment
) {
158 MOZ_ASSERT(aSegment
, "null segment");
159 aSegment
->Protect(reinterpret_cast<char*>(aSegment
->memory()),
160 aSegment
->Size(), RightsNone
);
163 static void Unprotect(SharedMemory
* aSegment
) {
164 MOZ_ASSERT(aSegment
, "null segment");
165 aSegment
->Protect(reinterpret_cast<char*>(aSegment
->memory()),
166 aSegment
->Size(), RightsRead
| RightsWrite
);
170 // In debug builds, we specially allocate shmem segments. The layout
173 // Page 0: "front sentinel"
176 // Page 1 through n-1:
178 // Page n: "back sentinel"
181 // The mapping can be in one of the following states, wrt to the
184 // State "unmapped": all pages are mapped with no access rights.
186 // State "mapping": all pages are mapped with read/write access.
188 // State "mapped": the front and back sentinels are mapped with no
189 // access rights, and all the other pages are mapped with
190 // read/write access.
192 // When a SharedMemory segment is first allocated, it starts out in
193 // the "mapping" state for the process that allocates the segment, and
194 // in the "unmapped" state for the other process. The allocating
195 // process will then create a Shmem, which takes the segment into the
196 // "mapped" state, where it can be accessed by clients.
198 // When a Shmem is sent to another process in an IPDL message, the
199 // segment transitions into the "unmapped" state for the sending
200 // process, and into the "mapping" state for the receiving process.
201 // The receiving process will then create a Shmem from the underlying
202 // segment, and take the segment into the "mapped" state.
204 // In the "mapping" state, we use the front sentinel to verify the
205 // integrity of the shmem segment. If valid, it has a size_t
206 // containing the number of bytes the user allocated followed by the
207 // magic bytes above.
209 // In the "mapped" state, the front and back sentinels have no access
210 // rights. They act as guards against buffer overflows and underflows
211 // in client code; if clients touch a sentinel, they die with SIGSEGV.
213 // The "unmapped" state is used to enforce single-owner semantics of
214 // the shmem segment. If a process other than the current owner tries
215 // to touch the segment, it dies with SIGSEGV.
218 Shmem::Shmem(SharedMemory
* aSegment
, id_t aId
)
219 : mSegment(aSegment
), mData(nullptr), mSize(0) {
220 MOZ_ASSERT(mSegment
, "null segment");
221 MOZ_ASSERT(aId
!= 0, "invalid ID");
229 GetSections(aSegment
, &header
, &frontSentinel
, &data
, &backSentinel
);
231 // do a quick validity check to avoid weird-looking crashes in libc
232 char check
= *frontSentinel
;
235 MOZ_ASSERT(!strncmp(header
->mMagic
, sMagic
, sizeof(sMagic
)),
237 mSize
= static_cast<size_t>(header
->mSize
);
239 size_t pageSize
= SharedMemory::SystemPageSize();
240 MOZ_ASSERT(mSegment
->Size() - (2 * pageSize
) >= mSize
,
241 "illegal size in shared memory segment");
243 // transition into the "mapped" state by protecting the front and
244 // back sentinels (which guard against buffer under/overflows)
245 mSegment
->Protect(frontSentinel
, pageSize
, RightsNone
);
246 mSegment
->Protect(backSentinel
, pageSize
, RightsNone
);
248 // don't set these until we know they're valid
253 void Shmem::AssertInvariants() const {
254 MOZ_ASSERT(mSegment
, "null segment");
255 MOZ_ASSERT(mData
, "null data pointer");
256 MOZ_ASSERT(mSize
> 0, "invalid size");
257 // if the segment isn't owned by the current process, these will
259 char checkMappingFront
= *reinterpret_cast<char*>(mData
);
260 char checkMappingBack
= *(reinterpret_cast<char*>(mData
) + mSize
- 1);
262 // avoid "unused" warnings for these variables:
263 Unused
<< checkMappingFront
;
264 Unused
<< checkMappingBack
;
267 void Shmem::RevokeRights() {
270 size_t pageSize
= SharedMemory::SystemPageSize();
271 Header
* header
= GetHeader(mSegment
);
273 // Open this up for reading temporarily
274 mSegment
->Protect(reinterpret_cast<char*>(header
), pageSize
, RightsRead
);
276 if (!header
->mUnsafe
) {
279 mSegment
->Protect(reinterpret_cast<char*>(header
), pageSize
, RightsNone
);
284 already_AddRefed
<Shmem::SharedMemory
> Shmem::Alloc(size_t aNBytes
, bool aUnsafe
,
286 NS_ASSERTION(aNBytes
<= UINT32_MAX
, "Will truncate shmem segment size!");
287 MOZ_ASSERT(!aProtect
|| !aUnsafe
, "protect => !unsafe");
289 size_t pageSize
= SharedMemory::SystemPageSize();
290 // |2*pageSize| is for the front and back sentinel
291 RefPtr
<SharedMemory
> segment
= CreateSegment(aNBytes
, 2 * pageSize
);
300 GetSections(segment
, &header
, &frontSentinel
, &data
, &backSentinel
);
302 // initialize the segment with Shmem-internal information
304 // NB: this can't be a static assert because technically pageSize
305 // isn't known at compile time, event though in practice it's always
307 MOZ_ASSERT(sizeof(Header
) <= pageSize
, "Shmem::Header has gotten too big");
308 memcpy(header
->mMagic
, sMagic
, sizeof(sMagic
));
309 header
->mSize
= static_cast<uint32_t>(aNBytes
);
310 header
->mUnsafe
= aUnsafe
;
312 if (aProtect
) Protect(segment
);
314 return segment
.forget();
318 already_AddRefed
<Shmem::SharedMemory
> Shmem::OpenExisting(
319 const IPC::Message
& aDescriptor
, id_t
* aId
, bool aProtect
) {
321 size_t pageSize
= SharedMemory::SystemPageSize();
322 // |2*pageSize| is for the front and back sentinels
323 RefPtr
<SharedMemory
> segment
=
324 ReadSegment(aDescriptor
, aId
, &size
, 2 * pageSize
);
329 Header
* header
= GetHeader(segment
);
331 if (size
!= header
->mSize
) {
332 // Deallocation should zero out the header, so check for that.
333 if (header
->mSize
|| header
->mUnsafe
|| header
->mMagic
[0] ||
334 memcmp(header
->mMagic
, &header
->mMagic
[1],
335 sizeof(header
->mMagic
) - 1)) {
336 NS_ERROR("Wrong size for this Shmem!");
338 NS_WARNING("Shmem was deallocated");
343 // The caller of this function may not know whether the segment is
345 if (!header
->mUnsafe
&& aProtect
) Protect(segment
);
347 return segment
.forget();
351 void Shmem::Dealloc(SharedMemory
* aSegment
) {
352 if (!aSegment
) return;
354 size_t pageSize
= SharedMemory::SystemPageSize();
359 GetSections(aSegment
, &header
, &frontSentinel
, &data
, &backSentinel
);
361 aSegment
->Protect(frontSentinel
, pageSize
, RightsWrite
| RightsRead
);
362 memset(header
->mMagic
, 0, sizeof(sMagic
));
364 header
->mUnsafe
= false; // make it "safe" so as to catch errors
366 DestroySegment(aSegment
);
369 #else // !defined(DEBUG)
371 Shmem::Shmem(SharedMemory
* aSegment
, id_t aId
)
372 : mSegment(aSegment
), mData(aSegment
->memory()), mSize(0), mId(aId
) {
373 mSize
= static_cast<size_t>(*PtrToSize(mSegment
));
374 MOZ_RELEASE_ASSERT(mSegment
->Size() - sizeof(uint32_t) >= mSize
,
375 "illegal size in shared memory segment");
379 already_AddRefed
<Shmem::SharedMemory
> Shmem::Alloc(size_t aNBytes
,
382 RefPtr
<SharedMemory
> segment
= CreateSegment(aNBytes
, sizeof(uint32_t));
387 *PtrToSize(segment
) = static_cast<uint32_t>(aNBytes
);
389 return segment
.forget();
393 already_AddRefed
<Shmem::SharedMemory
> Shmem::OpenExisting(
394 const IPC::Message
& aDescriptor
, id_t
* aId
, bool /*unused*/) {
396 RefPtr
<SharedMemory
> segment
=
397 ReadSegment(aDescriptor
, aId
, &size
, sizeof(uint32_t));
402 // this is the only validity check done in non-DEBUG builds
403 if (size
!= static_cast<size_t>(*PtrToSize(segment
))) {
407 return segment
.forget();
411 void Shmem::Dealloc(SharedMemory
* aSegment
) { DestroySegment(aSegment
); }
413 #endif // if defined(DEBUG)
415 UniquePtr
<IPC::Message
> Shmem::MkCreatedMessage(int32_t routingId
) {
418 auto msg
= MakeUnique
<ShmemCreated
>(routingId
, mId
, mSize
);
419 IPC::MessageWriter
writer(*msg
);
420 if (!mSegment
->WriteHandle(&writer
)) {
423 // close the handle to the segment after it is shared
424 mSegment
->CloseHandle();
428 UniquePtr
<IPC::Message
> Shmem::MkDestroyedMessage(int32_t routingId
) {
430 return MakeUnique
<ShmemDestroyed
>(routingId
, mId
);
433 void IPDLParamTraits
<Shmem
>::Write(IPC::MessageWriter
* aWriter
,
434 IProtocol
* aActor
, Shmem
&& aParam
) {
435 WriteIPDLParam(aWriter
, aActor
, aParam
.mId
);
437 aParam
.RevokeRights();
441 bool IPDLParamTraits
<Shmem
>::Read(IPC::MessageReader
* aReader
,
442 IProtocol
* aActor
, paramType
* aResult
) {
444 if (!ReadIPDLParam(aReader
, aActor
, &id
)) {
448 Shmem::SharedMemory
* rawmem
= aActor
->LookupSharedMemory(id
);
450 *aResult
= Shmem(rawmem
, id
);
458 } // namespace mozilla