Bug 1743256 [wpt PR 31764] - Implement contain-intrinsic-size: auto, a=testonly
[gecko.git] / ipc / glue / Shmem.cpp
blob0f56e7aea209dc8ef9bb0721d8f8b2ff5ac3b27b
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 "mozilla/Unused.h"
14 namespace mozilla {
15 namespace ipc {
17 class ShmemCreated : public IPC::Message {
18 private:
19 typedef Shmem::id_t id_t;
21 public:
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) {
35 uint32_t size = 0;
36 if (!IPC::ReadParam(msg, iter, aIPDLId) ||
37 !IPC::ReadParam(msg, iter, &size) ||
38 !IPC::ReadParam(msg, iter, reinterpret_cast<int32_t*>(aType))) {
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(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;
64 } else {
65 NS_ERROR("unknown Shmem type");
66 return nullptr;
70 static already_AddRefed<SharedMemory> CreateSegment(
71 SharedMemory::SharedMemoryType aType, size_t aNBytes, size_t aExtraSize) {
72 RefPtr<SharedMemory> segment = NewSegment(aType);
73 if (!segment) {
74 return nullptr;
76 size_t size = SharedMemory::PageAlignedSize(aNBytes + aExtraSize);
77 if (!segment->Create(size) || !segment->Map(size)) {
78 return nullptr;
80 return segment.forget();
83 static already_AddRefed<SharedMemory> ReadSegment(
84 const IPC::Message& aDescriptor, Shmem::id_t* aId, size_t* aNBytes,
85 size_t aExtraSize) {
86 if (SHMEM_CREATED_MESSAGE_TYPE != aDescriptor.type()) {
87 NS_ERROR("expected 'shmem created' message");
88 return nullptr;
90 SharedMemory::SharedMemoryType type;
91 PickleIterator iter(aDescriptor);
92 if (!ShmemCreated::ReadInfo(&aDescriptor, &iter, aId, aNBytes, &type)) {
93 return nullptr;
95 RefPtr<SharedMemory> segment = NewSegment(type);
96 if (!segment) {
97 return nullptr;
99 if (!segment->ReadHandle(&aDescriptor, &iter)) {
100 NS_ERROR("trying to open invalid handle");
101 return nullptr;
103 aDescriptor.EndRead(iter);
104 size_t size = SharedMemory::PageAlignedSize(*aNBytes + aExtraSize);
105 if (!segment->Map(size)) {
106 return nullptr;
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
115 if (aSegment) {
116 aSegment->Release();
120 #if defined(DEBUG)
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";
129 struct Header {
130 // Don't use size_t or bool here because their size depends on the
131 // architecture.
132 uint32_t mSize;
133 uint32_t mUnsafe;
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,
141 "null param(s)");
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) {
155 Header* header;
156 char* dontcare;
157 GetSections(aSegment, &header, &dontcare, &dontcare, &dontcare);
158 return header;
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
175 // is as follows
177 // Page 0: "front sentinel"
178 // size of mapping
179 // magic bytes
180 // Page 1 through n-1:
181 // user data
182 // Page n: "back sentinel"
183 // [nothing]
185 // The mapping can be in one of the following states, wrt to the
186 // current process.
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");
227 Unprotect(mSegment);
229 Header* header;
230 char* frontSentinel;
231 char* data;
232 char* backSentinel;
233 GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
235 // do a quick validity check to avoid weird-looking crashes in libc
236 char check = *frontSentinel;
237 (void)check;
239 MOZ_ASSERT(!strncmp(header->mMagic, sMagic, sizeof(sMagic)),
240 "invalid segment");
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
250 mData = data;
251 mId = aId;
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
259 // trigger SIGSEGV
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) {
269 AssertInvariants();
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) {
278 Protect(mSegment);
279 } else {
280 mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsNone);
284 // static
285 already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(PrivateIPDLCaller,
286 size_t aNBytes,
287 SharedMemoryType aType,
288 bool aUnsafe,
289 bool aProtect) {
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);
296 if (!segment) {
297 return nullptr;
300 Header* header;
301 char* frontSentinel;
302 char* data;
303 char* backSentinel;
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
310 // going to be 4KiB
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();
321 // static
322 already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting(
323 PrivateIPDLCaller, const IPC::Message& aDescriptor, id_t* aId,
324 bool aProtect) {
325 size_t size;
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);
330 if (!segment) {
331 return nullptr;
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!");
342 } else {
343 NS_WARNING("Shmem was deallocated");
345 return nullptr;
348 // The caller of this function may not know whether the segment is
349 // unsafe or not
350 if (!header->mUnsafe && aProtect) Protect(segment);
352 return segment.forget();
355 // static
356 void Shmem::Dealloc(PrivateIPDLCaller, SharedMemory* aSegment) {
357 if (!aSegment) return;
359 size_t pageSize = SharedMemory::SystemPageSize();
360 Header* header;
361 char* frontSentinel;
362 char* data;
363 char* backSentinel;
364 GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
366 aSegment->Protect(frontSentinel, pageSize, RightsWrite | RightsRead);
367 memset(header->mMagic, 0, sizeof(sMagic));
368 header->mSize = 0;
369 header->mUnsafe = false; // make it "safe" so as to catch errors
371 DestroySegment(aSegment);
374 #else // !defined(DEBUG)
376 // static
377 already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(PrivateIPDLCaller,
378 size_t aNBytes,
379 SharedMemoryType aType,
380 bool /*unused*/,
381 bool /*unused*/) {
382 RefPtr<SharedMemory> segment =
383 CreateSegment(aType, aNBytes, sizeof(uint32_t));
384 if (!segment) {
385 return nullptr;
388 *PtrToSize(segment) = static_cast<uint32_t>(aNBytes);
390 return segment.forget();
393 // static
394 already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting(
395 PrivateIPDLCaller, const IPC::Message& aDescriptor, id_t* aId,
396 bool /*unused*/) {
397 size_t size;
398 RefPtr<SharedMemory> segment =
399 ReadSegment(aDescriptor, aId, &size, sizeof(uint32_t));
400 if (!segment) {
401 return nullptr;
404 // this is the only validity check done in non-DEBUG builds
405 if (size != static_cast<size_t>(*PtrToSize(segment))) {
406 return nullptr;
409 return segment.forget();
412 // static
413 void Shmem::Dealloc(PrivateIPDLCaller, SharedMemory* aSegment) {
414 DestroySegment(aSegment);
417 #endif // if defined(DEBUG)
419 UniquePtr<IPC::Message> Shmem::MkCreatedMessage(PrivateIPDLCaller,
420 int32_t routingId) {
421 AssertInvariants();
423 auto msg = MakeUnique<ShmemCreated>(routingId, mId, mSize, mSegment->Type());
424 if (!mSegment->WriteHandle(msg.get())) {
425 return nullptr;
427 // close the handle to the segment after it is shared
428 mSegment->CloseHandle();
429 return msg;
432 UniquePtr<IPC::Message> Shmem::MkDestroyedMessage(PrivateIPDLCaller,
433 int32_t routingId) {
434 AssertInvariants();
435 return MakeUnique<ShmemDestroyed>(routingId, mId);
438 void IPDLParamTraits<Shmem>::Write(IPC::Message* aMsg, IProtocol* aActor,
439 Shmem&& aParam) {
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) {
449 paramType::id_t id;
450 if (!ReadIPDLParam(aMsg, aIter, aActor, &id)) {
451 return false;
454 Shmem::SharedMemory* rawmem = aActor->LookupSharedMemory(id);
455 if (rawmem) {
456 *aResult = Shmem(Shmem::PrivateIPDLCaller(), rawmem, id);
457 return true;
459 *aResult = Shmem();
460 return true;
463 } // namespace ipc
464 } // namespace mozilla