// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/memory/platform_shared_memory_region.h"

#include <algorithm>
#include <tuple>

#include "base/check.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/process/process_metrics.h"
#include "base/system/sys_info.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/gtest_util.h"
#include "base/test/test_shared_memory_util.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_APPLE)
#include <mach/vm_map.h>
#include <sys/mman.h>
#elif BUILDFLAG(IS_POSIX)
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#include "base/debug/proc_maps_linux.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#elif BUILDFLAG(IS_WIN)
#include <windows.h>

#include "base/features.h"
#include "base/logging.h"
#include "base/test/scoped_feature_list.h"
#elif BUILDFLAG(IS_FUCHSIA)
#include <lib/zx/object.h>
#include <lib/zx/process.h>

#include "base/fuchsia/fuchsia_logging.h"
#endif

using base::test::ErrorIs;
using base::test::HasValue;

namespace base::subtle {

const size_t kRegionSize = 1024;

class PlatformSharedMemoryRegionTest : public ::testing::Test {};

// Tests that a default constructed region is invalid and produces invalid
// mappings.
TEST_F(PlatformSharedMemoryRegionTest, DefaultConstructedRegionIsInvalid) {
  PlatformSharedMemoryRegion region;
  EXPECT_FALSE(region.IsValid());
  WritableSharedMemoryMapping mapping = MapForTesting(&region);
  EXPECT_FALSE(mapping.IsValid());
  PlatformSharedMemoryRegion duplicate = region.Duplicate();
  EXPECT_FALSE(duplicate.IsValid());
  EXPECT_FALSE(region.ConvertToReadOnly());
}

// Tests that creating a region of 0 size returns an invalid region.
TEST_F(PlatformSharedMemoryRegionTest, CreateRegionOfZeroSizeIsInvalid) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(0);
  EXPECT_FALSE(region.IsValid());

  PlatformSharedMemoryRegion region2 =
      PlatformSharedMemoryRegion::CreateUnsafe(0);
  EXPECT_FALSE(region2.IsValid());
}

// Tests that creating a region of size bigger than the integer max value
// returns an invalid region.
TEST_F(PlatformSharedMemoryRegionTest, CreateTooLargeRegionIsInvalid) {
  size_t too_large_region_size =
      static_cast<size_t>(std::numeric_limits<int>::max()) + 1;
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(too_large_region_size);
  EXPECT_FALSE(region.IsValid());

  PlatformSharedMemoryRegion region2 =
      PlatformSharedMemoryRegion::CreateUnsafe(too_large_region_size);
  EXPECT_FALSE(region2.IsValid());
}

// Tests that creating a region of maximum possible value returns an invalid
// region.
TEST_F(PlatformSharedMemoryRegionTest, CreateMaxSizeRegionIsInvalid) {
  size_t max_region_size = std::numeric_limits<size_t>::max();
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(max_region_size);
  EXPECT_FALSE(region.IsValid());

  PlatformSharedMemoryRegion region2 =
      PlatformSharedMemoryRegion::CreateUnsafe(max_region_size);
  EXPECT_FALSE(region2.IsValid());
}

// Tests that regions consistently report their size as the size requested at
// creation time even if their allocation size is larger due to platform
// constraints.
TEST_F(PlatformSharedMemoryRegionTest, ReportedSizeIsRequestedSize) {
  constexpr size_t kTestSizes[] = {1, 2, 3, 64, 4096, 1024 * 1024};
  for (size_t size : kTestSizes) {
    PlatformSharedMemoryRegion region =
        PlatformSharedMemoryRegion::CreateWritable(size);
    EXPECT_EQ(region.GetSize(), size);

    region.ConvertToReadOnly();
    EXPECT_EQ(region.GetSize(), size);
  }
}

// Tests that a writable region can be converted to read-only.
TEST_F(PlatformSharedMemoryRegionTest, ConvertWritableToReadOnly) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  EXPECT_EQ(region.GetMode(), PlatformSharedMemoryRegion::Mode::kWritable);
  ASSERT_TRUE(region.ConvertToReadOnly());
  EXPECT_EQ(region.GetMode(), PlatformSharedMemoryRegion::Mode::kReadOnly);
}

// Tests that a writable region can be converted to unsafe.
TEST_F(PlatformSharedMemoryRegionTest, ConvertWritableToUnsafe) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  EXPECT_EQ(region.GetMode(), PlatformSharedMemoryRegion::Mode::kWritable);
  ASSERT_TRUE(region.ConvertToUnsafe());
  EXPECT_EQ(region.GetMode(), PlatformSharedMemoryRegion::Mode::kUnsafe);
}

// Tests that the platform-specific handle converted to read-only cannot be used
// to perform a writable mapping with low-level system APIs like mmap().
TEST_F(PlatformSharedMemoryRegionTest, ReadOnlyHandleIsNotWritable) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  EXPECT_TRUE(region.ConvertToReadOnly());
  EXPECT_EQ(region.GetMode(), PlatformSharedMemoryRegion::Mode::kReadOnly);
  EXPECT_TRUE(
      CheckReadOnlyPlatformSharedMemoryRegionForTesting(std::move(region)));
}

// Tests that the PassPlatformHandle() call invalidates the region.
TEST_F(PlatformSharedMemoryRegionTest, InvalidAfterPass) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  std::ignore = region.PassPlatformHandle();
  EXPECT_FALSE(region.IsValid());
}

// Tests that the region is invalid after move.
TEST_F(PlatformSharedMemoryRegionTest, InvalidAfterMove) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  PlatformSharedMemoryRegion moved_region = std::move(region);
  EXPECT_FALSE(region.IsValid());  // NOLINT(bugprone-use-after-move)
  EXPECT_TRUE(moved_region.IsValid());
}

// Tests that calling Take() with the size parameter equal to zero returns an
// invalid region.
TEST_F(PlatformSharedMemoryRegionTest, TakeRegionOfZeroSizeIsInvalid) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  PlatformSharedMemoryRegion region2 = PlatformSharedMemoryRegion::Take(
      region.PassPlatformHandle(), region.GetMode(), 0, region.GetGUID());
  EXPECT_FALSE(region2.IsValid());
}

// Tests that calling Take() with the size parameter bigger than the integer max
// value returns an invalid region.
TEST_F(PlatformSharedMemoryRegionTest, TakeTooLargeRegionIsInvalid) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  PlatformSharedMemoryRegion region2 = PlatformSharedMemoryRegion::Take(
      region.PassPlatformHandle(), region.GetMode(),
      static_cast<size_t>(std::numeric_limits<int>::max()) + 1,
      region.GetGUID());
  EXPECT_FALSE(region2.IsValid());
}

TEST_F(PlatformSharedMemoryRegionTest, TakeOrFailReadOnly) {
  {
    PlatformSharedMemoryRegion region =
        PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
    ASSERT_TRUE(region.IsValid());
    ASSERT_TRUE(region.ConvertToReadOnly());

    auto result = PlatformSharedMemoryRegion::TakeOrFail(
        region.PassPlatformHandle(), region.GetMode(), region.GetSize(),
        region.GetGUID());
    ASSERT_TRUE(result.has_value());
    EXPECT_TRUE(result->IsValid());
  }

  {
    PlatformSharedMemoryRegion region =
        PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
    ASSERT_TRUE(region.IsValid());
    ASSERT_TRUE(region.ConvertToReadOnly());

    auto result = PlatformSharedMemoryRegion::TakeOrFail(
        region.PassPlatformHandle(),
        PlatformSharedMemoryRegion::Mode::kWritable, region.GetSize(),
        region.GetGUID());
    EXPECT_THAT(
        result,
        ErrorIs(
            PlatformSharedMemoryRegion::TakeError::kExpectedWritableButNot));
  }

  {
    PlatformSharedMemoryRegion region =
        PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
    ASSERT_TRUE(region.IsValid());
    ASSERT_TRUE(region.ConvertToReadOnly());

    auto result = PlatformSharedMemoryRegion::TakeOrFail(
        region.PassPlatformHandle(), PlatformSharedMemoryRegion::Mode::kUnsafe,
        region.GetSize(), region.GetGUID());
    EXPECT_THAT(
        result,
        ErrorIs(
            PlatformSharedMemoryRegion::TakeError::kExpectedWritableButNot));
  }
}

