// Copyright 2023 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/heap/trusted-range.h"
#include "src/heap/heap-inl.h"
#include "src/utils/allocation.h"
namespace v8 {
namespace internal {
#ifdef V8_ENABLE_SANDBOX
bool TrustedRange::InitReservation(size_t requested) {
DCHECK_LE(requested, kMaximalTrustedRangeSize);
DCHECK_GE(requested, kMinimumTrustedRangeSize);
auto page_allocator = GetPlatformPageAllocator();
const size_t kPageSize = MutablePageMetadata::kPageSize;
CHECK(IsAligned(kPageSize, page_allocator->AllocatePageSize()));
// We want the trusted range to be allocated above 4GB, for a few reasons:
// 1. Certain (sandbox) bugs allow access to (only) the first 4GB of the
// address space, so we don't want sensitive objects to live there.
// 2. When pointers to trusted objects have the upper 32 bits cleared, they
// may look like compressed pointers to some code in V8. For example, the
// stack spill slot visiting logic (VisitSpillSlot in frames.cc)
// currently assumes that when the top 32-bits are zero, then it's
// dealing with a compressed pointer and will attempt to decompress them
// with the main cage base, which in this case would break.
//
// To achieve this, we simply require 4GB alignment of the allocation and
// assume that we can never map the zeroth page.
const size_t base_alignment = size_t{4} * GB;
const Address requested_start_hint =
RoundDown(reinterpret_cast<Address>(page_allocator->GetRandomMmapAddr()),
base_alignment);
VirtualMemoryCage::ReservationParams params;
params.page_allocator = page_allocator;
params.reservation_size = requested;
params.page_size = kPageSize;
params.base_alignment = base_alignment;
params.requested_start_hint = requested_start_hint;
params.permissions = PageAllocator::Permission::kNoAccess;
params.page_initialization_mode =
base::PageInitializationMode::kAllocatedPagesCanBeUninitialized;
params.page_freeing_mode = base::PageFreeingMode::kMakeInaccessible;
bool success = VirtualMemoryCage::InitReservation(params);
if (success) {
// Reserve the null page to mitigate (compressed) nullptr dereference bugs.
//
// We typically use Smi::zero()/nullptr for protected pointer fields
// (compressed pointers in trusted space) if the field is empty.
// As such, we can have the equivalent of nullptr deref bugs if either some
// code doesn't handle empty fields or if objects aren't correctly
// initialized and fields are left empty. To mitigate these, we make the
// first pages of trusted space inaccessible so that any access is
// guaranteed to crash safely.
size_t guard_region_size = 1 * MB;
#if CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
guard_region_size =
std::max(guard_region_size, kContiguousReadOnlyReservationSize);
#endif // CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
DCHECK(IsAligned(guard_region_size, page_allocator_->AllocatePageSize()));
CHECK(page_allocator_->AllocatePagesAt(base(), guard_region_size,
PageAllocator::kNoAccess));
}
#ifdef V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
// Sandboxed code should never write to trusted memory.
SandboxHardwareSupport::RegisterOutOfSandboxMemory(
base(), size(), PagePermissions::kNoAccess);
#endif // V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
return success;
}
#endif // V8_ENABLE_SANDBOX
} // namespace internal
} // namespace v8