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

[wasm] Add PKU flag and alloc/free functions

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), with PKU we associate a key with each page once and then
change 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) adding a flag to turn on prototype PKU support; and if set to true
(2) allocates a protection key once per {WasmCodeManager} in x64 Linux
systems.

This is a partial reland of https://crrev.com/c/2850932, which was
reverted due to an added histogram failing Chromium integration.
Since the histogram (to record PKU support) is independent of the
functionality in this CL, we split it out into its own CL (to come).

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

Bug: v8:11714
Change-Id: I67c8679495c55fa51da8243582963649abde660b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2878738
Commit-Queue: Daniel Lehmann <dlehmann@google.com>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74435}
parent 8f65f820
......@@ -3092,6 +3092,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",
......@@ -4031,6 +4032,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",
......
......@@ -858,7 +858,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 PKU if available, no protection "
"without support; fallback to mprotect by adding "
"--wasm-write-protect-code-memory")
DEFINE_DEBUG_BOOL(trace_wasm_serialization, false,
"trace serialization/deserialization")
DEFINE_BOOL(wasm_async_compilation, true,
......
// 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);
}
#endif
return kNoMemoryProtectionKey;
}
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"
......@@ -844,12 +845,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;
......@@ -905,6 +901,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_;
//////////////////////////////////////////////////////////////////////////////
......
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