1 of 24

Sandboxing V8

Samuel Groß - saelo@google.com

2 of 24

What?

Lightweight, in-process sandbox for V8

Attacker then needs:

V8 Vulnerability +

V8 Sandbox escape +

Chrome Sandbox escape

3 of 24

Why?

JavaScript engines are very attractive for attackers…

Fundamental problem: JS engine bugs are often 2nd order vulnerabilities

Root cause is a logic issue in a compiler or the runtime environment...

which can then be exploited for (more or less arbitrary) memory corruption at runtime

=> Powerful “superbugs”

4 of 24

CSE

(common subexpression elimination)

BCE

(bounds-check elimination)

Range Analysis

“Pureness” Analysis

Type Safety

Spatial Memory Safety

Temporal Memory Safety

Type-Check Elimination

Type Inference

GC Modelling

Write Barrier Elision

Array Length Computation

Register Allocation

Lowering

Runtime State

LICM

(loop-invariant code motion)

Optimization

Analysis

“Breaks”

Pattern Matching

Other

GVN

(global value numbering)

Alias Analysis

5 of 24

How?

Basically: No more Pointers in V8! :)

6 of 24

Lower Addresses

Higher Addresses

0xa38000000000

V8 Sandbox (e.g. 1TB)

0xa48000000000

7 of 24

Lower Addresses

Higher Addresses

0xa38000000000

V8 Sandbox (e.g. 1TB)

0xa48000000000

0xa38100000000

V8 Heap (4GB)

8 of 24

Lower Addresses

Higher Addresses

0xa38000000000

V8 Sandbox (e.g. 1TB)

V8 Heap (4GB)

0xa48000000000

0xa38100000000

HeapObj4

ExternalObj1

ArrayBuffer1

Wasm Memory

Cage (10GB)

HeapObj5

HeapObj2

HeapObj3

HeapObj1

9 of 24

Lower Addresses

Higher Addresses

0xa38000000000

V8 Sandbox (e.g. 1TB)

V8 Heap (4GB)

0xa48000000000

HeapObj4

ArrayBuffer1

32-bit offset (compressed pointer)

Wasm Memory

Cage (10GB)

HeapObj5

HeapObj2

HeapObj1

HeapObj3

0xa38100000000

ExternalObj1

10 of 24

Lower Addresses

Higher Addresses

0xa38000000000

V8 Sandbox (e.g. 1TB)

V8 Heap (4GB)

0xa48000000000

HeapObj4

ArrayBuffer1

32-bit offset (compressed pointer)

Wasm Memory

Cage (10GB)

40-bit offset from sandbox base

HeapObj5

HeapObj2

HeapObj1

HeapObj3

0xa38100000000

ExternalObj1

11 of 24

Lower Addresses

Higher Addresses

0xa38000000000

V8 Sandbox (e.g. 1TB)

V8 Heap (4GB)

0xa48000000000

HeapObj4

ExternalObj1

ArrayBuffer1

~20-bit

Index

32-bit offset (compressed pointer)

Wasm Memory

Cage (10GB)

40-bit offset from sandbox base

HeapObj5

HeapObj2

HeapObj1

HeapObj3

0

Type + Pointer

External Pointer Table

Type + Pointer

1

0xa38100000000

12 of 24

Performance?

  • Goal: < 2% performance overhead (currently ~1.5%)
  • Sandbox primitives are designed to be as performant as possible:
    • Offsets only require a shift+add (1 instruction on arm64, 2 on x64) to turn into a full pointer
    • External pointers require ~1 additional memory load + bitwise AND for the type check
    • External pointer table supports efficient GC and compaction
    • Everything else basically has no impact on performance
  • Still, performance impact is noticeable…

void TurboAssembler::DecodeSandboxedPointer(const Register& value) {

// Sandbox overhead: single shift+add instruction

Add(value, kPtrComprCageBaseRegister,

Operand(value, LSR, kSandboxedPointerShift));

}

13 of 24

Implications (Beyond V8)

14 of 24

Lower Addresses

Higher Addresses

0xa38000000000

0xa48000000000

0xa38100000000

Everything in here is now

UNTRUSTED

15 of 24

Implications Beyond V8

int idx = ...;

v8_obj->SetInternalField(0, v8_num(idx));

...

...

uint idx = v8_obj->GetInternalField(0).to_uint();

return array[idx];

16 of 24

Implications Beyond V8

int idx = ...;

v8_obj->SetInternalField(0, v8_num(idx));

...

...

uint idx = v8_obj->GetInternalField(0).to_uint();

return array[idx];

17 of 24

Implications Beyond V8

int idx = ...;

v8_obj->SetInternalField(0, v8_num(idx));

...

...

uint idx = v8_obj->GetInternalField(0).to_uint();

CHECK_LT(idx, size_of_array); // Simple fix ...

// ... and a fuzzer will find this easily, at least ...

return array[idx];

18 of 24

Goals of the V8 Sandbox

19 of 24

Goals of the V8 Sandbox

  1. Introduce a new security boundary

V8-based Chrome exploit chains now requires 3 instead of 2 vulnerabilities

20 of 24

Goals of the V8 Sandbox

  • Introduce a new security boundary

V8-based Chrome exploit chains now requires 3 instead of 2 vulnerabilities

  • Reduce “2nd order” vulnerabilities to “1st order” ones

V8 sandbox escape bug is likely a “standard” memory corruption bug

21 of 24

State of the V8 Sandbox (Dec. 2022)

22 of 24

State of the V8 Sandbox (Dec. 2022)

  • Sandbox “foundation” launched this year
  • At this point, basically still a proof-of-concept

23 of 24

State of the V8 Sandbox (Dec. 2022)

  • Sandbox “foundation” launched this year
  • At this point, basically still a proof-of-concept
  • Up next:
    • Code pointer sandboxing (~= V8 sandbox CFI)
    • Fine-granular type tags for pointers to Blink objects (e.g. DOM nodes)
    • Building custom fuzzers for the sandbox attack surface
    • A ton of further fixes, code refactoring, etc.

24 of 24

Summary

V8 Sandbox architecture breaks down a hard problem�(secure and fast JS engine) into lots of smaller problems with�“relatively” easy solutions (OOB accesses, TOCTOU issues, UAFs, …).