Offensivecon 22 Attacking Javascript Engines
Offensivecon 22 Attacking Javascript Engines
Attacking JavaScript
Engines in 2022
Samuel Groß (@5aelo), Amy Burnett (@itszn13)
let v = 0x1337;
// typeof(v) == “number”
v: 0x1337
Basic JavaScript
“foobar”
let v = 0x1337;
// typeof(v) == “number”
v: <pointer>
v = "foobar";
// typeof(v) == “string”
Basic JavaScript
let v = 0x1337;
// typeof(v) == “number”
v: <pointer>
v = "foobar";
// typeof(v) == “string”
v = {a: 42, b: 43};
// typeof(v) == “object” JSObject
???
Basic JavaScript JSObject 1
----------------------- HiddenClass 1
Type: <pointer> -----------------------
let o1 = {a: 42, b: 43}; Extra: null Type: PlainObject
Slot 0: 42 .a: inline slot 0
console.log(o1.a); Slot 1: 43 .b: inline slot 1
Basic JavaScript JSObject 1
----------------------- HiddenClass 1
Type: <pointer> -----------------------
let o1 = {a: 42, b: 43}; Extra: null Type: PlainObject
Slot 0: 42 .a: inline slot 0
console.log(o1.a); Slot 1: 43 .b: inline slot 1
JSObject 2
-----------------------
Type: <pointer>
Extra: <pointer>
Slot 0: 13
Slot 1: 37
Basic JavaScript JSObject 1
----------------------- HiddenClass 1
Type: <pointer> -----------------------
let o1 = {a: 42, b: 43}; Extra: null Type: PlainObject
Slot 0: 42 .a: inline slot 0
console.log(o1.a); Slot 1: 43 .b: inline slot 1
o2.c = o1;
JSObject 2
-----------------------
Type: <???>
Extra: <???>
Slot 0: 13
Slot 1: 37
Basic JavaScript JSObject 1
----------------------- HiddenClass 1
Type: <pointer> -----------------------
let o1 = {a: 42, b: 43}; Extra: null Type: PlainObject
Slot 0: 42 .a: inline slot 0
console.log(o1.a); Slot 1: 43 .b: inline slot 1
Bytecode
Compiler
Runtime
(objects, globals, constructors,
functions, methods, …)
JIT
Compiler(s)
Garbage
Wasm Collector
Compiler(s) (GC)
function main() {
console.log(“Hello World!”);
}
main();
Interpreter
Bytecode
Compiler
Runtime
(objects, globals, constructors,
functions, methods, …)
JIT
Compiler(s)
Garbage
Wasm Collector
Compiler(s) (GC)
function main() {
console.log(“Hello World!”);
}
main();
Interpreter
Bytecode
Compiler
Runtime
(objects, globals, constructors,
functions, methods, …)
Bytecode
LdaGlobal
Star1
JIT LdaNamedProperty
Star0
Compiler(s)
Garbage
Wasm Collector
Compiler(s) (GC)
function main() {
console.log(“Hello World!”);
}
main();
Interpreter
Bytecode
Compiler
Runtime
(objects, globals, constructors,
functions, methods, …)
Bytecode
LdaGlobal
Star1
JIT LdaNamedProperty
Star0
Compiler(s)
Garbage
Wasm Collector
Compiler(s) (GC)
function main() {
console.log(“Hello World!”);
}
main();
Interpreter
Bytecode
Compiler
Runtime
(objects, globals, constructors,
functions, methods, …)
Garbage
Wasm Collector
Compiler(s) (GC)
Interpreter
Bytecode
Compiler
Runtime
(objects, globals, constructors,
functions, methods, …)
Garbage
Wasm Collector
Compiler(s) (GC)
Interpreter
Bytecode
Compiler
Runtime
(objects, globals, constructors,
functions, methods, …)
Garbage
Wasm Collector
Compiler(s) (GC)
Interpreter
Bytecode
Compiler
Runtime
(objects, globals, constructors,
functions, methods, …)
Garbage
Wasm Collector
Compiler(s) (GC)
Interpreter
Bytecode
Compiler
Runtime
(objects, globals, constructors,
functions, methods, …)
Bytecode
LdaGlobal
Star1
LdaNamedProperty Machine Code
Star0
JIT
add x3, x28, x3
Compiler(s) ldr x4, [x26, #376]
cmp w3, w4
b.eq #+0x23c
ldur w5, [x3, #-1]
(func
(param $lhs i32)
(param $rhs i32)
(result i32)
Garbage
local.get $lhs
local.get $rhs Wasm Collector
i32.add))
Compiler(s) (GC)
JIT Compilation
A (Hypothetical) JIT Optimization Example
const W = 64, H = 64;
const bmp = new Uint8Array(W * H);
function set(p, v) {
if (p.x < 0 || p.x >= W ||
p.y < 0 || p.y >= H) {
throw "invalid point";
}
bmp[p.x * W + p.y] = v;
}
Example: “Training” the JIT
const W = 64, H = 64;
const bmp = new Uint8Array(W * H);
function set(p, v) {
if (p.x < 0 || p.x >= W || // "Train" the JIT
for (let i = 0; i < 10000; i++) {
p.y < 0 || p.y >= H) { set({x: 1, y: 2}, 3);
throw "invalid point"; }
}
bmp[p.x * W + p.y] = v;
}
Example: Bytecode Parsing
const W = 64, H = 64; x1 = LoadProperty p, ‘x’
const bmp = new Uint8Array(W * H); GotoIf .throwException, x1 < 0
function set(p, v) { x2 = LoadProperty p, ‘x’
if (p.x < 0 || p.x >= W ||
GotoIf .throwException, x2 >= 64
p.y < 0 || p.y >= H) {
throw "invalid point";
}
bmp[p.x * W + p.y] = v;
}
Example: Speculation + Lowering
const W = 64, H = 64; CheckType p, ObjType1
const bmp = new Uint8Array(W * H); x1 = LoadField p, +8
function set(p, v) { GotoIf .throwException, x1 < 0
if (p.x < 0 || p.x >= W ||
CheckType p, ObjType1
p.y < 0 || p.y >= H) {
x2 = LoadField p, +8
throw "invalid point";
GotoIf .throwException, x2 >= 64
}
bmp[p.x * W + p.y] = v;
}
Example: Speculation + Lowering
const W = 64, H = 64; CheckType p, ObjType1
const bmp = new Uint8Array(W * H); x1 = LoadField p, +8
function set(p, v) { GotoIf .throwException, x1 < 0
if (p.x < 0 || p.x >= W ||
CheckType p, ObjType1
p.y < 0 || p.y >= H) {
x2 = LoadField p, +8
throw "invalid point";
GotoIf .throwException, x2 >= 64
}
bmp[p.x * W + p.y] = v;
}
Example: Redundancy Elimination
const W = 64, H = 64; CheckType p, ObjType1
const bmp = new Uint8Array(W * H); x1 = LoadField p, +8
function set(p, v) { GotoIf .throwException, x1 < 0
if (p.x < 0 || p.x >= W ||
CheckType p, ObjType1
p.y < 0 || p.y >= H) {
x2 = LoadField p, +8
throw "invalid point";
GotoIf .throwException, x1 >= 64
}
bmp[p.x * W + p.y] = v;
}
Example: Bytecode Parsing
const W = 64, H = 64; W = LoadGlobal ‘W’
const bmp = new Uint8Array(W * H); i1 = Mul x, W
function set(p, v) { i2 = Add i1, y
if (p.x < 0 || p.x >= W ||
bmp = LoadGlobal ‘bmp’
p.y < 0 || p.y >= H) {
StoreElememt bmp, i2, v
throw "invalid point";
}
bmp[p.x * W + p.y] = v;
}
Example: Constant Folding + Lowering
const W = 64, H = 64; i1 = IntegerMul x, 64
const bmp = new Uint8Array(W * H); i2 = IntegerAdd i1, y
function set(p, v) { CheckBounds i2, 4096
if (p.x < 0 || p.x >= W ||
CheckType v, Uint8
p.y < 0 || p.y >= H) {
StoreUint8Array bmp, i2, v
throw "invalid point";
}
bmp[p.x * W + p.y] = v;
}
Example: Range Analysis + Bounds Check Elimination
const W = 64, H = 64; // x = Range [0, 64)
const bmp = new Uint8Array(W * H); // y = Range [0, 64)
function set(p, v) { i1 = IntegerMul x, 64
if (p.x < 0 || p.x >= W ||
// i1 = Range [0, 4033)
p.y < 0 || p.y >= H) {
i2 = IntegerAdd i1, y
throw "invalid point";
// i2 = Range [0, 4096)
}
CheckBounds i2, 4096
bmp[p.x * W + p.y] = v;
...
}
Example: Final JIT IR Code
const W = 64, H = 64; CheckType p, ObjType1
const bmp = new Uint8Array(W * H); x = LoadField p, +8
function set(p, v) { y = LoadField p, +16
if (p.x < 0 || p.x >= W ||
GotoIf .throwException x < 0 || ...
p.y < 0 || p.y >= H) {
i1 = IntegerMul x, 64
throw "invalid point";
i2 = IntegerAdd i1, y
}
CheckType v, Uint8
bmp[p.x * W + p.y] = v;
StoreUint8Array bmp, i2, v
}
JIT Compilation (simplified) Static Analysis of
Input Code
Runtime State of
Feedback from Past Various Objects
Executions
Optimization
(mostly remove unnecessary stuff, but also
e.g. move things out of loops, …)
Optimized
Unoptimized Speculation
(insert type checks based Machine
Bytecode on feedback)
Code
Lowering
(convert higher-level IR to lower-level IR,
ultimately to machine code)
A (Hypothetical) JIT Bug Example
function replace(a, cond, v) {
let i = a.findIndex(cond);
a[i] = v;
a[i] = v; CheckBounds a, i
} StoreArray a, i, v
} CheckBounds a, i
} CheckBounds a, i
} CheckBounds a, i
} Check i >= 0
StoreArray a, i, v
A (Hypothetical) JIT Bug Example
function replace(a, cond, v) { CheckType a, ArrType1
} Check i >= 0
}, 42);
A (Hypothetical) JIT Bug Example
function replace(a, cond, v) { CheckType a, ArrType1
} Check i >= 0
}, 42);
Optimization Register
Analysis Allocation
CVE-2019-8623 CVE-2018-12386
Other
CVE-2019-8518
“Breaks” Type Spatial
Safety LICM Memory
(loop-invariant code
motion) Safety
GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias CVE-2017-2547 Array Length
Analysis Computation
Type CSE
(common subexpression
Inference Runtime elimination) Range
CVE-2018-4233
CVE-2018-17463 State Analysis
CVE-2019-11707 CVE-2019-8506 crbug 762874 (2017)
CVE-2020-6418 CVE-2021-30551 crbug 880207 (2018)
CVE-2021-30561C “Pureness” CVE-2019-13764
VE-2021-30632 Analysis
CVE-2020-9802 Pattern
Matching
Temporal Write Barrier
CVE-2021-30598
CVE-2021-30599
Memory Lowering
Elision GC
Safety Modelling
CVE-2021-21220
CVE-2019-4442
CVE-2019-8622
Optimization
Analysis
Other
“Breaks” Type Spatial
Safety Memory
Safety
Temporal
Memory
Safety
Optimization
Analysis
Other
“Breaks” Type Spatial
Safety Memory
Safety
BCE
(bounds-check
elimination)
CVE-2017-2547
Range
Analysis
crbug 762874 (2017)
crbug 880207 (2018)
CVE-2019-13764
Temporal
Memory
Safety
Optimization
Analysis
Other
“Breaks” Type Spatial
Safety Memory
Safety
Type-Check BCE
Elimination (bounds-check
elimination)
Type
Inference Range
CVE-2018-4233
CVE-2018-17463 CheckType o, ObjType1 Analysis
crbug 762874 (2017)
CVE-2019-11707 ... crbug 880207 (2018)
CVE-2020-6418
CheckType o, ObjType1 CVE-2019-13764
Temporal
Memory
Safety
Optimization
Analysis
Other
“Breaks” Type Spatial
Safety Memory
Safety
GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias
Analysis
Type
Inference Range
CVE-2018-4233
CVE-2018-17463 let tmp1 = x + y; Analysis
crbug 762874 (2017)
CVE-2019-11707 ... crbug 880207 (2018)
CVE-2020-6418
let tmp2 = x + y; tmp1; CVE-2019-13764
Temporal
Memory
Safety
Optimization
Analysis
Other
“Breaks” Type Spatial
Safety Memory
Safety
GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias
Analysis
Type
Inference Range
CVE-2018-4233
CVE-2018-17463
Analysis
CVE-2019-11707 crbug 762874 (2017)
CVE-2020-6418 crbug 880207 (2018)
CVE-2019-13764
GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias CVE-2017-2547
Analysis
Type
Inference Range
CVE-2018-4233 const x = 42; Analysis
CVE-2018-17463
CVE-2019-11707
for (let i = 0; i < 100; i++) { crbug 762874 (2017)
CVE-2020-6418 ...; crbug 880207 (2018)
CVE-2019-13764
a[x] = 1337;
}
GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias CVE-2017-2547 Array Length
Analysis Computation
Type CSE
(common subexpression
Inference elimination) Range
CVE-2018-4233
CVE-2018-17463
Analysis
CVE-2019-11707 crbug 762874 (2017)
CVE-2020-6418 crbug 880207 (2018)
“Pureness” CVE-2019-13764
Analysis
CVE-2020-9802 Pattern
Matching
Temporal Write Barrier
CVE-2021-30598
CVE-2021-30599
Memory Lowering
Elision GC
Safety Modelling
CVE-2021-21220
CVE-2019-4442
CVE-2019-8622
Optimization Register
Analysis Allocation
CVE-2019-8623 CVE-2018-12386
Other
CVE-2019-8518
“Breaks” Type Spatial
Safety LICM Memory
(loop-invariant code
motion) Safety
GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias CVE-2017-2547 Array Length
Analysis Computation
Type CSE
(common subexpression
Inference Runtime elimination) Range
CVE-2018-4233
CVE-2018-17463 State Analysis
CVE-2019-11707 CVE-2019-8506 crbug 762874 (2017)
CVE-2020-6418 CVE-2021-30551 crbug 880207 (2018)
CVE-2021-30561C “Pureness” CVE-2019-13764
VE-2021-30632 Analysis
CVE-2020-9802 Pattern
Matching
Temporal Write Barrier
CVE-2021-30598
CVE-2021-30599
Memory Lowering
Elision GC
Safety Modelling
CVE-2021-21220
CVE-2019-4442
CVE-2019-8622
Optimization Register
Analysis Allocation
CVE-2019-8623 CVE-2018-12386
Other
CVE-2019-8518
“Breaks” Type Spatial
Safety LICM Memory
(loop-invariant code
motion) Safety
GVN
Type-Check (global value numbering)
BCE
Elimination CVE-2019-9810 (bounds-check
elimination)
CVE-2019-17026 Alias CVE-2017-2547 Array Length
Analysis Computation
Type CSE
(common subexpression
Inference Runtime elimination) Range
CVE-2018-4233
CVE-2018-17463 State Analysis
CVE-2019-11707 CVE-2019-8506 crbug 762874 (2017)
CVE-2020-6418 CVE-2021-30551 crbug 880207 (2018)
CVE-2021-30561C “Pureness” CVE-2019-13764
VE-2021-30632 Analysis
CVE-2020-9802 Pattern
Matching
Temporal Write Barrier
CVE-2021-30598
CVE-2021-30599
Memory Lowering
Elision GC
Safety Modelling
CVE-2021-21220
CVE-2019-4442
CVE-2019-8622
Type Spatial
Safety Memory
Safety
Exploitation
- Choose (arbitrary) victim type Exploitation
- Choose (arbitrary) target type - Choose (arbitrary) victim array
- Choose (arbitrary) operation - Choose (arbitrary) OOB index
- Trigger bug to confuse objects - Choose read or write access
- Trigger bug to corrupt memory
Exploitation
Temporal - Choose (arbitrary) victim type
Memory - Choose (arbitrary) replacement type
Safety - Trigger bug and GC to cause UaF
JS Outside JIT
Interpreter
Bytecode CVE-2021-30517
Compiler CVE-2021-38001
CVE-2022-0102
Runtime
(objects, globals, constructors,
functions, methods, …)
CVE-2021-1789
CVE-2021-21225
● Plenty of complexity elsewhere CVE-2021-38003
Garbage
Wasm Collector
Compiler(s) (GC)
CVE-2021-37975
CVE-2021-30734
Exploitation & Mitigations
Exploit Flow Circa 2016
Create Fake
JSObject JIT
Overwrite
Arbitrary Memory
“Shellcode”
Bug Read & Write
Sandbox
Corrupt Escape
Existing
JSObject
What About Classical Mitigations?
Func1
Func3
"Modern" Mitigations - Not Quite There Yet
When most people think of modern mitigations they think of Control Flow Integrity (CFI)
Mostly used to protect code pointers, but may be used for data as well
0x00007fc75ae25b20 0xa9b6414141414141
0xa9b67fc75ae25b20 0x8000414141414141
Bypassing Pointer Authentication
PAC bypasses can be considered similar to bugs; ie patched quickly if disclosed
Example Bypass Methods
- Pointer Forgery: Writable memory which later gets signed [ref]
- Swap or use signed pointers which lack context
Writable Mem
ADRP X16, #_pow_ptr_3@PAGE Arbitrary
LDR X16, [X16,#_pow_ptr_3@PAGEOFF] 0x41414141 Write
0x41414141
PACIZA X16 Primitive
0x66c0000041414141
Build control flow with manipulated calls / actions made from JavaScript
Function
JS Bound Corrupted Using Data
corrupted_web_obj.do_action() API Web Api for IPC /
Object Data ObjC Call
The Rise Of Data-Only Attacks
On PAC devices and as CFI rolls out, shellcode/rop exec is becoming harder…
These attacks do not rely on code exec, only memory read and write
Exploitation Tricks: Winning Races With Linked Lists
A lot of data attacks become races: Either
“Scripted”
Create Fake Sandbox
JSObject Escape
Arbitrary Memory
Bug Read & Write
Corrupt Data-Only
Existing Sandbox
JSObject Escape
Renderer
Data
Exfiltration
Mitigating Arbitrary Read / Write
Arbitrary read/write is a very powerful primitive
class JSArrayBufferView {
using VectorPtr = CagedPtr<Gigacage::Primitive, void, tagCagedPtr>;
VectorPtr m_vector;
}
- Attackers just need to find single uncaged pointer they can r/w from
- This is made easier by faking object state
Is GigaCage Effective?
Current easiest method: make a fake JSArray…[ref]
JSArray
…
JSCell (Type Info) Properties
Slightly limited R/W, but allows corrupting more complex structures elsewhere
Moving Towards A Heap Sandbox
Attackers will continue to find objects with corruptible pointers
Solution: Hold these pointers outside the heap and reference with index #
Future V8 Heap Sandbox
All JS objects confined to sandbox memory
All other sensitive memory is outside:
- External pointers (and type) in table
- JIT compiler structures and code
- Any reference to other memory
“Scripted”
Sandbox
Fake Own Escape
JSObject
Arbitrary Memory
Read & Write
Bug
Data-Only
Corrupt
Sandbox
Existing
Escape
JSObject