Liam Wachter, Julian Gremminger
Supervised by: TT.-Prof. Dr. Christian Wressnegger, Dr. Flavio Toffalini, Prof. Dr. Mathias Payer
0 : 01 0d e8 ff ff 1f LdaSmi.ExtraWide [536870888]
6 : c5 Star0
7 : 13 00 LdaConstant [0]
9 : c2 Star3
10 : 2d f6 01 00 GetNamedProperty r3, [1], [0]
14 : c3 Star2
15 : 5e f7 f6 f9 02 CallProperty1 r2, r3, r0, [2]
20 : c4 Star1
21 : 2d f8 02 04 GetNamedProperty r1, [2], [4]
25 : c3 Star2
26 : 13 03 LdaConstant [3]
28 : c1 Star4
29 : 5f f7 f8 f5 f9 06 CallProperty2 r2, r1, r4, r0, [6]
35 : aa Return
Turn a program against its brothers and sisters
Turn a program against itself
"We disable the generation of complex statements such as function declarations, class declarations, try/catch statements, switch/case statements. We also avoid generating loop structures inside the function body of opt"
function opt(opt_param){
const v3 = [128,128,-271414277];
v3[128] = -271414277;
const v5 = new Uint32Array(v3);
const v6 = v5.sort();
const v7 = [];
const v8 = v3.__proto__;
const v10 = ["16"];
const v11 = v7.push;
const v12 = Reflect.apply(v11,v8,v10);
const v15 = String.fromCharCode(528439.4995012311);
const v16 = String(v5);
return v16;
}
transitioning javascript builtin probe_state(
js-implicit context: NativeContext, receiver: JSAny)(obj: JSAny): Undefined {
let curVal: Smi = *NativeContextSlot(ContextSlot::FUZZILLI_HASH_INDEX);
typeswitch (obj) {
case (Null): {
curVal += 1;
}
case (True): {
curVal += 2;
}
case (False): {
curVal += 4;
}
case (Undefined): {
curVal += 8;
}
case (String): {
curVal += 16;
}
case (s: Smi): {
curVal += 32;
let doubleValue: float64 = SmiToFloat64(s);
if (Float64IsNaN(doubleValue)) {
doubleValue = 1.0;
}
const lWord: uint32 = data_view::Float64ExtractLowWord32(doubleValue);
const hWord: uint32 = data_view::Float64ExtractHighWord32(doubleValue);
curVal += SmiFromUint32(lWord);
curVal += SmiFromUint32(hWord);
}
case (n: HeapNumber): {
curVal += 32;
let doubleValue: float64 = Convert<float64>(n);
if (Float64IsNaN(doubleValue)) {
doubleValue = 1.0;
}
const lWord: uint32 = data_view::Float64ExtractLowWord32(doubleValue);
const hWord: uint32 = data_view::Float64ExtractHighWord32(doubleValue);
curVal += SmiFromUint32(lWord);
curVal += SmiFromUint32(hWord);
}
case (Object): {
curVal += 64;
}
}
What key challenges did we address?
State Extraction: V8 modification
Differential Oracle: Fuzzilli extension
function f(o1, o2) {
return o1.a * o2.a;
}
Insert hooks at deopt points to extract interpreter state
→ partial use of existing deopt mechanism
Embedded disassembler before it was cool
static constexpr int kCallBuiltinInstructionSize = 0x5;
static constexpr int kCallDeoptSize = 0x6;
static constexpr int kFromOffToJumpInstruction = kCallBuiltinInstructionSize + kCallDeoptSize;
char *deopt_jump_instruction = ((char *)(from_)) - kFromOffToJumpInstruction;
// near jmp 0x0f8_|offset|
DCHECK_EQ(*deopt_jump_instruction, 0x0f);
DCHECK_EQ((*(deopt_jump_instruction + 1)) & 0xf0, 0x80);
int deopt_rel_offset = *((unsigned int*)(deopt_jump_instruction + 2));
int deopt_offset = deopt_rel_offset + ((int)(from_ - compiled_code_->instruction_start() - kCallBuiltinInstructionSize));
deopt_exit_index_ = (deopt_offset - deopt_start_offset) / kEagerDeoptExitSize;
void InterpreterAssembler::Dispatch() {
Comment("========= Dispatch");
DCHECK_IMPLIES(Bytecodes::MakesCallAlongCriticalPath(bytecode_), made_call_);
DumplingHook(...);
TNode<IntPtrT> target_offset = Advance();
TNode<WordT> target_bytecode = LoadBytecode(target_offset);
DispatchToBytecodeWithOptionalStarLookahead(target_bytecode);
}
IGNITION_HANDLER(Star, InterpreterAssembler) {
TNode<Object> accumulator = GetAccumulator();
StoreRegisterAtOperandIndex(accumulator, 0);
Dispatch();
}
__ CodeEntry();
{
RCS_BASELINE_SCOPE(Visit);
Prologue();
AddPosition();
for (; !iterator_.done(); iterator_.Advance()) {
DumplingHook(...);
VisitSingleBytecode();
AddPosition();
}
}
void BaselineCompiler::VisitLdaZero() {
__ Move(kInterpreterAccumulatorRegister,
Smi::FromInt(0));
}
void BaselineAssembler::Move(
interpreter::Register output,
Register source) {
return __ movq(RegisterFrameOperand(output),
source);
}
-------TurboFan frame dump-------
pc: 7
acc: 13.37
a0: <Object>{
__proto__: <Class C7>{<String[1]: f>[enumerable]<JSArray>[]},
<String[1]: a>[configurable][enumerable]42(enum cache: 2),
<String[1]: f>[configurable][enumerable]13.37(enum cache: 0)
}
r0: -INFINITY
context: <ScriptContext[4]>
receiver: <JSGlobalProxy>
closure: <JSFunction f0>
Function ID: 27
Obvious and less obvious non-deterministic allowed behavior in V8
Math.random();
Date.now();
performance.measure("");
performance.now();
...
Combination of mocking in V8 and JS to mitigate
+ Reexecuting on differential found
Fuzzer | Fuzzilli | JIT-Picker | FuzzJIT | Dumpling |
---|---|---|---|---|
Executions | 63,775,062 | 99,240,042 | 61,434,736 | 51,535,553 |
Found 8 new V8 bugs 🎉
Bug Id | Kind | Status | Changes | By | Description |
---|---|---|---|---|---|
CR41488094 | Diff | fixed | +28/-23 | D, J | Enumerating properties eagerly, has incorrect side effect |
CR335310000 | Diff | fixed | +15/0 | D | Property not marked as enumerable by Maglev and TurboFan |
CR332745405 | Diff | fixed | +5/0 | D | DefineOwnProperty called the setter of an existing accessor property |
CR329330868 | assert | dup | N/A | D, J | array.shift does not update pointers in spill slots |
CR41484971 | Diff | fixed | +52/-40 | D | Store inline cache handlers are incorrectly used for defining properties |
V8:14605 | Diff | fixed | +1/-1 | D | The JumpLoop bytecode does not clobber the accumulator in all cases |
CR345960102 | Diff | fixed | +6/-4 | D | TurboFan incorrectly optimizes 64 bit BigInt shifts |
CR346086168 | Diff | fixed | +109/-107 | D | Overflow in BigInt64 shift optimization |
V8:14556 | Diff | available | N/A | D | The arguments array is handled differently in optimizing compilers |
CR40945996 | assert | dup | N/A | D | The profiler in Maglev interferes with deoptimization |
function A() {
Object.defineProperty(this, "x", { writable: true, configurable: true, value: undefined });
}
class B extends A {
x = {};
}
for (let i = 0; i < 100; i++) {
new B();
}
Here not "visible", but already detectable by Dumpling
Other fuzzers would need to add
let b = new B();
console.log(b.propertyIsEnumerable("x"));
opt output: "true", unopt output: "false"
Find differentials between JS engine execution tiers automatically
Extract VM states during runtime and compare between JIT and interpreter
Leveraging deoptimization points, a mechanism already implemented in JS engines
Find bugs before they become "visible"