TEST_F(PlatformSharedMemoryRegionTest, TakeOrFailWritable) {
  {
    PlatformSharedMemoryRegion region =
        PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
    ASSERT_TRUE(region.IsValid());

    auto result = PlatformSharedMemoryRegion::TakeOrFail(
        region.PassPlatformHandle(), region.GetMode(), region.GetSize(),
        region.GetGUID());
    ASSERT_TRUE(result.has_value());
    EXPECT_TRUE(result->IsValid());
  }

  {
    PlatformSharedMemoryRegion region =
        PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
    ASSERT_TRUE(region.IsValid());

    auto result = PlatformSharedMemoryRegion::TakeOrFail(
        region.PassPlatformHandle(),
        PlatformSharedMemoryRegion::Mode::kReadOnly, region.GetSize(),
        region.GetGUID());
    EXPECT_THAT(
        result,
        ErrorIs(
            PlatformSharedMemoryRegion::TakeError::kExpectedReadOnlyButNot));
  }

  {
    PlatformSharedMemoryRegion region =
        PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
    ASSERT_TRUE(region.IsValid());

    auto result = PlatformSharedMemoryRegion::TakeOrFail(
        region.PassPlatformHandle(), PlatformSharedMemoryRegion::Mode::kUnsafe,
        region.GetSize(), region.GetGUID());
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
    EXPECT_THAT(
        result,
        ErrorIs(PlatformSharedMemoryRegion::TakeError::kUnexpectedReadOnlyFd));
#else
    // On other platforms, the permission-mode consistency checks cannot easily
    // detect potentially configuration mismatches between the two types of
    // writable shmem, but at least the region is writable so it's not
    // dangerously incorrect.
    EXPECT_THAT(result, HasValue());
#endif
  }
}

TEST_F(PlatformSharedMemoryRegionTest, TakeOrFailUnsafe) {
  {
    PlatformSharedMemoryRegion region =
        PlatformSharedMemoryRegion::CreateUnsafe(kRegionSize);
    ASSERT_TRUE(region.IsValid());

    auto result = PlatformSharedMemoryRegion::TakeOrFail(
        region.PassPlatformHandle(), region.GetMode(), region.GetSize(),
        region.GetGUID());
    ASSERT_TRUE(result.has_value());
    EXPECT_TRUE(result->IsValid());
  }

  {
    PlatformSharedMemoryRegion region =
        PlatformSharedMemoryRegion::CreateUnsafe(kRegionSize);
    ASSERT_TRUE(region.IsValid());

    auto result = PlatformSharedMemoryRegion::TakeOrFail(
        region.PassPlatformHandle(),
        PlatformSharedMemoryRegion::Mode::kReadOnly, region.GetSize(),
        region.GetGUID());
    EXPECT_THAT(
        result,
        ErrorIs(
            PlatformSharedMemoryRegion::TakeError::kExpectedReadOnlyButNot));
  }

  {
    PlatformSharedMemoryRegion region =
        PlatformSharedMemoryRegion::CreateUnsafe(kRegionSize);
    ASSERT_TRUE(region.IsValid());

    auto result = PlatformSharedMemoryRegion::TakeOrFail(
        region.PassPlatformHandle(),
        PlatformSharedMemoryRegion::Mode::kWritable, region.GetSize(),
        region.GetGUID());
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
    EXPECT_THAT(result,
                ErrorIs(PlatformSharedMemoryRegion::TakeError::kFcntlFailed));
#else
    // On other platforms, the permission-mode consistency checks cannot easily
    // detect potentially configuration mismatches between the two types of
    // writable shmem, but at least the region is writable so it's not
    // dangerously incorrect.
    EXPECT_THAT(result, HasValue());
#endif
  }
}
// Tests that mapping zero bytes fails.
TEST_F(PlatformSharedMemoryRegionTest, MapAtZeroBytesTest) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  WritableSharedMemoryMapping mapping = MapAtForTesting(&region, 0, 0);
  EXPECT_FALSE(mapping.IsValid());
}

