sandbox.h 7.07 KB
Newer Older
Samuel Groß's avatar
Samuel Groß committed
1 2 3 4 5 6 7 8 9
// 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.

#ifndef V8_SANDBOX_SANDBOX_H_
#define V8_SANDBOX_SANDBOX_H_

#include "include/v8-internal.h"
#include "include/v8-platform.h"
10
#include "include/v8config.h"
Samuel Groß's avatar
Samuel Groß committed
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
#include "src/common/globals.h"
#include "testing/gtest/include/gtest/gtest_prod.h"  // nogncheck

namespace v8 {

namespace internal {

#ifdef V8_SANDBOX_IS_AVAILABLE

/**
 * The V8 Sandbox.
 *
 * When enabled, V8 reserves a large region of virtual address space - the
 * sandbox - and places most of its objects inside of it. It is then assumed
 * that an attacker can, by exploiting a vulnerability in V8, corrupt memory
 * inside the sandbox arbitrarily and from different threads. The sandbox
 * attempts to stop an attacker from corrupting other memory in the process.
 *
 * The sandbox relies on a number of different mechanisms to achieve its goal.
 * For example, objects inside the sandbox can reference each other through
 * offsets from the start of the sandbox ("sandboxed pointers") instead of raw
 * pointers, and external objects can be referenced through indices into a
 * per-Isolate table of external pointers ("sandboxed external pointers").
 *
 * The pointer compression region, which contains most V8 objects, and inside
 * of which compressed (32-bit) pointers are used, is located at the start of
 * the sandbox. The remainder of the sandbox is mostly used for memory
 * buffers, in particular ArrayBuffer backing stores and WASM memory cages.
 *
 * As the embedder is responsible for providing ArrayBuffer allocators, V8
 * exposes the virtual address space backing the sandbox to the embedder.
 */
class V8_EXPORT_PRIVATE Sandbox {
 public:
  // +-  ~~~  -+----------------------------------------  ~~~  -+-  ~~~  -+
  // |  32 GB  |                 (Ideally) 1 TB                 |  32 GB  |
  // |         |                                                |         |
  // | Guard   |      4 GB      :  ArrayBuffer backing stores,  | Guard   |
  // | Region  |    V8 Heap     :  WASM memory buffers, and     | Region  |
  // | (front) |     Region     :  any other sandboxed objects. | (back)  |
  // +-  ~~~  -+----------------+-----------------------  ~~~  -+-  ~~~  -+
  //           ^                                                ^
  //           base                                             base + size

  Sandbox() = default;

  Sandbox(const Sandbox&) = delete;
  Sandbox& operator=(Sandbox&) = delete;

  bool Initialize(v8::VirtualAddressSpace* vas);
  void Disable() {
    CHECK(!initialized_);
    disabled_ = true;
  }

  void TearDown();

  bool is_initialized() const { return initialized_; }
  bool is_disabled() const { return disabled_; }
  bool is_enabled() const { return !disabled_; }
  bool is_partially_reserved() const { return is_partially_reserved_; }

  Address base() const { return base_; }
  Address end() const { return end_; }
  size_t size() const { return size_; }

  Address base_address() const { return reinterpret_cast<Address>(&base_); }
  Address end_address() const { return reinterpret_cast<Address>(&end_); }
  Address size_address() const { return reinterpret_cast<Address>(&size_); }

  v8::PageAllocator* page_allocator() const {
    return sandbox_page_allocator_.get();
  }

  v8::VirtualAddressSpace* address_space() const {
    return address_space_.get();
  }

  bool Contains(Address addr) const {
    return addr >= base_ && addr < base_ + size_;
  }

  bool Contains(void* ptr) const {
    return Contains(reinterpret_cast<Address>(ptr));
  }

#ifdef V8_SANDBOXED_POINTERS
  class SandboxedPointerConstants final {
   public:
    Address empty_backing_store_buffer() const {
      return empty_backing_store_buffer_;
    }
    Address empty_backing_store_buffer_address() const {
      return reinterpret_cast<Address>(&empty_backing_store_buffer_);
    }
    void set_empty_backing_store_buffer(Address value) {
      empty_backing_store_buffer_ = value;
    }

    void Reset() { empty_backing_store_buffer_ = 0; }

   private:
    Address empty_backing_store_buffer_ = 0;
  };
  const SandboxedPointerConstants& constants() const { return constants_; }
#endif

 private:
  // The SequentialUnmapperTest calls the private Initialize method to create a
  // sandbox without guard regions, which would consume too much memory.
  friend class SequentialUnmapperTest;

  // These tests call the private Initialize methods below.
  FRIEND_TEST(SandboxTest, InitializationWithSize);
  FRIEND_TEST(SandboxTest, PartiallyReservedSandboxInitialization);
  FRIEND_TEST(SandboxTest, PartiallyReservedSandboxPageAllocation);

  // We allow tests to disable the guard regions around the sandbox. This is
  // useful for example for tests like the SequentialUnmapperTest which track
  // page allocations and so would incur a large overhead from the guard
  // regions. The provided virtual address space must be able to allocate
  // subspaces. The size must be a multiple of the allocation granularity of the
  // virtual memory space.
  bool Initialize(v8::VirtualAddressSpace* vas, size_t size,
                  bool use_guard_regions);

  // Used when reserving virtual memory is too expensive. A partially reserved
  // sandbox does not reserve all of its virtual memory and so doesn't have the
  // desired security properties as unrelated mappings could end up inside of
  // it and be corrupted. The size and size_to_reserve parameters must be
  // multiples of the allocation granularity of the virtual address space.
  bool InitializeAsPartiallyReservedSandbox(v8::VirtualAddressSpace* vas,
                                            size_t size,
                                            size_t size_to_reserve);

  // Initialize the constant objects for this sandbox. Called by the Initialize
  // methods above.
  void InitializeConstants();

  Address base_ = kNullAddress;
  Address end_ = kNullAddress;
  size_t size_ = 0;

  // Base and size of the virtual memory reservation backing this sandbox.
  // These can be different from the sandbox base and size due to guard regions
  // or when a fake sandbox is used.
  Address reservation_base_ = kNullAddress;
  size_t reservation_size_ = 0;

  bool initialized_ = false;
  bool disabled_ = false;
  bool is_partially_reserved_ = false;

  // The virtual address subspace backing the sandbox.
  std::unique_ptr<v8::VirtualAddressSpace> address_space_;

  // The page allocator instance for this sandbox.
  std::unique_ptr<v8::PageAllocator> sandbox_page_allocator_;

#ifdef V8_SANDBOXED_POINTERS
  // Constant objects inside this sandbox.
  SandboxedPointerConstants constants_;
#endif
};

#endif  // V8_SANDBOX_IS_AVAILABLE

#ifdef V8_SANDBOX
// This function is only available when the sandbox is actually used.
V8_EXPORT_PRIVATE Sandbox* GetProcessWideSandbox();
#endif

V8_INLINE void* EmptyBackingStoreBuffer() {
#ifdef V8_SANDBOXED_POINTERS
  return reinterpret_cast<void*>(
      GetProcessWideSandbox()->constants().empty_backing_store_buffer());
#else
  return nullptr;
#endif
}

}  // namespace internal
}  // namespace v8

#endif  // V8_SANDBOX_SANDBOX_H_