Erlfuzz
erlfuzz
is a small standalone generator of random erlang programs used to fuzz the erlang compiler and VM (and other tools such as erlfmt, dialyzer, eqWAlizer, etc.). It does not currently use coverage information or do anything especially clever, except for carefully handling Erlang's scoping rules.
Requirements
Dependencies of the fuzzer itself:
- Rust (including cargo)
- Several libraries listed in
Cargo.toml
Dependencies of the scripts wrapping fuzzing targets:
- Bash
- GNU Parallel
- Timeout (part of GNU coreutils)
The fuzzer has been tested on both Linux and MacOS.
On the latter, the scripts need a small tweak to run: removing or replacing calls to the ulimit
command.
How to build
On a machine with access to the internet: cargo build --release
How to use
With a provided script
If the goal is to fuzz erlc
& erl
, the easiest way is to use the provided run_erl_once.sh
script from erlfuzz/
as follows:
mkdir -p out
mkdir -p interesting
seq 1000000 | parallel --line-buffer "./target/release/erlfuzz fuzz -c ./run_erl_once.sh --tmp-directory out --interesting-directory interesting test{}"
This will repeatedly run the fuzzer, store its output in a file in out/
, call erlc
and erl
on that file, and then either delete it or move it to interesting/
if it triggered a crash.
It will automatically make use of all cores on your machine. If that is not what you want, you can limit it to 5 cores (for example) by passing -j 5
to parallel.
It will automatically stop after 1M iterations, just increase the parameter to seq
if you want more.
I recommend running this in a window controlled by tmux
, screen
, etc., so it can be detached and survive the end of whatever terminal/ssh session you are using.
There are similar scripts for fuzzing other tools such as erlfmt
, dialyzer
, or eqwalizer
.
For some of these, it may be required to pass additional options to erlfuzz
:
run_eqwalizer_once.sh
:--disable-maybe
--disable-shadowing
run_gradualizer_once.sh
:--disable-map-comprehensions
--disable-maybe
run_erlfmt_once.sh
:--disable-map-comprehensions
--disable-maybe
run_infer_once.sh
:--disable-map-comprehensions
--disable-maybe
--disable-shadowing
--wrapper for-infer
verify_erl_jit.sh
:--deterministic
--wrapper printing
verify_erlc_opts.sh
:--deterministic
--wrapper printing
verify_infer_against_erl.sh
:--deterministic
--disable-map-comprehensions
--disable-maybe
--disable-shadowing
--wrapper printing
With another script
erlang fuzz
can be used to fuzz any other command that accepts an Erlang file.
A return code of 0 must be used to indicate that the program ran successfully, and non-0 to report that a bug was found.
I strongly recommend wrapping the program being fuzzed by a script that uses timeout
and ulimit -m
; you can look at run_erl_once.sh
for inspiration.
Also consider using stronger sandboxing (e.g. with cgroups) if the program under test is likely to have dangerous side effects.
Manually
Instead of using erlfuzz fuzz
on a script like above, you can use erlfuzz to generate an Erlang file on stdout, which you can then use however you want. See erlfuzz generate
for details.
Testcase minimization
Testcases found by erlfuzz can be automatically reduced, by using erlfuzz reduce
, and passing it the seed used to generate the testcase.
If any additional option was passed during generation time, it must also be passed at reduction time.
Both the seed and all relevant options are listed in a comment at the top of the testcase.
For example:
./target/release/erlfuzz reduce -c ./run_erl_once.sh --tmp-directory out --minimized-directory minimized --seed 3137934125722527840 foobar
Would reduce the testcase that was generated with seed 3137934125722527840, use out/foobar.erl
as a scratch file, and store the result in minimized/foobar.erl
.
Depending on the formatting of the error messages that are output by the tool under test, it can be necessary to adjust the similarity heuristic with the --max-distance
or the --num-lines
options. Dialyzer for example includes non-deterministic numbers (timestamp, process id) in its error messages, so --max-distance 10
is required for erlfuzz to ignore those.
You can ask erlfuzz to automatically reduce the bugs it finds as they are found, by using erlfuzz fuzz-and-reduce
.
This is a tiny wrapper around doing erlfuzz fuzz
followed by erlfuzz reduce
, and needs all of the same arguments.
Debugging erlfuzz
If erlfuzz itself is misbehaving, one way to investigate is to set the environment variable RUST_LOG=debug or RUST_LOG=trace (even more verbose) to make it emit a log of its actions to stderr. It is possible to set different modules to different levels of logging, for example RUST_LOG="warn,erlfuzz::reducer=info" will set all modules to level "warn" except for the testcase reducer which it will set to level info. See crate env_logger for details. The levels from least to most verbose are:
- error (default)
- warn
- info
- debug
- trace
I recommend the following setting to get a log of progress during reduction: RUST_LOG="debug,erlfuzz::environment=warn"
.
License
erlfuzz is Apache licensed.
Current limitations
None of the following is currently generated by the fuzzer:
receive
and!
(and more generally the code generated is currently non-concurrent)- records
- preprocessor commands
- map iterators
Erlfuzz also only knows about some bifs.
In particular, it does not yet generate any code using the ets
module.
Some of the bugs found so far
BEAM VM:
- erlang/otp#6634
- erlang/otp#6645
- erlang/otp#6655
- erlang/otp#6701
- erlang/otp#6717
- erlang/otp#6838
- erlang/otp#6839
- erlang/otp#7282
erlc:
- erlang/otp#6163
- erlang/otp#6164
- erlang/otp#6169
- erlang/otp#6183
- erlang/otp#6184
- erlang/otp#6409
- erlang/otp#6410
- erlang/otp#6418
- erlang/otp#6426
- erlang/otp#6427
- erlang/otp#6444
- erlang/otp#6445
- erlang/otp#6458
- erlang/otp#6415 (comment)
- erlang/otp#6467
- erlang/otp#6468
- erlang/otp#6415 (comment)
- erlang/otp#6415 (comment)
- erlang/otp#6474
- erlang/otp#6415 (comment)
- erlang/otp#6415 (comment)
- erlang/otp#6415 (comment)
- erlang/otp#6458 (comment)
- erlang/otp#6515
- erlang/otp#6551
- erlang/otp#6552
- erlang/otp#6553
- erlang/otp#6552 (comment)
- erlang/otp#6559 (comment)
- erlang/otp#6568
- erlang/otp#6571
- erlang/otp#6572
- erlang/otp#6593
- erlang/otp#6599
- erlang/otp#6601
- erlang/otp#6602
- erlang/otp#6603
- erlang/otp#6604
- erlang/otp#6612
- erlang/otp#6613
- erlang/otp#6619 (comment)
- erlang/otp#6630
- erlang/otp#6633
- erlang/otp#6643
- erlang/otp#6648
- erlang/otp#6651 (comment)
- erlang/otp#6651 (comment)
- erlang/otp#6660
- erlang/otp#6727 (comment)
- erlang/otp#6835
- erlang/otp#6847
- erlang/otp#6848
- erlang/otp#6851
- erlang/otp#6960
- erlang/otp#6962
- erlang/otp#6974
- erlang/otp#7011
- erlang/otp#7128
- erlang/otp#7142
- erlang/otp#7145
- erlang/otp#7147
- erlang/otp#7168
- erlang/otp#7170
- erlang/otp#7171
- erlang/otp#7178
- erlang/otp#7179
- erlang/otp#7180
- erlang/otp#7207
- erlang/otp#7222
- erlang/otp#7248
- erlang/otp#7251
- erlang/otp#7252
- erlang/otp#7271
- erlang/otp#7283
- erlang/otp#7281 (comment)
- erlang/otp#7340
- erlang/otp#7354
- erlang/otp#7370
dialyzer:
- erlang/otp#6419
- erlang/otp#6473
- erlang/otp#6518
- erlang/otp#6580
- erlang/otp#7138
- erlang/otp#7153
- erlang/otp#7181
- erlang/otp#7325
erlfmt:
eqWAlizer:
- https://github.com/WhatsApp/eqwalizer/commit/56634681d5de33819c371285ff5682d58384518b
- https://github.com/WhatsApp/eqwalizer/commit/a253f6ee5c202d683c2719d325fd31acc46221a3
- https://github.com/WhatsApp/eqwalizer/commit/d8afa8e7cc27a115570198539128cffd16e16866
- https://github.com/WhatsApp/eqwalizer/commit/75de28b27345b7796cf4c39a460d93b1070e02ac
- https://github.com/WhatsApp/eqwalizer/commit/ae608820ec08c2108e438a92b9d7a0fdf999a06b
inferl:
- https://github.com/facebook/infer/commit/dbdcf4863ee2751a6b671f072850b29b4916bf5b
- https://github.com/facebook/infer/commit/5453911ea7a69dfb66f2cb697976eeeb9c30b176
Gradualizer