// Copyright 2022 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_COMMON_CODE_MEMORY_ACCESS_H_ #define V8_COMMON_CODE_MEMORY_ACCESS_H_ #include <map> #include <optional> #include "include/v8-internal.h" #include "include/v8-platform.h" #include "src/base/build_config.h" #include "src/base/macros.h" #include "src/base/memory.h" #include "src/base/platform/mutex.h" #include "src/common/globals.h" namespace v8 { namespace internal { // We protect writes to executable memory in some configurations and whenever // we write to it, we need to explicitely allow it first. // // For this purposed, there are a few scope objects with different semantics: // // - CodePageMemoryModificationScopeForDebugging: // A scope only used in non-release builds, e.g. for code zapping. // - wasm::CodeSpaceWriteScope: // Allows access to Wasm code // // - RwxMemoryWriteScope: // A scope that uses per-thread permissions to allow access. Should not be // used directly, but rather is the implementation of one of the above. // - RwxMemoryWriteScopeForTesting: // Same, but for use in testing. class RwxMemoryWriteScopeForTesting; namespace wasm { class CodeSpaceWriteScope; } #if V8_HAS_PKU_JIT_WRITE_PROTECT // Alignment macros. // Adapted from partition_allocator/thread_isolation/alignment.h. // Page size is not a compile time constant, but we need it for alignment and // padding of our global memory. // We use the maximum expected value here (currently x64 only) and test in // ThreadIsolation::Initialize() that it's a multiple of the real pagesize. #define THREAD_ISOLATION_ALIGN_SZ … #define THREAD_ISOLATION_ALIGN … #define THREAD_ISOLATION_ALIGN_OFFSET_MASK … #define THREAD_ISOLATION_FILL_PAGE_SZ(size) … #else // V8_HAS_PKU_JIT_WRITE_PROTECT #define THREAD_ISOLATION_ALIGN_SZ … #define THREAD_ISOLATION_ALIGN #define THREAD_ISOLATION_FILL_PAGE_SZ … #endif // V8_HAS_PKU_JIT_WRITE_PROTECT // This scope is a wrapper for APRR/MAP_JIT machinery on MacOS on ARM64 // ("Apple M1"/Apple Silicon) or Intel PKU (aka. memory protection keys) // with respective low-level semantics. // // The semantics on MacOS on ARM64 is the following: // The scope switches permissions between writable and executable for all the // pages allocated with RWX permissions. Only current thread is affected. // This achieves "real" W^X and it's fast (see pthread_jit_write_protect_np() // for details). // By default it is assumed that the state is executable. // It's also assumed that the process has the "com.apple.security.cs.allow-jit" // entitlement. // // The semantics on Intel with PKU support is the following: // When Intel PKU is available, the scope switches the protection key's // permission between writable and not writable. The executable permission // cannot be retracted with PKU. That is, this "only" achieves write // protection, but is similarly thread-local and fast. // // On other platforms the scope is a no-op and thus it's allowed to be used. // // The scope is reentrant and thread safe. class V8_NODISCARD RwxMemoryWriteScope { … }; class WritableJitPage; class WritableJitAllocation; class WritableJumpTablePair; // The ThreadIsolation API is used to protect executable memory using per-thread // memory permissions and perform validation for any writes into it. // // It keeps metadata about all JIT regions in write-protected memory and will // use it to validate that the writes are safe from a CFI perspective. // Its tasks are: // * track JIT pages and allocations and check for validity // * check for dangling pointers on the shadow stack (not implemented) // * validate code writes like code creation, relocation, etc. (not implemented) class V8_EXPORT ThreadIsolation { … }; // A scope class that temporarily makes the JitAllocation writable. All writes // to executable memory should go through this object since it adds validation // that the writes are safe for CFI. class WritableJitAllocation { … }; // Similar to the WritableJitAllocation, all writes to free space should go // through this object since it adds validation that the writes are safe for // CFI. // For convenience, it can also be used for writes to non-executable memory for // which it will skip the CFI checks. class WritableFreeSpace { … }; extern template void WritableFreeSpace::ClearTagged<kTaggedSize>( size_t count) const; extern template void WritableFreeSpace::ClearTagged<2 * kTaggedSize>( size_t count) const; class WritableJitPage { … }; class WritableJumpTablePair { … }; template <class T> bool operator==(const ThreadIsolation::StlAllocator<T>&, const ThreadIsolation::StlAllocator<T>&) { … } template <class T> bool operator!=(const ThreadIsolation::StlAllocator<T>&, const ThreadIsolation::StlAllocator<T>&) { … } // This class is a no-op version of the RwxMemoryWriteScope class above. // It's used as a target type for other scope type definitions when a no-op // semantics is required. class V8_NODISCARD V8_ALLOW_UNUSED NopRwxMemoryWriteScope final { … }; // Same as the RwxMemoryWriteScope but without inlining the code. // This is a workaround for component build issue (crbug/1316800), when // a thread_local value can't be properly exported. class V8_NODISCARD RwxMemoryWriteScopeForTesting final : public RwxMemoryWriteScope { … }; #if V8_HEAP_USE_PTHREAD_JIT_WRITE_PROTECT // Metadata are not protected yet with PTHREAD_JIT_WRITE_PROTECT using CFIMetadataWriteScope = NopRwxMemoryWriteScope; #else CFIMetadataWriteScope; #endif #ifdef V8_ENABLE_MEMORY_SEALING using DiscardSealedMemoryScope = RwxMemoryWriteScope; #else DiscardSealedMemoryScope; #endif } // namespace internal } // namespace v8 #endif // V8_COMMON_CODE_MEMORY_ACCESS_H_