# War on JITs: Software-Based Attacks and Hybrid Defenses for JIT Compilers - A Comprehensive Survey

Quentin Ducasse, Pascal Cotret, Loïc Lagadec

<figure><img src="image_1.png" /></figure>

To cite this version:

Quentin Ducasse, Pascal Cotret, Loïc Lagadec. War on JITs: Software-Based Attacks and Hybrid Defenses for JIT Compilers - A Comprehensive Survey. ACM Computing Surveys, 2025, 57 (9), pp.1-36. 10.1145/3731598. hal-05041484

HAL Id: hal-05041484

https://hal.science/hal-05041484v1

Submitted on 21 Apr 2025

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_2.png" /></figure>

Distributed under a Creative Commons Attribution 4.0 International License
# War on JITs: Software-Based Attacks and Hybrid Defenses for JIT Compilers - A Comprehensive Survey

QUENTIN DUCASSE, Lab-STICC, ENSTA Bretagne, Brest, France and Inria, CNRS, IRISA, CentraleSupélec - 
Campus de Rennes, Cesson-Sévigné, France

PASCAL COTRET, Lab-STICC, ENSTA Bretagne, Brest, France

LOÏC LAGADEC, Lab-STICC, ENSTA Bretagne, Brest, France

Programming Language Virtual Machines (VMs) are composed of several components that together execute and manage languages efficiently. They are deployed in virtually all computing systems through modern web browsers. However, vulnerabilities in any VM component pose a significant threat to security and privacy. In this paper, we present a survey of software attacks on Just-In-Time (JIT) compilers, which dynamically produce optimized code at run time. We first present an overview and categorization of software attacks and their vectors as presented in the literature, identifying three main attack classes: code injection, code-reuse and data-only attacks. We show how each can lead to arbitrary code execution. Next, we present a comprehensive taxonomy of defenses, including diversification, strict memory permissions and capability containment. While some were integrated in modern VMs, we draw recommendations for future protections. Securing JIT compilers remains challenging due to inherent conflicts with security principles, such as W^X (Writable XOR eXecutable), and the complexity of JIT optimizations. Finally, we examine how newer architectures, like ARMv8 and RISC-V, face similar threats. With RISC-V's open architecture offering a promising platform for prototyping VM-specific protections and custom security instructions, we discuss hardware-assisted runtime protections and RISC-V extensions that could enhance VM security.

CCS Concepts: • Software and its engineering → Just-in-time compilers; Interpreters; • Security and privacy → Embedded systems security; Browser security.

Additional Key Words and Phrases: JIT spraying, ROP, Control-Flow Integrity, Memory Isolation

## 1 Introduction

The execution of dynamic programming languages is supported by their respective *Language Virtual Machine (VM)* which provides portability along with automated memory management. These VMs are widely deployed on computing systems in the form of runtime environments (e.g. JVM for Java, Pypy for Python), or to execute the JavaScript components of the modern web in web browsers (e.g. V8 for Google Chrome [56], JavaScriptCore for Safari [5], or SpiderMonkey for Firefox [94]). The basis of program execution relies on an interpreter, executing another representation of the source code, made for execution, named *bytecode*. This interpretation process is then sped up using one or multiple *Just-in-Time (JIT)* compiler(s) to recompile frequently used bytecode blocks into native machine code. The dynamic nature of VMs, with automated memory management through garbage collection, and run-time optimizations through JIT compiler(s), makes it complex to ensure their robustness.

The scope of software attacks against VMs goes beyond traditional control-flow hijacking, where an attacker corrupts code or/and data to perform malicious operations on the targeted system. VMs must handle untrusted user inputs, perform security-critical tasks and are embedded in widely used applications such as web browsers,

Authors' Contact Information: Quentin Ducasse, Lab-STICC, ENSTA Bretagne, Brest, France and Inria, CNRS, IRISA, CentraleSupélec - 
Campus de Rennes, Cesson-Sévigné, Brittany, France; e-mail: quentin.ducasse@centralesupelec.fr; Pascal Cotret, Lab-STICC, ENSTA Bretagne, 
Brest, France; e-mail: pascal.cotret@ensta.fr; Loïc Lagadec, Lab-STICC, ENSTA Bretagne, Brest, France; e-mail: loic.lagadec@ensta.fr.

<figure><img src="image_3.png" /></figure>

This work is licensed under a Creative Commons Attribution International 4.0 License.

© 2025 Copyright held by the owner/author(s).

ACM 1557-7341/2025/4-ART

https://doi.org/10.1145/3731598
making them both massively deployed directly on a user's computer and essential for privacy-critical operations. A vulnerable VM could be exploited to launch an arbitrary remote code execution directly on the victim's computer.

In this article, we present a review of software attacks against the components responsible for the execution of user inputs and its optimization, namely the interpreter and, most importantly, the JIT compiler. Each of these components plays a critical role in the execution and handles sensitive information: the interpreter through the execution of bytecode and the JIT compiler through the generation of executable machine code from a user input. We present a classification of demonstrated attacks against VMs in the literature into three categories: *code injection* attacks, in the form of direct injection [55] and JIT spraying [18, 77, 78, 122]; *code reuse* attacks, building on traditional *Return-Oriented Programming (ROP)* [120], using the dynamic nature of the JIT compiler to automatically generate gadgets and exploits [12, 123]; and *data-only* attacks targeting intermediate representations such as the bytecode itself [102], JIT intermediate representation [48] or internal structures [127].

We present a categorization of defense mechanisms setup around VMs, grouping them into four classes of defenses: *diversification* of both code, data, and localization of the JIT code region [63, 69, 135, 139]; *memory protection* dedicated to dynamic rewriting and code generation [28, 34]; *capability containment* in the form of sandboxes [4] or *Control-Flow Integrity (CFI)* [1] measures [96, 146]; *hardware-enforced isolation* using mechanisms present in recent processors such as ARM TrustZone [8] or Intel Memory Protection Keys (MPK) [66, Vol.3 Sec.4.6] around VMs [48, 102]. These defenses were set up incrementally and come at various performance costs. The latest defenses deployed along hardware support provide the most guarantees at a lesser impact.

We explore the portability of VM and JIT attacks on the RISC-V open Instruction Set Architecture (ISA) [111, 112], gaining increasing interest from both industries and academia. Despite the specifics of the architecture making attacks more convoluted, it would still be feasible to attack VMs deployed on this architecture. Additionally, we examine extensions to the modular capabilities of RISC-V, particularly its support for custom instructions and hardware features that can be leveraged for robust VM protection.

In summary, we present the following contributions:

* A thorough presentation and categorization of software attacks against language virtual machines and, in particular, against the just-in-time compiler.

* A categorized presentation of defenses implemented around language virtual machines and the different run-time elements they focus on.

* An investigation on the application of attacks and defenses over the RISC-V Instruction Set Architecture (ISA), gathering growing interest.

The remainder of this article is structured as follows, Section 2 presents the main components of a VM and the impact of vulnerabilities within them. Section 3 defines the three main types of attacks against JIT compilers. Section 4 highlights existing countermeasures that have been applied to VMs and mainly the JIT compiler. Section 5 presents a summarized version of attacks and defenses against VMs along with a discussion on the trends of both sides. Section 6 opens up perspectives on a growing new architecture, RISC-V. Finally, Section 7 sums up the review and defines the latest objectives for attacks and defenses on language VMs.

## 2 Language Virtual Machines

### 2.1 Managed Languages Runtime

Managed programming languages use a *runtime engine* and *environment* called a *virtual machine (VM)* to execute and manage source code written in the corresponding language. An overview of a VM is represented on Figure 1, derived from the one presented by Park et al. [102].

