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 16 namespace v8 { 17 namespace internal { 18 19 namespace wasm { 20 21 class 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. 46 class 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 IsInScope()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