// Tests that mapping bytes out of the region limits fails.
TEST_F(PlatformSharedMemoryRegionTest, MapAtOutOfTheRegionLimitsTest) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  WritableSharedMemoryMapping mapping =
      MapAtForTesting(&region, 0, region.GetSize() + 1);
  EXPECT_FALSE(mapping.IsValid());
}

// Tests that mapping with a size and offset causing overflow fails.
TEST_F(PlatformSharedMemoryRegionTest, MapAtWithOverflowTest) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(
          SysInfo::VMAllocationGranularity() * 2);
  ASSERT_TRUE(region.IsValid());
  size_t size = std::numeric_limits<size_t>::max();
  size_t offset = SysInfo::VMAllocationGranularity();
  // |size| + |offset| should be below the region size due to overflow but
  // mapping a region with these parameters should be invalid.
  EXPECT_LT(size + offset, region.GetSize());
  WritableSharedMemoryMapping mapping = MapAtForTesting(&region, offset, size);
  EXPECT_FALSE(mapping.IsValid());
}

#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_APPLE)
// Tests that the second handle is closed after a conversion to read-only on
// POSIX.
TEST_F(PlatformSharedMemoryRegionTest,
       ConvertToReadOnlyInvalidatesSecondHandle) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  ASSERT_TRUE(region.ConvertToReadOnly());
  FDPair fds = region.GetPlatformHandle();
  EXPECT_LT(fds.readonly_fd, 0);
}

// Tests that the second handle is closed after a conversion to unsafe on
// POSIX.
TEST_F(PlatformSharedMemoryRegionTest, ConvertToUnsafeInvalidatesSecondHandle) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  ASSERT_TRUE(region.ConvertToUnsafe());
  FDPair fds = region.GetPlatformHandle();
  EXPECT_LT(fds.readonly_fd, 0);
}
#endif

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
namespace {

// Returns true if |fd| refers to a memfd. Regions are backed by memfd unless
// the kernel does not support memfd_create(), in which case they fall back to
// an unlinked file in /dev/shm and the memfd-specific expectations below do
// not apply.
bool IsMemfd(int fd) {
  FilePath target;
  if (!ReadSymbolicLink(FilePath(StringPrintf("/proc/self/fd/%d", fd)),
                        &target)) {
    return false;
  }
  return StartsWith(target.value(), "/memfd:");
}

}  // namespace

