// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/sandbox/sandbox.h"

#include <vector>

#include "include/libplatform/libplatform.h"
#include "src/base/virtual-address-space.h"
#include "src/init/v8.h"
#include "test/unittests/test-utils.h"

#ifdef V8_OS_LINUX
#include "src/base/platform/platform-linux.h"
#endif

#ifdef V8_ENABLE_SANDBOX

namespace v8 {
namespace internal {

class SandboxTest : public v8::TestWithPlatform {};

TEST_F(SandboxTest, Initialization) {
  base::VirtualAddressSpace vas;

  Sandbox sandbox;

  EXPECT_FALSE(sandbox.is_initialized());
  EXPECT_FALSE(sandbox.is_partially_reserved());
  EXPECT_EQ(sandbox.size(), 0UL);

  sandbox.Initialize(platform(), &vas);

  EXPECT_TRUE(sandbox.is_initialized());
  EXPECT_NE(sandbox.base(), 0UL);
  EXPECT_GT(sandbox.size(), 0UL);

  sandbox.TearDown();

  EXPECT_FALSE(sandbox.is_initialized());
}

TEST_F(SandboxTest, InitializationWithSize) {
  base::VirtualAddressSpace vas;
  // This test only works if virtual memory subspaces can be allocated.
  if (!vas.CanAllocateSubspaces()) return;

  Sandbox sandbox;
  size_t size = 8ULL * GB;
  const bool use_guard_regions = false;
  sandbox.Initialize(platform(), &vas, size, use_guard_regions);

  EXPECT_TRUE(sandbox.is_initialized());
  EXPECT_FALSE(sandbox.is_partially_reserved());
  EXPECT_EQ(sandbox.size(), size);

  sandbox.TearDown();
}

TEST_F(SandboxTest, PartiallyReservedSandbox) {
  base::VirtualAddressSpace vas;
  Sandbox sandbox;
  // Total size of the sandbox.
  size_t size = kSandboxSize;
  // Size of the virtual memory that is actually reserved at the start of the
  // sandbox.
  size_t reserved_size = 2 * vas.allocation_granularity();
  EXPECT_TRUE(sandbox.InitializeAsPartiallyReservedSandbox(
      platform(), &vas, size, reserved_size));

  EXPECT_TRUE(sandbox.is_initialized());
  EXPECT_TRUE(sandbox.is_partially_reserved());
  EXPECT_NE(sandbox.base(), 0UL);
  EXPECT_EQ(sandbox.size(), size);
  EXPECT_EQ(sandbox.reservation_size(), reserved_size);

  EXPECT_FALSE(sandbox.ReservationContains(sandbox.base() - 1));
  EXPECT_TRUE(sandbox.ReservationContains(sandbox.base()));
  EXPECT_TRUE(sandbox.ReservationContains(sandbox.base() + reserved_size - 1));
  EXPECT_FALSE(sandbox.ReservationContains(sandbox.base() + reserved_size));

  sandbox.TearDown();

  EXPECT_FALSE(sandbox.is_initialized());
}

TEST_F(SandboxTest, Contains) {
  base::VirtualAddressSpace vas;
  Sandbox sandbox;
  sandbox.Initialize(platform(), &vas);

  if (sandbox.is_partially_reserved()) {
    // If we couldn't create a "full" sandbox, this test will fail, so skip it.
    static_assert(Sandbox::kFallbackToPartiallyReservedSandboxAllowed);
    sandbox.TearDown();
    return;
  }

  Address base = sandbox.base();
  size_t size = sandbox.size();
  base::RandomNumberGenerator rng(GTEST_FLAG_GET(random_seed));

  EXPECT_TRUE(sandbox.Contains(base));
  EXPECT_TRUE(sandbox.Contains(base + size - 1));

  EXPECT_TRUE(sandbox.ReservationContains(base));
  EXPECT_TRUE(sandbox.ReservationContains(base + size - 1));

  for (int i = 0; i < 10; i++) {
    size_t offset = rng.NextInt64() % size;
    EXPECT_TRUE(sandbox.Contains(base + offset));
    EXPECT_TRUE(sandbox.ReservationContains(base + offset));
  }

  EXPECT_FALSE(sandbox.Contains(base - 1));
  EXPECT_FALSE(sandbox.Contains(base + size));

  // ReservationContains also takes the guard regions into account.
  EXPECT_TRUE(sandbox.ReservationContains(base - 1));
  EXPECT_TRUE(sandbox.ReservationContains(base - kSandboxGuardRegionSize));
  EXPECT_TRUE(sandbox.ReservationContains(base + size));
  EXPECT_FALSE(sandbox.ReservationContains(base - kSandboxGuardRegionSize - 1));
  EXPECT_FALSE(
      sandbox.ReservationContains(base + size + kSandboxGuardRegionSize));

  for (int i = 0; i < 10; i++) {
    Address addr = rng.NextInt64();
    if (addr < base || addr >= base + size) {
      EXPECT_FALSE(sandbox.Contains(addr));
    }
    if (addr < base - kSandboxGuardRegionSize ||
        addr >= base + size + kSandboxGuardRegionSize) {
      EXPECT_FALSE(sandbox.ReservationContains(addr));
    }
  }

  sandbox.TearDown();
}

TEST_F(SandboxTest, PageAllocation) {
  base::VirtualAddressSpace root_vas;
  Sandbox sandbox;
  sandbox.Initialize(platform(), &root_vas);

  const size_t kAllocatinSizesInPages[] = {1, 1, 2, 3, 5, 8, 13, 21, 34};
  constexpr int kNumAllocations = arraysize(kAllocatinSizesInPages);

  VirtualAddressSpace* vas = sandbox.address_space();
  size_t allocation_granularity = vas->allocation_granularity();
  std::vector<Address> allocations;
  for (int i = 0; i < kNumAllocations; i++) {
    size_t length = allocation_granularity * kAllocatinSizesInPages[i];
    size_t alignment = allocation_granularity;
    Address ptr = vas->AllocatePages(VirtualAddressSpace::kNoHint, length,
                                     alignment, PagePermissions::kNoAccess);
    EXPECT_NE(ptr, kNullAddress);
    EXPECT_TRUE(sandbox.Contains(ptr));
    allocations.push_back(ptr);
  }

  for (int i = 0; i < kNumAllocations; i++) {
    size_t length = allocation_granularity * kAllocatinSizesInPages[i];
    vas->FreePages(allocations[i], length);
  }

  sandbox.TearDown();
}

#ifdef V8_OS_LINUX
TEST_F(SandboxTest, SandboxName) {
  base::VirtualAddressSpace vas;
  // This test only works if virtual memory subspaces can be allocated.
  if (!vas.CanAllocateSubspaces()) GTEST_SKIP();

  Sandbox sandbox;
  sandbox.Initialize(platform(), &vas);

  base::SignalSafeMapsParser parser;
  ASSERT_TRUE(parser.IsValid());

  // Check if the system supports naming.
  auto control_subspace = vas.AllocateSubspace(
      0, vas.allocation_granularity(), vas.allocation_granularity(),
      PagePermissions::kReadWrite, std::nullopt, std::nullopt);
  if (!control_subspace->SetName("test-name")) GTEST_SKIP();

  // If so, we expect the sandbox mapping to be named.
  bool found_sandbox_mapping = false;
  while (auto entry = parser.Next()) {
    if (strstr(entry->pathname, Sandbox::kSandboxAddressSpaceName)) {
      found_sandbox_mapping = true;
      break;
    }
  }
  EXPECT_TRUE(found_sandbox_mapping);

  sandbox.TearDown();
}
#endif  // V8_OS_LINUX

}  // namespace internal
}  // namespace v8

#endif  // V8_ENABLE_SANDBOX
