tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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