// Tests that memfd-backed regions have their size sealed, so that no holder of
// a descriptor can shrink or grow them, and that the set of seals is final.
TEST_F(PlatformSharedMemoryRegionTest, MemfdRegionsAreSizeSealed) {
  for (auto create : {&PlatformSharedMemoryRegion::CreateWritable,
                      &PlatformSharedMemoryRegion::CreateUnsafe}) {
    PlatformSharedMemoryRegion region = create(kRegionSize);
    ASSERT_TRUE(region.IsValid());
    const int fd = region.GetPlatformHandle().fd;
    if (!IsMemfd(fd)) {
      GTEST_SKIP() << "memfd_create() is not supported here";
    }
    const int seals = fcntl(fd, F_GET_SEALS);
    ASSERT_NE(seals, -1);
    EXPECT_EQ(seals & (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL),
              F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL);
    EXPECT_EQ(seals & (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE), 0);
    EXPECT_EQ(-1, HANDLE_EINTR(ftruncate(fd, 0)));
    EXPECT_EQ(EPERM, errno);
    EXPECT_EQ(-1, HANDLE_EINTR(ftruncate(fd, kRegionSize * 2)));
    EXPECT_EQ(EPERM, errno);
    // The region is still fully usable.
    WritableSharedMemoryMapping mapping = MapForTesting(&region);
    ASSERT_TRUE(mapping.IsValid());
    std::ranges::fill(mapping.GetMemoryAsSpan<uint8_t>(), 0xab);
  }
}

// Tests that the read-only descriptor of a memfd-backed writable region refers
// to the same memory and cannot be used to map it writable.
TEST_F(PlatformSharedMemoryRegionTest, MemfdReadOnlyDescriptor) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  if (!IsMemfd(region.GetPlatformHandle().fd)) {
    GTEST_SKIP() << "memfd_create() is not supported here";
  }
  WritableSharedMemoryMapping rw_mapping = MapForTesting(&region);
  ASSERT_TRUE(rw_mapping.IsValid());
  std::ranges::fill(rw_mapping.GetMemoryAsSpan<uint8_t>(), 0x5a);

  const int readonly_fd = region.GetPlatformHandle().readonly_fd;
  ASSERT_GE(readonly_fd, 0);
  EXPECT_TRUE(IsMemfd(readonly_fd));
  EXPECT_EQ(O_RDONLY, fcntl(readonly_fd, F_GETFL) & O_ACCMODE);
  void* rw = mmap(nullptr, kRegionSize, PROT_READ | PROT_WRITE, MAP_SHARED,
                  readonly_fd, 0);
  EXPECT_EQ(MAP_FAILED, rw);
  void* ro = mmap(nullptr, kRegionSize, PROT_READ, MAP_SHARED, readonly_fd, 0);
  ASSERT_NE(MAP_FAILED, ro);
  EXPECT_EQ(-1, mprotect(ro, kRegionSize, PROT_READ | PROT_WRITE));
  munmap(ro, kRegionSize);

  // After conversion the region is accepted as read-only and still maps the
  // same memory.
  ASSERT_TRUE(region.ConvertToReadOnly());
  auto read_only_region = PlatformSharedMemoryRegion::TakeOrFail(
      region.PassPlatformHandle(), PlatformSharedMemoryRegion::Mode::kReadOnly,
      kRegionSize, UnguessableToken::Create());
  ASSERT_THAT(read_only_region, HasValue());
  WritableSharedMemoryMapping ro_mapping =
      MapForTesting(&read_only_region.value());
  ASSERT_TRUE(ro_mapping.IsValid());
  EXPECT_EQ(0x5a, ro_mapping.GetMemoryAsSpan<const uint8_t>()[kRegionSize - 1]);
}
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)

