Commit f9b6964a authored by Clemens Backes's avatar Clemens Backes Committed by V8 LUCI CQ

[wasm] Consolidate checks for PKU support

This extracts all {dlsym} calls to a single function which is called
once during initialization.

R=jkummerow@chromium.org

Bug: v8:11974
Change-Id: I068f180e26b92d72e3e1e0ba7c6232c760f202e8
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3417439Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78831}
parent d836b693
...@@ -43,6 +43,47 @@ namespace v8 { ...@@ -43,6 +43,47 @@ namespace v8 {
namespace internal { namespace internal {
namespace wasm { namespace wasm {
namespace {
using pkey_alloc_t = int (*)(unsigned, unsigned);
using pkey_free_t = int (*)(int);
using pkey_mprotect_t = int (*)(void*, size_t, int, int);
using pkey_get_t = int (*)(int);
using pkey_set_t = int (*)(int, unsigned);
pkey_alloc_t pkey_alloc = nullptr;
pkey_free_t pkey_free = nullptr;
pkey_mprotect_t pkey_mprotect = nullptr;
pkey_get_t pkey_get = nullptr;
pkey_set_t pkey_set = nullptr;
#ifdef DEBUG
bool pkey_initialized = false;
#endif
} // namespace
void 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)
// Try to to find the pkey functions in glibc.
void* pkey_alloc_ptr = dlsym(RTLD_DEFAULT, "pkey_alloc");
if (!pkey_alloc_ptr) return;
// If {pkey_alloc} is available, the others must also be available.
void* pkey_free_ptr = dlsym(RTLD_DEFAULT, "pkey_free");
void* pkey_mprotect_ptr = dlsym(RTLD_DEFAULT, "pkey_mprotect");
void* pkey_get_ptr = dlsym(RTLD_DEFAULT, "pkey_get");
void* pkey_set_ptr = dlsym(RTLD_DEFAULT, "pkey_set");
CHECK(pkey_free_ptr && pkey_mprotect_ptr && pkey_get_ptr && pkey_set_ptr);
pkey_alloc = reinterpret_cast<pkey_alloc_t>(pkey_alloc_ptr);
pkey_free = reinterpret_cast<pkey_free_t>(pkey_free_ptr);
pkey_mprotect = reinterpret_cast<pkey_mprotect_t>(pkey_mprotect_ptr);
pkey_get = reinterpret_cast<pkey_get_t>(pkey_get_ptr);
pkey_set = reinterpret_cast<pkey_set_t>(pkey_set_ptr);
#endif
}
// TODO(dlehmann) Security: Are there alternatives to disabling CFI altogether // TODO(dlehmann) Security: Are there alternatives to disabling CFI altogether
// for the functions below? Since they are essentially an arbitrary indirect // for the functions below? Since they are essentially an arbitrary indirect
// call gadget, disabling CFI should be only a last resort. In Chromium, there // call gadget, disabling CFI should be only a last resort. In Chromium, there
...@@ -57,14 +98,9 @@ namespace wasm { ...@@ -57,14 +98,9 @@ namespace wasm {
// level code (probably in the order of 100 lines). // level code (probably in the order of 100 lines).
DISABLE_CFI_ICALL DISABLE_CFI_ICALL
int AllocateMemoryProtectionKey() { int AllocateMemoryProtectionKey() {
// See comment on the import on feature testing for PKEY support. DCHECK(pkey_initialized);
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64) if (!pkey_alloc) return kNoMemoryProtectionKey;
// Try to to find {pkey_alloc()} support in glibc.
typedef int (*pkey_alloc_t)(unsigned int, unsigned int);
// Cache the {dlsym()} lookup in a {static} variable.
static 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. // 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 // This might still return -1, e.g., because the kernel does not support
// PKU or because there is no more key available. // PKU or because there is no more key available.
...@@ -72,77 +108,48 @@ int AllocateMemoryProtectionKey() { ...@@ -72,77 +108,48 @@ int AllocateMemoryProtectionKey() {
// errno, e.g., EINVAL vs ENOSPC vs ENOSYS. See manpages and glibc manual // errno, e.g., EINVAL vs ENOSPC vs ENOSYS. See manpages and glibc manual
// (the latter is the authorative source): // (the latter is the authorative source):
// https://www.gnu.org/software/libc/manual/html_mono/libc.html#Memory-Protection-Keys // https://www.gnu.org/software/libc/manual/html_mono/libc.html#Memory-Protection-Keys
STATIC_ASSERT(kNoMemoryProtectionKey == -1);
return pkey_alloc(/* flags, unused */ 0, kDisableAccess); return pkey_alloc(/* flags, unused */ 0, kDisableAccess);
}
#endif
return kNoMemoryProtectionKey;
} }
DISABLE_CFI_ICALL DISABLE_CFI_ICALL
void FreeMemoryProtectionKey(int key) { void FreeMemoryProtectionKey(int key) {
DCHECK(pkey_initialized);
// Only free the key if one was allocated. // Only free the key if one was allocated.
if (key == kNoMemoryProtectionKey) return; if (key == kNoMemoryProtectionKey) return;
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
typedef int (*pkey_free_t)(int);
static auto* pkey_free =
bit_cast<pkey_free_t>(dlsym(RTLD_DEFAULT, "pkey_free"));
// If a valid key was allocated, {pkey_free()} must also be available.
DCHECK_NOT_NULL(pkey_free);
int ret = pkey_free(key);
CHECK_EQ(/* success */ 0, ret);
#else
// On platforms without PKU support, we should have already returned because // On platforms without PKU support, we should have already returned because
// the key must be {kNoMemoryProtectionKey}. // the key must be {kNoMemoryProtectionKey}.
UNREACHABLE(); DCHECK_NOT_NULL(pkey_free);
#endif CHECK_EQ(/* success */ 0, pkey_free(key));
} }
int GetProtectionFromMemoryPermission(PageAllocator::Permission permission) {
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64) #if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
// TODO(dlehmann): Copied from base/platform/platform-posix.cc. Should be // Mappings for PKU are either RWX (on this level) or no access.
// removed once this code is integrated in base/platform/platform-linux.cc. switch (permission) {
int GetProtectionFromMemoryPermission(base::OS::MemoryPermission access) { case PageAllocator::kNoAccess:
switch (access) {
case base::OS::MemoryPermission::kNoAccess:
case base::OS::MemoryPermission::kNoAccessWillJitLater:
return PROT_NONE; return PROT_NONE;
case base::OS::MemoryPermission::kRead: case PageAllocator::kReadWriteExecute:
return PROT_READ;
case base::OS::MemoryPermission::kReadWrite:
return PROT_READ | PROT_WRITE;
case base::OS::MemoryPermission::kReadWriteExecute:
return PROT_READ | PROT_WRITE | PROT_EXEC; return PROT_READ | PROT_WRITE | PROT_EXEC;
case base::OS::MemoryPermission::kReadExecute: default:
return PROT_READ | PROT_EXEC; UNREACHABLE();
} }
#endif
// Other platforms do not use PKU.
UNREACHABLE(); UNREACHABLE();
} }
#endif
DISABLE_CFI_ICALL DISABLE_CFI_ICALL
bool SetPermissionsAndMemoryProtectionKey( bool SetPermissionsAndMemoryProtectionKey(
PageAllocator* page_allocator, base::AddressRegion region, PageAllocator* page_allocator, base::AddressRegion region,
PageAllocator::Permission page_permissions, int key) { PageAllocator::Permission page_permissions, int key) {
DCHECK_NOT_NULL(page_allocator); DCHECK(pkey_initialized);
void* address = reinterpret_cast<void*>(region.begin()); void* address = reinterpret_cast<void*>(region.begin());
size_t size = region.size(); size_t size = region.size();
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64) if (pkey_mprotect) {
typedef int (*pkey_mprotect_t)(void*, size_t, int, int);
static auto* pkey_mprotect =
bit_cast<pkey_mprotect_t>(dlsym(RTLD_DEFAULT, "pkey_mprotect"));
if (pkey_mprotect == nullptr) {
// If there is no runtime support for {pkey_mprotect()}, no key should have
// been allocated in the first place.
DCHECK_EQ(kNoMemoryProtectionKey, key);
// Without PKU support, fallback to regular {mprotect()}.
return page_allocator->SetPermissions(address, size, page_permissions);
}
// Copied with slight modifications from base/platform/platform-posix.cc // Copied with slight modifications from base/platform/platform-posix.cc
// {OS::SetPermissions()}. // {OS::SetPermissions()}.
// TODO(dlehmann): Move this block into its own function at the right // TODO(dlehmann): Move this block into its own function at the right
...@@ -151,45 +158,38 @@ bool SetPermissionsAndMemoryProtectionKey( ...@@ -151,45 +158,38 @@ bool SetPermissionsAndMemoryProtectionKey(
DCHECK_EQ(0, region.begin() % page_allocator->CommitPageSize()); DCHECK_EQ(0, region.begin() % page_allocator->CommitPageSize());
DCHECK_EQ(0, size % page_allocator->CommitPageSize()); DCHECK_EQ(0, size % page_allocator->CommitPageSize());
int protection = GetProtectionFromMemoryPermission( int protection = GetProtectionFromMemoryPermission(page_permissions);
static_cast<base::OS::MemoryPermission>(page_permissions));
int ret = pkey_mprotect(address, size, protection, key); int ret = pkey_mprotect(address, size, protection, key);
return ret == /* success */ 0; return ret == /* success */ 0;
#else }
// If there is no runtime support for {pkey_mprotect()}, no key should have
// been allocated in the first place.
DCHECK_EQ(kNoMemoryProtectionKey, key);
// Without PKU support, fallback to regular {mprotect()}. // Without PKU support, fallback to regular {mprotect()}.
return page_allocator->SetPermissions(address, size, page_permissions); return page_allocator->SetPermissions(address, size, page_permissions);
#endif
} }
DISABLE_CFI_ICALL DISABLE_CFI_ICALL
void SetPermissionsForMemoryProtectionKey( void SetPermissionsForMemoryProtectionKey(
int key, MemoryProtectionKeyPermission permissions) { int key, MemoryProtectionKeyPermission permissions) {
DCHECK(pkey_initialized);
DCHECK_NE(kNoMemoryProtectionKey, key); DCHECK_NE(kNoMemoryProtectionKey, key);
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
typedef int (*pkey_set_t)(int, unsigned int);
static auto* pkey_set = bit_cast<pkey_set_t>(dlsym(RTLD_DEFAULT, "pkey_set"));
// If a valid key was allocated, {pkey_set()} must also be available. // If a valid key was allocated, {pkey_set()} must also be available.
DCHECK_NOT_NULL(pkey_set); DCHECK_NOT_NULL(pkey_set);
int ret = pkey_set(key, permissions); CHECK_EQ(0 /* success */, pkey_set(key, permissions));
CHECK_EQ(0 /* success */, ret);
#else
// On platforms without PKU support, this method cannot be called because
// no protection key can have been allocated.
UNREACHABLE();
#endif
} }
DISABLE_CFI_ICALL DISABLE_CFI_ICALL
MemoryProtectionKeyPermission GetMemoryProtectionKeyPermission(int key) { MemoryProtectionKeyPermission GetMemoryProtectionKeyPermission(int key) {
DCHECK(pkey_initialized);
DCHECK_NE(kNoMemoryProtectionKey, key); DCHECK_NE(kNoMemoryProtectionKey, key);
#if defined(V8_OS_LINUX) && defined(V8_HOST_ARCH_X64)
typedef int (*pkey_get_t)(int);
static auto* pkey_get = bit_cast<pkey_get_t>(dlsym(RTLD_DEFAULT, "pkey_get"));
// If a valid key was allocated, {pkey_get()} must also be available. // If a valid key was allocated, {pkey_get()} must also be available.
DCHECK_NOT_NULL(pkey_get); DCHECK_NOT_NULL(pkey_get);
...@@ -197,11 +197,6 @@ MemoryProtectionKeyPermission GetMemoryProtectionKeyPermission(int key) { ...@@ -197,11 +197,6 @@ MemoryProtectionKeyPermission GetMemoryProtectionKeyPermission(int key) {
CHECK(permission == kNoRestrictions || permission == kDisableAccess || CHECK(permission == kNoRestrictions || permission == kDisableAccess ||
permission == kDisableWrite); permission == kDisableWrite);
return static_cast<MemoryProtectionKeyPermission>(permission); return static_cast<MemoryProtectionKeyPermission>(permission);
#else
// On platforms without PKU support, this method cannot be called because
// no protection key can have been allocated.
UNREACHABLE();
#endif
} }
} // namespace wasm } // namespace wasm
......
...@@ -48,6 +48,10 @@ STATIC_ASSERT(kDisableAccess == PKEY_DISABLE_ACCESS); ...@@ -48,6 +48,10 @@ STATIC_ASSERT(kDisableAccess == PKEY_DISABLE_ACCESS);
STATIC_ASSERT(kDisableWrite == PKEY_DISABLE_WRITE); STATIC_ASSERT(kDisableWrite == PKEY_DISABLE_WRITE);
#endif #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 // Allocates a memory protection key on platforms with PKU support, returns
// {kNoMemoryProtectionKey} on platforms without support or when allocation // {kNoMemoryProtectionKey} on platforms without support or when allocation
// failed at runtime. // failed at runtime.
......
...@@ -1654,6 +1654,7 @@ GlobalWasmState* global_wasm_state = nullptr; ...@@ -1654,6 +1654,7 @@ GlobalWasmState* global_wasm_state = nullptr;
// static // static
void WasmEngine::InitializeOncePerProcess() { void WasmEngine::InitializeOncePerProcess() {
InitializeMemoryProtectionKeySupport();
DCHECK_NULL(global_wasm_state); DCHECK_NULL(global_wasm_state);
global_wasm_state = new GlobalWasmState(); 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