Hacking
Check out for the highlevel fuzzing logic. From there dive into any part that seems interesting.
Patches, additions, other contributions etc. to this project are very welcome! However, do quickly check .
It would be much appreciated if you could send a short note (possibly including a CVE number) to found with the help of this project so it can be included in the :)
Concept
When fuzzing for core interpreter bugs, e.g. in JIT compilers, semantic correctness of generated programs becomes a concern. This is in contrast to most other scenarios, e.g. fuzzing of runtime APIs, in which case semantic correctness can easily be worked around by wrapping the generated code in try-catch constructs. There are different possibilities to achieve an acceptable rate of semantically correct samples, one of them being a mutational approach in which all samples in the corpus are also semantically valid. In that case, each mutation only has a small chance of turning a valid sample into an invalid one.
To implement a mutation-based JavaScript fuzzer, mutations to JavaScript code have to be defined. Instead of mutating the AST, or other syntactic elements of a program, a custom intermediate language (IL) is defined on which mutations to the control and data flow of a program can more directly be performed. This IL is afterwards translated to JavaScript for execution. The intermediate language looks roughly as follows:
v0 <− LoadInteger '0'
v1 <− LoadInteger '10'
v2 <− LoadInteger '1'
v3 <− LoadInteger '0'
BeginFor v0, '<', v1, '+', v2 −> v4
v6 <− BinaryOperation v3, '+', v4
Reassign v3, v6
EndFor
v7 <− LoadString 'Result: '
v8 <− BinaryOperation v7, '+', v3
v9 <− LoadGlobal 'console'
v10 <− CallMethod v9, 'log', [v8]
Which can e.g. be trivially translated to the following JavaScript code:
const v0 = 0;
const v1 = 10;
const v2 = 1;
let v3 = 0;
for (let v4 = v0; v4 < v1; v4 = v4 + v2) {
const v6 = v3 + v4;
v3 = v6;
}
const v7 = "Result: ";
const v8 = v7 + v3;
const v9 = console;
const v10 = v9.log(v8);
Or to the following JavaScript code by inlining intermediate expressions:
let v3 = 0;
for (let v4 = 0; v4 < 10; v4++) {
v3 = v3 + v4;
}
console.log("Result: " + v3);
FuzzIL has a number of properties:
- A FuzzIL program is simply a list of instructions.
- A FuzzIL instruction is an operation together with input and output variables and potentially one or more parameters (enclosed in single quotes in the notation above).
- Inputs to instructions are always variables, there are no immediate values.
- Every output of an instruction is a new variable, and existing variables can only be reassigned through dedicated operations such as the
Reassigninstruction. - Every variable is defined before it is used.
A number of mutations can then be performed on these programs:
- : generates code and inserts it somewhere in the mutated program. Code is generated either by running a : inserts a program from the corpus into a random position in the mutated program.
- , with some parts (e.g. coverage measurements, socket interactions, etc.) implemented in C.
Architecture
A fuzzer instance (implemented in : produces new programs from existing ones by applying : executes programs of the target language.
- : has knowledge of the runtime environment, e.g. the available builtins, property names, and methods.
- : evaluates whether a sample is interesting according to some metric, e.g. code coverage.
- : gathers various pieces of statistical information.
- : synchronize multiple instances within the same process.
- for the full list of events. The event mechanism effectively decouples the various components of the fuzzer and makes it easy to implement additional modules.
A FuzzIL program can be built up using a . For that, the target engine is modified to accept a script input over pipes and/or shared memory, execute it, then reset its internal state and wait for the next script. This removes the overhead from process creation and to a large part from the engine ininitializaiton.
Scalability
There is one , conceptually corresponding to a single thread. As a rule of thumb, every interaction with a Fuzzer instance must happen on that instance’s dispatch queue. This guarantees thread-safety as the queue is serial. For more details see to a master instance. In turn, the master instances also synchronize their corpus with the workers. Communication between masters and workers can happen in different ways, each implemented as a module:
- : synchronize instances over a simple TCP-based protocol.
This design allows the fuzzer to scale to many cores on a single machine as well as to many different machines. As one master instance can quickly become overloaded if too many workers send programs to it, it is also possible to configure multiple tiers of master instances, e.g. one master instance, 16 intermediate masters connected to the master, and 256 workers connected to the intermediate masters.
Resources
Further resources about this fuzzer:
- A for which the initial implementation was done.
- A by Doyensec about fuzzing the JerryScript engine with Fuzzilli
Bug Showcase
The following is a list of some of the bugs found with the help of Fuzzilli. Only bugs with security impact are included in the list. Special thanks to all users of Fuzzilli who have reported bugs found by it!
WebKit/JavaScriptCore
- uses incorrect output register for NumberIsInteger operation
- : compileMathIC produces incorrect machine code
- : CodeBlock UaF due to dangling Watchpoints
- : Loop-invariant code motion (LICM) in DFG JIT leaves stack variable uninitialized
- : DFG: Loop-invariant code motion (LICM) leaves object property access unguarded
- : JSC fails to run haveABadTime() when some prototypes are modified, leading to type confusions
- : GetterSetter type confusion during DFG compilation
- : GetterSetter type confusion in FTL JIT code (due to not always safe LICM)
Gecko/Spidermonkey
- : IonMonkey's type inference is incorrect for constructors entered via OSR
- : unexpected ObjectGroup in ObjectGroupDispatch operation
- : IonMonkey incorrectly predicts return type of Array.prototype.pop, leading to type confusions
- : Turbofan may read a Map pointer out-of-bounds when optimizing Reflect.construct
- : Incorrect map processing in V8
- : Bug in inlining heuristic
- : Memory corruption in regexp length check
- : Type Confusion in V8
: Unstable valstack pointer in putprop
- : Memory corruption due to incorrect property enumeration
- : Memory corruption due to error handling in case of OOM
- : Memory corruption due to incorrect handling of property keys for Proxy objects
- : Incorrect error handling in SerializeJSONProperty function
- : Memory corruption due to incorrect TypedArray initialization
SOCIAL SHARE CARD GENERATOR