Branch: refs/heads/main Home: https://github.com/WebKit/WebKit Commit: d5e7d2a3eeeeab55e93553b2fc91fc61327a6ffb https://github.com/WebKit/WebKit/commit/d5e7d2a3eeeeab55e93553b2fc91fc61327a6ffb Author: Mark Lam <mark....@apple.com> Date: 2025-08-17 (Sun, 17 Aug 2025)
Changed paths: M Source/JavaScriptCore/interpreter/VMEntryRecord.h M Source/JavaScriptCore/llint/InPlaceInterpreter.asm M Source/JavaScriptCore/llint/InPlaceInterpreter.cpp M Source/JavaScriptCore/llint/InPlaceInterpreter.h M Source/JavaScriptCore/llint/InPlaceInterpreter64.asm M Source/JavaScriptCore/llint/LLIntData.cpp M Source/JavaScriptCore/llint/LLIntData.h M Source/JavaScriptCore/llint/LowLevelInterpreter.asm M Source/JavaScriptCore/llint/LowLevelInterpreter64.asm M Source/JavaScriptCore/llint/WebAssembly.asm M Source/JavaScriptCore/offlineasm/arm64e.rb M Source/JavaScriptCore/runtime/InitializeThreading.cpp M Source/JavaScriptCore/runtime/JSCConfig.h M Source/JavaScriptCore/runtime/JSCJSValue.cpp M Source/JavaScriptCore/runtime/JSCPtrTag.h M Source/JavaScriptCore/runtime/ParseInt.h M Source/WTF/wtf/WTFConfig.cpp M Source/WTF/wtf/WTFConfig.h Log Message: ----------- Harden the interpreter dispatchers more for ARM64E. https://bugs.webkit.org/show_bug.cgi?id=295595 rdar://155356829 Reviewed by Daniel Liu. We have 3 goals: 1. Ensure that LLInt opcode maps are robust against corruption. 2. Ensure that processes that don't need the LLInt can disable it. 3. Ensure that opcode maps are properly initialized before use. Goal 1: Ensure that LLInt opcode maps are robust against corruption. ==================================================================== We previously achieve this by PAC signing (address diversified) the handlers PCs in g_opcodeMap (and peers). The handler PCs are authenticated on each bytecode dispatch. This is slow because PAC authentication is slow. We now achieve this goal by moving g_opcodeMap (and peers) into a g_opcodeConfigStorage page that can be permanently frozen as Read Only. With this, the opcode map can no longer be corrupted, and we can do away with the PAC signing. The handler PCs will now be stored as naked pointers. Here are the steps we use to initialize this Read Only opcode maps: 1.1. Call llint_entry to fill in the PAC signed handler PCs into a temp buffer. Next, we strip these handler PCs of their PAC signatures and store them as naked code pointers into the g_opcodeConfigStorage. 1.2. Permanently freeze (protect) the g_opcodeConfigStorage page with PROT_READ permission only. 1.3. For each handler PC in the opcode maps, PAC authenticate their counterpart in the temp buffer, and then, assert that the naked handler PC matches the authenticated counterpart. This steps guarantees that the opcode maps have been initialized properly, and are not corrupted in any way. 1.4. Initialize g_jscConfig.llint.gateMap[Gate::vmEntryToJavaScript] with its PAC signed pointer. This pointer is the vector thru which vmEntryToJavaScript calls into the interpreter (or JIT generated functions). See makeJavaScriptCall(). Goal 2. Ensure that processes that don't need the LLInt can disable it. ======================================================================= We previously achieve this using a neuterOpcodeMaps() function that fills the opcode maps with the PC of a function that will crash. We now achieve this by permanently freeze (protect) the g_opcodeConfigStorage page with PROT_NONE. This ensures that LLInt cannot use the opcode maps. Any attempt to use them will now result in a crash. We also do NOT initialize g_jscConfig.llint.gateMap[Gate::vmEntryToJavaScript] (nor any of the other values initialized by LLInt:initialize()). Also added a check for a "com.apple.security.script-restrictions" entitlement that can be used by processes to opt into disabling the LLInt. This is not currently used by any processes. We may change the implementation of this goal in the future if a more performant way becomes available. See rdar://158509720. Goal 3. Ensure that opcode maps are properly initialized before use. ==================================================================== "before use" means cases where the process' code will naturally call into the LLInt. What we're hardening against here are cases where the LLInt hasn't been initialized yet, but its data has been corrupted such that LLInt will mistakenly think that it has already been initialized. However, all code paths to enter the LLInt will have to dispatch through g_jscConfig.llint.gateMap[Gate::vmEntryToJavaScript], which will PAC authenticate its pointer. Hence, the only way to enter the LLInt is if the Gate::vmEntryToJavaScript pointer is properly signed. And the only way it will be properly signed is if we have already gone through LLInt::initialized(), and properly initialized the opcode maps. Just to ensure that g_jscConfig.llint.gateMap[Gate::vmEntryToJavaScript] is uniquely signed, we introduce a new VMEntryToJITGatePtrTag that is only used exclusively for this pointer. See rdar://155356829 for more details. We also made these additional changes: 1. Secure IPInt dispatchers ======================== This is achieved by calling validateOpcodeConfig() at all the following places: a. the top of all IPInt handler for Wasm opcode that concerns control flow. b. before any operation calls. c. the top of loops in IPInt, unless it the loop is already covered by (a) or (b). The idea is to only validateOpcodeConfig() at all places that concerns control flow. The alternative would be do it on every bytecode dispatch, but that turns out to be too costly in terms of performance (~2-5% overhead on non-JIT IPInt only). validateOpcodeConfig() simply loads a word from g_opcodeConfigStorage. If the LLint is disabled, g_opcodeConfigStorage will be mapped PROT_NONE, and this load will result in a crash. The word loaded by validateOpcodeConfig() is ignored. No other instructions depend on it. Hence, this should help reduce the impact of that load in terms of performance. We also make the IPInt gc_dispatch, conversion_dispatch, simd_dispatch, and atomic_dispatch use base pointers from g_opcodeConfigStorage. These have not been shown to impact performance so far. So, they are fine for now. See rdar://155356829 for more details. 2. Moved IPInt initialization into LLInt::initialize() so that it can be done at the right time before we protect the g_opcodeConfigStorage. IPInt dispatch base pointers are now moved from g_config into g_opcodeConfigStorage. 3. Now that we only store naked code pointers in the opcode maps, a lot of the LLInt bytecode accessor methods can be simplified significantly. This has been done. 4. Move the error handling crash case out of argumINTDispatch(), mintArgDispatch(), mintRetDispatch(), and uintDispatch(). This makes the code slightly more performant. 5. Initially, this set of changes resulted in a RAMification memory regression. To fix this, we do the following: a. Move the definition of the radixDigits const array from ParseInt.h to JSCJSValue.cpp. The contents of the array is not used to enable any constant folding. So, it doesn't help anything to make the array inlineable. This move reduces the memory use of this array from ~280K down to 37 bytes. b. Put g_config and g_opcodeConfigStorage pages in their own custom __DATA sections (using the __attribute__ modifier). Previously, these 2 globals were linked in between other globals in __DATA. Due to their page alignment requirements, the linker padded the space before them with multiple KBs of unused memory. Putting them into their own sections allows the linker to pack in other global variables more efficiently. With (a) and (b), the RAMification regression was completely resolved. 7. Added a static_assert to ensure that VMEntryRecord::m_prevTopEntryFrame is located adjacent to VMEntryRecord::m_prevTopCallFrame. We have pre-existing code that rely on this invariant. This change was shown to be performance neutral on benchmarks with JIT enabled. With JIT disabled, the IPInt hardening also appears to be performance neutral. * Source/JavaScriptCore/bytecode/Opcode.h: * Source/JavaScriptCore/interpreter/VMEntryRecord.h: * Source/JavaScriptCore/llint/InPlaceInterpreter.asm: * Source/JavaScriptCore/llint/InPlaceInterpreter.cpp: (JSC::IPInt::initialize): (JSC::IPInt::verifyInitialization): * Source/JavaScriptCore/llint/InPlaceInterpreter.h: * Source/JavaScriptCore/llint/InPlaceInterpreter64.asm: * Source/JavaScriptCore/llint/LLIntData.cpp: (JSC::LLInt::scriptingIsForbidden): (JSC::LLInt::initialize): (JSC::LLInt::neuterOpcodeMaps): Deleted. * Source/JavaScriptCore/llint/LLIntData.h: (JSC::LLInt::addressOfOpcodeConfig): (JSC::LLInt::getCodePtrImpl): (JSC::LLInt::getCodePtr): (JSC::LLInt::getWide16CodePtr): (JSC::LLInt::getWide32CodePtr): (JSC::LLInt::getOpcodeAddress): Deleted. (JSC::LLInt::getOpcodeWide16Address): Deleted. (JSC::LLInt::getOpcodeWide32Address): Deleted. * Source/JavaScriptCore/llint/LowLevelInterpreter.asm: * Source/JavaScriptCore/llint/LowLevelInterpreter64.asm: * Source/JavaScriptCore/llint/WebAssembly.asm: * Source/JavaScriptCore/offlineasm/arm64e.rb: * Source/JavaScriptCore/runtime/InitializeThreading.cpp: (JSC::initialize): * Source/JavaScriptCore/runtime/JSCConfig.h: * Source/JavaScriptCore/runtime/JSCJSValue.cpp: * Source/JavaScriptCore/runtime/JSCPtrTag.h: * Source/JavaScriptCore/runtime/ParseInt.h: * Source/WTF/wtf/WTFConfig.cpp: (WTF::Config::permanentlyFreeze): (WTF::permanentlyFreezePages): * Source/WTF/wtf/WTFConfig.h: Canonical link: https://commits.webkit.org/298816@main To unsubscribe from these emails, change your notification settings at https://github.com/WebKit/WebKit/settings/notifications _______________________________________________ webkit-changes mailing list webkit-changes@lists.webkit.org https://lists.webkit.org/mailman/listinfo/webkit-changes