void CheckReadOnlyMapProtection(void* addr) {
#if BUILDFLAG(IS_APPLE)
  vm_region_basic_info_64 basic_info;
  vm_size_t dummy_size = 0;
  mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
  mach_port_t object_name;
  kern_return_t kr = vm_region_64(
      mach_task_self(), reinterpret_cast<vm_address_t*>(&addr), &dummy_size,
      VM_REGION_BASIC_INFO_64, reinterpret_cast<vm_region_info_t>(&basic_info),
      &info_count, &object_name);
  mach_port_deallocate(mach_task_self(), object_name);

  ASSERT_EQ(kr, KERN_SUCCESS);
  EXPECT_EQ(basic_info.protection & VM_PROT_ALL, VM_PROT_READ);
  EXPECT_EQ(basic_info.max_protection & VM_PROT_ALL, VM_PROT_READ);
#elif BUILDFLAG(IS_POSIX)
  std::string proc_maps;
  ASSERT_TRUE(base::debug::ReadProcMaps(&proc_maps));
  std::vector<base::debug::MappedMemoryRegion> regions;
  ASSERT_TRUE(base::debug::ParseProcMaps(proc_maps, &regions));
  auto it = std::ranges::find_if(
      regions, [addr](const base::debug::MappedMemoryRegion& region) {
        return region.start == reinterpret_cast<uintptr_t>(addr);
      });
  ASSERT_TRUE(it != regions.end());
  // PROT_READ may imply PROT_EXEC on some architectures, so just check that
  // permissions don't contain PROT_WRITE bit.
  EXPECT_FALSE(it->permissions & base::debug::MappedMemoryRegion::WRITE);
#elif BUILDFLAG(IS_WIN)
  MEMORY_BASIC_INFORMATION memory_info;
  size_t result = VirtualQueryEx(GetCurrentProcess(), addr, &memory_info,
                                 sizeof(memory_info));

  ASSERT_GT(result, 0ULL) << "Failed executing VirtualQueryEx "
                          << logging::SystemErrorCodeToString(
                                 logging::GetLastSystemErrorCode());
  EXPECT_EQ(memory_info.AllocationProtect, static_cast<DWORD>(PAGE_READONLY));
  EXPECT_EQ(memory_info.Protect, static_cast<DWORD>(PAGE_READONLY));
#elif BUILDFLAG(IS_FUCHSIA)
// TODO(alexilin): We cannot call zx_object_get_info ZX_INFO_PROCESS_MAPS in
// this process. Consider to create an auxiliary process that will read the
// test process maps.
#endif
}

bool TryToRestoreWritablePermissions(void* addr, size_t len) {
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_IOS)
  int result = mprotect(addr, len, PROT_READ | PROT_WRITE);
  return result != -1;
#elif BUILDFLAG(IS_WIN)
  DWORD old_protection;
  return VirtualProtect(addr, len, PAGE_READWRITE, &old_protection);
#elif BUILDFLAG(IS_FUCHSIA)
  zx_status_t status =
      zx::vmar::root_self()->protect(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
                                     reinterpret_cast<uintptr_t>(addr), len);
  return status == ZX_OK;
#else
  return false;
#endif
}

// Tests that protection bits are set correctly for read-only region.
TEST_F(PlatformSharedMemoryRegionTest, MappingProtectionSetCorrectly) {
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  ASSERT_TRUE(region.ConvertToReadOnly());
  WritableSharedMemoryMapping ro_mapping = MapForTesting(&region);
  ASSERT_TRUE(ro_mapping.IsValid());
  CheckReadOnlyMapProtection(ro_mapping.data());

  // SAFETY: There's no public way to get a span of the full mapped memory size.
  // The `mapped_size()` is larger then `size()` but is the actual size of the
  // shared memory backing.
  auto full_map_mem =
      UNSAFE_BUFFERS(span(ro_mapping.data(), ro_mapping.mapped_size()));
  EXPECT_FALSE(TryToRestoreWritablePermissions(full_map_mem.data(),
                                               full_map_mem.size()));

  CheckReadOnlyMapProtection(ro_mapping.data());
}

