Line data Source code
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/. */
6 :
7 : #include "Shmem.h"
8 :
9 : #include "ProtocolUtils.h"
10 : #include "SharedMemoryBasic.h"
11 :
12 : #include "mozilla/Unused.h"
13 :
14 :
15 : namespace mozilla {
16 : namespace ipc {
17 :
18 9 : class ShmemCreated : public IPC::Message
19 : {
20 : private:
21 : typedef Shmem::id_t id_t;
22 :
23 : public:
24 3 : ShmemCreated(int32_t routingId,
25 : id_t aIPDLId,
26 : size_t aSize,
27 3 : SharedMemory::SharedMemoryType aType) :
28 3 : IPC::Message(routingId, SHMEM_CREATED_MESSAGE_TYPE, 0, NESTED_INSIDE_CPOW)
29 : {
30 3 : IPC::WriteParam(this, aIPDLId);
31 3 : IPC::WriteParam(this, aSize);
32 3 : IPC::WriteParam(this, int32_t(aType));
33 3 : }
34 :
35 : static bool
36 2 : ReadInfo(const Message* msg, PickleIterator* iter,
37 : id_t* aIPDLId,
38 : size_t* aSize,
39 : SharedMemory::SharedMemoryType* aType)
40 : {
41 6 : if (!IPC::ReadParam(msg, iter, aIPDLId) ||
42 4 : !IPC::ReadParam(msg, iter, aSize) ||
43 2 : !IPC::ReadParam(msg, iter, reinterpret_cast<int32_t*>(aType)))
44 0 : return false;
45 2 : return true;
46 : }
47 :
48 : void Log(const std::string& aPrefix,
49 : FILE* aOutf) const
50 : {
51 : fputs("(special ShmemCreated msg)", aOutf);
52 : }
53 : };
54 :
55 0 : class ShmemDestroyed : public IPC::Message
56 : {
57 : private:
58 : typedef Shmem::id_t id_t;
59 :
60 : public:
61 0 : ShmemDestroyed(int32_t routingId,
62 0 : id_t aIPDLId) :
63 0 : IPC::Message(routingId, SHMEM_DESTROYED_MESSAGE_TYPE)
64 : {
65 0 : IPC::WriteParam(this, aIPDLId);
66 0 : }
67 : };
68 :
69 : static SharedMemory*
70 5 : NewSegment(SharedMemory::SharedMemoryType aType)
71 : {
72 5 : if (SharedMemory::TYPE_BASIC == aType) {
73 5 : return new SharedMemoryBasic;
74 : } else {
75 0 : NS_ERROR("unknown Shmem type");
76 0 : return nullptr;
77 : }
78 : }
79 :
80 : static already_AddRefed<SharedMemory>
81 3 : CreateSegment(SharedMemory::SharedMemoryType aType, size_t aNBytes, size_t aExtraSize)
82 : {
83 6 : RefPtr<SharedMemory> segment = NewSegment(aType);
84 3 : if (!segment) {
85 0 : return nullptr;
86 : }
87 3 : size_t size = SharedMemory::PageAlignedSize(aNBytes + aExtraSize);
88 3 : if (!segment->Create(size) || !segment->Map(size)) {
89 0 : return nullptr;
90 : }
91 3 : return segment.forget();
92 : }
93 :
94 : static already_AddRefed<SharedMemory>
95 2 : ReadSegment(const IPC::Message& aDescriptor, Shmem::id_t* aId, size_t* aNBytes, size_t aExtraSize)
96 : {
97 2 : if (SHMEM_CREATED_MESSAGE_TYPE != aDescriptor.type()) {
98 0 : NS_ERROR("expected 'shmem created' message");
99 0 : return nullptr;
100 : }
101 : SharedMemory::SharedMemoryType type;
102 2 : PickleIterator iter(aDescriptor);
103 2 : if (!ShmemCreated::ReadInfo(&aDescriptor, &iter, aId, aNBytes, &type)) {
104 0 : return nullptr;
105 : }
106 4 : RefPtr<SharedMemory> segment = NewSegment(type);
107 2 : if (!segment) {
108 0 : return nullptr;
109 : }
110 2 : if (!segment->ReadHandle(&aDescriptor, &iter)) {
111 0 : NS_ERROR("trying to open invalid handle");
112 0 : return nullptr;
113 : }
114 2 : aDescriptor.EndRead(iter);
115 2 : size_t size = SharedMemory::PageAlignedSize(*aNBytes + aExtraSize);
116 2 : if (!segment->Map(size)) {
117 0 : return nullptr;
118 : }
119 : // close the handle to the segment after it is mapped
120 2 : segment->CloseHandle();
121 2 : return segment.forget();
122 : }
123 :
124 : static void
125 0 : DestroySegment(SharedMemory* aSegment)
126 : {
127 : // the SharedMemory dtor closes and unmaps the actual OS shmem segment
128 0 : if (aSegment) {
129 0 : aSegment->Release();
130 : }
131 0 : }
132 :
133 :
134 : #if defined(DEBUG)
135 :
136 : static const char sMagic[] =
137 : "This little piggy went to market.\n"
138 : "This little piggy stayed at home.\n"
139 : "This little piggy has roast beef,\n"
140 : "This little piggy had none.\n"
141 : "And this little piggy cried \"Wee! Wee! Wee!\" all the way home";
142 :
143 :
144 : struct Header {
145 : // Don't use size_t or bool here because their size depends on the
146 : // architecture.
147 : uint32_t mSize;
148 : uint32_t mUnsafe;
149 : char mMagic[sizeof(sMagic)];
150 : };
151 :
152 : static void
153 16 : GetSections(Shmem::SharedMemory* aSegment,
154 : Header** aHeader,
155 : char** aFrontSentinel,
156 : char** aData,
157 : char** aBackSentinel)
158 : {
159 16 : MOZ_ASSERT(aSegment && aFrontSentinel && aData && aBackSentinel,
160 : "null param(s)");
161 :
162 16 : *aFrontSentinel = reinterpret_cast<char*>(aSegment->memory());
163 16 : MOZ_ASSERT(*aFrontSentinel, "null memory()");
164 :
165 16 : *aHeader = reinterpret_cast<Header*>(*aFrontSentinel);
166 :
167 16 : size_t pageSize = Shmem::SharedMemory::SystemPageSize();
168 16 : *aData = *aFrontSentinel + pageSize;
169 :
170 16 : *aBackSentinel = *aFrontSentinel + aSegment->Size() - pageSize;
171 16 : }
172 :
173 : static Header*
174 5 : GetHeader(Shmem::SharedMemory* aSegment)
175 : {
176 : Header* header;
177 : char* dontcare;
178 5 : GetSections(aSegment, &header, &dontcare, &dontcare, &dontcare);
179 5 : return header;
180 : }
181 :
182 : static void
183 0 : Protect(SharedMemory* aSegment)
184 : {
185 0 : MOZ_ASSERT(aSegment, "null segment");
186 0 : aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()),
187 : aSegment->Size(),
188 0 : RightsNone);
189 0 : }
190 :
191 : static void
192 8 : Unprotect(SharedMemory* aSegment)
193 : {
194 8 : MOZ_ASSERT(aSegment, "null segment");
195 8 : aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()),
196 : aSegment->Size(),
197 8 : RightsRead | RightsWrite);
198 8 : }
199 :
200 : //
201 : // In debug builds, we specially allocate shmem segments. The layout
202 : // is as follows
203 : //
204 : // Page 0: "front sentinel"
205 : // size of mapping
206 : // magic bytes
207 : // Page 1 through n-1:
208 : // user data
209 : // Page n: "back sentinel"
210 : // [nothing]
211 : //
212 : // The mapping can be in one of the following states, wrt to the
213 : // current process.
214 : //
215 : // State "unmapped": all pages are mapped with no access rights.
216 : //
217 : // State "mapping": all pages are mapped with read/write access.
218 : //
219 : // State "mapped": the front and back sentinels are mapped with no
220 : // access rights, and all the other pages are mapped with
221 : // read/write access.
222 : //
223 : // When a SharedMemory segment is first allocated, it starts out in
224 : // the "mapping" state for the process that allocates the segment, and
225 : // in the "unmapped" state for the other process. The allocating
226 : // process will then create a Shmem, which takes the segment into the
227 : // "mapped" state, where it can be accessed by clients.
228 : //
229 : // When a Shmem is sent to another process in an IPDL message, the
230 : // segment transitions into the "unmapped" state for the sending
231 : // process, and into the "mapping" state for the receiving process.
232 : // The receiving process will then create a Shmem from the underlying
233 : // segment, and take the segment into the "mapped" state.
234 : //
235 : // In the "mapping" state, we use the front sentinel to verify the
236 : // integrity of the shmem segment. If valid, it has a size_t
237 : // containing the number of bytes the user allocated followed by the
238 : // magic bytes above.
239 : //
240 : // In the "mapped" state, the front and back sentinels have no access
241 : // rights. They act as guards against buffer overflows and underflows
242 : // in client code; if clients touch a sentinel, they die with SIGSEGV.
243 : //
244 : // The "unmapped" state is used to enforce single-owner semantics of
245 : // the shmem segment. If a process other than the current owner tries
246 : // to touch the segment, it dies with SIGSEGV.
247 : //
248 :
249 8 : Shmem::Shmem(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
250 8 : SharedMemory* aSegment, id_t aId) :
251 : mSegment(aSegment),
252 : mData(nullptr),
253 8 : mSize(0)
254 : {
255 8 : MOZ_ASSERT(mSegment, "null segment");
256 8 : MOZ_ASSERT(aId != 0, "invalid ID");
257 :
258 8 : Unprotect(mSegment);
259 :
260 : Header* header;
261 : char* frontSentinel;
262 : char* data;
263 : char* backSentinel;
264 8 : GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
265 :
266 : // do a quick validity check to avoid weird-looking crashes in libc
267 8 : char check = *frontSentinel;
268 : (void)check;
269 :
270 8 : MOZ_ASSERT(!strncmp(header->mMagic, sMagic, sizeof(sMagic)),
271 : "invalid segment");
272 8 : mSize = static_cast<size_t>(header->mSize);
273 :
274 8 : size_t pageSize = SharedMemory::SystemPageSize();
275 : // transition into the "mapped" state by protecting the front and
276 : // back sentinels (which guard against buffer under/overflows)
277 8 : mSegment->Protect(frontSentinel, pageSize, RightsNone);
278 8 : mSegment->Protect(backSentinel, pageSize, RightsNone);
279 :
280 : // don't set these until we know they're valid
281 8 : mData = data;
282 8 : mId = aId;
283 8 : }
284 :
285 : void
286 11 : Shmem::AssertInvariants() const
287 : {
288 11 : MOZ_ASSERT(mSegment, "null segment");
289 11 : MOZ_ASSERT(mData, "null data pointer");
290 11 : MOZ_ASSERT(mSize > 0, "invalid size");
291 : // if the segment isn't owned by the current process, these will
292 : // trigger SIGSEGV
293 11 : char checkMappingFront = *reinterpret_cast<char*>(mData);
294 11 : char checkMappingBack = *(reinterpret_cast<char*>(mData) + mSize - 1);
295 :
296 : // avoid "unused" warnings for these variables:
297 : Unused << checkMappingFront;
298 : Unused << checkMappingBack;
299 11 : }
300 :
301 : void
302 3 : Shmem::RevokeRights(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead)
303 : {
304 3 : AssertInvariants();
305 :
306 3 : size_t pageSize = SharedMemory::SystemPageSize();
307 3 : Header* header = GetHeader(mSegment);
308 :
309 : // Open this up for reading temporarily
310 3 : mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsRead);
311 :
312 3 : if (!header->mUnsafe) {
313 0 : Protect(mSegment);
314 : } else {
315 3 : mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsNone);
316 : }
317 3 : }
318 :
319 : // static
320 : already_AddRefed<Shmem::SharedMemory>
321 3 : Shmem::Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
322 : size_t aNBytes,
323 : SharedMemoryType aType,
324 : bool aUnsafe,
325 : bool aProtect)
326 : {
327 3 : NS_ASSERTION(aNBytes <= UINT32_MAX, "Will truncate shmem segment size!");
328 3 : MOZ_ASSERT(!aProtect || !aUnsafe, "protect => !unsafe");
329 :
330 3 : size_t pageSize = SharedMemory::SystemPageSize();
331 : // |2*pageSize| is for the front and back sentinel
332 6 : RefPtr<SharedMemory> segment = CreateSegment(aType, aNBytes, 2*pageSize);
333 3 : if (!segment) {
334 0 : return nullptr;
335 : }
336 :
337 : Header* header;
338 : char *frontSentinel;
339 : char *data;
340 : char *backSentinel;
341 3 : GetSections(segment, &header, &frontSentinel, &data, &backSentinel);
342 :
343 : // initialize the segment with Shmem-internal information
344 :
345 : // NB: this can't be a static assert because technically pageSize
346 : // isn't known at compile time, event though in practice it's always
347 : // going to be 4KiB
348 3 : MOZ_ASSERT(sizeof(Header) <= pageSize,
349 : "Shmem::Header has gotten too big");
350 3 : memcpy(header->mMagic, sMagic, sizeof(sMagic));
351 3 : header->mSize = static_cast<uint32_t>(aNBytes);
352 3 : header->mUnsafe = aUnsafe;
353 :
354 3 : if (aProtect)
355 0 : Protect(segment);
356 :
357 3 : return segment.forget();
358 : }
359 :
360 : // static
361 : already_AddRefed<Shmem::SharedMemory>
362 2 : Shmem::OpenExisting(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
363 : const IPC::Message& aDescriptor,
364 : id_t* aId,
365 : bool aProtect)
366 : {
367 : size_t size;
368 2 : size_t pageSize = SharedMemory::SystemPageSize();
369 : // |2*pageSize| is for the front and back sentinels
370 4 : RefPtr<SharedMemory> segment = ReadSegment(aDescriptor, aId, &size, 2*pageSize);
371 2 : if (!segment) {
372 0 : return nullptr;
373 : }
374 :
375 2 : Header* header = GetHeader(segment);
376 :
377 2 : if (size != header->mSize) {
378 : // Deallocation should zero out the header, so check for that.
379 0 : if (header->mSize || header->mUnsafe || header->mMagic[0] ||
380 0 : memcmp(header->mMagic, &header->mMagic[1], sizeof(header->mMagic)-1)) {
381 0 : NS_ERROR("Wrong size for this Shmem!");
382 : } else {
383 0 : NS_WARNING("Shmem was deallocated");
384 : }
385 0 : return nullptr;
386 : }
387 :
388 : // The caller of this function may not know whether the segment is
389 : // unsafe or not
390 2 : if (!header->mUnsafe && aProtect)
391 0 : Protect(segment);
392 :
393 2 : return segment.forget();
394 : }
395 :
396 : // static
397 : void
398 0 : Shmem::Dealloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
399 : SharedMemory* aSegment)
400 : {
401 0 : if (!aSegment)
402 0 : return;
403 :
404 0 : size_t pageSize = SharedMemory::SystemPageSize();
405 : Header* header;
406 : char *frontSentinel;
407 : char *data;
408 : char *backSentinel;
409 0 : GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
410 :
411 0 : aSegment->Protect(frontSentinel, pageSize, RightsWrite | RightsRead);
412 0 : memset(header->mMagic, 0, sizeof(sMagic));
413 0 : header->mSize = 0;
414 0 : header->mUnsafe = false; // make it "safe" so as to catch errors
415 :
416 0 : DestroySegment(aSegment);
417 : }
418 :
419 :
420 : #else // !defined(DEBUG)
421 :
422 : // static
423 : already_AddRefed<Shmem::SharedMemory>
424 : Shmem::Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
425 : size_t aNBytes,
426 : SharedMemoryType aType,
427 : bool /*unused*/,
428 : bool /*unused*/)
429 : {
430 : RefPtr<SharedMemory> segment = CreateSegment(aType, aNBytes, sizeof(uint32_t));
431 : if (!segment) {
432 : return nullptr;
433 : }
434 :
435 : *PtrToSize(segment) = static_cast<uint32_t>(aNBytes);
436 :
437 : return segment.forget();
438 : }
439 :
440 : // static
441 : already_AddRefed<Shmem::SharedMemory>
442 : Shmem::OpenExisting(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
443 : const IPC::Message& aDescriptor,
444 : id_t* aId,
445 : bool /*unused*/)
446 : {
447 : size_t size;
448 : RefPtr<SharedMemory> segment = ReadSegment(aDescriptor, aId, &size, sizeof(uint32_t));
449 : if (!segment) {
450 : return nullptr;
451 : }
452 :
453 : // this is the only validity check done in non-DEBUG builds
454 : if (size != static_cast<size_t>(*PtrToSize(segment))) {
455 : return nullptr;
456 : }
457 :
458 : return segment.forget();
459 : }
460 :
461 : // static
462 : void
463 : Shmem::Dealloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
464 : SharedMemory* aSegment)
465 : {
466 : DestroySegment(aSegment);
467 : }
468 :
469 : #endif // if defined(DEBUG)
470 :
471 : IPC::Message*
472 3 : Shmem::ShareTo(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
473 : base::ProcessId aTargetPid,
474 : int32_t routingId)
475 : {
476 3 : AssertInvariants();
477 :
478 6 : IPC::Message *msg = new ShmemCreated(routingId, mId, mSize, mSegment->Type());
479 3 : if (!mSegment->ShareHandle(aTargetPid, msg)) {
480 0 : return nullptr;
481 : }
482 : // close the handle to the segment after it is shared
483 3 : mSegment->CloseHandle();
484 3 : return msg;
485 : }
486 :
487 : IPC::Message*
488 0 : Shmem::UnshareFrom(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
489 : base::ProcessId aTargetPid,
490 : int32_t routingId)
491 : {
492 0 : AssertInvariants();
493 0 : return new ShmemDestroyed(routingId, mId);
494 : }
495 :
496 : } // namespace ipc
497 : } // namespace mozilla
|