Figure 1 presents a VM using *bytecode* and executing an object-oriented language (e.g. JavaScript, C#, Python, etc). Several well-known VMs (V8 for JavaScript, JVM for Java, CLR for C#, etc.) first compile the code into an
<figure><img src="image_4.png" /><figcaption>Fig. 1. Virtual machine overview and interactions between components (derived from [102]). The components of the VM are presented on the left, and the elements they define and manipulate in memory at run time on the right. This figure serves as a basis for future presentations of attacks and defenses around VMs.</figcaption></figure>

intermediate representation called *bytecode* and then interpret this bytecode at run time. The interpretation involves a switch-case mechanism that processes the bytecode linearly. Having this layer of abstraction allows a developer to target this intermediate representation in their compilation output, benefitting from the portability of the VM across different architectures. Only the VM needs to be ported to new architectures without requiring major modification of the source code. For example, Java was advertised with the famous slogan "*Write Once Run Anywhere*". In addition to portability, VMs also provide tools like automated memory management using a *Garbage Collector (GC)* and a *Foreign Function Interface (FFI)* with other programs.

A common issue with bytecode interpretation (even with a threaded interpreter [107]) is that it is slower than direct compilation to machine code and execution. One solution is the use of one or multiple JIT compiler(s), which recompiles frequently executed bytecode blocks into machine code (referred to as JIT code) at run time. The corresponding hooks to transition between static code and JIT code, called *trampolines*, are compiled in the JIT code region as well. While this solution speeds up the base interpretation process, a new compilation process needs to be rewritten for each new backend the VM supports.

## 2.2 Optimizations

JIT compilation is an optimization over basic bytecode interpretation. However, the compilation process itself can be further optimized. Many JIT compilers use an intermediate representation to (1) translate bytecode into machine code and (2) perform optimizations on an architecture-independent representation.

Dynamically-typed object-oriented languages are prone to slowdowns during *method-lookup* operations. Finding the correct class from which to extract a named method and apply it to an instance is a costly process, usually sped up through a collection of optimizations. *Inline caches* insert a switch statement as a machine code stub in the JIT code region to redirect to the correct target efficiently. Initially, the cache site is empty, becoming a *monomorphic inline cache* once triggered, and later extending to a *polymorphic inline cache* as defined by Hölzle et al. [61]. V8 implements this concept calling them maps or hidden classes, while SpiderMonkey calls them
shapes. *Type feedback* utilizes data collected by *inline caches* on the recurring types in dynamic message sends, allowing dynamically dispatched calls to be inlined quickly.

Additionally, VMs can use different compilers with varying levels of optimization depth, taking progressively more time to produce increasingly optimized output. Hölzle et al. [62] present the concept of *adaptive optimization*, introducing a baseline compiler that initially compiles a method naively and compiles it with an optimizing compiler only if it is frequently reused.

All of these optimizations have been incorporated into production VMs such as the JVM or V8. They are central to the challenges faced by JIT compilers. For dynamically-typed languages, a set of assumptions accompanies these optimizations, and a deoptimization process is triggered if the assumptions no longer hold. Careful activation of JIT compilers with different optimization tiers provides the best overall performance.

## 2.3 Vulnerabilities in VMs

**Real-world example:** Following the example of a VM in a web browser, Zhang et al. [146] outline the flow that the Firefox Gecko engine undergoes to execute and display resources from the Internet. HTML, CSS, and multimedia inputs are consolidated into a *Document Object Model (DOM)*, which is used by the *layout engine* and *rendering engine* to present the page to users. The DOM interface itself is accessed by the JavaScript VM (SpiderMonkey for Firefox) to interact with page elements. Scripts are interpreted and then recompiled by the IonMonkey JIT compiler (superseded by WarpMonkey in 2020) to generate platform-specific native instructions, which are installed in the JIT code region and may be patched with optimization techniques described earlier. The garbage collector allocates and reclaims memory for necessary objects. Major web browsers embed production JavaScript VMs: Firefox uses SpiderMonkey, Chrome employs V8, and Safari features JavaScriptCore. A vulnerability in a VM can be exploited by an attacker distributing a malicious web page that renders through a victim's browser. Executing the malicious JavaScript code can provide the attacker with arbitrary code execution directly on the victim's computer [116].

**Vulnerabilities Classification and Weaponization:** VMs are written in low-level languages such as C++, requiring careful oversight to prevent common memory vulnerabilities like overflows, use-after-frees, and type confusion bugs. In each case, access to a memory corruption vulnerability can enable an attacker to perform arbitrary reads or writes in the heap, stack, or data structures used by the VM. These vulnerabilities are assigned high severity scores according to the Common Vulnerability Scoring System (CVSS) due to their significant impact on VM users. An overflow in an object, for instance, can write to adjacent objects in the heap, potentially overwriting nearby addresses or metadata. CVE-2016-7190 [88] presents a heap overflow in ChakraCore [25] `Array.map()` function, allowing arbitrary read and write capabilities. Similarly, a use-after-free vulnerability occurs when an object is freed, but its pointer remains accessible, leading to undefined behavior and potential access to the object's previous memory location.

In typed languages, type confusion bugs can occur when an object of one type is incorrectly treated as an instance of another class. Accessing attributes in this mismatched type can lead to out-of-bounds memory access. For example, CVE-2016-4622 [87], extensively analyzed by Samuel Groß [116], exploits weak typing in JavaScript, where type conversions are sometimes applied incorrectly. In this case, improper conversions in arrays and slices led to arbitrary reads and writes. Similarly, CVE-2019-11707 [89] affects SpiderMonkey via a type confusion bug between a `Uint32Array` and `Uint8Array`, enabling a read-write primitive similar to a buffer overflow. Regardless of the vulnerability type, an attacker with arbitrary read-write capabilities can rewrite addresses to redirect control flow or modify object metadata to execute arbitrary code [110, 116].

**Discovering vulnerabilities:** The optimizations presented in Subsection 2.2, combined with the ever-growing base code that VMs represent, make finding vulnerabilities a challenging task. Members of Google Project Zero have proposed Fuzzilli [57], a fuzzer engine designed to discover new vulnerabilities in VMs and their
components. It uses a mutable Intermediate Representation (IR) dedicated to JIT compilation, employing Single-Static Assignment (SSA) form to achieve a high likelihood of semantic correctness in the generated JavaScript program. FuzzJIT [131] is an oracle-based fuzzer that wraps fuzzed code in a function and triggers JIT compilation over this function. It checks the function output before and after JIT compilation, capturing non-crashing mismatches. Built upon Fuzzilli, FuzzJIT has exposed 33 JIT compiler bugs in mainstream JavaScript engines. Fuzzilli is still actively used and has uncovered over 50 bugs in JavaScript engines to date.

## 2.4 Threat Model

The threat model presented here outlines the foundational defenses established around a target VM, alongside the capabilities of potential attackers. In the remainder of this article, the threat model is refined to more accurately represent the capabilities of emerging attacks.

**Static code protections:** Based on the expected setup of modern VMs, static code defenses include a strict Writable XOR eXecutable (W^X) policy and Data Execution Prevention (DEP), which prevent direct code injection into static code regions. Data randomization is enforced through Address Space Layout Randomization (ASLR), preventing attackers from knowing the addresses of allocated data regions in advance. These defenses are implemented by modern operating systems on the statically compiled code.

**Attacker capabilities:** An attacker is assumed to have the ability to supply arbitrary input code to the VM, which the VM will compile, execute, and potentially recompile transparently. This allows the attacker to adapt a malicious payload at runtime. The attacker also has access to at least one memory-corruption vulnerability, as described in the previous section, enabling them to disclose and manipulate data memory at known addresses. While this may seem an extensive assumption, it aligns with common threat models for browser exploits [17, 28, 33, 48, 96, 119, 123].

**Orthogonal works:** Side-channel attacks, which derive information from auxiliary channels (either physical [73, 80] or software-induced [21, 58]), are orthogonal research areas to be considered in VM defense design. We also do not expand on garbage collector side-channel attacks [106, 140] but acknowledge their significant impact on VM security.

## 3 JIT Attacks

Attacks and defenses in the field of security follow the usual cat and mouse game, where new attacks are discovered, and corresponding security mechanisms are designed to counter them. This has led to increasingly complex attacks and more refined defense mechanisms. Attacks against VMs usually target statically compiled code, extended in the dynamic context of VMs, and taking advantage of the low-level operations performed by the JIT compiler. These attacks are classified into three main categories: *code injection* attacks, *code reuse* attacks and *data-only* attacks. This categorization is presented in chronological order to highlight the incremental evolution of VM exploitation techniques and their similarities with traditional attack methods.

### 3.1 JIT Code Injection Attacks

**Threat Model Refinement:** The defenses set up by the base threat model presented in the previous section should mitigate classic code injection attacks on the statically compiled code of the VM.

**Code Injection:** Traditional code injection attacks exploit a vulnerability to write a payload in memory. The first example of stack smashing [98] wrote malicious shellcode on the stack and exploited a buffer overflow vulnerability to overwrite a return address and execute the injected code. Injections have then used other vectors, such as the heap [3] or shared libraries [24], to place malicious code in executable memory and redirect the control flow back to it.
<figure><img src="image_5.png" /><figcaption>Fig. 2. JIT spraying example. The JavaScript code using the constant x (top-left) forces the JIT compilation to trigger, generating the corresponding machine code (top-right). When the alignment is shifted by one byte, a NOP slide and payload are revealed (bottom-left, bottom-right).</figcaption></figure>

**Application to VMs:** When applying the methodology to VMs, the JIT code region holds both code and data recompiled from bytecode blocks. JIT compilers initially wrote all run-time generated code onto memory pages that were simultaneously writable and executable throughout the execution. Since the code needs to be written at run time to be executed, it has to use memory that is both writable and executable. This lets an attacker perform *code-injection attacks* such as the one presented in the pwn2own competition where a malicious payload was injected into the JIT memory to gain arbitrary code execution in Chrome [55]. Calling a function that was previously recompiled directly triggers the injected code.

A new category of code injection attacks called *JIT spraying* attacks are initially presented by Dion Blazakis [18]. This type of attack includes large constants (or XOR chains of large constants) that hide a sequence of native machine code instructions when shifted by a given amount of bytes. A latter disruption of the control flow reveals the hidden instructions composing the exploit. Figure 2 is an example of such an attack and can be split into three parts: on the left, extracted from the original exploit presentation, the ActionScript statement that XORs three large constants; on the right, the corresponding machine code instructions when compiled by the ActionScript JIT compiler. As the generated code using the immediate values is stored in executable memory, these values can be interpreted as other instructions by shifting the execution point.

The sequence is composed of a NOP succession and cmp al instructions, semantic NOPs that consume the original xor eax opcodes (0x35 bytes) as operands. This chain can be extended at will and means that as long as execution begins at any of the unintended instruction boundaries (i.e. anywhere but the middle of the CMP instruction), the NOP chain executes. As long as the most significant byte of each immediate is 0x3c, the XOR opcode will be masked into a CMP. The shellcode can then be put at the end, replacing the 0x90 chains with instructions up to 3 bytes in length. Filling memory with a long NOP chain defeats the idea of randomization, as executing the NOP chain at any point brings to the shellcode. Note that the vulnerability used to divert the control flow can come from the VM or from the JIT code itself. A summary of the walkthrough of a JIT spraying attack is presented in Figure 3.
<figure><img src="image_6.png" /><figcaption>Fig. 3. Code injection attacks summary: the payload is passed through a large constant (1), the exploit triggers the JIT compilation (2) and a memory vulnerability (3) in either an object in memory, the JIT stack or JIT code itself transfers the control-flow and executes the hidden payload (4).</figcaption></figure>

**Extensions of the attack:** The attack has been extended by Alexey Sintsov by demonstrating an ActionScript JIT spray shellcode for x86 [122] and for other JITs such as the JavaScriptCore Baseline JIT. Rohlf et al. [114] extend the method against Mozilla JaegerMonkey and TraceMonkey JavaScript engines on the x86 architecture. Gawlik et al. [53] extend the idea to the Mozilla Firefox ASM.JS compiler, a module that uses a subset of JavaScript and compiled *ahead-of-time*. They manage to perform a new version of the JIT spraying attack, forcing large constants to be sprayed in the JIT region.

As this type of attack was first performed on the x86 architecture due to its lack of instruction alignment
requirement, the question of feasibility is raised for other architectures that have stricter requirements, both in
terms of alignment and calling conventions. Lian et al. [77] present JIT attacks on ARM and how JIT spraying
applies. They first show that using a self-sustaining JIT spray payload which contains long sequences of unintended
instructions that do not resynchronize (i.e. fall back to the initial instruction stream) is not feasible for ARM. This
is due to the alignment requirement that (even with Thumb-2 and a 2-bytes alignment requirement) does not allow
the execution of a long unaligned instruction stream. They also try to reinterpret part of a binary as its compressed
form, i.e. encode malicious Thumb-2 instructions in a genuine ARM instruction sequence. Incompatibilities
between the Thumb and ARM instruction encoding make it hard to create a useful reinterpretation payload and
only a code reuse attack was effective. However, in follow-up work, they were able to construct a self-sustaining
ARM JIT spraying payload without relying on gadgets [78]. By forcing the emission of 32-bit ARM bitwise AND
instructions, they have the control of 20 bits over each and can make them interpreted as two 16-bit Thumb-2
instructions. This way, the first instruction is used to perform useful operations for the attacker and the second
as an unconditional PC-relative forward branch to the next unintended instruction.

**Summary:** JIT spraying attacks blurred the lines between code injection and code reuse attacks, providing new opportunities for attackers by directly translating large constants into the JIT code executable region. The x86 architecture, with its variable length instruction encoding, allowed for nearly direct spraying, by placing
attacker-controlled input into executable memory. Extending the technique to ARM, with its stricter alignment requirements, reduces the attacker's space for injection but remains feasible.

## 3.2 JIT Code Reuse Attacks

**Threat Model Refinement:** On top of the existing static code protections, defenses now involve strong defenses against JIT spraying on dynamic code: diversification of the output code through NOP insertion or code randomization and enforcement of the W^X policy on the JIT code region. The scenario remains the same, and the attacker is expected to know the existence of a memory vulnerability to redirect the control flow, as well as a memory disclosure vulnerability to find the JIT code region.

**Return-Oriented Programming:** Even with defenses setup against code injection attacks, the JIT code is still vulnerable to code-reuse attacks that traverse and dynamically disassemble code pages to generate a *Return Oriented Programming (ROP)* [120] chain at run time. The objective is to identify "gadgets", units of machine code that hold a particular function (i.e. load, store) and end with a *ret* instruction. The original author presents the *Galileo* algorithm to statically analyze executables and libraries, searching for gadgets. The chain provided by the attacker consists of a succession of return addresses that each point to a given gadget and its dedicated function. Linking the gadgets together allows the attacker to run arbitrary code when disrupting control flow to the chain. The most common ROP payload chains together gadgets that change the stack pointer to an attacker-controlled payload, allocate RWX memory, copy a larger shellcode payload into it and execute it. To locate gadgets, attackers make use of the deterministic nature of the heap allocator when handling carefully crafted memory allocation/retrieval operations to defeat ASLR: it is known as the *Heap Feng-Shui* technique [125].

**Application to VMs:** The dynamic nature of JIT compilation amplifies ROP attacks. While library modules may contain more easier-to-find gadgets, JIT page allocations are more predictable and under the influence of untrusted inputs. If the attacker (1) knows how to trigger JIT compilation and (2) has an idea of the resulting machine code, they can generate arbitrary gadgets in machine code memory. These assumptions are plausible as (1) loops are often a sufficient way to generate compute load and trigger the JIT compiler, and (2) similarly to Heap Feng-Shui [125], JIT Feng-Shui [114] uses high level language patterns to compel the JIT compiler to produce predictable code chunks and therefore useful gadgets (called *gaJITs* by the authors). Even though the ROP technique has to go through ASLR, an attacker can still force the engine into allocating many copies of the native code throughout memory. Additionally, all coarse-grained ASLR solutions (i.e. per module) are vulnerable to *memory disclosure* attacks where even a single run-time address is enough to disclose its page and relations with others. The two following attacks are presented on Figure 4.

Snow et al. [123] use JIT compilation to generate a payload composed with gadgets extracted from the VM static code. As a clarification, the process, named JIT-ROP, is not directly related to the JIT code region. It describes a technique to repeatedly locate, read and disassemble static code and then uses a code-reuse payload built with the JIT compiler. The exploit requires the attacker to use a memory disclosure vulnerability to reveal a code pointer. Once the initial byte is disclosed and provided, the code page harvesting begins (1a), detecting system or library calls as well as discovering gadgets. The target program is specified in a DSL that compiles down to the ROP chain with gadgets discovered in the static code of the VM as well as API function pointers. This program is JIT-compiled to memory, along with an exploit buffer (2a) that transfers control flow to the ROP chain. A call to the JITed function triggers the execution of the exploit buffer (3) and the payload (4). This idea consists of the usual ROP attack against static disassembled code but helps defeat fine-grained ASLR that would be applied to the static binaries and provide a way to trigger the payload through the use of the JIT compiler.

Another version of the attack, focused on the dynamically-generated code only, is presented by Athanasakis et al. [12]. They show a ROP attack only using gadgets from the generated JIT code. They expect the static
<figure><img src="image_7.png" /><figcaption>Fig. 4. Example of two ROP attacks against VMs [12, 123]. The first one (labelled a) constructs a ROP chain from the static code of the VM (1a), then uses the VM itself (2a) to propagate the chain in executable memory and execute it. The second one (labelled b) provides a source program that is known to produce desired gadgets (1b) and constructs the ROP chain from the generated gadgets (2b). The execution of the exploit in the JIT code (3) triggers the payload 4 in both cases.</figcaption></figure>

```
1 pop %ebx      (ret) ; page address
2 pop %ecx      (ret) ; region size
3 xor %eax, %eax (ret) ; zero eax
4 mov 0x7d, %al  (ret) ; mprotect nb
5 xor %edx, %edx (ret) ; zero edx
6 mov 0x7, %dl   (ret) ; RWX code
7 int 0x80      (ret) ; syscall
8 ; Linux 32-bit
```

```
1 pop %r8      (ret) ; page address
2 pop %r9      (ret) ; region size
3 pop %rcx     (ret) ; protection
4 pop %rdx     (ret) ; output
5 pop %rax     (ret) ; stack adjust
6
7
8 ; Windows 64-bit
```

<caption>Fig. 5. Succession of gadgets to use the system calls mprotect (left) and VirtualProtect (right).</caption>

system to be hardened against code-reuse attacks, which means the binary and libraries are compiled with G-Free [97], a compiler that produces binaries without gadgets. The authors use 2-bytes constants to generate gadgets in memory, similarly to JIT spraying. They attack IonMonkey (Firefox) and ChakraCore (Internet Explorer) and overcome their defenses. The main goal is to call mprotect in the case of Firefox on 32-bit Linux and VirtualProtect for Internet Explorer on Windows 64-bit. Both system calls modify the permissions of given memory pages. As an example, the required gadgets to call mprotect are presented in Figure 5.

The first two gadgets store the page address and region length in %ebx and %ecx. The next two gadgets zero out %eax and copy 0x7d (system call number for mprotect) to it (%al corresponds to the lowest byte of the %eax register). The two following gadgets store the value 0x7, code for Read/Write/Execute permissions is copied to %edx. Finally, the last gadget issues the system call. The gadgets needed are injected through the input source code (1b) alongside a memory vulnerability. The location of the JIT code region is found using a memory disclosure
vulnerability, and JIT code is then disassembled to construct a ROP chain (2b). Control flow is handed to the ROP chain using the injected memory vulnerability ③ and the exploit is launched ④. To deploy desired gadgets to executable memory, the authors force the JIT compilation of constants in a dummy function, resembling *JIT spraying*. A complete exploit that propagates the desired gadgets in executable memory and redirect the control-flow to their injected ROP chain is conducted, allowing for remote arbitrary code execution.

**Summary:** Taking advantage of the dynamic nature of JIT compilers, attackers have used them to place desired gadgets in the JIT code region. As with more classical attacks, ROP attacks and their variants [17, 19, 20, 26] remain powerful attacks up to this date, and especially in the context of JIT compilation. They can be found on all architecture, even ones that do not rely on the stack to *call/return*, such as ARM [77] or RISC-V [32, 67]. They require expensive defense mechanisms to be properly mitigated, especially on dynamically-generated code.

### 3.3 Data-Only Attacks

**Threat Model Refinement:** On top of the existing static and dynamic code protections, defenses now involve strong defenses against JIT code reuse: strong CFI is enforced on both static and dynamic code. The scenario remains the same, and the attacker is expected to know the existence of a memory vulnerability.

**Data-only attacks:** Other attacks focus on data only (also known as *non-control-data attacks* [29]). They come as an opposition to *control-data attacks*, where the attacker aims to alter the program control data (e.g. return addresses and function pointers) to execute malicious code with the privilege of the victim process. *Non-control-data attacks* focus on security-critical data that applications use and manipulate, such as: configuration data, user input, user identity data or decision-making data. Hu et al. [65] extend the concept by creating *data-oriented gadgets* and unlocking *data-oriented programming (DOP)*, an attack technique that only uses data values for malicious purposes, maintaining complete integrity of the control flow.

**Application to VMs:** Looking at virtual machines (VMs), data objects often embed metadata related to the underlying implementation, such as context information or pointers to other functions in the hierarchy, which blurs the line between code and data. By treating live objects as data, we define data-only attacks as attacks not targeting directly the interpreter or JIT compiler, but rather their inputs. Data-only attacks against VMs focus on internal data structures linked to the program code. They take place either at interpretation stage through bytecode [102] or at JIT compilation stage, focusing on the JIT intermediate representation (IR) [48] or compilation internals [127]. The points of interest triggering the execution of malicious code is presented on Figure 6. Instead of injecting malicious code or gadget chains into the JIT code region, data-only attacks exploit the chain of input data of the VM interpreter or JIT compiler, allowing executable memory to contain directly usable code without disrupting the control flow.

The JIT compilation process is vulnerable as well both through its inputs and internals. Frassetto et al. [48] present a Data-Only JIT Attack (DOJITA) that targets JIT IR. They perform the attack against the ChakraCore VM according to the following process. The attacker exploits a memory-corruption vulnerability to read and write arbitrary data memory. They identify a “hot” function in the input program. They then inject crafted C++ objects, the payload, in the IR (1b) of this function. The JIT IR consists of a chained list of instructions, coming from bytecode and directed to the JIT compiler. The JIT compiler spreads the payload to JIT code as its input comes from trusted bytecode. The generated code now contains a malicious payload that will be executed when the victim “hot” function is called (2b). On the other hand, team Theori [127] presents an attack that targets ChakraCore too and bypasses the JIT Control-Flow Graph (CFG). It targets a temporary buffer used during compilation that is first readable and writable, then marked as readable and executable once the JIT compiler has produced all instructions. Similarly to the precedent attack, it overwrites the buffer contents once the JIT compilation is triggered, lets it compile the payload into the JIT code region. Both exploits keep the VM control flow untouched, only manipulating critical data structure used as input by the JIT compiler. Defenses set up
around the JIT compiler do not distinguish benign and malicious IR instructions and cannot properly mitigate these attacks.

<figure><img src="image_8.png" /><figcaption>Fig. 6. Overview of data-only attacks. Malicious data structures are injected by targeting the intermediate representation. Bytecode is targeted (1a) and directly executed through the interpreter (2a) [102]. JIT IR can also be targeted (1b), propagating the corresponding JITed code into executable memory (2b).</figcaption></figure>

Aiming at the interpreter and bytecode, Park et al. [102] present an attack on the interpreter through the compilation of malicious bytecode. The attack exploits the use of indirection in the bytecode structure and JavaScript function objects. A call to a function uses a bytecode pointing to the function object holding the callee's address. Corrupting this function object and redirecting the bytecode to call it effectively executes malicious code. The attack proceeds in four main phases against SpiderMonkey. First, the attacker leaks the location of a JavaScript context object and the JavaScript function object of a victim function. Then, the attacker overwrites the function address contained in the function object with the address of a target function (e.g. the C library system function) that holds the required bytecode blocks to hold the payload. The address of the victim function is overwritten in the metadata of native objects of the JavaScript VM. The attacker also overwrites the contents of the context object to hold a string representation of the path to the desired program to be executed (e.g. /bin/sh). The interpreter loads the objects onto the stack (2a) and launches the payload, following its usual execution process.

**Summary:** Even with strict control flow integrity applied on the static code of the VM and the dynamic JIT code region, an unsupervised use of execution data can compromise the system. The JIT compiler interacts with several security-critical memory regions during runtime, including JIT intermediate representation (IR), JIT data, and JIT code, which are the compiler inputs and outputs. While multiple attacks (and corresponding defenses) have primarily targeted the compiler output, the inputs have largely remained unaddressed. A similar focus applies to the interpreter: while bytecode and object tables are generated during source code compilation and typically remain static at runtime, it relies on runtime objects, which are modified as the program runs. These runtime objects hold sensitive information, such as function pointers and metadata related to object shape and scope. Each of these elements is security-critical and requires special attention and proper isolation, around the components and the different data structures it relies on.
# 4 JIT Countermeasures

Defenses applied to VMs began by establishing standard protections around the JIT code region, as it is the main target for attackers and a critical component of VM runtime. Incrementally, these defenses have expanded to cover other internal structures of the JIT compiler to increase resilience. These solutions refine the isolation between components, reducing the potential impact area of attacks that target any one of them. They can be grouped into four main categories: **diversification**, **memory protection**, **capability containment**, and **hardware-enforced isolation**. Presented mostly in chronological order, these categories illustrate the focus on achieving finer isolation with minimal performance cost.

## 4.1 Diversification

Answers to JIT spraying have the main objective of fuzzing the output of the JIT compiler. The deterministic generation of JIT code is what is exploited by attackers to hide the payload in constants. As stated by Forrest et al. [47], *"the role of diversity is to extract robustness against malicious events similarly to biological systems"*. Diversification solutions for the JIT code region can be split in two main categories, as presented by Lian et al. [78]: **intra-instruction** and **code layout randomization**. The extension of ASLR for **location randomization**, along with re-randomization techniques, is discussed next.

### 4.1.1 Intra-instruction Randomization.

**Motivation:** Adding entropy to JIT compiler output disrupts deterministic assumptions and predictability in JIT code, defending against injection and reuse attacks by randomizing instruction order, register usage, and constant encoding.

*Register randomization* [28, 135, 139] and *Callee-saved register save slot reordering* [34, 99] add entropy in the generated output. Register operands become unpredictable from an attacker point of view. The callee/caller saved values are saved and recovered in randomized manner. An attacker has to perform additional work to deduce the role of registers and this has to be performed again each time the compilation process is re-triggered. These solutions come as additional compiler passes and add fixed overhead. As this solution is not used alone, the specific costs are not detailed. However, the summarized performance is presented in Section 5, Table 1.

*Constant Folding* [114, 139], and its counterpart *constant blinding* handle large constants in generated machine code. It runs as a traditional compiler optimization pass that pre-computes known operations on constants at compile time. Remaining large user-supplied immediate values are split into smaller 2-bytes values. This result in additional instructions depending on the size of the values but makes it harder for an attacker to fit the necessary instructions in two bytes.

*Constant blinding* [12, 28, 63, 114, 139] modifies instructions using large immediate values. Implementations usually split the instructions from their untrusted immediate operand source and XORs it with a random value at compile time. At run time, the XOR result is XORed against the random value again to unveil the immediate value. Constant blinding generates two additional instructions by first loading the randomization value, then the constant and XORs them to deobfuscate the hidden value. The overhead comes as increased execution time and code size. Constant folding and blinding (for 3-bytes constants and larger) have been implemented in some major web browsers such as Google Chrome or Microsoft Edge as detailed by Gawlik et al. [53].

However, Athanasakis et al. [12] show that attacks are still possible using 2-bytes long or shorter constants and defend that applying the solution to all immediate operands regardless of their size adds an overhead of up to 80% in terms of number of instructions. The authors of libmask [68] transforms all constants into global variables and stores them in a read-only part of memory, showing a 5% performance overhead. Maisuradze et al. [86] present non-trivial cases found by their fuzzer Dachshund where JavaScript constants are embedded in code and manage to avoid obfuscation. For example, they show [85] an attacker can use the conditional jump
1 ; Before
2 func:
3 ; Stack frame creation
4 push ebp
5 mov ebp, esp
6 ; Callee-saved registers storage
7 push ebx
8 push ecx
9 push edx
10 ; Function body
11 mov eax, [ebp+8]
12 add eax, [ebp+12]
13 mov ebx, 0x12341234
14 xor eax, ebx
15 ; Callee-saved registers restoration
16 pop edx
17 pop ecx
18 pop ebx
19 ; Stack frame destruction and return
20 pop ebp
21 ret
22
23
24 ; ----------------------------
25 ; RER: Register Randomization
26 ; CST: Constant Blinding
27 ; NOP: NOP Insertion
28 ; ----------------------------

1 ; After
2 func:
3 ; Stack frame creation
4 push ebp
5 mov ebp, esp
6 ; Callee-saved registers storage
7 push edx ; RER
8 nop ; NOP
9 push ebx ; RER
10 push ecx ; RER
11 ; Function body
12 mov ecx, [ebp+8] ; RER
13 xchg eax, eax ; NOP
14 add ecx, [ebp+12] ; RER
15 mov eax, 0x34fed567 ; CST
16 mov edx, 0x26cac753 ; CST
17 xor eax, edx ; CST
18 mov ecx, ecx ; NOP
19 xor ecx, eax ; RER
20 lea esi, [esi] ; NOP
21 mov eax, ecx ; RER
22 ; Callee-saved registers restoration
23 pop ecx ; RER
24 pop ebx ; RER
25 pop edx ; RER
26 ; Stack frame destruction and return
27 pop ebp
28 ret

<caption>Fig. 7. Code diversification example.</caption>

targets as vectors for propagating gadgets to the JIT code, since these particular constants are not consistently blinded by the JIT compiler. They present an alternative proxy-based rewriting engine that complements constant blinding to properly remove them from JavaScript code, incurring a 22% performance overhead.

**Discussion:** Intra-instruction randomization disrupts the determinism of JIT code, targeting common attack vectors such as registers and large constants. Implementing these solutions presents non-trivial challenges, including the correct parsing of all embedded constants in dynamically typed high-level languages. While these solutions help mitigate injection attacks through large constants, they remain vulnerable to code reuse and data-only attacks.

4.1.2 *Code Layout Randomization*.

**Motivation:** Adding obfuscation to the JIT code removes some predictability from the compiler output. While the contents of the JIT code matter, the layout of its components also serves as a crucial attack vector, particularly under JIT Feng-Shui principles [114, 125].

NOP *insertion* [34, 63, 69, 114, 139] is the process of adding a random amount of semantic NOPs between legitimate instructions. The accumulation of NOPs makes the location of each instruction harder to predict. It also breaks hidden sequences of instructions that cannot expect the payload to be processed sequentially anymore. Jangda et al. [69] use a list of NOPs with different sizes and semantic NOPs, which use alternative instructions that preserve the processor state and minimize the risk of creating new gadgets. The authors insert these NOPs into the generated code to add noise to the output.

*Function Permutation* [71] randomizes the order of functions, procedure bodies in the code segment and adds high entropy at a low performance cost to the generated code and data. Readactor [34] use an indirection layer to
hide the code pointers of the randomized functions to prevent any access to the hidden functions, as they are
stored in unreadable memory (see Section 4.2.3).

**Example:** An example of *intra-instruction* and *code layout* randomization is presented on Figure 7. The input function on the left size simply adds two arguments, XORs the result with a given large constant and outputs the result. An example of an equivalent obfuscated version is presented on the right. Lines ending with NOP consist of NOP insertion of varying sizes but maintain the same semantic NOP behavior. Lines 15-17 ending with CST load the random value used to blind the constant at compile time, then load the obfuscated value and perform a XOR between them to deobfuscate the constant. Finally, lines ending with RER are register randomization techniques, using unpredictable registers both during function execution and when storing/restoring callee-saved registers.

**Discussion:** Code-layout randomization comes as low-cost solutions to counter code-reuse attacks. These solutions add entropy to the generated code and make it harder to be directly used by an attacker. The simplicity of the solution still falls short when the obfuscation techniques are known by the attacker. As these solutions are often used along intra-instruction randomization solutions presented above, performance overheads are presented for both in Section 5, Table 1.

4.1.3 Location Randomization.

**Motivation:** Address Space Layout Randomization (ASLR) [103] is implemented at OS-level in OpenBSD and Linux, effectively randomizing the memory location of code and data in virtual memory. For each process, the base address of the executable, libraries as well as the stack and heap are hidden and randomized from an attacker. These addresses have to be guessed, at the risk of crashing the running application if mistaken. ASLR and fine-grain randomization are defeated under the JIT-ROP [123] assumption. JIT-spraying attacks also break this randomization, as the JIT compiler provides access to the JIT code region. Enforcing back the OS-level idea on the generated JIT code would add complexity for an attacker. Randomization techniques either re-randomize the address space layout on a time basis or insert randomization components in the JIT compiler flow.

*Run-time fine-grained randomization* is needed to randomize the JIT memory region. Librando [63] is a framework that applies to a JIT compiler in a black-box manner. The operating system memory allocation functions are intercepted to analyze, diversify and rewrite the JIT code. All basic blocks generated are therefore randomized after the JIT compiler has generated the output machine code but before its installation in memory. The contents of the calling stack are preserved, making the VM use it transparently without reworking the compiler internals. The price in performance comes with a slowdown of 1.15x on the JVM and 3.5x for V8. Adding fine-grained, per-process memory randomization prevents code sharing since there is no longer any identical code to share between processes. Without shared libraries, memory savings are forfeit, explaining large memory footprints and performance overhead. Oxymoron [14] keeps code sharing available while providing fine-grained memory randomization, but was proven to be vulnerable in the context of JIT-ROP [38]. Oxymoron hides code pointers encoded into direct branches but does not handle the ones present in the heap and stack. The authors of the exploit against Oxymoron present Isomeron [38], a solution that combines code randomization along with execution path randomization to defeat ROP and JIT-ROP attacks, for a run-time overhead of 19%. Note that while Oxymoron and Isomeron are built in the context of JIT-ROP, they are not VM-specific measures.

*Re-randomization* techniques [16, 30, 31, 36, 81, 138] help defeat code-reuse attacks by adding a recurring constraint to an attacker. Ahmed et al. [2] compare solutions around fine-grained ASLR techniques under the constraints of JIT-ROP [123]. They conclude that fine-grained randomization techniques do not significantly prevent gadget corruption under a JIT-ROP threat model (see Section 3.2). They provide a way to determine the re-randomization interval upper bound for a given application (ranging from 1.5 to 3.5 seconds) to make randomization really effective.
**Discussion:** Code and data randomization counter code-reuse attacks by making it harder to disclose the memory location of code or data pointers. OS-level ASLR alone is insufficient to protect dynamically generated code and the VM in general. Fine-grained randomization (either dedicated to the JIT code region or not) is challenging to implement in practice, especially in the context of JIT-ROP, and incurs significant performance overhead. Re-randomization techniques allow runtime application of memory space randomization, though they also incur substantial performance costs (which can still be optimized according to established rules [2]).

## 4.2 Memory Protection

A common security principle to defend from code injection attacks is to prevent execution of data on the stack and the heap. This defense became famous with the PaX team [105] as W^X or Data Execution Prevention (DEP) [104, 129]. Enforcing the idea of mutually exclusive permissions is essential to protect applications and systems from malicious injection and execution through the stack or heap. The concept of W^X clashes with the main idea of a JIT compiler, as it needs to permanently rewrite machine code that has to be executed later on. It needs explicit access to a memory region that has both rights.

### 4.2.1 Transient Protection.

**Motivation:** Restricting the permissions of the JIT code following the principle of least privilege handles one part of the W^X policy, granting new temporary permissions at run time lets the compiler write or execute a given stub of machine code in JIT code memory.

A way to enforce W^X is to give restricted privileges to the JIT code region and grant the additional one at run time. Authors of JITDefender [27] and JITSafe [28] propose an adaptation of the concept over the Tamarin Flash VM [90]. They define the JIT code memory region as RW by default. The accesses for execution are instrumented to make the pages executable but not writable (RX) then removing the execution permission right after. The incurred overhead is less than 1% but these solutions do not protect against JIT spraying attacks where the control-flow vulnerability is triggered from within the JIT code. Taking a different approach, JITScope [146] and Readactor [34] authors make the JIT code RX by default. JITScope implements three delegates (code routines) to instrument JIT code access. Those three delegates serve different purposes, namely write gives access with write permission (RW) to the JIT code region, fwd-exec and bwd-exec instruments the call to and return from JIT code and make sure it can only be accessed through them. As it re-protects memory more frequently, the incurred performance overhead is higher (from 1.6% to 6.0%). Readactor switches between X-only pages and RW pages at rewrite and installation time and adds an overhead of 4%.

**Discussion:** While these solutions protect against single-threaded environment, race-condition attacks have been proposed by Song et al. [124] against the V8 VM. They use Web Workers, a mechanism that allow JavaScript programs to spawn multiple threads with communication and syncing capabilities. As an attacker uses one thread to trigger JIT compilation and has access to writable memory, another exploits a vulnerability to corrupt the just-accessed writable memory. While this requires the attacker to predict the address of the memory that will be made writable by the first thread, it defeats the assumption of W^X.

### 4.2.2 Double Mapping.

**Motivation:** Preventing race condition attacks, having a fixed set of permissions over a memory region. Virtual memory makes it possible to get two different virtual mappings with different permissions linked to the same physical memory.

Single-process dual-mapping solutions have been tested by the SpiderMonkey team [91] and incurred about 1% slowdown but were not ported to production due to the fact that an information leak vulnerability could reveal
the location of both mappings and defeat the defense [78]. JITGuard [48] maps the same region for trampolines two times in the virtual address space of the process. The first one (RW) can only be accessed by the JIT compiler from within the enclave, and the second one (RX) by the untrusted code. In their design, the trampolines are responsible for the calls between static and dynamic code, and each method in the JIT code region has its dedicated trampoline. The authors rely on an already in-place W^X enforcement on the JIT code itself. To avoid the permission switch of the JIT code memory regions depending on the use case, mapping the same physical addresses two times helps confine the capabilities based on the memory view it holds. Lobotomy [70] and SDCG [124] separate the RX and RW mappings across process boundaries and are discussed in more details in Section 4.3.1. A double mapping of the JIT code region has been integrated in JavaScriptCore that uses, since iOS 10, two virtual mappings to the same physical memory region [116], one writable and the other one executable. A special version of memcpy is then emitted at run time, which contains the random address of the writable region and is mapped as executable-only, preventing an attacker from reading the address.

### 4.2.3 Specific Read Primitives.

**Motivation:** Other solutions use specific memory permissions to alter the read component of the memory access and thwart the memory disclosure vulnerabilities at the base of ROP attacks and make it impossible to disassemble the generated JIT code.

Backes et al. [13] use eXecute no Read memory to prevent the JIT code from being read while leaving the memory WX. The authors ensure that code can be executed by the processor and cannot be read as data. The primitive is implemented as a kernel-level modification for both Linux and Windows and emulated in software at the cost of an execution time increased of 2.2% and 3.4% respectively. As specified earlier, while Readactor [34] uses a transient protection principle, it switches between X-only and RW permissions over the memory region.

As reading the memory is the first step for an usual ROP exploit, making read executable memory unusable is another way of fixing the issue. Heisenbyte [126] authors use destructive code reads to restrict the ability of adversaries to access executable memory that has been exposed through a memory disclosure vulnerability. They use hardware virtualization support to identify read operations on executable memory and can instrument the JIT code accordingly, forcing the program to crash if executing a read memory zone. In the same vein, No Execute After Read (NEAR) [137] prevents the execution of pages that have been disclosed but has not been applied to JIT compilation as it does not support writable pages.

**Discussion:** Tweaking the read primitives brings strong guarantees to the underlying memory. As they rely on specific hardware, these solutions have not been ported to production VMs. They can guarantee the integrity and privacy of the generated JIT code. The question of the integration of this type of solution in modern VMs and the issues it brings up for the VM itself has to be addressed.

## 4.3 Capability Containment

**Motivation:** Containing and isolating untrusted elements to prevent collateral damage reduces the impact of attackers. Isolation techniques to avoid side effects (e.g. the execution of a "stack pivot" [108, 143]) from a critical VM component fall into two main categories: *process isolation* and *control-flow integrity*. The first method isolates critical component(s) from the rest of the system, while the second ensures that only legitimate execution paths are followed, even when processing untrusted input (assuming the control-flow integrity is sufficiently precise).

### 4.3.1 Process Isolation and Sandboxing.

**Process Isolation:** Without using a sandbox, Jauernig et al. present Lobotomy [70] (① in Figure 8), an architecture that separates the compiler and executor of a JIT engine into different processes, which share the
JIT code memory region. The two processes interact through separate execution contexts and synchronize over shared memory. Concretely, the compiler process clones itself into the executor, which then waits on a semaphore. For a new execution trace, when the interpretation detects that it is ready for compilation, the compiler is triggered. Once compilation is complete, the compiler unblocks the executor to execute JITed code. For an existing trace, control is given to the executor directly. Each time blocking and unblocking occur, a *context switch* is performed via two system calls: *getcontext* (save to data structure) and *setcontext* (resume from given data). Shared memory allows the execution contexts to share data, serving two main purposes: (1) double-mapping the JIT code pages (RW for the compiler, RX for the executor) and (2) facilitating communication between the compiler and executor through mutexes and data sharing. Overall, this technique provides a clear separation of the two main processes accessing the JIT code. It incurs a runtime overhead of approximately 27% at the 95th percentile, but in some cases, the overhead can exceed 387%, primarily due to the proof-of-concept nature of the authors' implementation.

<figure><img src="image_9.png" /><figcaption>Fig. 8. Overview of process isolation techniques around VMs. The solutions split the execution of the VM into two main processes: a *compiler* (in green) that accesses the JIT code region in W mode, and an *executor* (in blue) that accesses the JIT code region in X mode.</figcaption></figure>

**Sandboxing:** Sandboxes are execution environments that restrict system resource access for applications. They implement system call interposition at different levels—kernel, user, or both. Pure user-level sandboxes can use software-based isolation techniques, such as Software Fault Isolation (SFI). OS-based mechanisms, like hypervisors, operate entirely in the kernel and rely on hardware memory. Delegation-based sandbox architectures (e.g., Ostia [52]) offer a hybrid approach, where the sandboxed process drops most privileges and delegates sensitive operations to a trusted process. Sandboxing has been applied to JIT compilation by Ansel et al. [4] in Google now-dePRECATED Native Client (NaCl) [144], a user-level sandbox architecture based on SFI that combines static analysis with software guards enforced on JIT code. Song et al. introduced *Secure Dynamic Code Generation (SDCG)* [124], a multi-process architecture using a delegate-based sandboxing approach (specifically *seccomp* [46]). In both cases, dynamic code generation, modification, installation, and deletion are either relocated or instrumented.

In the case of NaCl [4] (③ in Figure 8), the JIT compiler is defined as *untrusted NaCl code*, generates machine-code in a buffer, then invokes the interface with the NaCl trusted runtime to verify the generated machine code and install it. Other interface functions modify and delete the code stubs as requested by the JIT compiler. The JIT
code is separated from JIT data to be properly instrumented. All generated machine code is validated by the NaCl
runtime using SFI: the target code address is verified to be in boundaries with the NaCl memory and aligned, the
code is then copied to private memory of the trusted runtime, it is verified using the NaCl validator, the target
address range is checked to be unused then reserved, and the code is copied to. The solution proposes interesting
security properties but shows a high execution overhead from 28% up to 60%.

SDCG [124] (② in Figure 8) takes an opposite approach by designating the JIT compiler as the trusted process. The code cache is stored in shared memory, mapped as RX in the original process and WR in the trusted dynamic code generation process. This setup keeps the code cache read-only in the untrusted process, allowing the JIT compiler to perform code generation, installation, patching and deletion securely. The authors extend Lobotomy’s design [70] with a separate process that performs these additional functions beyond JIT compilation. To maintain transparency, wrappers allow the trusted process to be invoked via remote procedure calls. SDCG employs a delegation-based sandbox architecture to intercept system calls related to virtual memory management, enforcing three key policies: (1) memory cannot be mapped as both writable and executable, (2) when mapping a region as executable, the base address and size must be provided by the trusted process and the memory is always mapped as RX, and (3) permissions on non-writable memory cannot be altered. Applied to V8, SDCG incurs approximately 11% overhead while isolating the dynamic code generation process.

An industrial application of process isolation without sandboxing was introduced with *Arbitrary Code Guard* (ACG) in Microsoft Edge [49]. When ACG is applied to a Microsoft Edge process, it prevents the allocation of new executable memory or modification of existing executable memory. Since this conflicts with JIT compilation, the JIT compilation process is separated from the content process. The isolated JIT process receives JavaScript bytecode from the content process and sends back compiled, executable code.

**Summary and Discussion:** The concept of process separation aims to protect the system from attackers attempting to inject malicious code by clearly isolating processes and their accesses to memory. While it protects against code injection, it is ineffective against data-only attacks where an attacker does not need to break out of the current process. Additionally, it does not provide adequate protection against Return-Oriented Programming (ROP) attacks, both with and without a scripting environment [49]. In contrast, using sandboxing effectively isolates the JIT compilation process and/or dynamically generated code, but it comes with significant instrumentation costs and can induce overhead of over 50% in the worst cases.

### 4.3.2 System Call Filtering and Control-Flow Integrity.

**System Call Filtering:** Before resorting to precise Control-Flow Integrity (CFI) [1] for enforcing program control flow, simpler instruction filtering mechanisms can prevent critical instructions from executing. At the process level, JITSec [40] decouples sensitive from non-sensitive code (system calls from normal function calls). A kernel module blocks sensitive code execution from writable memory pages by interposing between user processes and the system call handler. It uses a small trampoline of assembly code that (1) stores register contents on the stack, (2) calls a policy-enforcing monitor function, and (3) restores the registers. In step (2), it checks the return address of the system call against stack and heap memory; if it is found there, the system call is replaced with a call to exit. This solution, which incurs minimal performance costs, effectively filters system calls like Linux seccomp [46] but does not prevent code-reuse attacks.

**Control-Flow Integrity:** As defined by Abadi et al. [1], CFI is a technique that defines security policies that software execution must follow and comply with a predefined Control-Flow Graph (CFG). This CFG is defined ahead of time through analysis methods such as source code analysis, binary analysis, or execution profiling. CFI requires that, at run time, whenever a machine code instruction transfers control, it jumps to a valid destination determined by the CFG. If most instructions target a constant destination, the verification can be performed statically. However, for computed transfers, a dynamic check needs to be performed to ensure the validity of the
destination. The checks should be performed on both *forward-edge* (i.e., calls, jumps) and *backward-edge* (i.e., returns) transfers in the call graph. Typical solutions enforce these checks at runtime using **tags** and **runtime checks** for *forward-edges*, or **shadow stacks** [22] for *backward-edges*. The tag/check accompanies the constructed CFG to verify that a control-flow branch is correct. A shadow stack copies the return addresses on calls and checks the runtime one against the saved one on returns, ensuring they have not been tampered with by an attacker. The granularity of the solution depends on the precision of the branch identification and, therefore, the precision of the CFG.

Porting this type of solution to dynamically generated machine code, such as JIT-compiled code, is a challenge, especially due to the performance impact that CFI enforcement techniques usually incur. Niu et al. present RockJIT [96], a solution enforcing modular control-flow integrity (MCFI) [95] for JIT compilers. MCFI is a CFI technique that allows modules to be independently instrumented and linked statically or dynamically. They address the security aspects of both the JIT compiler (static code) and the JIT code region (dynamic code) with different levels of CFI enforcement. In the first case, the JIT compiler is compiled and instrumented to generate an MCFI module. RockJIT then generates a CFG based on information in the module and constructs MCFI tables for later use that define this graph before launching the execution. For the VM, the CFG generation strategy is relatively *fine-grained* and performed offline. In comparison, the CFG for the JIT code is *coarse-grained*, as it compares information emitted by the compiler at indirect-branch generation time against a set of sane targets. To determine if the destination of a control-flow transfer is sane, RockJIT uses a verifier that maintains three address sets populated at machine code generation time: start addresses, indirect branch targets, and direct branch targets. These sets are updated after code installation, deletion, or modification. This way, the JIT compiler itself runs under a fine-grained CFI policy while the JIT code operates under a coarse-grained policy (to reduce the impact on a performance-critical component). The authors argue that this level of protection is sufficient to safeguard JIT code, as it cannot contain dangerous instructions such as system calls. Overall, the solution incurs around 15% of performance overhead.

<figure><img src="image_10.png" /><figcaption>Fig. 9. Overview of CFI solutions for VMs. On the left, static code uses a fully precise CFG and a shadow stack for *forward- and backward-edge CFI*. On the right, dynamic JIT code employs a degraded version of a CFG through address sets or tags for *forward-edge protection*.</figcaption></figure>
The authors of JITScope [146] present a similar idea of mixed-level CFI enforcement. The solution uses type information from the source code to deploy fine-grained CFI on the statically compiled code. For each function, it computes an ID derived from the function type information and instruments a CFI security check to match the transfer target ID against the expected ID. For the JIT code, a JavaScript delegate called CodeGen is added to instrument a random ID at compile time, before generating the machine code for an input function. When the JIT compiler is triggered to generate an indirect call or jump, the delegate instruments CFI security checks in the generated code before the call or jump. It validates the existence of the ID before the target function and ensures the transfer target is a valid JIT function entry. In addition to *forward-edge* transfer protection, *backward-edge* transfers are protected with a shadow stack. At each function entry, the return address of the current function frame is stored. Upon function exit, it checks the return address of the main stack against the one stored in the shadow stack. If they do not match, the stack has been tampered with, and the program raises an exception. The shadow stack is located in a separate memory region, indexed by a dedicated segment register (available on the x86 architecture and not accessible through normal instructions). A new stack is also built for each thread, using thread-local storage to guarantee a thread-safe shadow stack. Overall, the solution adds around 10% of performance overhead.

**Summary and Discussion:** Both solutions are summarized on Figure 9. The static code is protected using a CFG and a shadow stack. Although the dynamic code is also protected with a shadow stack, it is not possible to extract a precise CFG at runtime; therefore, a degraded version of the CFG is used, either through function tagging or a restricted set of possible addresses. Overall, CFI enforcement solutions protect against complex attacks but usually add significant performance overhead to performance-critical components. They provide clear containment of untrusted code. While a fine-grained CFI solution is always necessary for the static code of the VM, the performance impact of a lighter solution on the dynamic (JIT-generated) code hinders its application. The typical security/performance tradeoff is challenging to balance in this case and can be managed with hardware-assisted solutions.

## 4.4 Hardware-Enforced Isolation

Modern computing platforms are now diverse in terms of architectures, software provisioning models, and the types of applications they support. Data confidentiality and integrity are concerns not only from attacks over the network but also from those originating from software or hardware components on the same platform. This requirement arises from systems hosting multiple mutually untrusted software components that share a cloud platform, or from code and data belonging to different users. It extends to any computation involving security-critical components or sensitive data. Solutions to these memory isolation requirements come at several levels: complete isolation in the form of **Trusted Execution Environments (TEEs)**, lightweight permission changes with **memory protection keys**, and **hardware-assisted CFI**. Hardware-assisted technologies have been integrated into modern ISAs, and we present the application of these techniques to a JIT compiler as documented in the literature.

### 4.4.1 Trusted Execution Environments.

**Motivation:** The first hardware-supported memory isolation solution is the Trusted Execution Environment (TEE). Implementations vary in their security goals. Schneider et al. [117] categorize the protections provided by TEEs into four levels: (1) verifiable launch of the execution environment for sensitive code and data, allowing verification by a remote entity; (2) run-time isolation to enforce confidentiality and integrity of sensitive code and data; (3) trusted I/O to enable secure access to peripherals and accelerators; and (4) secure storage for TEE data that must be stored persistently and accessed later only by authorized entities. Main industry and research implementations are reviewed in the same article, covering their respective capabilities.
Intel Software Guard eXtension (SGX) [66, Vol.3 Ch.34-39] is a TEE integrated into Intel CPUs, first introduced in 2015 with the sixth generation of microprocessors. SGX enables isolated execution environments called enclaves, encrypted regions within a user-mode process that cannot be accessed by any system entity, even those with higher privileges. The CPU decrypts the enclave on the fly using an enclave-specific key, and, before loading the enclave memory, SGX verifies its integrity to ensure that its contents have not been tampered with.

JITGuard [48] authors based their defense solution around an SGX enclave, an overview of the design is presented on Figure 10. Since interacting with the contents of an SGX enclave adds considerable overhead, especially through context switches, isolating the whole JIT engine along with the JIT code region in an enclave results in a huge amount of performance overhead. In particular, since the JIT code frequently interacts with static code and is called transparently, it cannot reside in an enclave. Therefore, JITGuard adds three important security principles to its design: (1) isolate the JIT compiler and its data in an enclave, (2) randomize addresses of the JIT code and JIT stack memory, and (3) build an indirection layer for trampolines (transitions between JIT and static code).

<figure><img src="image_11.png" /><figcaption>Fig. 10. Overview of the JITGuard solution. The JIT compiler and intermediate representation are stored within a hardware enclave along with a randomization key, which is used to locate the JIT code region in memory. Trampolines (T) and a segmentation register (R) facilitate faster access to the JIT code region for other VM components.</figcaption></figure>

During JITGuard initialization, two primary memory regions are allocated: one for trampolines and another for the JITGuard region (containing JIT code, JIT stack, and a writable mapping of trampolines). The enclave (1) is then launched, holding both the JIT compiler and a secret pointing to the JITGuard region location. The randomization (2) of the JITGuard region is enforced by having all memory accesses for JIT code generation, modification, or deletion mediated through the enclave. For trampoline indirection (3), achieved via a double-mapping, one region is executable while the other is writable, accessed only by the JIT compiler and kept hidden within the JITGuard region. To facilitate faster interactions between static and JIT code, trampolines (T in the figure) can quickly access JITed functions without requiring an enclave transition each time. This indirection leverages an offset over a segmentation register (R in the figure), whose contents can only be accessed via a system call. Both segment memory regions are protected, as they can only be reached through the arch_prctl system call, restricted in JITGuard threat model, or the rdgsbase instruction, disabled by default on Linux. To further isolate JIT code from static code, JITGuard uses a dedicated stack located within the JITGuard-region. In total, JITGuard's full solution incurs a performance overhead of 9.8%.

**Discussion:** While TEEs offer a promising solution for secure execution, Intel SGX was discontinued in 2021 due to several discovered vulnerabilities, particularly those involving side-channel attacks. For instance,
the SGXe attack [130] leverages transient execution to recover SGX attestation keys from fully updated Intel machines trusted by the attestation server. Additionally, SmashEx [35] demonstrated that SGX asynchronous exception handling mechanism is vulnerable to re-entrancy attacks.

### 4.4.2 Memory Protection Keys.

**Motivation:** Memory Protection Keys (MPK) provide efficient and secure in-process isolation by extending page-based memory permissions. They allow permission changes on memory ranges without requiring slow kernel-level page table modifications. They come as efficient in-process permission changes. Implementations vary in the number of keys, types of permissions, and memory region granularity they support [60, 118, 128, 142].

Intel *Memory Protection Keys (MPK)* [66, Vol.3 Sec.4.6] use 4-bit keys stored in page-table entries, allowing for up to 16 distinct domains per process. Permissions are changed via an unprivileged instruction that writes to the thread-local *User Page Key Register (PKRU)*, enabling fast, user-space domain switching. The Linux kernel supports Intel MPK (since v4.6) with three system calls: `pkey_alloc`, `pkey_free`, and `pkey_mprotect`, while maintaining a bitmap to track allocated keys. More recent implementations [60, 101, 118, 128, 142] suggest that Intel MPK alone does not guarantee secure in-process isolation and needs to be paired with mechanisms like CFI or binary scanning for enhanced security.

The authors of `libmpk` [101] extend Intel MPK with a software abstraction layer to virtualize protection keys, applying their solution to three JavaScript VMs (SpiderMonkey, ChakraCore, and V8) to enforce W^X on JIT-generated code. Their goal is to protect the VM from race conditions [124]. They demonstrate two enforcement techniques: one key per page, which uses MPK as a faster, context-free replacement for permission switches; and one key per process, which secures the entire JIT code region under a single protection key, ensuring only one thread has access to the key at any given time.

<figure><img src="image_12.png" /><figcaption>Fig. 11. Overview of the NoJITsu design. Each data and code element used by the VM is attributed restricted permissions by default. Intermediate representation are read-only, as well as base objects. MPKs are used to switch the permissions of the JIT code from X to W when the JIT compiler is triggered (green dotted line).</figcaption></figure>

NoJITsu [102] extend the usage of protection keys, and the authors design their solution around Intel MPK. An overview of the solution is provided in Figure 11. They compartmentalize the JavaScript engine along three axes: (1) separating JIT data from JIT code and assigning different permissions to each, (2) adding a routine to enable temporary accesses through combined calls to `set_pkru`, `write_pkru`, and `recover_pkru` to modify
the value of PKRU, and (3) isolating sensitive objects, including sensitive types (e.g., script, shape, function) and primitive types (e.g., scalar and array data types), from other objects. Their strategy emphasizes fine-grained isolation of critical elements. All memory write instructions are instrumented to apply the appropriate run-time memory permissions based on the data types accessed. Instrumentation targets are identified through dynamic object-flow analysis, which detects accessor functions—functions that directly modify JavaScript objects. These accessor functions are categorized into four types: *member accessors*, which are member functions of a JavaScript object class that write to private variables; *payload accessors*, which update the actual payload of a JavaScript object; *initialization accessors*, which initialize JavaScript objects; and *garbage collection accessors*, which update metadata to facilitate garbage collection. Overall, the solution incurs a moderate performance overhead of 5%, while guaranteeing strong compartmentalization.

**Discussion:** Memory Protection Keys enable fine-grained isolation at a small cost in performance. However, several issues prevent the widespread usage of Intel implementation. First, the built-in number of keys is not sufficient for fully-fledged applications (such as OpenSSL [101] or persistent memory objects [142]) and use-after-free vulnerabilities have been discovered on the mechanism. Other implementations make up for these issues and provide comparable capabilities [60, 118, 128, 142] to enforce strong properties on the JIT code region.

### 4.4.3 Hardware-Assisted Control-Flow Integrity.

**Motivation:** Ever since Control-Flow Integrity (CFI) was presented by Abadi et al. [1], a wide range of solutions emerged in different forms, either software-only, hardware-assisted, or hardware-only. Authors of the FineIBT proposal [50] categorize and present the different solutions in more details. Its acceleration in hardware and integration in modern processors and compilation chains makes it an important security measure to consider for a reduced performance cost.

Intel Control-Flow Enhancement Technology (CET) [66, Vol.1 Ch.17] is an extension, available in modern Intel CPUs, for assisting with control-flow confinement. CET adds a shadow stack placed in an isolated memory region to handle backward-edge control-flow transfers. The memory area where shadow stacks are stored guarantees its integrity by disallowing memory stores (executed from any ring) in the shadow stack pages. The only possible write is through a dedicated instruction wrss during legal rewriting of return addresses (e.g. a C++ exception). This region is managed through a new register named %ssp. For each regular stack, CET adds a corresponding shadow stack in this region. On each call instruction, the return address is pushed both to the base call stack and the corresponding shadow stack in this area. When a corresponding ret instruction is executed, the addresses from both stack are popped and compared. An exception is raised if a mismatch is noticed. CET also defines Indirect Branch Tracking (IBT), adding landing pads for forward-edge control-flow transfers. It adds a new instruction, named endbr, which becomes the only valid target for indirect call/jmp instructions.

A framework for memory isolation, called CETIS [141], is developed as a retrofit to CET. CETIS uses an isolated shadow-stack region as a memory area, accessible only by trusted code using the wrss instruction. The framework introduces a new memory file structure, a cmfile, where each new isolated region is abstracted as a file. Each cmfile can be accessed in read/write or append mode, and a complete API is provided for interaction. Notably, the addition of this isolated memory region in the shadow stack pages does not interfere with traditional shadow stack usage. For a VM and its JIT compiler, a dedicated cmfile stores the code cache contents, which the VM updates in append mode as it installs recompiled functions. CETIS is applied to ChakraCore, where the JIT compiler protects the JIT code cache using the mprotect() system call. The compiler first emits JIT code fragments into a temporary buffer, copies them into the code cache with RW permissions, then switches permissions to RX via mprotect(). CETIS appends the emitted JIT code directly to a cmfile through its API and ensures the integrity of the temporary buffer via checksum computation. Using the CET shadow-stack on the
JIT code adds an 11.08% performance overhead. Using CETIS in read/write mode adds a 10.59% performance
overhead, and 8.53% in append mode.

**Discussion:** CETIS retrofits hardware-assisted CFI features available on the latest Intel processors. It deploys intra-process isolation of JIT code, alongside CFI verifications for backward edges using the built-in shadow stack and coarse-grained forward-edge verifications via landing pads provided by Intel CET. The performance overhead remains moderate for achieving strong isolation of JIT code. Additional care is needed to isolate intermediate representations (such as JIT IR and bytecode) and the static code of the VM from data-only attacks.

# 5 Summary and Discussion

This section summarizes the article content by presenting a taxonomy of the various defense solutions discussed, along with the relevant benchmarks, platforms, and associated performance impacts. Additionally, we outline the main challenges faced by JIT compilers and provide recommendations drawn from the literature.
<table><caption>Table 1. Solutions are categorized by implemented features (marked by ●) and sorted chronologically. Major web browser updates, including sandboxing (circa 2011), systematic constant blinding and NOP insertion (circa 2014), as well as W^X, stricter permissions enforcement, and location randomization (circa 2015), are labeled with ○. For mitigations building on existing features, ⊙ is used. Hardware-assisted modules are noted with a † subscript. While sandboxing solutions also handle syscall filtering and provide process isolation, a separate column highlights their unique mechanisms [53, 84, 86, 124].</caption><thead><tr><th rowspan="2">Category</th><th colspan="3">Diversification</th><th colspan="3">Memory Permissions</th><th colspan="5">Capability Containment</th><th rowspan="2">VM</th><th rowspan="2">Benchmark</th><th rowspan="2">Performance Impact</th></tr><tr><th>Name and Year</th><th>Intra-instruction randomization</th><th>Code layout randomization</th><th>Location Randomization</th><th>Transient Protection</th><th>Restricted Permissions</th><th>IR Isolation</th><th>Process Isolation</th><th>syscall Filtering</th><th>Sandboxing</th><th>JIT code CFI</th></tr></thead><tbody><tr><td>INSeRT [135]<br>2010</td><td>•</td><td>•</td><td></td><td></td><td></td><td></td><td></td><td></td><td>•</td><td></td><td></td><td>V8</td><td>SunSpider</td><td>&lt;5%</td></tr><tr><td>JITSec [40]<br>2010</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>-</td><td>syscall execution</td><td>1.84%</td></tr><tr><td>JITDefender [27]<br>2011</td><td></td><td></td><td></td><td></td><td>•</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Tamarin<br>V8<br>JavaScriptCore</td><td>Grande<br>Octane<br>Octane</td><td>0.9%<br>0.5%<br>0.1%</td></tr><tr><td>NaCl [4]<br>2011</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>•</td><td>•</td><td>•</td><td>V8<br>Mono</td><td>Octane, SunSpider<br>SciMark</td><td>28% / 60% (64)<br>2% / 21%</td></tr><tr><td>RIM [139]</td><td>•</td><td>•</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>○</td><td></td><td>Tamarin</td><td>jsbench</td><td>&lt;1%</td></tr><tr><td>JITSafe [28]<br>2013</td><td>•</td><td>•</td><td></td><td></td><td>•</td><td></td><td></td><td></td><td></td><td>○</td><td></td><td>Tamarin</td><td>Grande</td><td>2.8%</td></tr><tr><td>librando [63]<br>2013</td><td>•</td><td>•</td><td></td><td></td><td>•</td><td></td><td></td><td></td><td></td><td>○</td><td></td><td>HotSpot<br>V8</td><td>CLBG<br>SunSpider</td><td>15%<br>250%</td></tr><tr><td>Lobotomy [70]<br>2014</td><td>○</td><td>○</td><td></td><td></td><td></td><td></td><td>•</td><td>•</td><td></td><td>○</td><td></td><td>SpiderMonkey</td><td>Browser usage</td><td>27% →387%</td></tr><tr><td>RockJIT [96]<br>2014</td><td>○</td><td>○</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>○</td><td>•</td><td>V8</td><td>Octane</td><td>14.6%</td></tr><tr><td>Readactor [34]<br>2015</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td></td><td></td><td></td><td>○</td><td></td><td>V8</td><td>Octane</td><td>7.8%</td></tr><tr><td>- [69]<br>2015</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td></td><td></td><td></td><td>○</td><td></td><td>JikesRVM</td><td>DaCapo 2009</td><td>6% →50%</td></tr><tr><td>SDCG [124]<br>2015</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td></td><td>•</td><td>•</td><td>○</td><td></td><td>V8</td><td>Octane</td><td>5% →11%</td></tr><tr><td>JITScope [146]<br>2015</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td></td><td>•</td><td></td><td>○</td><td>•</td><td>SpiderMonkey</td><td>Octane, SunSpider</td><td>9.51%</td></tr><tr><td>libmask [68]<br>2016</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td></td><td></td><td></td><td>○</td><td></td><td>V8</td><td>Octane</td><td>5.31%</td></tr><tr><td>Dachshund [86]<br>2017</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td></td><td></td><td></td><td>○</td><td></td><td>V8<br>ChakraCore</td><td>Octane<br>Octane</td><td>21%<br>25%</td></tr><tr><td>JITGuard [48]<br>2017</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>•↑</td><td>•↑</td><td>•</td><td>○</td><td></td><td>SpiderMonkey</td><td>SunSpider</td><td>9.8%</td></tr><tr><td>libmpk [101]<br>2019</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td></td><td>•↑</td><td></td><td>○</td><td></td><td>SpiderMonkey<br>ChakraCore<br>V8</td><td>Octane<br>Octane<br>Octane</td><td>-4.75%<br>-31.1%<br>0.8%</td></tr><tr><td>NoJITTu [102]<br>2020</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>•</td><td>•</td><td></td><td>○</td><td></td><td>SpiderMonkey</td><td>LongSpider</td><td>5%</td></tr><tr><td>CETIS [141]<br>2022</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>○</td><td>•↑</td><td>•↑</td><td>•↑</td><td>○</td><td>•↑</td><td>ChakraCore</td><td>Octane</td><td>8.5% →11.1%</td></tr></tbody></table>
**Taxonomy of attacks and defenses:** Table 1 presents the primary defense solutions deployed around VMs, categorized by their main defense mechanisms. These solutions are listed chronologically, highlighting advancements in obfuscating attack vectors, restricting permissions, containing capabilities, and enforcing control-flow integrity. Some have since been integrated into VMs, as shown in the table. The latest hardware-assisted solutions offer the strongest security guarantees with moderate performance costs. Most solutions have primarily been implemented in JavaScript VMs within modern web browsers, with performance commonly measured by the now-dePRECATED Octane benchmark. All solutions are focused on the x86/x64 instruction set. Some hardware-assisted solutions even outperform the existing implementations in VM, such as the implementation of libmpk over the mprotect base usage.

**Challenges of JIT Compilation:** Several design decisions make adopting new security solutions challenging. The core requirement of JIT compilation—having memory regions both writable (for JIT-compiled code installation) and executable—clashes with W^X principles. Performance optimizations for dynamically-typed languages introduce layers of optimizations and runtime type guards, adding complexity. Efficient JIT tuning is essential for high performance, and deoptimization mechanisms are necessary when type checks fail, complicating control flow. Accurate Control-Flow Graph generation for JIT code requires significant runtime code scanning, which is rarely implemented. The JIT compiler may recompile “hot” paths at various granularities (traces, functions, or basic blocks), mixing code and data, potentially conflicting with Data Execution Prevention (DEP) principles. Overall, the complexity of JIT compilers increases their vulnerability potential, risking severe impacts on the VM and its users.

**Recommendations:** Based on incremental implementations of defense solutions, we present recommendations for effectively protecting a VM. Constants and immediate values, which can conceal malicious payloads, should be effectively blinded [86]. Additionally, JIT data should be separated from JIT code and assigned more restrictive permissions [102]. Obfuscating JIT code through randomized layouts (e.g., NOP insertion) and randomized memory locations reduces the predictability of injected code. Reinforcing W^X for JIT code is essential, preferably through a double mapping of the region, to avoid race condition attacks. Sandboxing techniques should sanitize JIT code prior to execution to provide robust containment. A precise Control Flow Integrity (CFI) technique should be applied to the static VM code, with at least a coarse-grained version for JIT code [96, 146]. Additionally, the VM internal data structures, intermediate representations (IR), and sensitive objects should be safeguarded with restrictive permissions [48, 102].

While these recommendations may seem extensive, deploying hardware-assisted modules in the latest processor architectures helps meet high-performance requirements. In practice, obfuscation techniques, JIT code protection, and sandboxing are already implemented in modern browsers to varying degrees [41, 53, 84, 92, 93]. Hardware-assisted CFI solutions are integrated into V8 [115] using ARM (GCS, BTI, PAC) and Intel (CET, IBT) CFI solutions. Fine-grained CFI is essential for accurate JIT code execution, and the FineIBT proposal [50] strengthens restrictions through the OS.

# 6 Open Possibilities with RISC-V

## 6.1 RISC-V Presentation

**Modularity:** RISC-V [111, 112] is an Instruction Set Architecture (ISA) that is gaining significant attention from both academia and industry. Its modularity lies in the extensions built over the base RV32I (Integer) instruction set. The most common extensions, necessary to run a fully-featured operating system, include RV64IMAFDC. These represent 64-bit instructions (RV64I, a superset of RV32I), multiplications and divisions (M), atomic instructions (A), compressed instructions (C, similar to ARM Thumb-2), single-precision floating-point operations (F), and double-precision floating-point operations (D). Writing an application supported by a specific set of extensions
enables compatibility with broader configurations as well. Overall, several extensions are available (finalized, such as the J extension for dynamically translated languages, or pending ratification), and some open to proposals.

**Extensibility:** RISC-V is an extensible ISA, allowing custom instructions to be added to the base set due to its generous opcode space. This extensibility supports specialized functionalities; for example, the PULP platform [109] focuses on energy efficiency and incorporates custom instructions to enhance core heterogeneity and machine learning tasks. In addition to room for new instructions, the RISC-V standard designates "hint" instructions for custom use. These hints are variations of existing instructions with restricted arguments and act as no-ops (semantic NOPs) on cores that do not implement them. While the standard use of hints has not yet been finalized, 15 hint instructions are reserved in the RV32I instruction set. Eventually, they may support features like memory-system locality (spatial and temporal), branch prediction, security tags, or instrumentation flags, while remaining fully compatible with cores that do not support them.

**Open-source:** Another significant aspect of the RISC-V ISA is its open-source nature. Several core RTL implementations of application-class processors are available open-source, including Rocket [10], BOOM [11], Shakti [51] and CVA6 [145]. These cores differ in their design approaches and supported extensions, yet all are accessible to developers alongside the core RISC-V toolchain (compiler, simulator, and tests). A comparison between these open cores has been conducted by Dörflinger et al. [44]. This ecosystem makes it a promising candidate for advancing flexible and customizable computing.

## 6.2 RISC-V Attacks

Even though the RISC-V ISA is relatively new, traditional attacks can still be adapted and extended for it. For instance, spraying was found to be more challenging on ARMv7 due to constraints like alignment requirements [77]. However, the authors developed a method to consistently spray instructions in memory [78], paving the way for new attacks on non-x86 architectures.

Cloosters et al. [32] categorize five elements that make it more challenging to determine ROP gadgets on RISC-V and ARM64 (rather than x86 or even ARM32): (1) The program counter register is not a general-purpose register; (2) There is no stack-based return instruction; (3) The arguments are passed to functions via dedicated registers rather than via the stack; (4) Memory alignment prevents execution of hidden unaligned instruction sequences; and (5) Long function prologue/epilogue sequences introduce side effects.

The authors of RiscyROP [32] introduce a gadget-finding and chaining tool based on symbolic execution, built on the angr framework [121], which effectively addresses the challenges of finding ROP gadgets on RISC-V. RiscyROP scans through binaries to extract interesting gadgets and their associated information, then chains them into a Return-Oriented Programming (ROP) sequence capable of handling attacker-supplied data. Gilles et al. [54] present a Jump-Oriented Programming (JOP) attack designed to perform system calls on systems with a limited attack surface, specifically supporting the RV32I architecture. They identify gadgets that end with indirect jumps to create a JOP chain (similar to a ROP chain but ending with jalr instead of ret) that sets up three argument registers to target a private key, its size, and a file descriptor before executing a write system call. Additionally, traditional attacks have been ported to RISC-V, including ROP [59] and alphanumeric encoding [15], highlighting the need for careful consideration in its implementation. Jaloyan et al. [67] extend the Galileo algorithm [120] to consider not only the main execution path but also a *hidden* execution path. They introduce two hidden instruction patterns: overlapping 32-bit instructions, formed by using the last 16 bits of one instruction and the first 16 bits of the next, and two compressed 16-bit instructions that together constitute a valid 32-bit instruction. This mechanism allows attackers to conceal a backdoor in a RISC-V binary.

While few JIT compilers have been ported to RISC-V [45, 74], advanced attacks against the ISA already exist and should soon exploit the corresponding JIT compilers. As more VMs are ported to RISC-V [75] and the traction this architecture is gaining, defenses must be available on it.
## 6.3 RISC-V Defenses

While the flexibility of the RISC-V ISA has allowed a plethora of solutions to develop, mainly summarized in a survey by Tao Lu [82], we focus our interest towards hardware-assisted run-time protections to draw parallels with industrial applications present in Intel and ARM latest architectures. This type of solution provides real-time defenses, enabling enhanced control flow integrity (CFI) and isolation mechanisms directly supported by the hardware.

**Hardware CFI:** For backward-edge Control Flow Integrity (CFI), shadow stacks have been implemented through Intel Control-flow Enforcement Technology (CET) [66, Vol.1 Ch.17] and ARMv9 Guarded Control Stack (GCS) [9]. These implementations define a dedicated space for the shadow stack and introduce new instructions for interacting with it. RISC-V has explored similar solutions in the form of co-processors, such as PHMon [43] and FIXER [39], which react to *calls* and *returns* while verifying the correspondence between the real return address and the duplicated shadow stack. RIMI [72] operates within the Physical Memory Protection (PMP) module specified in RISC-V, attributing instructions to their corresponding address ranges.

For forward-edge CFI, Intel Indirect Branch Tracking (IBT) [66, Vol.1 Ch.17] and ARMv8 Branch Target Identification [7, Ch.B6] have added support for landing pads, which ensure that only valid branch targets are executed. RISC-V provides landing pads through co-processors, including a modified version of PHMon [23], which works in tandem with μCFI [64], a landing pad extractor. Bratter [100] utilizes “hint” instructions to encode tags in basic blocks and defines custom instructions for runtime verification of these tags. Hints are reserved opcodes in standard instructions that allow for customization without affecting functionality in non-implementing cores.

Additionally, a RISC-V specification [113] has been ratified to support both shadow stacks and landing pads, which aim to accelerate control-flow graph (CFG) verification in dedicated hardware. Various levels of CFG precision are employed, either enforcing control-flow changes at the function or basic block level or utilizing a complete function CFG. All solutions require binaries compiled with additional information, with more precise solutions potentially increasing compilation time.

**Memory tagging:** To enforce temporal and spatial memory safety, ARMv8.5 introduces the Memory Tagging Extension (MTE) [6], which defines a separate memory region to store one-to-one tag associations with data. At compile time, binaries are instrumented with associated “colors” to partition memory into isolated regions [79]. This concept is implemented in TIMBER-V [136] on the RISC-V ISA, which provides tagged memory to define isolated regions called enclaves. TIMBER-V separates a *root* region, a *supervisor* region, and an *untrusted* application region. It features a trust manager named TagRoot, which bootstraps the enclaves and ensures isolated execution. While the memory overhead is low due to shared memory reuse among enclaves (achieved through stack and heap interleaving), the performance penalty associated with this isolation averages around 25%.

**Pointer extensions:** The extension of pointers allows for authentication and signing, thereby strengthening control-flow integrity (CFI). A Pointer Authentication Code (PAC) is computed before a control-flow change using a lightweight algorithm. Given that the actual address space in 64-bit architectures uses less than 64 bits, the PAC is stored in the unused portion of the address. ARMv8 implements pointer authentication for indirect jumps through PAC [7, Ch.B6] and for return addresses through PAC-RET [7, Ch.B6]. It introduces instructions that compute the PAC based on the address and a context, which may include the processor state or values from the stack. On RISC-V architectures, RetTag [132] ensures return address integrity through PAC authentication, while Zipper [76] chains return addresses using Message Authentication Codes (MACs). These solutions establish integrity through pointer signing rather than duplicating the call stack. Calls and returns are instrumented with encryption/decryption of the signature, and the performance overhead is directly tied to the hardware implementation of this mechanism.
**Capabilities:** Adding more metadata to pointers, "fat pointers" are defined, embedding multiple authentication values and access ranges directly within the pointers themselves. The SHAKTI-MS [37] RISC-V processor extension enforces memory safety by incorporating a Stack Frame Cookie (SFC) unique to each function, a Read-Only Data Cookie (RODC) to protect the program read-only memory segment, and a base/bound pair that determines the maximum permissible range for pointer access. Capability Hardware Enhanced RISC Instructions (CHERI) [133, 134] extend fat pointers further by including both a base and an offset to define the maximum bounds of memory access. They also associate permissions with the pointer, such as fetching instructions, loading/storing data, and loading/storing capabilities. Each physical memory location that can hold a capability is tagged with a hidden validity indicator, signifying the presence of a valid capability. CHERI is now ported in dedicated toolchains for both ARMv8 and RISC-V ISAs [133], offering robust security guarantees, at a significant cost in code size overhead.

**In-process isolation:** Intel Memory Protection Keys (MPK) [66, Vol.3 Sec.4.6] were discussed in previous sections. The RISC-V variant, SealPK [42], addresses use-after-free errors found in Intel implementation and introduces a co-processing unit capable of managing up to 1,024 keys. It stores these keys in the reserved bits of Page Table Entries (PTE) and tracks memory pages at the OS level. Additionally, Schrammel et al. propose Domain Keys for in-process isolation (Donky) [118], which builds on the MPK support in the Linux kernel. This framework creates a secure user-space software environment that monitors domain permissions, complemented by a lightweight hardware extension that stores various domain policies using a dedicated Control and Status Register (CSR). RIMI [72] defines duplicated memory access instructions, linking each to different address ranges through the RISC-V Physical Memory Protection (PMP) module to enforce domain isolation effectively. Each defined domain can only access its corresponding data section, and switches between domains are monitored via dedicated instructions.

## 6.4 Discussion

As ARM and RISC-V attacks [32, 67, 77, 78] have proven to be as effective, though often more complex than their x86 counterparts, it is anticipated that similar security challenges will emerge as VMs become more common on these architectures. While equivalent defenses should prioritize diversification and strict memory permissions, RISC-V offers a promising platform for prototyping hardware-based solutions that could enable fine-grained isolation, enforce CFI at a lower cost, or define pointer capabilities. Run-time, hardware-enforced security solutions are implemented at different levels of the core pipeline through integrated primitives and co-processors. Their impact can be evaluated based on performance costs to the protected program, the invasiveness of modifications to the software layer, and the area of the co-processor. In the context of JIT compilers and corresponding JIT code regions, code instrumentation is abstracted behind dynamic generation, allowing direct use of custom instructions. This raises the question of which instructions would be beneficial for a JIT compiler and a VM. The RISC-V J extension [113] provides initial insights into how garbage collection or dynamic typing might benefit from custom instructions [83].

## 7 Conclusion

In this survey on software-based attacks targeting JIT compilers and VMs, we have identified three primary classes of attacks: code injection, code reuse, and data-only attacks. They exploit the complex interactions among core VM components and their dynamic behavior, often resulting in arbitrary code execution on users' systems—for example through malicious web pages exploiting vulnerable JavaScript processing in web browsers. The JIT code region is particularly sensible, as it generates executable code at runtime. Additionally, the intermediate representations and internal data structures of the VM remain vulnerable to tampering. In summary, securing VMs and their JIT compilers demands a careful balance between performance and protection. Defensive strategies
have evolved to include enhancements in obfuscating attack vectors, access control, and code integrity, with the strongest guarantees provided by hardware-assisted solutions. However, challenges remain, as JIT compilation inherently conflicts with security principles like W^X and requires complex optimization and control-flow management, which can introduce vulnerabilities. Mitigating these risks effectively requires a layered approach, including obfuscation, memory randomization, strict permission management, and sandboxing, particularly when reinforced by hardware-assisted modules. Modern browsers demonstrate the feasibility of such protections in JIT environments, achieving robust security with manageable performance trade-offs. Ultimately, a layered approach combining hardware-backed CFI and fine-grained isolation is crucial to securing VMs while accommodating the demands of JIT optimization and dynamic execution. As ARM and RISC-V architectures face increasingly complex attack vectors, VMs on these platforms are likely to encounter security challenges similar to those on x86. RISC-V's open architecture is promising for prototyping hardware-based defenses, enabling fine-grained isolation, low-cost CFI enforcement, and pointer capabilities directly in hardware. For JIT compilers, custom instructions like those in RISC-V's J or Zicfilp extensions could enhance VM operations and security.

## References

[1] Martin Abadi, Mihai Budiu, Ulfar Erlingsson, and Jay Ligatti. 2009. Control-flow integrity principles, implementations, and applications. *ACM TISSEC* 13, 1 (2009), 1–40. doi:10.1145/1609956.1609960

[2] Salman Ahmed, Ya Xiao, Kevin Z Snow, Gang Tan, Fabian Monrose, and Danfeng Yao. 2020. Methodologies for quantifying (re-)randomization security and timing under JIT-ROP. In *Proceedings of ACM SIGSAC CCS'20*. 1803–1820. doi:10.1145/3372297.3417248

[3] anonymous. 2001. Once upon a free. https://phrack.org/issues/57/9.html Phrack magazine.

[4] Jason Ansel, Petr Marchenko, Ulfar Erlingsson, Elijah Taylor, Brad Chen, Derek L Schuff, David Sehr, Cliff L Biffle, and Bennet Yee. 2011. Language-independent sandboxing of just-in-time compilation and self-modifying code. In *Proceedings of ACM SIGPLAN PLDI'11*. 355–366. doi:10.1145/1993498.1993540

[5] Apple. 2024. Apple JavaScript engine WebKit. https://webkit.org/.

[6] ARM. 2019. *ARM-v8.5-A Memory Tagging Extension*. https://developer.arm.com/-/media/Arm%20Developer%20Community/PDF/Arm_Memory Tagging Extension_Whitepaper.pdf

[7] ARM. 2021. *ARM-v8-M Reference Manual*. https://developer.arm.com/documentation/ddi0553/bw/

[8] ARM. 2021. *TrustZone for AArch64, Version 1.2*. ARM. https://developer.arm.com/documentation/102418/0101/

[9] ARM. 2022. *ARM A-Profile Architecture Developments 2022*. https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/post/arm-a-profile-architecture-2022

[10] Krste Asanovic, Rimas Avizienis, Jonathan Bachrach, Scott Beamer, David Biancolin, Christopher Celio, Henry Cook, Daniel Dabbelt, John Hauser, Adam Izraelevitz, et al. 2016. *The Røket chip generator*. Technical Report. EECS Department, University of California, Berkeley.

[11] Krste Asanovic, David A Patterson, and Christopher Celio. 2015. *The berkeley out-of-order machine (BOOM): an industry-competitive, synthesizable, parameterized RISC-V processor*. Technical Report. EECS Department, Berkeley.

[12] Michalis Athanasakis, Elias Athanasopoulos, Michalis Polychronakis, Georgios Portokalidis, and Sotiris Ioannidis. 2015. The devil is in the constants: bypassing defenses in browser JIT engines. In *Proceedings of NDSS'15*. 13 pages. doi:10.14722/ndss.2015.23209

[13] Michael Backes, Thorsten Holz, Benjamin Kollenda, Philipp Koppe, Stefan Nürnberger, and Jannik Pewny. 2014. You can run but you can't read: preventing disclosure exploits in executable code. In *Proceedings of ACM SIGSAC CCS'14*. 1342–1353. doi:10.1145/2660267.2660378

[14] Michael Backes and Stefan Nürnberger. 2014. Oxymoron: Making Fine-Grained Memory Randomization Practical by Allowing Code Sharing. In *Proceedings of USENIX Security'14*. 433–447. https://www.usenix.org/conference/usenixsecurity14/technical-sessions/presentation/backes

[15] Hadrien Barral, Rémi Géraud-Stewart, Georges-Axel Jaloyan, and David Naccache. 2019. RISC-V: #AlphanumericShellcoding. In *Proceedings of USENIX WOOT'19*. USENIX Association. https://www.usenix.org/conference/woot19/presentation/barral

[16] David Bigelow, Thomas Hobson, Robert Rudd, William Streilein, and Hamed Okhravi. 2015. Timely rerandomization for mitigating memory disclosures. In *Proceedings of ACM SIGSAC CCS'15*. 268–279. doi:10.1145/2810103.2813691

[17] Andrea Bittau, Adam Belay, Ali Mashtizadeh, David Mazières, and Dan Boneh. 2014. Hacking Blind. In *Proceedings of IEEE S&P'14*. 227–242. doi:10.1109/sp.2014.22

[18] Dionysus Blazakis. 2010. Interpreter Exploitation. In *Proceedings of USENIX WOOT'10*. https://dl.acm.org/doi/10.5555/1925004.1925011

[19] Tyler Bletsch, Xuxian Jiang, Vince W Freeh, and Zhenkai Liang. 2011. Jump-oriented programming: a new class of code-reuse attack. In *Proceedings of ACM ASIACCS'11*. 30–40. doi:10.1145/1966913.1966919
[20] Erik Bosman and Herbert Bos. 2014. Framing signals - A return to portable shellcode. In Proceedings of IEEE S&P'14. 243-258. doi:10.1109/SP.2014.23

[21] Tegan Brennan, Nicolás Rosner, and Tevfik Bultan. 2020. JIT leaks: inducing timing side channels through just-in-time compilation. In Proceedings of IEEE S&P'20. 1207-1222. doi:10.1109/sp40000.2020.00007

[22] Nathan Burow, Xinping Zhang, and Mathias Payer. 2019. SoK: shining light on shadow stacks. In Proceedings of IEEE S&P'19. 985-999. doi:10.1109/sp.2019.00076

[23] Sadullah Canakci, Leila Delshadtehrani, Boyou Zhou, Ajay Joshi, and Manuel Egele. 2020. Efficient Context-Sensitive CFI Enforcement through a Hardware Monitor. In Proceedings of DIMVA'20. 259-279. doi:10.1007/978-3-030-52683-2_13

[24] Silvio Cesare. 2000. Shared library call redirection via ELF PLT infection. https://phrack.org/issues/56/7.html Phrack magazine.

[25] ChakraCore. 2024. ChakraCore. https://github.com/chakra-core/ChakraCore.

[26] Stephen Checkoway, Lucas Davi, Alexandra Dmitrienko, Ahmad-Reza Sadeghi, Hovav Shacham, and Marcel Winandy. 2010. Return-oriented programming without returns. In Proceedings of ACM SIGSAC CCS'10. 559-572. doi:10.1145/1866307.1866370

[27] Ping Chen, Yi Fang, Bing Mao, and Li Xie. 2011. JITDefender: a defense against JIT spraying attacks. In Proceedings of SEC'11. 142-153. doi:10.1007/978-3-642-21424-0_12

[28] Ping Chen, Rui Wu, and Bing Mao. 2013. JITSafe: a framework against just-in-time spraying attacks. IET Information Security 7, 4 (2013), 283-292. doi:10.1049/iet-ifs.2012.0142

[29] Shuo Chen, Jun Xu, Emre Can Sezer, Prachi Gauriar, and Ravishankar K Iyer. 2005. Non-control-data attacks are realistic threats. In Proceedings of USENIX Security'05. 177-191. https://www.usenix.org/conference/14th-usenix-security-symposium/non-control-data-attacks-are-realistic-threats

[30] Xi Chen, Herbert Bos, and Cristiano Giuffrida. 2017. CodeArmor: virtualizing the code space to counter disclosure attacks. In Proceedings of IEEE EuroS&P'17. 514-529. doi:10.1109/eurosp.2017.17

[31] Yue Chen, Zhi Wang, David Whalley, and Long Lu. 2016. Remix: on-demand live randomization. In Proceedings of ACM CODASPY'16. 50-61. doi:10.1145/2857705.2857726

[32] Tobias Cloosters, David Paaßen, Jianqiang Wang, Ouussama Draissi, Patrick Jauernig, Emmanuel Stapf, Lucas Davi, and Ahmad Sadeghi. 2022. RiscyROP: automated return-oriented programming attacks on RISC-V and ARM64. In Proceedings of RAID'22. 30-42. doi:10.1145/3545948.3545997

[33] Mauro Conti, Stephen Crane, Lucas Davi, Michael Franz, Per Larsen, Marco Negro, Christopher Liebchen, Mohaned Qunaibit, and Ahmad-Reza Sadeghi. 2015. Losing control: On the effectiveness of control-flow integrity under stack attacks. In Proceedings of ACM SIGSAC CCS'15. 952-963. doi:10.1145/2810103.2813671

[34] Stephen Crane, Christopher Liebchen, Andrei Homescu, Lucas Davi, Per Larsen, Ahmad-Reza Sadeghi, Stefan Brunthaler, and Michael Franz. 2015. Readactor: practical code randomization resilient to memory disclosure. In Proceedings of IEEE S&P'15. 763-780. doi:10.1109/sp.2015.52

[35] Jinhua Cui, Jason Zhijingcheng Yu, Shweta Shinde, Prateek Saxena, and Zhiping Cai. 2021. SmashEx: Smashing SGX Enclaves Using Exceptions. In Proceedings of ACM SIGSAC CCS'21. 779-793. doi:10.1145/3460120.3484821

[36] Charlie Curtsinger and Emery D Berger. 2013. Stabilizer: statistically sound performance evaluation. ACM Computer Architecture News (SIGARCH) 41, 1 (2013), 219-228. doi:10.1145/2490301.2451141

[37] Sourav Das, R Harikrishnan Unnithan, Arjun Menon, Chester Rebeiro, and Kamakoti Veezhinathan. 2019. SHAKTI-MS: a RISC-V processor for memory safety in C. In Proceedings of ACM SIGPLAN/SIGBED LCTES'19. ACM, 19-32. doi:10.1145/3316482.3326356

[38] Lucas Davi, Christopher Liebchen, Ahmad-Reza Sadeghi, Kevin Z Snow, and Fabian Monrose. 2015. Isomeron: code randomization resilient to (just-in-time) return-oriented programming. In Proceedings of NDSS'15. 15 pages. doi:10.14722/ndss.2015.23262

[39] Asmit De, Aditya Basu, Swaroop Ghosh, and Trent Jaeger. 2019. FIXER: Flow integrity extensions for embedded RISC-V. In Proceedings of DATE'19. 348-353. doi:10.23919/date.2019.8714980

[40] Willem De Groef, Nick Nikiforakis, Yves Younan, and Frank Piessens. 2010. JITsec: Just-in-Time Security for Code Injection Attacks. In Proceedings of WISSec'10. 1-15.

[41] Jan De Mooij. 2015. W'X JIT-code enabled in Firefox. https://jandemooy.nl/blog/wx-jit-code-enabled-in-firefox/.

[42] Leila Delshadtehrani, Sadullah Canakci, Manuel Egele, and Ajay Joshi. 2021. SealPK: sealable protection keys for RISC-V. In DATE'21. 1278-1281. doi:10.23919/date51398.2021.9473932

[43] Leila Delshadtehrani, Sadullah Canakci, Boyou Zhou, Schuyler Eldridge, Ajay Joshi, and Manuel Egele. 2020. PHMon: a programmable hardware monitor and its security use cases. In Proceedings of USENIX Security'20. 807-824. https://www.usenix.org/conference/usenixsecurity20/presentation/delshadtehrani

[44] Alexander Dörflinger, Mark Albers, Benedikt Kleinbeck, Yejun Guan, Harald Michalik, Raphael Klink, Christopher Blochwitz, Anouar Nechi, and Mladen Berekovic. 2021. A comparative survey of open-source application-class RISC-V processor implementations. In Proceedings of ACM CF'21. ACM, 12-20. doi:10.24355/dbbs.084-202105101615-0

[45] Quentin Ducasse, Guillermo Polito, Pablo Tesone, Pascal Cotret, and Loïc Lagadec. 2022. Porting a JIT Compiler to RISC-V: Challenges and Opportunities. In Proceedings of MPLR'22. 112-118. doi:10.1145/3546918.3546924
[46] Jake Edge. 2015. A seccomp overview. (2015). https://lwn.net/Articles/656307/ Linux Weekly News.

[47] Stephanie Forrest, Anil Somayaji, and David H Ackley. 1997. Building diverse computer systems. In Proceedings of HotOS'97. 67-72. doi:10.1109/hotos.1997.595185

[48] Tommaso Frassetto, David Gens, Christopher Liebchen, and Ahmad-Reza Sadeghi. 2017. JITguard: hardening just-in-time compilers with SGX. In Proceedings of ACM SIGSAC CCS'17. 2405-2419. doi:10.1145/3133956.3134037

[49] Ivan Fratric. 2018. Bypassing mitigations by attacking JIT server in Microsoft Edge. Technical Report. Google Project Zero. https://googleprojectzero.blogspot.com/2018/05/bypassing-mitigations-by-attacking-jit.html

[50] Alexander J Gaidis, Joao Moreira, Ke Sun, Alyssa Milburn, Vaggelis Atlidakis, and Vasileios P Kemerlis. 2023. FineIBT: Fine-grain Control-flow Enforcement with Indirect Branch Tracking. In Proceedings of RAID'23. 527-546.

[51] Neel Gala, Arjun Menon, Rahul Bodduna, GS Madhusudan, and V Kamakoti. 2016. SHAKTI processors: an open-source hardware initiative. In Proceedings of VLSID'16. IEEE, 7-8. doi:10.1109/vlsid.2016.130

[52] Tal Garfinkel, Ben Pfaff, Mendel Rosenblum, et al. 2004. Ostia: a delegating architecture for secure system call interposition. In Proceedings of NDSS'04. 15 pages. https://www.ndss-symposium.org/ndss2004/ostia-delegating-architecture-secure-system-call-interposition/

[53] Robert Gawlik and Thorsten Holz. 2018. SoK: make JIT-spray great again. In Proceedings of USENIX WOOT'18. 14 pages. https://www.usenix.org/conference/woot18/presentation/gawlik

[54] Olivier Gilles, Franck Viguier, Nikolai Kosmatov, and Daniel Gracia Pérez. 2022. Control-Flow Integrity at RISC: Attacking RISC-V by Jump-Oriented Programming. arXiv:2211.16212 [cs.CR] https://arxiv.org/abs/2211.16212

[55] Guang Gong. 2016. Pwn a Nexus Device with a Single Vulnerability. (2016). https://github.com/secmob/cansecwest2016 CanSecWest.

[56] Google. 2024. Google JavaScript engine V8. https://v8.dev/.

[57] Samuel Groß, Simon Koch, Lukas Bernhard, Thorsten Holz, and Martin Johns. 2023. FUZZILLI: Fuzzing for JavaScript JIT Compiler Vulnerabilities. In Proceedings of NDSS'23. 17 pages. doi:10.14722/ndss.2023.24290

[58] Daniel Gruss, Clémentine Maurice, and Stefan Mangard. 2016. Rowhammer.js: a remote software-induced fault attack in JavaScript. In Proceedings of DIMVA'16. 300-321. doi:10.1007/978-3-319-40667-1_15

[59] Garrett Gu and Hovav Shacham. 2020. Return-Oriented Programming in RISC-V. arXiv:2007.14995 [cs.CR] https://arxiv.org/abs/2007.14995

[60] Mohammad Hedayati, Spyridoula Gravani, Ethan Johnson, John Criswell, Michael L Scott, Kai Shen, and Mike Marty. 2019. Hodor: process isolation for throughput data plane libraries. In Proceedings of USENIX ATC'19. 489-504. http://www.usenix.org/conference/atc19/presentation/hedayati-hodor

[61] Urs Hölzle, Craig Chambers, and David Ungar. 1991. Optimizing dynamically-typed object-oriented languages with polymorphic inline caches. In Proceedings of ACM SIGPLAN ECOOP'91. 21-38. doi:10.1007/bfb0057013

[62] Urs Hölzle and David Ungar. 1994. A third-generation Self implementation: reconciling responsiveness with performance. In Proceedings of OOPSLA'94. 229-243. doi:10.1145/191080.191116

[63] Andrei Homescu, Stefan Brunthaler, Per Larsen, and Michael Franz. 2013. Librando: transparent code randomization for just-in-time compilers. In Proceedings of ACM SIGSAC CCS'13. 993-1004. doi:10.1145/2508859.2516675

[64] Hong Hu, Chenxiong Qian, Carter Yagemann, Simon Pak Ho Chung, William R Harris, Taesoo Kim, and Wenke Lee. 2018. Enforcing unique code target property for control-flow integrity. In Proceedings of ACM SIGSAC CCS'18. ACM, 1470-1486. doi:10.1145/3243734.3243797

[65] Hong Hu, Shweta Shinde, Sendroi Adrian, Zheng Leong Chua, Prateek Saxena, and Zhenkai Liang. 2016. Data-oriented programming: on the expressiveness of non-control data attacks. In Proceedings of IEEE S&P'16. 969-986. doi:10.1109/sp.2016.62

[66] Intel. 2023. Intel 64 and IA-32 architectures software developer's manual combined volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4. Intel. https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html

[67] Georges-Axel Jaloyan, Konstantinos Markantonakis, Raja Naeem Akram, David Robin, Keith Mayes, and David Naccache. 2020. Return-oriented programming on RISC-V. In Proceedings of ACM ASIACCS'20. 471-480. doi:10.1145/3320269.3384738

[68] Abhinav Jangda, Mohit Mishra, and Benoit Baudry. 2016. libmask: Protecting Browser JIT Engines from the Devil in the Constants. In Proceedings of PST'16. 301-308. doi:10.1109/pst.2016.7906978

[69] Abhinav Jangda, Mohit Mishra, and Bjorn De Sutter. 2015. Adaptive just-in-time code diversification. In Proceedings of ACM MTD'15. 49-53. doi:10.1145/2808475.2808487

[70] Martin Jauernig, Matthias Neugschwandtner, Christian Platzer, and Paolo Milani Comparetti. 2014. Lobotomy: an architecture for JIT spraying mitigation. In Proceedings of ARES'13. 50-58. doi:10.1109/ares.2014.14

[71] Chongkyung Kil, Jinsuk Jun, Christopher Bookholt, Jun Xu, and Peng Ning. 2006. Address Space Layout Permutation (ASLP): Towards fine-grained randomization of commodity software. In Proceedings of ACSAC'06. 339-348. doi:10.1109/acsac.2006.9

[72] Haeyoung Kim, Jinjae Lee, Derry Pratama, Asep Muhamad Awaludin, Howon Kim, and Donghyun Kwon. 2020. RIMI: instruction-level memory isolation for embedded systems on RISC-V. In Proceedings of ICCAD'20. 1-9. doi:10.1145/3400302.3415727

[73] Paul Kocher, Jann Horn, Anders Fogh, Daniel Genkin, Daniel Gruss, Werner Haas, Mike Hamburg, Moritz Lipp, Stefan Mangard, Thomas Prescher, et al. 2019. Spectre attacks: exploiting speculative execution. In Proceedings of IEEE S&P'19. 1-19. doi:10.1109/sp.2019.00002
[74] PLCT Lab. 2021. 2021 PLCT lab roadmap. https://plctlab.github.io/PLCT-Roadmap-2021.html.

[75] PLCT Lab. 2022. 2022 PLCT lab roadmap. https://plctlab.github.io/PLCT-Roadmap-2022.en.html.

[76] Jinfeng Li, Liwei Chen, Qizhen Xu, Linan Tian, Gang Shi, Kai Chen, and Dan Meng. 2020. Zipper stack: Shadow stacks without shadow. In Proceedings of ESORICS'20. Springer, 338–358. doi:10.1007/978-3-030-58951-6_17

[77] Wilson Lian, Hovav Shacham, and Stefan Savage. 2015. Too LeJIT to quit: extending JIT spraying to ARM. In Proceedings of NDSS'15. 15 pages. doi:10.14722/ndss.2015.23288

[78] Wilson Lian, Hovav Shacham, and Stefan Savage. 2017. A call to ARMs: understanding the costs and benefits of JIT spraying mitigations. In Proceedings of NDSS'17. 15 pages. doi:10.14722/ndss.2017.23108

[79] Hans Liljestrand, Carlos Chinea, Rémi Denis-Courmont, Jan-Erik Ekberg, and N Asokan. 2022. Color My World: Deterministic Tagging for Memory Safety. arXiv:2204.03781 [cs.CR] https://arxiv.org/abs/2204.03781

[80] Moritz Lipp, Michael Schwarz, Daniel Gruss, Thomas Prescher, Werner Haas, Anders Fogh, Jann Horn, Stefan Mangard, Paul Kocher, Daniel Genkin, et al. 2018. Meltdown: reading kernel memory from user space. In Proceedings of USENIX Security'18. 973–990. https://www.usenix.org/conference/usenixsecurity18/presentation/lipp

[81] Kangjie Lu, Wenke Lee, Stefan Nürnberger, and Michael Backes. 2016. How to make ASLR win the clone wars: runtime re-randomization. In Proceedings of NDSS'16. 15 pages. doi:10.14722/ndss.2016.23173

[82] Tao Lu. 2021. A Survey on RISC-V Security: Hardware and Architecture. arXiv:2107.04175 [cs.CR] https://arxiv.org/abs/2107.04175

[83] Martin Maas, Krste Asanović, and John Kubiatowicz. 2018. A hardware accelerator for tracing garbage collection. In Proceedings of ISCA'18. 138–151.

[84] Madaidan. 2022. Firefox and Chromium. https://madaidans-insecurities.github.io/firefox-chromium.html.

[85] Giorgi Maisuradze, Michael Backes, and Christian Rossow. 2016. What Cannot Be Read, Cannot Be Leveraged? Revisiting Assumptions of JIT-ROP Defenses. In Proceedings of USENIX Security'16. 139–156. https://www.usenix.org/conference/usenixsecurity16/technical-sessions/presentation/maisuradze

[86] Giorgi Maisuradze, Michael Backes, and Christian Rossow. 2017. Dachshund: digging for and securing against (non-) blinded constants in JIT code. In Proceedings of NDSS'17. 15 pages. doi:10.14722/ndss.2017.23224

[87] MITRE. 2016. CVE-2016-4622. https://nvd.nist.gov/vuln/detail/CVE-2016-4622.

[88] MITRE. 2016. CVE-2016-7190. https://nvd.nist.gov/vuln/detail/CVE-2016-7190.

[89] MITRE. 2019. CVE-2019-11707. https://nvd.nist.gov/vuln/detail/CVE-2019-11707.

[90] Mozilla. 2008. Tamarin Project. https://www-archive.mozilla.org/projects/tamarin/.

[91] Mozilla. 2009. Bug 506693 - SELinux is preventing JIT from changing memory segment address. (2009). https://bugzilla.mozilla.org/show_bug.cgi?id=506693 Firefox transient memory protection bug.

[92] Mozilla. 2011. Land JIT hardening nop insertion. https://bugzilla.mozilla.org/show_bug.cgi?id=729824.

[93] Mozilla. 2017. Land JIT Hardening Constant Blinding. https://bugzilla.mozilla.org/show_bug.cgi?id=1376819.

[94] Mozilla. 2024. Mozilla JavaScript engine SpiderMonkey. https://spidermonkey.dev/.

[95] Ben Niu and Gang Tan. 2014. Modular control-flow integrity. In Proceedings of ACM SIGPLAN PLDI'14. 577–587. doi:10.1145/2594291.2594295

[96] Ben Niu and Gang Tan. 2014. RockJIT: securing just-in-time compilation using modular control-flow integrity. In Proceedings of ACM SIGSAC CCS'14. 1317–1328. doi:10.1145/2660267.2660281

[97] Kaan Onarlioglu, Leyla Bilge, Andrea Lanzi, Davide Balzarotti, and Engin Kirda. 2010. G-Free: Defeating Return-Oriented Programming through Gadget-Less Binaries. In Proceedings of ACSAC'10. 49–58. doi:10.1145/1920261.1920269

[98] Aleph One. 1996. Smashing the stack for fun and profit. http://phrack.org/issues/49/14.html Phrack magazine.

[99] Vasilis Pappas, Michalis Polychronakis, and Angelos D Keromytis. 2012. Smashing the gadgets: Hindering return-oriented programming using in-place code randomization. In Proceedings of IEEE S&P'12. 601–615. doi:10.1109/SP.2012.41

[100] Seonghwan Park, Dongwook Kang, Jeonghwan Kang, and Donghyun Kwon. 2022. Bratter: An Instruction Set Extension for Forward Control-Flow Integrity in RISC-V. Sensors 22, 4 (2022), 1392.

[101] Soyeon Park, Sangho Lee, Wen Xu, Hyungon Moon, and Taesoo Kim. 2019. libmpk: software abstraction for Intel Memory Protection Keys (Intel MPK). In Proceedings of USENIX ATC'19. 241–254. https://www.usenix.org/conference/atc19/presentation/park-soyeon

[102] Taemin Park, Karel Dhondt, David Gens, Yeoul Na, Stijn Volckaert, and Michael Franz. 2020. NoJITsu: locking down JavaScript engines. In Proceedings of NDSS'20. 17 pages. doi:10.14722/ndss.2020.24262

[103] Team PaX. 2003. PaX Address Space Layout Randomization (ASLR). (2003). http://pax.grsecurity.net/docs/aslr.txt

[104] Team PaX. 2003. PaX Non-Executable Pages Design (NOEXEC). (2003). http://pax.grsecurity.net/docs/noexec.txt

[105] Team PaX. 2003. PaX team objectives. (2003). http://pax.grsecurity.net/docs/pax.txt

[106] Mathias V Pedersen and Aslan Askarov. 2017. From trash to treasure: timing-sensitive garbage collection. In Proceedings of IEEE S&P'17. 693–709. doi:10.1109/sp.2017.64

[107] Ian Piumarta and Fabio Riccardi. 1998. Optimizing direct threaded code by selective inlining. In Proceedings of ACM SIGPLAN PLDI'98. 291–300. doi:10.1145/277650.277743
[108] Aravind Prakash and Heng Yin. 2015. Defeating ROP through denial of stack pivot. In *Proceedings of the 31st Annual Computer Security Applications Conference (ACSAC'15)*. 111–120. doi:10.1145/2818000.2818023

[109] Antonio Pullini, Davide Rossi, Igor Loi, Giuseppe Tagliavini, and Luca Benini. 2019. Mr. Wolf: an energy-precision scalable parallel ultra low power SoC for IoT edge processing. *IEEE JSSC* 54, 7 (2019), 1970–1981. doi:10.1109/jssc.2019.2912307

[110] Vignesh Rao. 2019. Writeup for CVE-2019-11707. https://vigneshsrao.github.io/posts/writeup/.

[111] RISC-V. 2024. *The RISC-V instruction set manual, Volume I: Unprivileged Architecture*. Tag riscv-isa-release-a97e505-2024-12-03. RISC-V International. https://github.com/riscv/riscv-isa-manual/releases

[112] RISC-V. 2024. *The RISC-V Instruction Set Manual, Volume II: Privileged Architecture*. Tag riscv-isa-release-a97e505-2024-12-03. RISC-V International. https://github.com/riscv/riscv-isa-manual/releases

[113] RISC-V. 2024. RISC-V J extension. https://github.com/riscv/riscv-j-extension.

[114] Chris Rohlf and Yan Ivnitskiy. 2011. Attacking clientside JIT compilers. (2011). https://media.blackhat.com/bh-us-11/Rohlf/BH_US_11_RohlfIvnitskiy_AttackingClient_Side_JIT_Compilers_WP.pdf Black Hat USA.

[115] Stephen Röttger. 2023. Control-flow integrity in V8. https://v8.dev/blog/control-flow-integrity/.

[116] saelo. 2021. Attacking JavaScript Engines A Case Study of JavaScriptCore and CVE-2016-4622. https://phrack.org/issues/70/3.html Phrack magazine.

[117] Moritz Schneider, Ramya Jayaram Masti, Shweta Shinde, Srdjan Capkun, and Ronald Perez. 2022. SoK: Hardware-supported Trusted Execution Environments. arXiv:2205.12742 [cs.CR] https://arxiv.org/abs/2205.12742

[118] David Schrammel, Samuel Weiser, Stefan Steinegger, Martin Schwarzl, Michael Schwarz, Stefan Mangard, and Daniel Gruss. 2020. Donky: Domain keys - Efficient In-Process Isolation for RISC-V and x86. In *Proceedings of USENIX Security'20*. 1677–1694. https://www.usenix.org/conference/usenixsecurity20/presentation/schrammel

[119] Felix Schuster, Thomas Tendyck, Christopher Liebchen, Lucas Davi, Ahmad-Reza Sadeghi, and Thorsten Holz. 2015. Counterfeit object-oriented programming: On the difficulty of preventing code reuse attacks in C++ applications. In *Proceedings of IEEE SP'15*. 745–762. doi:10.1109/SP.2015.51

[120] Hovav Shacham. 2007. The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86). In *Proceedings of CCS'07*. 552–561. doi:10.1145/1315245.1315313

[121] Yan Shoshitaishvili, Ruoyu Wang, Christopher Salls, Nick Stephens, Mario Polino, Andrew Dutcher, John Grosen, Siji Feng, Christophe Hauser, Christopher Kruegel, et al. 2016. SoK:(State of) the art of war: offensive techniques in binary analysis. In *Proceedings of IEEE S&P'16*. 138–157. doi:10.1109/sp.2016.17

[122] Alexey Sintsov. 2010. *Writing JIT-spray shellcode for fun and profit*. Technical Report. DSecRG.

[123] Kevin Z Snow, Fabian Monrose, Lucas Davi, Alexandra Dmitrienko, Christopher Liebchen, and Ahmad-Reza Sadeghi. 2013. Just-in-time code reuse: on the effectiveness of fine-grained address space layout randomization. In *Proceedings of IEEE S&P'13*. 574–588. doi:10.1109/SP.2013.45

[124] Chengyu Song, Chao Zhang, Tielei Wang, Wenke Lee, and David Melski. 2015. Exploiting and protecting dynamic code generation. In *Proceedings of NDSS'15*. 15 pages. doi:10.14722/ndss.2015.23233

[125] Alexander Sotirov. 2007. Heap Feng-Shui in JavaScript. (2007). https://www.blackhat.com/presentations/bh-europe-07/Sotirov/Presentation/bh-eu-07-sotirov-apr19.pdf Black Hat Europe.

[126] Adrian Tang, Simha Sethumadhavan, and Salvatore Stolfo. 2015. Heisenbyte: thwarting memory disclosure attacks using destructive code reads. In *Proceedings of ACM SIGSAC CCS'15*. 256–267. doi:10.1145/2810103.2813685

[127] Theori. 2016. Chakra JIT CFG bypass. (2016). https://blog.theori.io/chakra-jit-cfg-bypass-ff5a88677268

[128] Anjo Vahldiek-Oberwagner, Eslam Elnikety, Nuno O Duarte, Michael Sammler, Peter Druschel, and Deepak Garg. 2019. ERIM: secure, efficient in-process isolation with protection keys (MPK). In *Proceedings of USENIX Security'19*. 1221–1238. https://www.usenix.org/conference/usenixsecurity19/presentation/vahldiek-oberwagner

[129] Arjan Van de Ven. 2004. *New security enhancements in Red Hat Enterprise Linux v3, update 3*. Technical Report. Red Hat.

[130] Stephan van Schaik, Andrew Kwong, Daniel Genkin, and Yuval Yarom. 2020. SGAxe: How SGX Fails in Practice. https://sgaxe.com/.

[131] Junjie Wang, Zhiyi Zhang, Shuang Liu, Xiaoning Du, and Junjie Chen. 2023. FuzzJIT: Oracle-Enhanced Fuzzing for JavaScript Engine JIT Compiler. In *Proceedings of USENIX Security'23*. USENIX Association, 1865–1882. https://www.usenix.org/conference/usenixsecurity23/presentation/wang-junjie

[132] Yu Wang, Jinting Wu, Tai Yue, Zhenyu Ning, and Fengwei Zhang. 2022. RetTag: Hardware-assisted return address integrity on RISC-V. In *Proceedings of EUROSEC'22*. 50–56. doi:10.1145/3517208.3523758

[133] Robert NM Watson, Peter G Neumann, Jonathan Woodruff, Michael Roe, Hesham Almatary, Jonathan Anderson, John Baldwin, David Chisnall, Brooks Davis, Nathaniel Wesley Filardo, et al. 2019. *Capability hardware enhanced RISC instructions: CHERI instruction-set architecture (version 7)*. Technical Report. University of Cambridge.

[134] Robert NM Watson, Jonathan Woodruff, Peter G Neumann, Simon W Moore, Jonathan Anderson, David Chisnall, Nirav Dave, Brooks Davis, Khilan Gudka, Ben Laurie, et al. 2015. CHERI: A hybrid capability-system architecture for scalable software compartmentalization. In *Proceedings of IEEE S&P'15*. IEEE, 20–37. doi:10.1109/sp.2015.9
<footnote>[135] Tao Wei, Tielei Wang, Lei Duan, and Jing Luo. 2010. Secure dynamic code generation against spraying. In *Proceedings of ACM SIGSAC CCS'10*. 738-740. doi:10.1145/1866307.1866415</footnote>

<footnote>[136] Samuel Weiser, Mario Werner, Ferdinand Brasser, Maja Malenko, Stefan Mangard, and Ahmad-Reza Sadeghi. 2019. TIMBER-V: Tag-isolated memory bringing fine-grained enclaves to RISC-V. In *Proceedings of NDSS'19*. The Internet Society. doi:10.14722/ndss.2019.23068</footnote>

<footnote>[137] Jan Werner, George Baltas, Rob Dallara, Nathan Otterness, Kevin Z Snow, Fabian Monrose, and Michalis Polychronakis. 2016. No-Execute-After-Read: preventing code disclosure in commodity software. In *Proceedings of ACM ASIACCS'16*. 35-46. doi:10.1145/2897845.2897891</footnote>

<footnote>[138] David Williams-King, Graham Gobieski, Kent Williams-King, James P Blake, Xinhao Yuan, Patrick Colp, Michelle Zheng, Vasileios P Kemerlis, Junfeng Yang, and William Aiello. 2016. Shuffler: fast and deployable continuous code re-randomization. In *Proceedings of USENIX OSDI'16*. 367-382. https://dl.acm.org/doi/10.5555/3026877.3026906</footnote>

<footnote>[139] Rui Wu, Ping Chen, Bing Mao, and Li Xie. 2012. RIM: a method to defend from JIT spraying attack. In *Proceedings of ARES'12*. 143-148. doi:10.1109/ares.2012.11</footnote>

<footnote>[140] Xiaofeng Wu, Kun Suo, Yong Zhao, and Jia Rao. 2018. A side-channel attack on HotSpot heap management. In *Proceedings of USENIX HotCloud'18*. 6 pages. https://www.usenix.org/conference/hotcloud18/presentation/wu</footnote>

<footnote>[141] Mengyao Xie, Chenggang Wu, Yinqian Zhang, Jiali Xu, Yuanming Lai, Yan Kang, Wei Wang, and Zhe Wang. 2022. CETIS: Retrofitting Intel CET for generic and efficient intra-process memory isolation. In *Proceedings of ACM SIGSAC CCS'22*. 2989-3002. doi:10.1145/3548606.3559344</footnote>

<footnote>[142] Yuanchao Xu, ChenCheng Ye, Yan Solihin, and Xipeng Shen. 2020. Hardware-based domain virtualization for intra-process isolation of persistent memory objects. In *Proceedings of ACM/IEEE ISCA'20*. 680-692. doi:10.1109/isca45697.2020.00062</footnote>

<footnote>[143] Fei Yan, Fan Huang, Lei Zhao, Huirong Peng, and Qian Wang. 2016. Baseline is fragile: on the effectiveness of stack pivot defense. In *Proceedings of IEEE ICPADS'16*. 406-413. doi:10.1109/icpads.2016.60</footnote>

<footnote>[144] Bennet Yee, David Sehr, Gregory Dardyk, J Bradley Chen, Robert Muth, Tavis Ormandy, Shiki Okasaka, Neha Narula, and Nicholas Fullagar. 2010. Native Client: a sandbox for portable, untrusted x86 native code. In *Proceedings of SP'09*. 79-93. doi:10.1145/1629175.1629203</footnote>

<footnote>[145] F. Zaruba and L. Benini. 2019. The cost of application-class processing: energy and performance analysis of a Linux-ready 1.7-GHz 64-Bit RISC-V core in 22-nm FDSOI technology. *IEEE TVLSI* 27, 11 (2019), 2629-2640. doi:10.1109/tvlsi.2019.2926114</footnote>

<footnote>[146] Chao Zhang, Mehrdad Niknami, Kevin Zhijie Chen, Chengyu Song, Zhaofeng Chen, and Dawn Song. 2015. JITScope: protecting web users from control-flow hijacking attacks. In *Proceedings of IEEE INFOCOM'15*. 567-575. doi:10.1109/infocom.2015.7218424</footnote>