// Tests that platform handle permissions are checked correctly.
TEST_F(PlatformSharedMemoryRegionTest,
       CheckPlatformHandlePermissionsCorrespondToMode) {
  using Mode = PlatformSharedMemoryRegion::Mode;
  auto check = [](const PlatformSharedMemoryRegion& region,
                  PlatformSharedMemoryRegion::Mode mode) {
    return PlatformSharedMemoryRegion::
        CheckPlatformHandlePermissionsCorrespondToMode(
            region.GetPlatformHandle(), mode, region.GetSize());
  };

  using TakeError = PlatformSharedMemoryRegion::TakeError;
  // Check kWritable region.
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  EXPECT_THAT(check(region, Mode::kWritable), HasValue());
  EXPECT_THAT(check(region, Mode::kReadOnly),
              ErrorIs(TakeError::kExpectedReadOnlyButNot));

  // Check kReadOnly region.
  ASSERT_TRUE(region.ConvertToReadOnly());
  EXPECT_THAT(check(region, Mode::kReadOnly), HasValue());
  EXPECT_THAT(check(region, Mode::kWritable),
              ErrorIs(TakeError::kExpectedWritableButNot));
  EXPECT_THAT(check(region, Mode::kUnsafe),
              ErrorIs(TakeError::kExpectedWritableButNot));

  // Check kUnsafe region.
  PlatformSharedMemoryRegion region2 =
      PlatformSharedMemoryRegion::CreateUnsafe(kRegionSize);
  ASSERT_TRUE(region2.IsValid());
  EXPECT_THAT(check(region2, Mode::kUnsafe), HasValue());
  EXPECT_THAT(check(region2, Mode::kReadOnly),
              ErrorIs(TakeError::kExpectedReadOnlyButNot));
}

// Tests that it's impossible to create read-only platform shared memory region.
TEST_F(PlatformSharedMemoryRegionTest, CreateReadOnlyRegionDeathTest) {
#ifdef OFFICIAL_BUILD
  // The official build does not print the reason a CHECK failed.
  const char kErrorRegex[] = "";
#else
  const char kErrorRegex[] =
      "Creating a region in read-only mode will lead to this region being "
      "non-modifiable";
#endif
  EXPECT_DEATH_IF_SUPPORTED(
      PlatformSharedMemoryRegion::Create(
          PlatformSharedMemoryRegion::Mode::kReadOnly, kRegionSize),
      kErrorRegex);
}

// Tests that it's prohibited to duplicate a writable region.
TEST_F(PlatformSharedMemoryRegionTest, DuplicateWritableRegionDeathTest) {
#ifdef OFFICIAL_BUILD
  const char kErrorRegex[] = "";
#else
  const char kErrorRegex[] =
      "Duplicating a writable shared memory region is prohibited";
#endif
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  EXPECT_DEATH_IF_SUPPORTED(region.Duplicate(), kErrorRegex);
}

