1// Copyright 2020 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#if !V8_ENABLE_WEBASSEMBLY 6#error This header should only be included if WebAssembly is enabled. 7#endif // !V8_ENABLE_WEBASSEMBLY 8 9#ifndef V8_WASM_CODE_SPACE_ACCESS_H_ 10#define V8_WASM_CODE_SPACE_ACCESS_H_ 11 12#include "src/base/build_config.h" 13#include "src/base/macros.h" 14#include "src/common/globals.h" 15 16namespace v8 { 17namespace internal { 18 19namespace wasm { 20 21class NativeModule; 22 23// Within the scope, the code space is writable (and for Apple M1 also not 24// executable). After the last (nested) scope is destructed, the code space is 25// not writable. 26// This uses three different implementations, depending on the platform, flags, 27// and runtime support: 28// - On MacOS on ARM64 ("Apple M1"/Apple Silicon), it uses APRR/MAP_JIT to 29// switch only the calling thread between writable and executable. This achieves 30// "real" W^X and is thread-local and fast. 31// - When Intel PKU (aka. memory protection keys) are available, it switches 32// the protection keys' permission between writable and not writable. The 33// executable permission cannot be retracted with PKU. That is, this "only" 34// achieves write-protection, but is similarly thread-local and fast. 35// - As a fallback, we switch with {mprotect()} between R-X and RWX (due to 36// concurrent compilation and execution). This is slow and process-wide. With 37// {mprotect()}, we currently switch permissions for the entire module's memory: 38// - for AOT, that's as efficient as it can be. 39// - for Lazy, we don't have a heuristic for functions that may need patching, 40// and even if we did, the resulting set of pages may be fragmented. 41// Currently, we try and keep the number of syscalls low. 42// - similar argument for debug time. 43// MAP_JIT on Apple M1 cannot switch permissions for smaller ranges of memory, 44// and for PKU we would need multiple keys, so both of them also switch 45// permissions for all code pages. 46class V8_NODISCARD CodeSpaceWriteScope final { 47 public: 48 explicit V8_EXPORT_PRIVATE CodeSpaceWriteScope(NativeModule*); 49 V8_EXPORT_PRIVATE ~CodeSpaceWriteScope(); 50 51 // Disable copy constructor and copy-assignment operator, since this manages 52 // a resource and implicit copying of the scope can yield surprising errors. 53 CodeSpaceWriteScope(const CodeSpaceWriteScope&) = delete; 54 CodeSpaceWriteScope& operator=(const CodeSpaceWriteScope&) = delete; 55 56 static bool IsInScope() { return current_native_module_ != nullptr; } 57 58 private: 59 // The M1 implementation knows implicitly from the {MAP_JIT} flag during 60 // allocation which region to switch permissions for. On non-M1 hardware 61 // without memory protection key support, we need the code space from the 62 // {NativeModule}. 63 static thread_local NativeModule* current_native_module_; 64 65 // {SetWritable} and {SetExecutable} implicitly operate on 66 // {current_native_module_} (for mprotect-based protection). 67 static void SetWritable(); 68 static void SetExecutable(); 69 70 // Returns {true} if switching permissions happens on a per-module level, and 71 // not globally (like for MAP_JIT and PKU). 72 static bool SwitchingPerNativeModule(); 73 74 // Save the previous module to put it back in {current_native_module_} when 75 // exiting this scope. 76 NativeModule* const previous_native_module_; 77}; 78 79} // namespace wasm 80} // namespace internal 81} // namespace v8 82 83#endif // V8_WASM_CODE_SPACE_ACCESS_H_ 84