TestSharedMemory.cpp (16779B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "gtest/gtest.h" 8 9 #include "mozilla/MathAlgorithms.h" 10 #include "mozilla/ipc/SharedMemoryCursor.h" 11 #include "mozilla/ipc/SharedMemoryHandle.h" 12 #include "mozilla/ipc/SharedMemoryMapping.h" 13 14 #ifdef XP_LINUX 15 # include <errno.h> 16 # include <linux/magic.h> 17 # include <stdio.h> 18 # include <string.h> 19 # include <sys/statfs.h> 20 # include <sys/utsname.h> 21 #endif 22 23 #ifdef XP_WIN 24 # include <windows.h> 25 #endif 26 27 namespace mozilla::ipc { 28 29 #define ASSERT_SHMEM(handle, size) \ 30 do { \ 31 ASSERT_EQ((handle).Size(), size_t(size)); \ 32 if (size_t(size) == 0) { \ 33 ASSERT_FALSE((handle).IsValid()); \ 34 ASSERT_FALSE(handle); \ 35 } else { \ 36 ASSERT_TRUE((handle).IsValid()); \ 37 ASSERT_TRUE(handle); \ 38 } \ 39 } while (0) 40 41 template <typename T> 42 struct IPCSharedMemoryFixture : public testing::Test {}; 43 44 using HandleAndMappingTypes = 45 testing::Types<MutableSharedMemoryHandle, ReadOnlySharedMemoryHandle, 46 FreezableSharedMemoryHandle, SharedMemoryMapping, 47 ReadOnlySharedMemoryMapping, FreezableSharedMemoryMapping, 48 MutableOrReadOnlySharedMemoryMapping>; 49 TYPED_TEST_SUITE(IPCSharedMemoryFixture, HandleAndMappingTypes); 50 51 TYPED_TEST(IPCSharedMemoryFixture, Null) { 52 TypeParam t; 53 ASSERT_SHMEM(t, 0); 54 55 if constexpr (std::is_same_v<TypeParam, MutableSharedMemoryHandle> || 56 std::is_same_v<TypeParam, ReadOnlySharedMemoryHandle>) { 57 auto cloned = t.Clone(); 58 ASSERT_SHMEM(cloned, 0); 59 ASSERT_SHMEM(t, 0); 60 } 61 } 62 63 TEST(IPCSharedMemoryHandle, Create) 64 { 65 auto handle = shared_memory::Create(1); 66 ASSERT_SHMEM(handle, 1); 67 } 68 69 TEST(IPCSharedMemoryHandle, Move) 70 { 71 auto handle = shared_memory::Create(1); 72 73 MutableSharedMemoryHandle newHandle(std::move(handle)); 74 ASSERT_SHMEM(handle, 0); 75 ASSERT_SHMEM(newHandle, 1); 76 77 MutableSharedMemoryHandle assignedHandle; 78 assignedHandle = std::move(newHandle); 79 ASSERT_SHMEM(newHandle, 0); 80 ASSERT_SHMEM(assignedHandle, 1); 81 } 82 83 TEST(IPCSharedMemoryHandle, ToReadOnly) 84 { 85 auto handle = shared_memory::Create(1); 86 auto roHandle = std::move(handle).ToReadOnly(); 87 ASSERT_SHMEM(handle, 0); 88 ASSERT_SHMEM(roHandle, 1); 89 } 90 91 TEST(IPCSharedMemoryHandle, Clone) 92 { 93 auto handle = shared_memory::Create(1); 94 auto clonedHandle = handle.Clone(); 95 ASSERT_SHMEM(handle, 1); 96 ASSERT_SHMEM(clonedHandle, 1); 97 } 98 99 TEST(IPCSharedMemoryHandle, ROClone) 100 { 101 auto handle = shared_memory::Create(1).ToReadOnly(); 102 auto clonedHandle = handle.Clone(); 103 ASSERT_SHMEM(handle, 1); 104 ASSERT_SHMEM(clonedHandle, 1); 105 } 106 107 TEST(IPCSharedMemoryHandle, CreateFreezable) 108 { 109 auto handle = shared_memory::CreateFreezable(1); 110 ASSERT_SHMEM(handle, 1); 111 } 112 113 TEST(IPCSharedMemoryHandle, WontFreeze) 114 { 115 auto handle = shared_memory::CreateFreezable(1); 116 ASSERT_SHMEM(handle, 1); 117 118 auto mHandle = std::move(handle).WontFreeze(); 119 ASSERT_SHMEM(handle, 0); 120 ASSERT_SHMEM(mHandle, 1); 121 } 122 123 TEST(IPCSharedMemoryHandle, Freeze) 124 { 125 auto handle = shared_memory::CreateFreezable(1); 126 ASSERT_SHMEM(handle, 1); 127 128 auto roHandle = std::move(handle).Freeze(); 129 ASSERT_SHMEM(handle, 0); 130 ASSERT_SHMEM(roHandle, 1); 131 } 132 133 TEST(IPCSharedMemory, Map) 134 { 135 auto handle = shared_memory::Create(1); 136 137 auto mapping = handle.Map(); 138 ASSERT_SHMEM(handle, 1); 139 ASSERT_SHMEM(mapping, 1); 140 } 141 142 TEST(IPCSharedMemory, ROMap) 143 { 144 auto handle = shared_memory::Create(1).ToReadOnly(); 145 146 auto mapping = handle.Map(); 147 ASSERT_SHMEM(handle, 1); 148 ASSERT_SHMEM(mapping, 1); 149 } 150 151 TEST(IPCSharedMemory, FreezeMap) 152 { 153 auto handle = shared_memory::CreateFreezable(1); 154 155 auto mapping = std::move(handle).Map(); 156 ASSERT_SHMEM(handle, 0); 157 ASSERT_SHMEM(mapping, 1); 158 } 159 160 TEST(IPCSharedMemoryMapping, Move) 161 { 162 auto handle = shared_memory::Create(1); 163 164 auto mapping = handle.Map(); 165 166 SharedMemoryMapping moved(std::move(mapping)); 167 ASSERT_SHMEM(mapping, 0); 168 ASSERT_SHMEM(moved, 1); 169 170 SharedMemoryMapping moveAssigned; 171 moveAssigned = std::move(moved); 172 ASSERT_SHMEM(moved, 0); 173 ASSERT_SHMEM(moveAssigned, 1); 174 } 175 176 TEST(IPCSharedMemoryMapping, ROMove) 177 { 178 auto handle = shared_memory::Create(1).ToReadOnly(); 179 180 auto mapping = handle.Map(); 181 182 ReadOnlySharedMemoryMapping moved(std::move(mapping)); 183 ASSERT_SHMEM(mapping, 0); 184 ASSERT_SHMEM(moved, 1); 185 186 ReadOnlySharedMemoryMapping moveAssigned; 187 moveAssigned = std::move(moved); 188 ASSERT_SHMEM(moved, 0); 189 ASSERT_SHMEM(moveAssigned, 1); 190 } 191 192 TEST(IPCSharedMemoryMapping, FreezeMove) 193 { 194 auto handle = shared_memory::CreateFreezable(1); 195 196 auto mapping = std::move(handle).Map(); 197 198 FreezableSharedMemoryMapping moved(std::move(mapping)); 199 ASSERT_SHMEM(mapping, 0); 200 ASSERT_SHMEM(moved, 1); 201 202 FreezableSharedMemoryMapping moveAssigned; 203 moveAssigned = std::move(moved); 204 ASSERT_SHMEM(moved, 0); 205 ASSERT_SHMEM(moveAssigned, 1); 206 } 207 208 TEST(IPCSharedMemoryMapping, MutableOrReadOnly) 209 { 210 auto handle = shared_memory::Create(1); 211 auto roHandle = handle.Clone().ToReadOnly(); 212 213 MutableOrReadOnlySharedMemoryMapping mapping; 214 mapping = handle.Map(); 215 ASSERT_SHMEM(mapping, 1); 216 ASSERT_FALSE(mapping.IsReadOnly()); 217 218 mapping = roHandle.Map(); 219 ASSERT_SHMEM(mapping, 1); 220 ASSERT_TRUE(mapping.IsReadOnly()); 221 } 222 223 TEST(IPCSharedMemoryMapping, FreezableFreeze) 224 { 225 auto handle = shared_memory::CreateFreezable(1); 226 227 auto mapping = std::move(handle).Map(); 228 auto roHandle = std::move(mapping).Freeze(); 229 ASSERT_SHMEM(mapping, 0); 230 ASSERT_SHMEM(roHandle, 1); 231 } 232 233 TEST(IPCSharedMemoryMapping, FreezableFreezeWithMutableMapping) 234 { 235 auto handle = shared_memory::CreateFreezable(1); 236 237 auto mapping = std::move(handle).Map(); 238 auto [roHandle, m] = std::move(mapping).FreezeWithMutableMapping(); 239 ASSERT_SHMEM(mapping, 0); 240 ASSERT_SHMEM(roHandle, 1); 241 ASSERT_SHMEM(m, 1); 242 } 243 244 TEST(IPCSharedMemoryMapping, FreezableUnmap) 245 { 246 auto handle = shared_memory::CreateFreezable(1); 247 248 auto mapping = std::move(handle).Map(); 249 handle = std::move(mapping).Unmap(); 250 ASSERT_SHMEM(handle, 1); 251 ASSERT_SHMEM(mapping, 0); 252 } 253 254 // Try to map a frozen shm for writing. Threat model: the process is 255 // compromised and then receives a frozen handle. 256 TEST(IPCSharedMemory, FreezeAndMapRW) 257 { 258 // Create 259 auto handle = ipc::shared_memory::CreateFreezable(1); 260 ASSERT_TRUE(handle); 261 262 // Initialize 263 auto mapping = std::move(handle).Map(); 264 ASSERT_TRUE(mapping); 265 auto* mem = mapping.DataAs<char>(); 266 ASSERT_TRUE(mem); 267 *mem = 'A'; 268 269 // Freeze 270 auto [roHandle, rwMapping] = std::move(mapping).FreezeWithMutableMapping(); 271 ASSERT_TRUE(rwMapping); 272 ASSERT_TRUE(roHandle); 273 274 auto roMapping = roHandle.Map(); 275 ASSERT_TRUE(roMapping); 276 const auto* roMem = roMapping.DataAs<char>(); 277 ASSERT_TRUE(roMem); 278 ASSERT_EQ(*roMem, 'A'); 279 } 280 281 // Try to restore write permissions to a frozen mapping. Threat 282 // model: the process has mapped frozen shm normally and then is 283 // compromised, or as for FreezeAndMapRW (see also the 284 // proof-of-concept at https://crbug.com/project-zero/1671 ). 285 TEST(IPCSharedMemory, FreezeAndReprotect) 286 { 287 // Create 288 auto handle = ipc::shared_memory::CreateFreezable(1); 289 ASSERT_TRUE(handle); 290 291 // Initialize 292 auto mapping = std::move(handle).Map(); 293 ASSERT_TRUE(mapping); 294 auto* mem = mapping.DataAs<char>(); 295 ASSERT_TRUE(mem); 296 *mem = 'A'; 297 298 // Freeze 299 auto roHandle = std::move(mapping).Freeze(); 300 ASSERT_TRUE(roHandle); 301 302 auto roMapping = roHandle.Map(); 303 ASSERT_TRUE(roMapping); 304 305 const auto* roMem = roMapping.DataAs<char>(); 306 ASSERT_EQ(*roMem, 'A'); 307 308 // Try to alter protection; should fail 309 EXPECT_FALSE(ipc::shared_memory::LocalProtect( 310 (char*)roMem, 1, ipc::shared_memory::AccessReadWrite)); 311 } 312 313 #if !defined(XP_WIN) && !defined(XP_DARWIN) 314 // This essentially tests whether FreezeAndReprotect would have failed 315 // without the freeze. 316 // 317 // It doesn't work on Windows: VirtualProtect can't exceed the permissions set 318 // in MapViewOfFile regardless of the security status of the original handle. 319 // 320 // It doesn't work on MacOS: we can set a higher max_protection for the memory 321 // when creating the handle, but we wouldn't want to do this for freezable 322 // handles (to prevent creating additional RW mappings that break the memory 323 // freezing invariants). 324 TEST(IPCSharedMemory, Reprotect) 325 { 326 // Create 327 auto handle = ipc::shared_memory::CreateFreezable(1); 328 ASSERT_TRUE(handle); 329 330 // Initialize 331 auto mapping = std::move(handle).Map(); 332 ASSERT_TRUE(mapping); 333 auto* mem = mapping.DataAs<char>(); 334 ASSERT_TRUE(mem); 335 *mem = 'A'; 336 337 // Unmap without freezing. 338 auto rwHandle = std::move(mapping).Unmap().WontFreeze(); 339 ASSERT_TRUE(rwHandle); 340 auto roHandle = std::move(rwHandle).ToReadOnly(); 341 ASSERT_TRUE(roHandle); 342 343 // Re-map 344 auto roMapping = roHandle.Map(); 345 ASSERT_TRUE(roMapping); 346 const auto* cmem = roMapping.DataAs<char>(); 347 ASSERT_EQ(*cmem, 'A'); 348 349 // Try to alter protection; should succeed, because not frozen 350 EXPECT_TRUE(ipc::shared_memory::LocalProtect( 351 (char*)cmem, 1, ipc::shared_memory::AccessReadWrite)); 352 } 353 #endif 354 355 #ifdef XP_WIN 356 // Try to regain write permissions on a read-only handle using 357 // DuplicateHandle; this will succeed if the object has no DACL. 358 // See also https://crbug.com/338538 359 TEST(IPCSharedMemory, WinUnfreeze) 360 { 361 // Create 362 auto handle = ipc::shared_memory::CreateFreezable(1); 363 ASSERT_TRUE(handle); 364 365 // Initialize 366 auto mapping = std::move(handle).Map(); 367 ASSERT_TRUE(mapping); 368 auto* mem = mapping.DataAs<char>(); 369 ASSERT_TRUE(mem); 370 *mem = 'A'; 371 372 // Freeze 373 auto roHandle = std::move(mapping).Freeze(); 374 ASSERT_TRUE(roHandle); 375 376 // Extract handle. 377 auto platformHandle = std::move(roHandle).TakePlatformHandle(); 378 379 // Unfreeze. 380 HANDLE newHandle = INVALID_HANDLE_VALUE; 381 bool unfroze = ::DuplicateHandle( 382 GetCurrentProcess(), platformHandle.release(), GetCurrentProcess(), 383 &newHandle, FILE_MAP_ALL_ACCESS, false, DUPLICATE_CLOSE_SOURCE); 384 ASSERT_FALSE(unfroze); 385 } 386 #endif 387 388 // Test that a read-only copy sees changes made to the writeable 389 // mapping in the case that the page wasn't accessed before the copy. 390 TEST(IPCSharedMemory, ROCopyAndWrite) 391 { 392 auto handle = ipc::shared_memory::CreateFreezable(1); 393 ASSERT_TRUE(handle); 394 395 auto [roHandle, rwMapping] = 396 std::move(handle).Map().FreezeWithMutableMapping(); 397 ASSERT_TRUE(rwMapping); 398 ASSERT_TRUE(roHandle); 399 400 auto roMapping = roHandle.Map(); 401 402 auto* memRW = rwMapping.DataAs<char>(); 403 ASSERT_TRUE(memRW); 404 const auto* memRO = roMapping.DataAs<char>(); 405 ASSERT_TRUE(memRO); 406 407 ASSERT_NE(memRW, memRO); 408 409 *memRW = 'A'; 410 EXPECT_EQ(*memRO, 'A'); 411 } 412 413 // Test that a read-only copy sees changes made to the writeable 414 // mapping in the case that the page was accessed before the copy 415 // (and, before that, sees the state as of when the copy was made). 416 TEST(IPCSharedMemory, ROCopyAndRewrite) 417 { 418 auto handle = ipc::shared_memory::CreateFreezable(1); 419 ASSERT_TRUE(handle); 420 421 auto [roHandle, rwMapping] = 422 std::move(handle).Map().FreezeWithMutableMapping(); 423 ASSERT_TRUE(rwMapping); 424 ASSERT_TRUE(roHandle); 425 426 auto roMapping = roHandle.Map(); 427 428 auto* memRW = rwMapping.DataAs<char>(); 429 ASSERT_TRUE(memRW); 430 *memRW = 'A'; 431 432 const auto* memRO = roMapping.DataAs<char>(); 433 ASSERT_TRUE(memRO); 434 435 ASSERT_NE(memRW, memRO); 436 437 ASSERT_EQ(*memRW, 'A'); 438 EXPECT_EQ(*memRO, 'A'); 439 *memRW = 'X'; 440 EXPECT_EQ(*memRO, 'X'); 441 } 442 443 #ifndef FUZZING 444 TEST(IPCSharedMemory, BasicIsZero) 445 { 446 static constexpr size_t kSize = 65536; 447 auto shm = ipc::shared_memory::Create(kSize).Map(); 448 449 auto* mem = shm.DataAs<char>(); 450 for (size_t i = 0; i < kSize; ++i) { 451 ASSERT_EQ(mem[i], 0) << "offset " << i; 452 } 453 } 454 #endif 455 456 #if defined(XP_LINUX) && !defined(ANDROID) 457 class IPCSharedMemoryLinuxTest : public ::testing::Test { 458 int mMajor = 0; 459 int mMinor = 0; 460 461 protected: 462 void SetUp() override { 463 if (mMajor != 0) { 464 return; 465 } 466 struct utsname uts{}; 467 ASSERT_EQ(uname(&uts), 0) << strerror(errno); 468 ASSERT_STREQ(uts.sysname, "Linux"); 469 ASSERT_EQ(sscanf(uts.release, "%d.%d", &mMajor, &mMinor), 2); 470 } 471 472 bool HaveKernelVersion(int aMajor, int aMinor) { 473 return mMajor > aMajor || (mMajor == aMajor && mMinor >= aMinor); 474 } 475 476 bool ShouldHaveMemfd() { return HaveKernelVersion(3, 17); } 477 478 bool ShouldHaveMemfdNoExec() { return HaveKernelVersion(6, 3); } 479 }; 480 481 // Test that memfd_create is used where expected. 482 // 483 // More precisely: if memfd_create support is expected, verify that 484 // shared memory isn't subject to a filesystem size limit. 485 TEST_F(IPCSharedMemoryLinuxTest, IsMemfd) { 486 auto handle = ipc::shared_memory::Create(1); 487 UniqueFileHandle fd = std::move(handle).TakePlatformHandle(); 488 ASSERT_TRUE(fd); 489 490 struct statfs fs{}; 491 ASSERT_EQ(fstatfs(fd.get(), &fs), 0) << strerror(errno); 492 EXPECT_EQ(fs.f_type, TMPFS_MAGIC); 493 static constexpr decltype(fs.f_blocks) kNoLimit = 0; 494 if (ShouldHaveMemfd()) { 495 EXPECT_EQ(fs.f_blocks, kNoLimit); 496 } else { 497 // On older kernels, we expect the memfd / no-limit test to fail. 498 // (In theory it could succeed if backported memfd support exists; 499 // if that ever happens, this check can be removed.) 500 EXPECT_NE(fs.f_blocks, kNoLimit); 501 } 502 } 503 504 TEST_F(IPCSharedMemoryLinuxTest, MemfdNoExec) { 505 const bool expectExec = ShouldHaveMemfd() && !ShouldHaveMemfdNoExec(); 506 507 auto handle = ipc::shared_memory::Create(1); 508 UniqueFileHandle fd = std::move(handle).TakePlatformHandle(); 509 ASSERT_TRUE(fd); 510 511 struct stat sb{}; 512 ASSERT_EQ(fstat(fd.get(), &sb), 0) << strerror(errno); 513 // Check that mode is reasonable. 514 EXPECT_EQ(sb.st_mode & (S_IRUSR | S_IWUSR), mode_t(S_IRUSR | S_IWUSR)); 515 // Chech the exec bit 516 EXPECT_EQ(sb.st_mode & S_IXUSR, mode_t(expectExec ? S_IXUSR : 0)); 517 } 518 #endif 519 520 TEST(IPCSharedMemory, CursorWriteRead) 521 { 522 // Select a chunk size which is at least as big as the allocation granularity, 523 // as smaller sizes will not be able to map. 524 const size_t chunkSize = ipc::shared_memory::SystemAllocationGranularity(); 525 ASSERT_TRUE(IsPowerOfTwo(chunkSize)); 526 527 const uint64_t fullSize = chunkSize * 20; 528 auto handle = ipc::shared_memory::Create(fullSize); 529 ASSERT_TRUE(handle.IsValid()); 530 ASSERT_EQ(handle.Size(), fullSize); 531 532 // Map the entire region. 533 auto mapping = handle.Map(); 534 ASSERT_TRUE(mapping.IsValid()); 535 ASSERT_EQ(mapping.Size(), fullSize); 536 537 // Use a cursor to write some data. 538 ipc::shared_memory::Cursor cursor(std::move(handle)); 539 ASSERT_EQ(cursor.Offset(), 0u); 540 ASSERT_EQ(cursor.Size(), fullSize); 541 542 // Set the chunk size to ensure we use multiple mappings for this data region. 543 cursor.SetChunkSize(chunkSize); 544 545 // Two basic blocks of data which are used for writeReadTest. 546 const char data[] = "Hello, World!"; 547 const char data2[] = "AnotherString"; 548 auto writeReadTest = [&]() { 549 uint64_t initialOffset = cursor.Offset(); 550 551 // Clear out the buffer to a known state so that any checks will fail if 552 // they're depending on previous writes. 553 memset(mapping.Address(), 0xe5, mapping.Size()); 554 555 // Write "Hello, World" at the offset, and ensure it is reflected in the 556 // full mapping. 557 ASSERT_TRUE(cursor.Write(data, std::size(data))); 558 ASSERT_EQ(cursor.Offset(), initialOffset + std::size(data)); 559 ASSERT_STREQ(mapping.DataAs<char>() + initialOffset, data); 560 561 // Write some data in the full mapping at the same offset, and enure it can 562 // be read. 563 memcpy(mapping.DataAs<char>() + initialOffset, data2, std::size(data2)); 564 cursor.Seek(initialOffset); 565 ASSERT_EQ(cursor.Offset(), initialOffset); 566 char buffer[std::size(data2)]; 567 ASSERT_TRUE(cursor.Read(buffer, std::size(buffer))); 568 ASSERT_EQ(cursor.Offset(), initialOffset + std::size(buffer)); 569 ASSERT_STREQ(buffer, data2); 570 }; 571 572 writeReadTest(); 573 574 // Run the writeReadTest at various offsets within the buffer, including at 575 // every chunk boundary, and in the middle of each chunk. 576 for (size_t offset = chunkSize - 3; offset < fullSize - 3; 577 offset += chunkSize / 2) { 578 cursor.Seek(offset); 579 writeReadTest(); 580 } 581 582 // Do a writeReadTest at the very end of the allocated region to ensure that 583 // edge case is handled. 584 cursor.Seek(mapping.Size() - std::max(std::size(data), std::size(data2))); 585 writeReadTest(); 586 587 // Ensure that writes past the end fail safely. 588 cursor.Seek(mapping.Size() - 3); 589 ASSERT_FALSE(cursor.Write(data, std::size(data))); 590 } 591 592 } // namespace mozilla::ipc