// Tests that it's prohibited to convert an unsafe region to read-only.
TEST_F(PlatformSharedMemoryRegionTest, UnsafeRegionConvertToReadOnlyDeathTest) {
#ifdef OFFICIAL_BUILD
  const char kErrorRegex[] = "";
#else
  const char kErrorRegex[] =
      "Only writable shared memory region can be converted to read-only";
#endif
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateUnsafe(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  EXPECT_DEATH_IF_SUPPORTED(region.ConvertToReadOnly(), kErrorRegex);
}

// Tests that it's prohibited to convert a read-only region to read-only.
TEST_F(PlatformSharedMemoryRegionTest,
       ReadOnlyRegionConvertToReadOnlyDeathTest) {
#ifdef OFFICIAL_BUILD
  const char kErrorRegex[] = "";
#else
  const char kErrorRegex[] =
      "Only writable shared memory region can be converted to read-only";
#endif
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  EXPECT_TRUE(region.ConvertToReadOnly());
  EXPECT_DEATH_IF_SUPPORTED(region.ConvertToReadOnly(), kErrorRegex);
}

// Tests that it's prohibited to convert a read-only region to unsafe.
TEST_F(PlatformSharedMemoryRegionTest, ReadOnlyRegionConvertToUnsafeDeathTest) {
#ifdef OFFICIAL_BUILD
  const char kErrorRegex[] = "";
#else
  const char kErrorRegex[] =
      "Only writable shared memory region can be converted to unsafe";
#endif
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  ASSERT_TRUE(region.ConvertToReadOnly());
  EXPECT_DEATH_IF_SUPPORTED(region.ConvertToUnsafe(), kErrorRegex);
}

// Tests that it's prohibited to convert an unsafe region to unsafe.
TEST_F(PlatformSharedMemoryRegionTest, UnsafeRegionConvertToUnsafeDeathTest) {
#ifdef OFFICIAL_BUILD
  const char kErrorRegex[] = "";
#else
  const char kErrorRegex[] =
      "Only writable shared memory region can be converted to unsafe";
#endif
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateUnsafe(kRegionSize);
  ASSERT_TRUE(region.IsValid());
  EXPECT_DEATH_IF_SUPPORTED(region.ConvertToUnsafe(), kErrorRegex);
}

#if BUILDFLAG(IS_WIN)
namespace {

int g_fake_create_file_mapping_call_count = 0;

// Fake CreateFileMapping() implementation for testing.
// It simulates ERROR_COMMITMENT_LIMIT failures for the first 5 calls,
// then delegates to the real API.
HANDLE WINAPI FakeCreateFileMapping(HANDLE file,
                                    SECURITY_ATTRIBUTES* sa,
                                    DWORD protect,
                                    DWORD max_size_high,
                                    DWORD max_size_low,
                                    LPCWSTR name) {
  g_fake_create_file_mapping_call_count++;

  // Fail the first 5 times to trigger the retry logic.
  if (g_fake_create_file_mapping_call_count <= 5) {
    ::SetLastError(ERROR_COMMITMENT_LIMIT);
    return nullptr;
  }

  // On the 6th try, call the real API to return a valid handle.
  return ::CreateFileMapping(file, sa, protect, max_size_high, max_size_low,
                             name);
}

}  // namespace

// Tests that the retry logic operates correctly when CreateFileMapping() fails
// with ERROR_COMMITMENT_LIMIT.
TEST_F(PlatformSharedMemoryRegionTest, CreateRetryOnCommitLimit) {
  // Enable the retry feature.
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kRetryCreateFileMappingOnCommitLimit);

  // Install the hook.
  g_fake_create_file_mapping_call_count = 0;
  PlatformSharedMemoryRegion::SetCreateFileMappingCallbackForTesting(
      &FakeCreateFileMapping);

  // Create a region.
  // This will fail 5 times inside the loop, wait, and succeed on the 6th try.
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);

  // Verify that the retry loop ran exactly as expected (5 failures + 1
  // success).
  EXPECT_EQ(g_fake_create_file_mapping_call_count, 6);
  EXPECT_TRUE(region.IsValid());

  // Cleanup: Remove the hook to avoid affecting other tests.
  PlatformSharedMemoryRegion::SetCreateFileMappingCallbackForTesting(nullptr);
}

// Tests that the retry logic does not run if the feature is disabled.
TEST_F(PlatformSharedMemoryRegionTest, NoRetryWhenFeatureDisabled) {
  // Disable the retry feature.
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kRetryCreateFileMappingOnCommitLimit);

  // Install the hook.
  g_fake_create_file_mapping_call_count = 0;
  PlatformSharedMemoryRegion::SetCreateFileMappingCallbackForTesting(
      &FakeCreateFileMapping);

  // Create a region.
  // The hook fails immediately with ERROR_COMMITMENT_LIMIT.
  // Since the feature is disabled, it should return an invalid region
  // immediately without retrying.
  PlatformSharedMemoryRegion region =
      PlatformSharedMemoryRegion::CreateWritable(kRegionSize);

  // Verify that only 1 call was made (the failure).
  EXPECT_EQ(g_fake_create_file_mapping_call_count, 1);
  EXPECT_FALSE(region.IsValid());

  // Cleanup: Remove the hook to avoid affecting other tests.
  PlatformSharedMemoryRegion::SetCreateFileMappingCallbackForTesting(nullptr);
}
#endif  // BUILDFLAG(IS_WIN)

}  // namespace base::subtle
