Bug 1834537 - Part 6: Simplify GCRuntime::checkAllocatorState a little r=sfink
[gecko.git] / ipc / glue / Shmem.cpp
blob775d852faa06aac804470902e4fd1e80f0c6f230
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/. */
7 #include "Shmem.h"
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"
15 namespace mozilla {
16 namespace ipc {
18 class ShmemCreated : public IPC::Message {
19 private:
20 typedef Shmem::id_t id_t;
22 public:
23 ShmemCreated(int32_t routingId, id_t aIPDLId, size_t aSize)
24 : IPC::Message(
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,
36 size_t* aSize) {
37 uint32_t size = 0;
38 if (!IPC::ReadParam(aReader, aIPDLId) || !IPC::ReadParam(aReader, &size)) {
39 return false;
41 *aSize = size;
42 return true;
45 void Log(const std::string& aPrefix, FILE* aOutf) const {
46 fputs("(special ShmemCreated msg)", aOutf);
50 class ShmemDestroyed : public IPC::Message {
51 private:
52 typedef Shmem::id_t id_t;
54 public:
55 ShmemDestroyed(int32_t routingId, id_t aIPDLId)
56 : IPC::Message(
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,
68 size_t aExtraSize) {
69 RefPtr<SharedMemory> segment = NewSegment();
70 if (!segment) {
71 return nullptr;
73 size_t size = SharedMemory::PageAlignedSize(aNBytes + aExtraSize);
74 if (!segment->Create(size) || !segment->Map(size)) {
75 return nullptr;
77 return segment.forget();
80 static already_AddRefed<SharedMemory> ReadSegment(
81 const IPC::Message& aDescriptor, Shmem::id_t* aId, size_t* aNBytes,
82 size_t aExtraSize) {
83 if (SHMEM_CREATED_MESSAGE_TYPE != aDescriptor.type()) {
84 NS_ERROR("expected 'shmem created' message");
85 return nullptr;
87 IPC::MessageReader reader(aDescriptor);
88 if (!ShmemCreated::ReadInfo(&reader, aId, aNBytes)) {
89 return nullptr;
91 RefPtr<SharedMemory> segment = NewSegment();
92 if (!segment) {
93 return nullptr;
95 if (!segment->ReadHandle(&reader)) {
96 NS_ERROR("trying to open invalid handle");
97 return nullptr;
99 reader.EndRead();
100 size_t size = SharedMemory::PageAlignedSize(*aNBytes + aExtraSize);
101 if (!segment->Map(size)) {
102 return nullptr;
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
111 if (aSegment) {
112 aSegment->Release();
116 #if defined(DEBUG)
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";
125 struct Header {
126 // Don't use size_t or bool here because their size depends on the
127 // architecture.
128 uint32_t mSize;
129 uint32_t mUnsafe;
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,
137 "null param(s)");
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) {
151 Header* header;
152 char* dontcare;
153 GetSections(aSegment, &header, &dontcare, &dontcare, &dontcare);
154 return header;
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
171 // is as follows
173 // Page 0: "front sentinel"
174 // size of mapping
175 // magic bytes
176 // Page 1 through n-1:
177 // user data
178 // Page n: "back sentinel"
179 // [nothing]
181 // The mapping can be in one of the following states, wrt to the
182 // current process.
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");
223 Unprotect(mSegment);
225 Header* header;
226 char* frontSentinel;
227 char* data;
228 char* backSentinel;
229 GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
231 // do a quick validity check to avoid weird-looking crashes in libc
232 char check = *frontSentinel;
233 (void)check;
235 MOZ_ASSERT(!strncmp(header->mMagic, sMagic, sizeof(sMagic)),
236 "invalid segment");
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
249 mData = data;
250 mId = aId;
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
258 // trigger SIGSEGV
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() {
268 AssertInvariants();
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) {
277 Protect(mSegment);
278 } else {
279 mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsNone);
283 // static
284 already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(size_t aNBytes, bool aUnsafe,
285 bool aProtect) {
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);
292 if (!segment) {
293 return nullptr;
296 Header* header;
297 char* frontSentinel;
298 char* data;
299 char* backSentinel;
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
306 // going to be 4KiB
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();
317 // static
318 already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting(
319 const IPC::Message& aDescriptor, id_t* aId, bool aProtect) {
320 size_t size;
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);
325 if (!segment) {
326 return nullptr;
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!");
337 } else {
338 NS_WARNING("Shmem was deallocated");
340 return nullptr;
343 // The caller of this function may not know whether the segment is
344 // unsafe or not
345 if (!header->mUnsafe && aProtect) Protect(segment);
347 return segment.forget();
350 // static
351 void Shmem::Dealloc(SharedMemory* aSegment) {
352 if (!aSegment) return;
354 size_t pageSize = SharedMemory::SystemPageSize();
355 Header* header;
356 char* frontSentinel;
357 char* data;
358 char* backSentinel;
359 GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
361 aSegment->Protect(frontSentinel, pageSize, RightsWrite | RightsRead);
362 memset(header->mMagic, 0, sizeof(sMagic));
363 header->mSize = 0;
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");
378 // static
379 already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(size_t aNBytes,
380 bool /*unused*/,
381 bool /*unused*/) {
382 RefPtr<SharedMemory> segment = CreateSegment(aNBytes, sizeof(uint32_t));
383 if (!segment) {
384 return nullptr;
387 *PtrToSize(segment) = static_cast<uint32_t>(aNBytes);
389 return segment.forget();
392 // static
393 already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting(
394 const IPC::Message& aDescriptor, id_t* aId, bool /*unused*/) {
395 size_t size;
396 RefPtr<SharedMemory> segment =
397 ReadSegment(aDescriptor, aId, &size, sizeof(uint32_t));
398 if (!segment) {
399 return nullptr;
402 // this is the only validity check done in non-DEBUG builds
403 if (size != static_cast<size_t>(*PtrToSize(segment))) {
404 return nullptr;
407 return segment.forget();
410 // static
411 void Shmem::Dealloc(SharedMemory* aSegment) { DestroySegment(aSegment); }
413 #endif // if defined(DEBUG)
415 UniquePtr<IPC::Message> Shmem::MkCreatedMessage(int32_t routingId) {
416 AssertInvariants();
418 auto msg = MakeUnique<ShmemCreated>(routingId, mId, mSize);
419 IPC::MessageWriter writer(*msg);
420 if (!mSegment->WriteHandle(&writer)) {
421 return nullptr;
423 // close the handle to the segment after it is shared
424 mSegment->CloseHandle();
425 return msg;
428 UniquePtr<IPC::Message> Shmem::MkDestroyedMessage(int32_t routingId) {
429 AssertInvariants();
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();
438 aParam.forget();
441 bool IPDLParamTraits<Shmem>::Read(IPC::MessageReader* aReader,
442 IProtocol* aActor, paramType* aResult) {
443 paramType::id_t id;
444 if (!ReadIPDLParam(aReader, aActor, &id)) {
445 return false;
448 Shmem::SharedMemory* rawmem = aActor->LookupSharedMemory(id);
449 if (rawmem) {
450 *aResult = Shmem(rawmem, id);
451 return true;
453 *aResult = Shmem();
454 return true;
457 } // namespace ipc
458 } // namespace mozilla