From 53e048ffc798f6a531cd264d14922904614891d6 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Tue, 12 Sep 2023 16:26:42 -0700 Subject: [PATCH] fix: crash on x86_64 systems that support memory protection keys (#1318) Co-authored-by: Matt Mastracci --- src/binding.cc | 58 +++++++++++++++++++ src/lib.rs | 1 + src/platform.rs | 54 +++++++++++++++++ tests/slots.rs | 2 +- tests/test_api.rs | 2 +- tests/test_api_entropy_source.rs | 4 +- tests/test_api_flags.rs | 4 +- ...test_platform_atomics_pump_message_loop.rs | 4 +- 8 files changed, 124 insertions(+), 5 deletions(-) diff --git a/src/binding.cc b/src/binding.cc index 12cb83f7..6dab9846 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1,9 +1,11 @@ // Copyright 2019-2021 the Deno authors. All rights reserved. MIT license. +#include #include #include #include #include #include +#include #include "support.h" #include "unicode/locid.h" @@ -17,9 +19,12 @@ #include "v8/include/v8.h" #include "v8/src/api/api-inl.h" #include "v8/src/api/api.h" +#include "v8/src/base/debug/stack_trace.h" +#include "v8/src/base/sys-info.h" #include "v8/src/execution/isolate-utils-inl.h" #include "v8/src/execution/isolate-utils.h" #include "v8/src/flags/flags.h" +#include "v8/src/libplatform/default-platform.h" #include "v8/src/objects/objects-inl.h" #include "v8/src/objects/objects.h" #include "v8/src/objects/smi.h" @@ -2579,6 +2584,49 @@ v8::StartupData v8__SnapshotCreator__CreateBlob( return self->CreateBlob(function_code_handling); } +class UnprotectedDefaultPlatform : public v8::platform::DefaultPlatform { + using IdleTaskSupport = v8::platform::IdleTaskSupport; + using InProcessStackDumping = v8::platform::InProcessStackDumping; + using PriorityMode = v8::platform::PriorityMode; + using TracingController = v8::TracingController; + + static constexpr int kMaxThreadPoolSize = 16; + + public: + explicit UnprotectedDefaultPlatform( + int thread_pool_size, IdleTaskSupport idle_task_support, + std::unique_ptr tracing_controller = {}, + PriorityMode priority_mode = PriorityMode::kDontApply) + : v8::platform::DefaultPlatform(thread_pool_size, idle_task_support, + std::move(tracing_controller), + priority_mode) {} + + static std::unique_ptr New( + int thread_pool_size, IdleTaskSupport idle_task_support, + InProcessStackDumping in_process_stack_dumping, + std::unique_ptr tracing_controller = {}, + PriorityMode priority_mode = PriorityMode::kDontApply) { + // This implementation is semantically equivalent to the implementation of + // `v8::platform::NewDefaultPlatform()`. + DCHECK_GE(thread_pool_size, 0); + if (thread_pool_size < 1) { + thread_pool_size = + std::max(v8::base::SysInfo::NumberOfProcessors() - 1, 1); + } + thread_pool_size = std::min(thread_pool_size, kMaxThreadPoolSize); + if (in_process_stack_dumping == InProcessStackDumping::kEnabled) { + v8::base::debug::EnableInProcessStackDumping(); + } + return std::make_unique( + thread_pool_size, idle_task_support, std::move(tracing_controller), + priority_mode); + } + + v8::ThreadIsolatedAllocator* GetThreadIsolatedAllocator() override { + return nullptr; + } +}; + v8::Platform* v8__Platform__NewDefaultPlatform(int thread_pool_size, bool idle_task_support) { return v8::platform::NewDefaultPlatform( @@ -2589,6 +2637,16 @@ v8::Platform* v8__Platform__NewDefaultPlatform(int thread_pool_size, .release(); } +v8::Platform* v8__Platform__NewUnprotectedDefaultPlatform( + int thread_pool_size, bool idle_task_support) { + return UnprotectedDefaultPlatform::New( + thread_pool_size, + idle_task_support ? v8::platform::IdleTaskSupport::kEnabled + : v8::platform::IdleTaskSupport::kDisabled, + v8::platform::InProcessStackDumping::kDisabled, nullptr) + .release(); +} + v8::Platform* v8__Platform__NewSingleThreadedDefaultPlatform( bool idle_task_support) { return v8::platform::NewSingleThreadedDefaultPlatform( diff --git a/src/lib.rs b/src/lib.rs index a205007a..a6c2a69c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -120,6 +120,7 @@ pub use module::*; pub use object::*; pub use platform::new_default_platform; pub use platform::new_single_threaded_default_platform; +pub use platform::new_unprotected_default_platform; pub use platform::Platform; pub use primitives::*; pub use private::*; diff --git a/src/platform.rs b/src/platform.rs index 01b42905..a1a15dfd 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -14,6 +14,10 @@ extern "C" { thread_pool_size: int, idle_task_support: bool, ) -> *mut Platform; + fn v8__Platform__NewUnprotectedDefaultPlatform( + thread_pool_size: int, + idle_task_support: bool, + ) -> *mut Platform; fn v8__Platform__NewSingleThreadedDefaultPlatform( idle_task_support: bool, ) -> *mut Platform; @@ -58,6 +62,16 @@ pub struct Platform(Opaque); /// If |idle_task_support| is enabled then the platform will accept idle /// tasks (IdleTasksEnabled will return true) and will rely on the embedder /// calling v8::platform::RunIdleTasks to process the idle tasks. +/// +/// The default platform for v8 may include restrictions and caveats on thread +/// creation and initialization. This platform should only be used in cases +/// where v8 can be reliably initialized on the application's main thread, or +/// the parent thread to all threads in the system that will use v8. +/// +/// One example of a restriction is the use of Memory Protection Keys (pkeys) on +/// modern Linux systems using modern Intel/AMD processors. This particular +/// technology requires that all threads using v8 are created as descendent +/// threads of the thread that called `v8::Initialize`. #[inline(always)] pub fn new_default_platform( thread_pool_size: u32, @@ -66,6 +80,18 @@ pub fn new_default_platform( Platform::new(thread_pool_size, idle_task_support) } +/// Creates a platform that is identical to the default platform, but does not +/// enforce thread-isolated allocations. This may reduce security in some cases, +/// so this method should be used with caution in cases where the threading +/// guarantees of `new_default_platform` cannot be upheld (generally for tests). +#[inline(always)] +pub fn new_unprotected_default_platform( + thread_pool_size: u32, + idle_task_support: bool, +) -> UniqueRef { + Platform::new_unprotected(thread_pool_size, idle_task_support) +} + /// The same as new_default_platform() but disables the worker thread pool. /// It must be used with the --single-threaded V8 flag. /// @@ -88,6 +114,16 @@ impl Platform { /// If |idle_task_support| is enabled then the platform will accept idle /// tasks (IdleTasksEnabled will return true) and will rely on the embedder /// calling v8::platform::RunIdleTasks to process the idle tasks. + /// + /// The default platform for v8 may include restrictions and caveats on thread + /// creation and initialization. This platform should only be used in cases + /// where v8 can be reliably initialized on the application's main thread, or + /// the parent thread to all threads in the system that will use v8. + /// + /// One example of a restriction is the use of Memory Protection Keys (pkeys) + /// on modern Linux systems using modern Intel/AMD processors. This particular + /// technology requires that all threads using v8 are created as descendent + /// threads of the thread that called `v8::Initialize`. #[inline(always)] pub fn new( thread_pool_size: u32, @@ -101,6 +137,24 @@ impl Platform { } } + /// Creates a platform that is identical to the default platform, but does not + /// enforce thread-isolated allocations. This may reduce security in some + /// cases, so this method should be used with caution in cases where the + /// threading guarantees of `new_default_platform` cannot be upheld (generally + /// for tests). + #[inline(always)] + pub fn new_unprotected( + thread_pool_size: u32, + idle_task_support: bool, + ) -> UniqueRef { + unsafe { + UniqueRef::from_raw(v8__Platform__NewUnprotectedDefaultPlatform( + thread_pool_size.min(16) as i32, + idle_task_support, + )) + } + } + /// The same as new() but disables the worker thread pool. /// It must be used with the --single-threaded V8 flag. /// diff --git a/tests/slots.rs b/tests/slots.rs index 5e204c5b..e491fbe5 100644 --- a/tests/slots.rs +++ b/tests/slots.rs @@ -15,7 +15,7 @@ fn setup() { START.call_once(|| { v8::V8::set_flags_from_string("--expose_gc"); v8::V8::initialize_platform( - v8::new_default_platform(0, false).make_shared(), + v8::new_unprotected_default_platform(0, false).make_shared(), ); v8::V8::initialize(); }); diff --git a/tests/test_api.rs b/tests/test_api.rs index 0f578858..6c418bce 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -55,7 +55,7 @@ mod setup { "--no_freeze_flags_after_init --expose_gc --harmony-import-assertions --harmony-shadow-realm --allow_natives_syntax --turbo_fast_api_calls", ); v8::V8::initialize_platform( - v8::new_default_platform(0, false).make_shared(), + v8::new_unprotected_default_platform(0, false).make_shared(), ); v8::V8::initialize(); }); diff --git a/tests/test_api_entropy_source.rs b/tests/test_api_entropy_source.rs index 3611e4f0..e8aa329d 100644 --- a/tests/test_api_entropy_source.rs +++ b/tests/test_api_entropy_source.rs @@ -18,7 +18,9 @@ fn set_entropy_source() { true }); - v8::V8::initialize_platform(v8::new_default_platform(0, false).make_shared()); + v8::V8::initialize_platform( + v8::new_unprotected_default_platform(0, false).make_shared(), + ); v8::V8::initialize(); // Assumes that every isolate creates a PRNG from scratch, which is currently true. diff --git a/tests/test_api_flags.rs b/tests/test_api_flags.rs index 713b58c2..5ee6a2b4 100644 --- a/tests/test_api_flags.rs +++ b/tests/test_api_flags.rs @@ -4,7 +4,9 @@ #[test] fn set_flags_from_string() { v8::V8::set_flags_from_string("--use_strict"); - v8::V8::initialize_platform(v8::new_default_platform(0, false).make_shared()); + v8::V8::initialize_platform( + v8::new_unprotected_default_platform(0, false).make_shared(), + ); v8::V8::initialize(); let isolate = &mut v8::Isolate::new(Default::default()); let scope = &mut v8::HandleScope::new(isolate); diff --git a/tests/test_platform_atomics_pump_message_loop.rs b/tests/test_platform_atomics_pump_message_loop.rs index 8c1c5599..76fa5275 100644 --- a/tests/test_platform_atomics_pump_message_loop.rs +++ b/tests/test_platform_atomics_pump_message_loop.rs @@ -3,7 +3,9 @@ fn atomics_pump_message_loop() { v8::V8::set_flags_from_string( "--allow-natives-syntax --harmony-sharedarraybuffer", ); - v8::V8::initialize_platform(v8::new_default_platform(0, false).make_shared()); + v8::V8::initialize_platform( + v8::new_unprotected_default_platform(0, false).make_shared(), + ); v8::V8::initialize(); let isolate = &mut v8::Isolate::new(Default::default()); let scope = &mut v8::HandleScope::new(isolate);