#include "src/sandbox/hardware-support.h"
#include "src/base/platform/memory-protection-key.h"
#include "src/base/platform/platform.h"
#include "src/flags/flags.h"
#ifdef V8_OS_LINUX
#include <sys/utsname.h>
#endif
namespace v8 {
namespace internal {
#ifdef V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
int SandboxHardwareSupport::sandbox_pkey_ =
base::MemoryProtectionKey::kNoMemoryProtectionKey;
int SandboxHardwareSupport::out_of_sandbox_pkey_ =
base::MemoryProtectionKey::kNoMemoryProtectionKey;
int SandboxHardwareSupport::extension_pkey_ =
base::MemoryProtectionKey::kNoMemoryProtectionKey;
uint32_t SandboxHardwareSupport::sandboxed_mode_pkey_mask_ = 0;
bool SandboxHardwareSupport::TryActivateBeforeThreadCreation() {
bool success = TryActivate();
CHECK_IMPLIES(v8_flags.force_memory_protection_keys, success);
return success;
}
bool SandboxHardwareSupport::IsActive() {
return sandbox_pkey_ != base::MemoryProtectionKey::kNoMemoryProtectionKey;
}
bool SandboxHardwareSupport::IsStrict() {
return out_of_sandbox_pkey_ ==
base::MemoryProtectionKey::kDefaultProtectionKey;
}
void SandboxHardwareSupport::EnableForCurrentThread() {
if (!IsActive()) return;
if (!IsStrict()) return;
base::OS::EnsureAlternativeSignalStackIsAvailableForCurrentThread();
if (!IsStrict()) return;
CHECK(
base::MemoryProtectionKey::SetKeyForCurrentThreadsStack(extension_pkey_));
}
void SandboxHardwareSupport::RegisterOutOfSandboxMemory(
Address addr, size_t size, PagePermissions permissions) {
if (!IsActive()) return;
CHECK(base::MemoryProtectionKey::SetPermissionsAndKey(
{addr, size}, permissions, out_of_sandbox_pkey_));
}
void SandboxHardwareSupport::RegisterUnsafeSandboxExtensionMemory(Address addr,
size_t size) {
if (!IsActive()) return;
CHECK(base::MemoryProtectionKey::SetPermissionsAndKey(
{addr, size}, PagePermissions::kReadWrite, extension_pkey_));
}
void SandboxHardwareSupport::RegisterReadOnlyMemoryInsideSandbox(
Address addr, size_t size, PagePermissions current_permissions) {
if (!IsActive()) return;
CHECK(base::MemoryProtectionKey::SetPermissionsAndKey(
{addr, size}, current_permissions,
base::MemoryProtectionKey::kDefaultProtectionKey));
}
void SandboxHardwareSupport::EnterSandboxedExecutionModeForCurrentThread() {
if (!IsActive()) return;
DCHECK_EQ(CurrentSandboxingMode(), CodeSandboxingMode::kUnsandboxed);
base::MemoryProtectionKey::SetPermissionsForKey(
out_of_sandbox_pkey_,
base::MemoryProtectionKey::Permission::kDisableWrite);
}
void SandboxHardwareSupport::ExitSandboxedExecutionModeForCurrentThread() {
if (!IsActive()) return;
DCHECK_EQ(CurrentSandboxingMode(), CodeSandboxingMode::kSandboxed);
base::MemoryProtectionKey::SetPermissionsForKey(
out_of_sandbox_pkey_,
base::MemoryProtectionKey::Permission::kNoRestrictions);
}
CodeSandboxingMode SandboxHardwareSupport::CurrentSandboxingMode() {
if (!IsActive()) return CodeSandboxingMode::kUnsandboxed;
auto key_permissions =
base::MemoryProtectionKey::GetKeyPermission(out_of_sandbox_pkey_);
if (key_permissions == base::MemoryProtectionKey::Permission::kDisableWrite) {
return CodeSandboxingMode::kSandboxed;
} else {
DCHECK_EQ(key_permissions,
base::MemoryProtectionKey::Permission::kNoRestrictions);
return CodeSandboxingMode::kUnsandboxed;
}
}
bool SandboxHardwareSupport::CurrentSandboxingModeIs(
CodeSandboxingMode expected_mode) {
if (!IsActive()) return true;
return CurrentSandboxingMode() == expected_mode;
}
bool SandboxHardwareSupport::KernelSupportsSignalDeliveryInSandbox() {
#ifdef V8_OS_LINUX
struct utsname buffer;
CHECK_EQ(uname(&buffer), 0);
int major, minor;
CHECK_EQ(sscanf(buffer.release, "%d.%d", &major, &minor), 2);
return major > 6 || (major == 6 && minor >= 12);
#else
UNREACHABLE();
#endif
}
bool SandboxHardwareSupport::TryActivate() {
DCHECK(!IsActive());
if (!base::MemoryProtectionKey::HasMemoryProtectionKeyAPIs()) {
return false;
}
sandbox_pkey_ = base::MemoryProtectionKey::AllocateKey();
if (sandbox_pkey_ == base::MemoryProtectionKey::kNoMemoryProtectionKey) {
return false;
}
extension_pkey_ = base::MemoryProtectionKey::AllocateKey();
if (extension_pkey_ == base::MemoryProtectionKey::kNoMemoryProtectionKey) {
base::MemoryProtectionKey::FreeKey(sandbox_pkey_);
sandbox_pkey_ = base::MemoryProtectionKey::kNoMemoryProtectionKey;
return false;
}
if (v8_flags.strict_pkey_sandbox) {
out_of_sandbox_pkey_ = base::MemoryProtectionKey::kDefaultProtectionKey;
#ifdef V8_OS_LINUX
char* bind_now = getenv("LD_BIND_NOW");
char* glibc_tunables = getenv("GLIBC_TUNABLES");
if (!bind_now || !glibc_tunables || strcmp(bind_now, "1") != 0 ||
strstr(glibc_tunables, "pthread.rseq=0") == nullptr) {
FATAL(
"Missing necessary environment variables: `LD_BIND_NOW=1` and "
"`GLIBC_TUNABLES=glibc.pthread.rseq=0`. See crbug.com/428179540");
}
#endif
} else {
out_of_sandbox_pkey_ = base::MemoryProtectionKey::AllocateKey();
if (out_of_sandbox_pkey_ ==
base::MemoryProtectionKey::kNoMemoryProtectionKey) {
base::MemoryProtectionKey::FreeKey(sandbox_pkey_);
sandbox_pkey_ = base::MemoryProtectionKey::kNoMemoryProtectionKey;
base::MemoryProtectionKey::FreeKey(extension_pkey_);
extension_pkey_ = base::MemoryProtectionKey::kNoMemoryProtectionKey;
return false;
}
}
sandboxed_mode_pkey_mask_ =
base::MemoryProtectionKey::ComputeRegisterMaskForPermissionSwitch(
out_of_sandbox_pkey_,
base::MemoryProtectionKey::Permission::kDisableWrite);
CHECK_NE(sandboxed_mode_pkey_mask_, 0);
EnableForCurrentThread();
CHECK(IsActive());
CHECK_EQ(v8_flags.strict_pkey_sandbox, IsStrict());
return true;
}
#ifdef DEBUG
thread_local unsigned disallow_sandbox_access_activation_counter_ = 0;
thread_local bool has_active_allow_sandbox_access_scope_ = false;
DisallowSandboxAccess::DisallowSandboxAccess() {
pkey_ = SandboxHardwareSupport::sandbox_pkey_;
if (pkey_ == base::MemoryProtectionKey::kNoMemoryProtectionKey) {
return;
}
DCHECK_WITH_MSG(!has_active_allow_sandbox_access_scope_,
"DisallowSandboxAccess cannot currently be nested inside an "
"AllowSandboxAccess");
if (disallow_sandbox_access_activation_counter_ == 0) {
DCHECK_EQ(base::MemoryProtectionKey::GetKeyPermission(pkey_),
base::MemoryProtectionKey::Permission::kNoRestrictions);
base::MemoryProtectionKey::SetPermissionsForKey(
pkey_, base::MemoryProtectionKey::Permission::kDisableAccess);
}
disallow_sandbox_access_activation_counter_ += 1;
}
DisallowSandboxAccess::~DisallowSandboxAccess() {
if (pkey_ == base::MemoryProtectionKey::kNoMemoryProtectionKey) {
return;
}
DCHECK_NE(disallow_sandbox_access_activation_counter_, 0);
disallow_sandbox_access_activation_counter_ -= 1;
if (disallow_sandbox_access_activation_counter_ == 0) {
DCHECK_EQ(base::MemoryProtectionKey::GetKeyPermission(pkey_),
base::MemoryProtectionKey::Permission::kDisableAccess);
base::MemoryProtectionKey::SetPermissionsForKey(
pkey_, base::MemoryProtectionKey::Permission::kNoRestrictions);
}
}
AllowSandboxAccess::AllowSandboxAccess() {
if (disallow_sandbox_access_activation_counter_ == 0) {
pkey_ = base::MemoryProtectionKey::kNoMemoryProtectionKey;
return;
}
DCHECK_WITH_MSG(!has_active_allow_sandbox_access_scope_,
"AllowSandboxAccess scopes cannot be nested");
has_active_allow_sandbox_access_scope_ = true;
pkey_ = SandboxHardwareSupport::sandbox_pkey_;
DCHECK_NE(pkey_, base::MemoryProtectionKey::kNoMemoryProtectionKey);
DCHECK_EQ(base::MemoryProtectionKey::GetKeyPermission(pkey_),
base::MemoryProtectionKey::Permission::kDisableAccess);
base::MemoryProtectionKey::SetPermissionsForKey(
pkey_, base::MemoryProtectionKey::Permission::kNoRestrictions);
}
AllowSandboxAccess::~AllowSandboxAccess() {
if (pkey_ == base::MemoryProtectionKey::kNoMemoryProtectionKey) {
DCHECK_EQ(disallow_sandbox_access_activation_counter_, 0);
return;
}
DCHECK_GT(disallow_sandbox_access_activation_counter_, 0);
DCHECK(has_active_allow_sandbox_access_scope_);
has_active_allow_sandbox_access_scope_ = false;
DCHECK_EQ(base::MemoryProtectionKey::GetKeyPermission(pkey_),
base::MemoryProtectionKey::Permission::kNoRestrictions);
base::MemoryProtectionKey::SetPermissionsForKey(
pkey_, base::MemoryProtectionKey::Permission::kDisableAccess);
}
#endif
#endif
}
}