Commit a4c37558 authored by Daniel Lehmann's avatar Daniel Lehmann Committed by V8 LUCI CQ

[wasm] Add PKU alloc/free and support counter

To enforce W^X for the WebAssembly code space, we want to explore using
Intel memory protection keys for userspace, also known as MPK, PKEYs, or
PKU. Instead of flipping page protection flags with mprotect (which
incurs a high syscall overhead; and which switches flags for the whole
process), this associates a key with each page once, and then changes
the permissions of that key with a fast thread-local register write.
That is, this gives both finger-grained permissions (per-thread) and
more performance.

This CL is starts experimenting with PKUs by
(1) trying to allocate a protection key once per {WasmEngine} in x64
Linux systems, and
(2) adding a counter for recording the sucess/failure of that, to assess
the support for PKUs on the target machine.

The low-level PKU allocating functions should be moved into base/platform
long-term, but are inside wasm/ for this CL.

R=clemensb@chromium.org
CC=​jkummerow@chromium.org

Bug: v8:11714
Change-Id: Ia4858970ced4d0b84cc8c2651e86dceb532c88a7
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2850932
Commit-Queue: Daniel Lehmann <dlehmann@google.com>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74319}
parent 09479110
......@@ -3107,6 +3107,7 @@ 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.h",
......@@ -4045,6 +4046,7 @@ 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",
......
......@@ -854,7 +854,11 @@ DEFINE_VALUE_IMPLICATION(single_threaded, wasm_num_compilation_tasks, 0)
DEFINE_DEBUG_BOOL(trace_wasm_native_heap, false,
"trace wasm native heap events")
DEFINE_BOOL(wasm_write_protect_code_memory, false,
"write protect code memory on the wasm native heap")
"write protect code memory on the wasm native heap with mprotect")
DEFINE_BOOL(wasm_memory_protection_keys, false,
"protect wasm code memory with Intel PKU if available, no "
"protection without support; fallback to mprotect by adding "
"--wasm-write-protect-code-memory (under development)")
DEFINE_DEBUG_BOOL(trace_wasm_serialization, false,
"trace serialization/deserialization")
DEFINE_BOOL(wasm_async_compilation, true,
......
......@@ -85,6 +85,9 @@ namespace internal {
HR(wasm_modules_per_engine, V8.WasmModulesPerEngine, 1, 1024, 30) \
/* bailout reason if Liftoff failed, or {kSuccess} (per function) */ \
HR(liftoff_bailout_reasons, V8.LiftoffBailoutReasons, 0, 20, 21) \
/* support for PKEYs/PKU by testing result of pkey_alloc() */ \
HR(wasm_memory_protection_keys_support, V8.WasmMemoryProtectionKeysSupport, \
0, 1, 2) \
/* number of thrown exceptions per isolate */ \
HR(wasm_throw_count, V8.WasmThrowCount, 0, 100000, 30) \
/* number of rethrown exceptions per isolate */ \
......
// 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/wasm/memory-protection-key.h"
#include "src/base/build_config.h"
#include "src/base/macros.h"
// Runtime-detection of PKU support with {dlsym()}.
//
// For now, we support memory protection keys/PKEYs/PKU only for Linux on x64
// based on glibc functions {pkey_alloc()}, {pkey_free()}, etc.
// Those functions are only available since glibc version 2.27:
// https://man7.org/linux/man-pages/man2/pkey_alloc.2.html
// However, if we check the glibc verison with V8_GLIBC_PREPREQ here at compile
// time, this causes two problems due to dynamic linking of glibc:
// 1) If the compiling system _has_ a new enough glibc, the binary will include
// calls to {pkey_alloc()} etc., and then the runtime system must supply a
// new enough glibc version as well. That is, this potentially breaks runtime
// compatability on older systems (e.g., Ubuntu 16.04 with glibc 2.23).
// 2) If the compiling system _does not_ have a new enough glibc, PKU support
// will not be compiled in, even though the runtime system potentially _does_
// have support for it due to a new enough Linux kernel and glibc version.
// That is, this results in non-optimal security (PKU available, but not used).
// Hence, we do _not_ check the glibc version during compilation, and instead
// only at runtime try to load {pkey_alloc()} etc. with {dlsym()}.
// TODO(dlehmann): Move this import and freestanding functions below to
// base/platform/platform.h {OS} (lower-level functions) and
// {base::PageAllocator} (exported API).
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
#include <dlfcn.h>
#endif
namespace v8 {
namespace internal {
namespace wasm {
int AllocateMemoryProtectionKey() {
// See comment on the import on feature testing for PKEY support.
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
// Try to to find {pkey_alloc()} support in glibc.
typedef int (*pkey_alloc_t)(unsigned int, unsigned int);
auto pkey_alloc = bit_cast<pkey_alloc_t>(dlsym(RTLD_DEFAULT, "pkey_alloc"));
if (pkey_alloc != nullptr) {
// If there is support in glibc, try to allocate a new key.
// This might still return -1, e.g., because the kernel does not support
// PKU or because there is no more key available.
// Different reasons for why {pkey_alloc()} failed could be checked with
// errno, e.g., EINVAL vs ENOSPC vs ENOSYS. See manpages and glibc manual
// (the latter is the authorative source):
// https://www.gnu.org/software/libc/manual/html_mono/libc.html#Memory-Protection-Keys
return pkey_alloc(/* flags, unused */ 0, kDisableAccess);
} else {
// On Linux x64 without {pkey_alloc()} in glibc, i.e., without runtime
// support.
return kNoMemoryProtectionKey;
}
#else
// On platforms without compile-time PKU support.
return kNoMemoryProtectionKey;
#endif
}
void FreeMemoryProtectionKey(int key) {
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
// Only free the key if one was allocated.
if (key != kNoMemoryProtectionKey) {
typedef int (*pkey_free_t)(int);
auto pkey_free = bit_cast<pkey_free_t>(dlsym(RTLD_DEFAULT, "pkey_free"));
// If a key was allocated with {pkey_alloc()}, {pkey_free()} must also be
// available.
CHECK_NOT_NULL(pkey_free);
CHECK_EQ(/* success */ 0, pkey_free(key));
}
#else
// On platforms without support even compiled in, no key should have been
// allocated.
CHECK_EQ(kNoMemoryProtectionKey, key);
#endif
}
} // namespace wasm
} // namespace internal
} // 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.
#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_
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 permissions by mprotect.
// 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,
};
// 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);
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_MEMORY_PROTECTION_KEY_H_
......@@ -26,6 +26,7 @@
#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/wasm-debug.h"
#include "src/wasm/wasm-engine.h"
......@@ -1685,10 +1686,22 @@ NativeModule::~NativeModule() {
WasmCodeManager::WasmCodeManager(size_t max_committed)
: max_committed_code_space_(max_committed),
critical_committed_code_space_(max_committed / 2) {
critical_committed_code_space_(max_committed / 2),
memory_protection_key_(FLAG_wasm_memory_protection_keys
? AllocateMemoryProtectionKey()
: kNoMemoryProtectionKey) {
DCHECK_LE(max_committed, FLAG_wasm_max_code_space * MB);
}
WasmCodeManager::~WasmCodeManager() {
// No more committed code space.
DCHECK_EQ(0, total_committed_code_space_.load());
if (FLAG_wasm_memory_protection_keys) {
FreeMemoryProtectionKey(memory_protection_key_);
}
}
#if defined(V8_OS_WIN64)
bool WasmCodeManager::CanRegisterUnwindInfoForNonABICompliantCodeRange() const {
return win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange() &&
......
......@@ -26,6 +26,7 @@
#include "src/trap-handler/trap-handler.h"
#include "src/utils/vector.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"
......@@ -848,12 +849,7 @@ class V8_EXPORT_PRIVATE WasmCodeManager final {
WasmCodeManager(const WasmCodeManager&) = delete;
WasmCodeManager& operator=(const WasmCodeManager&) = delete;
#ifdef DEBUG
~WasmCodeManager() {
// No more committed code space.
DCHECK_EQ(0, total_committed_code_space_.load());
}
#endif
~WasmCodeManager();
#if defined(V8_OS_WIN64)
bool CanRegisterUnwindInfoForNonABICompliantCodeRange() const;
......@@ -909,6 +905,8 @@ class V8_EXPORT_PRIVATE WasmCodeManager final {
// and updated after each GC.
std::atomic<size_t> critical_committed_code_space_;
const int memory_protection_key_;
mutable base::Mutex native_modules_mutex_;
//////////////////////////////////////////////////////////////////////////////
......
......@@ -18,6 +18,7 @@
#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"
......@@ -989,6 +990,15 @@ void WasmEngine::AddIsolate(Isolate* isolate) {
DCHECK_EQ(0, isolates_.count(isolate));
isolates_.emplace(isolate, std::make_unique<IsolateInfo>(isolate));
// Record memory protection key support.
if (FLAG_wasm_memory_protection_keys) {
auto* histogram =
isolate->counters()->wasm_memory_protection_keys_support();
bool has_mpk =
code_manager()->memory_protection_key_ != kNoMemoryProtectionKey;
histogram->AddSample(has_mpk ? 1 : 0);
}
// Install sampling GC callback.
// TODO(v8:7424): For now we sample module sizes in a GC callback. This will
// bias samples towards apps with high memory pressure. We should switch to
......
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