At [1], the values for both the arguments a0
and a1
are loaded. The numbers 5/2
and 6/3
refer to Node 5/Variable 2
and Node 6/Variable 3
. Nodes are used in the initial Maglev IR graphs and the variables are used when the final register allocation graphs are being generated. Therefore, the arguments will be referred by their respective Nodes and Variables. At [2], two CheckedSmiUntag
operations are performed on the values loaded at [1]. This operation checks that the argument is a small integer and removes the tag. These untagged values are now fed into Int32AddWithOverflow
that takes the operands from v9/n9
and v10/n10
(the results from the CheckedSmiUntag
operations) and places the result in n11/v11
. Finally, at [4], the graph converts the resulting operation into a JavaScript number via Int32ToNumber
of n11/v11
, and places the result into v13/n13
which is then returned by the Return
operation.
Ubercage
Ubercage, also known as the V8 Sandbox (not to be confused with the Chrome Sandbox), is a new mitigation within V8 that tries to enforce memory read and write bounds even after a successful V8 vulnerability has been exploited.
The design involves relocating the V8 heap into a pre-reserved virtual address space called the sandbox, assuming an attacker can corrupt V8 heap memory. This relocation restricts memory accesses within the process, preventing arbitrary code execution in the event of a successful V8 exploit. It creates an in-process sandbox for V8, transforming potential arbitrary writes into bounded writes with minimal performance overhead (roughly 1% on real-world workloads).
Another mechanism of Ubercage is Code Pointer Sandboxing, in which the implementation removes the code pointer within the JavaScript object itself, and turns it into an index in a table. This table will hold type information and the actual address of the code to be run in a separate isolated part in memory. This prevents attackers from modifying JavaScript function code pointers as during an exploit, initially, only bound access to the V8 heap is attained.
Finally, Ubercage also signified the removal of full 64bit pointers on Typed Array objects. In the past the backing store (or data pointer) of these objects was used to craft arbitrary read and write primitives but, with the implementation of Ubercage, this is now no longer a viable route for attackers.
Garbage Collection
JavaScript engines make intensive use of memory due to the freedom theΒ specification provides while making use of objects, as theirΒ types and references can be changed at any point in time, effectively changing theirΒ in-memory shape and location. All objects that are referenced by root objectsΒ (objects pointed by registers or stack variables) either directly, or through aΒ chain of references, are considered live. Any object that is not in any suchΒ reference is considered dead and subject to be freeβd by the Garbage Collector.
This intensive and dynamic usage of objects has led to research which provesΒ that most objects will die young, known as the βThe GenerationalΒ Hypothesisβ[1], which is used by V8 as a basis for its garbageΒ collection procedures. In addition it uses a semi-space approach, in order toΒ prevent traversing the entire heap-space in order to mark alive/dead objects,Β where it considers a βYoung Generationβ and an βOld Generationβ depending on howΒ many garbage collection cycles each object has managed to survive.
In V8 there exist two main garbage collectors, Major GC and Minor GC. The MajorΒ GC traverses the entire heap space in order to mark object statusΒ (alive/dead), sweep the memory space to free the dead objects, and finally,Β compact the memory depending on fragmentation. The Minor GC, traverses only theΒ Young Generation heap space and does the same operations but including anotherΒ semi-space scheme, taking surviving objects from the βFrom-spaceβ to theΒ βTo-spaceβ space, all in an interleaved manner.
Orinoco is part of the V8 Garbage Collector and tries to implementΒ state-of-the-art garbage collection techniques, including fully concurrent,Β parallel, and incremental mechanisms for marking and freeing memory. Orinoco isΒ applied to the Minor GC as it uses parallelization of tasks in order to mark andΒ iterate the βYoung generationβ. It is also applied to the Major GC by implementingΒ concurrency in the marking phases. All of this prevents previously observableΒ jank and screen stutter caused by the Garbage Collector stopping all tasks withΒ the intention of freeing memory, known as Stop-the-World approach.[2]
Object Representation
V8 on 64-bit builds uses pointer compression. This is, all the pointers areΒ stored in the V8 heap as 32-bit values. To distinguish whether the currentΒ 32-bit value is a pointer or a small integer (SMI), V8 uses anotherΒ technique called pointer tagging:
- If the value is a pointer, it will set the last bit of the pointer to 1.
- If the value is a SMI, it will bitwise left shift (
<<
) the value by 1.Β Leaving the last bit unset.Β Therefore, when reading a 32-bit value from the heap, the first thing thatΒ is checked is whether it has a pointer tag (last bit set to 1) and if soΒ the value of a register (r14
on x86 systems) is added, which corresponds to the V8 heap baseΒ address, therefore decompressing the pointer to its full value. If it is a SMI it will check thatΒ the last bit is set to 0 and then bitwise right shift (>>
) the valueΒ before using it.
The best way to understand how V8 represents JavaScript objects internally is toΒ look at the output of a DebugPrint
statement, when executed in a d8
shellΒ with an argument representing a simple object.