Commit 2be21090 authored by ishell@chromium.org's avatar ishell@chromium.org Committed by V8 LUCI CQ

[pku] Move PKU support from Wasm to base/platform

The header is only slightly refactored:
* function names are slightly shortened,
* global functions and enums are converted to static methods and enums
  of a MemoryProtectionKey class.

This is a first step towards adding PKU support for V8 code space.

Bug: v8:13023
Change-Id: Iebcb075b07286d18d6834fbcf6697327f08c9f50
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3762584Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81732}
parent c360a250
......@@ -646,6 +646,8 @@ filegroup(
"src/base/platform/condition-variable.cc",
"src/base/platform/condition-variable.h",
"src/base/platform/elapsed-timer.h",
"src/base/platform/memory-protection-key.cc",
"src/base/platform/memory-protection-key.h",
"src/base/platform/mutex.cc",
"src/base/platform/mutex.h",
"src/base/platform/platform.h",
......@@ -2506,8 +2508,6 @@ filegroup(
"src/wasm/leb-helper.h",
"src/wasm/local-decl-encoder.cc",
"src/wasm/local-decl-encoder.h",
"src/wasm/memory-protection-key.cc",
"src/wasm/memory-protection-key.h",
"src/wasm/memory-tracing.cc",
"src/wasm/memory-tracing.h",
"src/wasm/module-compiler.cc",
......
......@@ -3613,7 +3613,6 @@ v8_header_set("v8_internal_headers") {
"src/wasm/jump-table-assembler.h",
"src/wasm/leb-helper.h",
"src/wasm/local-decl-encoder.h",
"src/wasm/memory-protection-key.h",
"src/wasm/memory-tracing.h",
"src/wasm/module-compiler.h",
"src/wasm/module-decoder-impl.h",
......@@ -4705,7 +4704,6 @@ v8_source_set("v8_base_without_compiler") {
"src/wasm/graph-builder-interface.cc",
"src/wasm/jump-table-assembler.cc",
"src/wasm/local-decl-encoder.cc",
"src/wasm/memory-protection-key.cc",
"src/wasm/memory-tracing.cc",
"src/wasm/module-compiler.cc",
"src/wasm/module-decoder.cc",
......@@ -5317,6 +5315,8 @@ v8_component("v8_libbase") {
"src/base/platform/condition-variable.cc",
"src/base/platform/condition-variable.h",
"src/base/platform/elapsed-timer.h",
"src/base/platform/memory-protection-key.cc",
"src/base/platform/memory-protection-key.h",
"src/base/platform/mutex.cc",
"src/base/platform/mutex.h",
"src/base/platform/platform.h",
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/wasm/memory-protection-key.h"
#include "src/base/platform/memory-protection-key.h"
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
#include <sys/mman.h> // For {mprotect()} protection macros.
......@@ -39,8 +39,7 @@
#endif
namespace v8 {
namespace internal {
namespace wasm {
namespace base {
namespace {
using pkey_alloc_t = int (*)(unsigned, unsigned);
......@@ -58,9 +57,29 @@ pkey_set_t pkey_set = nullptr;
#ifdef DEBUG
bool pkey_initialized = false;
#endif
int GetProtectionFromMemoryPermission(PageAllocator::Permission permission) {
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
// Mappings for PKU are either RWX (for code), no access (for uncommitted
// memory), or RW (for assembler buffers).
switch (permission) {
case PageAllocator::kNoAccess:
return PROT_NONE;
case PageAllocator::kReadWrite:
return PROT_READ | PROT_WRITE;
case PageAllocator::kReadWriteExecute:
return PROT_READ | PROT_WRITE | PROT_EXEC;
default:
UNREACHABLE();
}
#endif
// Other platforms do not use PKU.
UNREACHABLE();
}
} // namespace
void InitializeMemoryProtectionKeySupport() {
void MemoryProtectionKey::InitializeMemoryProtectionKeySupport() {
// Flip {pkey_initialized} (in debug mode) and check the new value.
DCHECK_EQ(true, pkey_initialized = !pkey_initialized);
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
......@@ -114,8 +133,9 @@ void InitializeMemoryProtectionKeySupport() {
// An alternative would be to not rely on glibc and instead implement PKEY
// directly on top of Linux syscalls + inline asm, but that is quite some low-
// level code (probably in the order of 100 lines).
// static
DISABLE_CFI_ICALL
int AllocateMemoryProtectionKey() {
int MemoryProtectionKey::AllocateKey() {
DCHECK(pkey_initialized);
if (!pkey_alloc) return kNoMemoryProtectionKey;
......@@ -130,8 +150,9 @@ int AllocateMemoryProtectionKey() {
return pkey_alloc(/* flags, unused */ 0, kDisableAccess);
}
// static
DISABLE_CFI_ICALL
void FreeMemoryProtectionKey(int key) {
void MemoryProtectionKey::FreeKey(int key) {
DCHECK(pkey_initialized);
// Only free the key if one was allocated.
if (key == kNoMemoryProtectionKey) return;
......@@ -142,29 +163,11 @@ void FreeMemoryProtectionKey(int key) {
CHECK_EQ(/* success */ 0, pkey_free(key));
}
int GetProtectionFromMemoryPermission(PageAllocator::Permission permission) {
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
// Mappings for PKU are either RWX (for code), no access (for uncommitted
// memory), or RW (for assembler buffers).
switch (permission) {
case PageAllocator::kNoAccess:
return PROT_NONE;
case PageAllocator::kReadWrite:
return PROT_READ | PROT_WRITE;
case PageAllocator::kReadWriteExecute:
return PROT_READ | PROT_WRITE | PROT_EXEC;
default:
UNREACHABLE();
}
#endif
// Other platforms do not use PKU.
UNREACHABLE();
}
// static
DISABLE_CFI_ICALL
bool SetPermissionsAndMemoryProtectionKey(
PageAllocator* page_allocator, base::AddressRegion region,
PageAllocator::Permission page_permissions, int key) {
bool MemoryProtectionKey::SetPermissionsAndKey(
v8::PageAllocator* page_allocator, base::AddressRegion region,
v8::PageAllocator::Permission page_permissions, int key) {
DCHECK(pkey_initialized);
void* address = reinterpret_cast<void*>(region.begin());
......@@ -200,9 +203,10 @@ bool SetPermissionsAndMemoryProtectionKey(
return page_allocator->SetPermissions(address, size, page_permissions);
}
// static
DISABLE_CFI_ICALL
void SetPermissionsForMemoryProtectionKey(
int key, MemoryProtectionKeyPermission permissions) {
void MemoryProtectionKey::SetPermissionsForKey(int key,
Permission permissions) {
DCHECK(pkey_initialized);
DCHECK_NE(kNoMemoryProtectionKey, key);
......@@ -212,8 +216,9 @@ void SetPermissionsForMemoryProtectionKey(
CHECK_EQ(0 /* success */, pkey_set(key, permissions));
}
// static
DISABLE_CFI_ICALL
MemoryProtectionKeyPermission GetMemoryProtectionKeyPermission(int key) {
MemoryProtectionKey::Permission MemoryProtectionKey::GetKeyPermission(int key) {
DCHECK(pkey_initialized);
DCHECK_NE(kNoMemoryProtectionKey, key);
......@@ -223,9 +228,8 @@ MemoryProtectionKeyPermission GetMemoryProtectionKeyPermission(int key) {
int permission = pkey_get(key);
CHECK(permission == kNoRestrictions || permission == kDisableAccess ||
permission == kDisableWrite);
return static_cast<MemoryProtectionKeyPermission>(permission);
return static_cast<Permission>(permission);
}
} // namespace wasm
} // namespace internal
} // namespace base
} // namespace v8
// 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_BASE_PLATFORM_MEMORY_PROTECTION_KEY_H_
#define V8_BASE_PLATFORM_MEMORY_PROTECTION_KEY_H_
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
#include <sys/mman.h> // For static_assert of permission values.
#undef MAP_TYPE // Conflicts with MAP_TYPE in Torque-generated instance-types.h
#endif
#include "include/v8-platform.h"
#include "src/base/address-region.h"
namespace v8 {
namespace base {
// ----------------------------------------------------------------------------
// MemoryProtectionKey
//
// This class has static methods for the different platform specific
// functions related to memory protection key support.
// TODO(dlehmann): Consider adding this to {base::PageAllocator} (higher-level,
// exported API) once the API is more stable and we have converged on a better
// design (e.g., typed class wrapper around int memory protection key).
class V8_BASE_EXPORT MemoryProtectionKey {
public:
// Sentinel value if there is no PKU support or allocation of a key failed.
// This is also the return value on an error of pkey_alloc() and has the
// benefit that calling pkey_mprotect() with -1 behaves the same as regular
// mprotect().
static constexpr int kNoMemoryProtectionKey = -1;
// Permissions for memory protection keys on top of the page's permissions.
// NOTE: Since there is no executable bit, the executable permission cannot be
// withdrawn by memory protection keys.
enum Permission {
kNoRestrictions = 0,
kDisableAccess = 1,
kDisableWrite = 2,
};
// If sys/mman.h has PKEY support (on newer Linux distributions), ensure that
// our definitions of the permissions is consistent with the ones in glibc.
#if defined(PKEY_DISABLE_ACCESS)
static_assert(kDisableAccess == PKEY_DISABLE_ACCESS);
static_assert(kDisableWrite == PKEY_DISABLE_WRITE);
#endif
// Call exactly once per process to determine if PKU is supported on this
// platform and initialize global data structures.
static void InitializeMemoryProtectionKeySupport();
// Allocates a memory protection key on platforms with PKU support, returns
// {kNoMemoryProtectionKey} on platforms without support or when allocation
// failed at runtime.
static int AllocateKey();
// Frees the given memory protection key, to make it available again for the
// next call to {AllocateKey()}. Note that this does NOT
// invalidate access rights to pages that are still tied to that key. That is,
// if the key is reused and pages with that key are still accessable, this
// might be a security issue. See
// https://www.gnu.org/software/libc/manual/html_mono/libc.html#Memory-Protection-Keys
static void FreeKey(int key);
// Associates a memory protection {key} with the given {region}.
// If {key} is {kNoMemoryProtectionKey} this behaves like "plain"
// {SetPermissions()} and associates the default key to the region. That is,
// explicitly calling with {kNoMemoryProtectionKey} can be used to
// disassociate any protection key from a region. This also means "plain"
// {SetPermissions()} disassociates the key from a region, making the key's
// access restrictions irrelevant/inactive for that region. Returns true if
// changing permissions and key was successful. (Returns a bool to be
// consistent with {SetPermissions()}). The {page_permissions} are the
// permissions of the page, not the key. For changing the permissions of the
// key, use {SetPermissionsForKey()} instead.
static bool SetPermissionsAndKey(
v8::PageAllocator* page_allocator, base::AddressRegion region,
v8::PageAllocator::Permission page_permissions, int key);
// Set the key's permissions. {key} must be valid, i.e. not
// {kNoMemoryProtectionKey}.
static void SetPermissionsForKey(int key, Permission permissions);
// Get the permissions of the protection key {key} for the current thread.
static Permission GetKeyPermission(int key);
};
} // namespace base
} // namespace v8
#endif // V8_BASE_PLATFORM_MEMORY_PROTECTION_KEY_H_
// 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.
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
#ifndef V8_WASM_MEMORY_PROTECTION_KEY_H_
#define V8_WASM_MEMORY_PROTECTION_KEY_H_
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
#include <sys/mman.h> // For static_assert of permission values.
#undef MAP_TYPE // Conflicts with MAP_TYPE in Torque-generated instance-types.h
#endif
#include "include/v8-platform.h"
#include "src/base/address-region.h"
namespace v8 {
namespace internal {
namespace wasm {
// TODO(dlehmann): Move this to base/platform/platform.h {OS} (lower-level API)
// and {base::PageAllocator} (higher-level, exported API) once the API is more
// stable and we have converged on a better design (e.g., typed class wrapper
// around int memory protection key).
// Sentinel value if there is no PKU support or allocation of a key failed.
// This is also the return value on an error of pkey_alloc() and has the
// benefit that calling pkey_mprotect() with -1 behaves the same as regular
// mprotect().
constexpr int kNoMemoryProtectionKey = -1;
// Permissions for memory protection keys on top of the page's permissions.
// NOTE: Since there is no executable bit, the executable permission cannot be
// withdrawn by memory protection keys.
enum MemoryProtectionKeyPermission {
kNoRestrictions = 0,
kDisableAccess = 1,
kDisableWrite = 2,
};
// If sys/mman.h has PKEY support (on newer Linux distributions), ensure that
// our definitions of the permissions is consistent with the ones in glibc.
#if defined(PKEY_DISABLE_ACCESS)
static_assert(kDisableAccess == PKEY_DISABLE_ACCESS);
static_assert(kDisableWrite == PKEY_DISABLE_WRITE);
#endif
// Call exactly once per process to determine if PKU is supported on this
// platform and initialize global data structures.
void InitializeMemoryProtectionKeySupport();
// Allocates a memory protection key on platforms with PKU support, returns
// {kNoMemoryProtectionKey} on platforms without support or when allocation
// failed at runtime.
int AllocateMemoryProtectionKey();
// Frees the given memory protection key, to make it available again for the
// next call to {AllocateMemoryProtectionKey()}. Note that this does NOT
// invalidate access rights to pages that are still tied to that key. That is,
// if the key is reused and pages with that key are still accessable, this might
// be a security issue. See
// https://www.gnu.org/software/libc/manual/html_mono/libc.html#Memory-Protection-Keys
void FreeMemoryProtectionKey(int key);
// Associates a memory protection {key} with the given {region}.
// If {key} is {kNoMemoryProtectionKey} this behaves like "plain"
// {SetPermissions()} and associates the default key to the region. That is,
// explicitly calling with {kNoMemoryProtectionKey} can be used to disassociate
// any protection key from a region. This also means "plain" {SetPermissions()}
// disassociates the key from a region, making the key's access restrictions
// irrelevant/inactive for that region.
// Returns true if changing permissions and key was successful. (Returns a bool
// to be consistent with {SetPermissions()}).
// The {page_permissions} are the permissions of the page, not the key. For
// changing the permissions of the key, use
// {SetPermissionsForMemoryProtectionKey()} instead.
bool SetPermissionsAndMemoryProtectionKey(
PageAllocator* page_allocator, base::AddressRegion region,
PageAllocator::Permission page_permissions, int key);
// Set the key's permissions. {key} must be valid, i.e. not
// {kNoMemoryProtectionKey}.
void SetPermissionsForMemoryProtectionKey(
int key, MemoryProtectionKeyPermission permissions);
// Get the permissions of the protection key {key} for the current thread.
MemoryProtectionKeyPermission GetMemoryProtectionKeyPermission(int key);
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_MEMORY_PROTECTION_KEY_H_
......@@ -12,6 +12,7 @@
#include "src/base/build_config.h"
#include "src/base/iterator.h"
#include "src/base/macros.h"
#include "src/base/platform/memory-protection-key.h"
#include "src/base/platform/platform.h"
#include "src/base/small-vector.h"
#include "src/base/string-format.h"
......@@ -30,7 +31,6 @@
#include "src/wasm/compilation-environment.h"
#include "src/wasm/function-compiler.h"
#include "src/wasm/jump-table-assembler.h"
#include "src/wasm/memory-protection-key.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/names-provider.h"
#include "src/wasm/wasm-debug.h"
......@@ -1884,7 +1884,7 @@ NativeModule::~NativeModule() {
WasmCodeManager::WasmCodeManager()
: max_committed_code_space_(FLAG_wasm_max_code_space * MB),
critical_committed_code_space_(max_committed_code_space_ / 2),
memory_protection_key_(AllocateMemoryProtectionKey()) {
memory_protection_key_(base::MemoryProtectionKey::AllocateKey()) {
// Ensure that RwxMemoryWriteScope and other dependent scopes (in particular,
// wasm::CodeSpaceWriteScope) are allowed to be used.
CHECK(RwxMemoryWriteScope::IsAllowed());
......@@ -1894,7 +1894,7 @@ WasmCodeManager::~WasmCodeManager() {
// No more committed code space.
DCHECK_EQ(0, total_committed_code_space_.load());
FreeMemoryProtectionKey(memory_protection_key_);
base::MemoryProtectionKey::FreeKey(memory_protection_key_);
}
#if defined(V8_OS_WIN64)
......@@ -1950,7 +1950,7 @@ void WasmCodeManager::Commit(base::AddressRegion region) {
"Setting rwx permissions and memory protection key %d for 0x%" PRIxPTR
":0x%" PRIxPTR "\n",
memory_protection_key_, region.begin(), region.end());
success = SetPermissionsAndMemoryProtectionKey(
success = base::MemoryProtectionKey::SetPermissionsAndKey(
GetPlatformPageAllocator(), region, permission, memory_protection_key_);
} else {
TRACE_HEAP("Setting rwx permissions for 0x%" PRIxPTR ":0x%" PRIxPTR "\n",
......@@ -2151,8 +2151,8 @@ size_t WasmCodeManager::EstimateNativeModuleMetaDataSize(
void WasmCodeManager::SetThreadWritable(bool writable) {
DCHECK(MemoryProtectionKeysEnabled());
MemoryProtectionKeyPermission permissions =
writable ? kNoRestrictions : kDisableWrite;
auto permissions = writable ? base::MemoryProtectionKey::kNoRestrictions
: base::MemoryProtectionKey::kDisableWrite;
// When switching to writable we should not already be writable. Otherwise
// this points at a problem with counting writers, or with wrong
......@@ -2161,11 +2161,13 @@ void WasmCodeManager::SetThreadWritable(bool writable) {
TRACE_HEAP("Setting memory protection key %d to writable: %d.\n",
memory_protection_key_, writable);
SetPermissionsForMemoryProtectionKey(memory_protection_key_, permissions);
base::MemoryProtectionKey::SetPermissionsForKey(memory_protection_key_,
permissions);
}
bool WasmCodeManager::HasMemoryProtectionKeySupport() const {
return memory_protection_key_ != kNoMemoryProtectionKey;
return memory_protection_key_ !=
base::MemoryProtectionKey::kNoMemoryProtectionKey;
}
bool WasmCodeManager::MemoryProtectionKeysEnabled() const {
......@@ -2173,8 +2175,8 @@ bool WasmCodeManager::MemoryProtectionKeysEnabled() const {
}
bool WasmCodeManager::MemoryProtectionKeyWritable() const {
return GetMemoryProtectionKeyPermission(memory_protection_key_) ==
MemoryProtectionKeyPermission::kNoRestrictions;
return base::MemoryProtectionKey::GetKeyPermission(memory_protection_key_) ==
base::MemoryProtectionKey::kNoRestrictions;
}
void WasmCodeManager::InitializeMemoryProtectionKeyPermissionsIfSupported()
......@@ -2183,9 +2185,10 @@ void WasmCodeManager::InitializeMemoryProtectionKeyPermissionsIfSupported()
// The default permission is {kDisableAccess}. Switch from that to
// {kDisableWrite}. Leave other permissions untouched, as the thread did
// already use the memory protection key in that case.
if (GetMemoryProtectionKeyPermission(memory_protection_key_) ==
kDisableAccess) {
SetPermissionsForMemoryProtectionKey(memory_protection_key_, kDisableWrite);
if (base::MemoryProtectionKey::GetKeyPermission(memory_protection_key_) ==
base::MemoryProtectionKey::kDisableAccess) {
base::MemoryProtectionKey::SetPermissionsForKey(
memory_protection_key_, base::MemoryProtectionKey::kDisableWrite);
}
}
......@@ -2208,9 +2211,9 @@ base::AddressRegion WasmCodeManager::AllocateAssemblerBufferSpace(int size) {
}
auto region =
base::AddressRegionOf(reinterpret_cast<uint8_t*>(mapped), size);
CHECK(SetPermissionsAndMemoryProtectionKey(page_allocator, region,
PageAllocator::kReadWrite,
memory_protection_key_));
CHECK(base::MemoryProtectionKey::SetPermissionsAndKey(
page_allocator, region, PageAllocator::kReadWrite,
memory_protection_key_));
return region;
}
#endif // defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
......
......@@ -19,13 +19,13 @@
#include "src/base/address-region.h"
#include "src/base/bit-field.h"
#include "src/base/macros.h"
#include "src/base/platform/memory-protection-key.h"
#include "src/base/vector.h"
#include "src/builtins/builtins.h"
#include "src/handles/handles.h"
#include "src/tasks/operations-barrier.h"
#include "src/trap-handler/trap-handler.h"
#include "src/wasm/compilation-environment.h"
#include "src/wasm/memory-protection-key.h"
#include "src/wasm/wasm-features.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-module-sourcemap.h"
......
......@@ -5,6 +5,7 @@
#include "src/wasm/wasm-engine.h"
#include "src/base/functional.h"
#include "src/base/platform/memory-protection-key.h"
#include "src/base/platform/time.h"
#include "src/common/globals.h"
#include "src/debug/debug.h"
......@@ -21,7 +22,6 @@
#include "src/strings/string-hasher-inl.h"
#include "src/utils/ostreams.h"
#include "src/wasm/function-compiler.h"
#include "src/wasm/memory-protection-key.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/module-instantiate.h"
......@@ -1620,7 +1620,7 @@ GlobalWasmState* global_wasm_state = nullptr;
// static
void WasmEngine::InitializeOncePerProcess() {
InitializeMemoryProtectionKeySupport();
base::MemoryProtectionKey::InitializeMemoryProtectionKeySupport();
DCHECK_NULL(global_wasm_state);
global_wasm_state = new GlobalWasmState();
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment