Olympia is a bug synthesizer based on Fuzzle, able to generate buggy
solidity
programs for evaluating solidity fuzzers.
Olympia uses randomly created mazes and path constraints from existing, known
CVEs to generate more realistic programs.
Please follow the steps of the provided installation instructions inside the INSTALL.md
.
Interacting with olympia is done via two top level scripts:
olympia/olympia.py
olympia/olympia_wrapper.py
For a quick usage information on available options run:
$ python olympia/olympia.py -h
or
$ python olympia/olympia_wrapper.py -h
The basic benchmark generation is controlled with the olympia/olympia.py
script.
Olympia requires a dimension size and an output folder. For example, the following command generates a benchmark
folder for a single benchmark instance inspired by a 10x10 maze.
$ python olympia/olympia.py -d 10 -o benchmark
The generated folder structure will look as follows:
./benchmark/
|
+--- bin/
|
+--- png/
|
+--- sln/
|
+--- sol_tx/
|
+--- src/
|
+--- txt/
|
+--- programs.list
|
+--- reproducible_generation.sh
(Since olympia is based on Fuzzle, the bin
folder is generated for compatibility reasons and can be ignored.)
The png
folder contains images of the mazes used for the benchmark instances.
The sln
folder contains *.txt
files with the solution on how to reach the entry of the maze. The solution is provided by a list of cells inside of the maze.
The sol_tx
folder contains *.txt
files with a solution on how to reach the buggy state of the generated contract. The files contain a list of arrays which are used as arguments to the generated step()
functions.
The src
folder contains the solidity benchmark files. For each instance, olympia will generate a simple solidity contract inside a *.sol
file and a foundry compatible *.foundry.sol
file.
The txt
folder contains a textual representation of the maze in form of 0s (walls) and 1s (walkable tiles).
The programs.list
file contains information on the generated benchmark instances. Each line contains information on maze properties used for translation.
The reproducible_generation.sh
can be seen as a history of the benchmark folder and contains all the commands to reproduce the current folder using the underlying generation.sh
script.
To conveniently generate a benchmark with multiple instances olympia provides the olympia/olympia_wrapper.py
.
As the name suggests, it wraps the original olympia generation and provides additional functionality.
Additionally to the output folder, olympia's wrapper takes a list of dimensions and a number of the amount of desired instances per provided dimensions. For example, the following command generates 2 instances for maze dimensions 5x5 and 10x10 each and stores them in the benchmark
folder.
$ python olympia/olympia_wrapper.py -d 5 10 -i 2 -o benchmark
One of the main limitations is the dimension (-d
) of the used mazes.
Olympia limits the provided dimension to be at least 5.
While there is no enforced upper bound, everything above 30 will likely exceed the EVM allowed byte-size.
The following list of options provides an overview of the possible settings for olympia benchmark generation. Each flag can be provided to olympia and its wrapper. The underlying generation process will then pick one at random for each generated instance.
-a
list of the maze generation algorithmBacktracking
Kruskal
Prims
Wilsons
Sidewinder
-m
list of generation methods responsible to fill the different branch condition holes.default
(path conditions will only consist of simple "<", "<=", ">" and ">=" relations)equality
(based on equality percentage provided with-e
, path conditions may contain the "==" relations)CVE
(uses the provided CVE files to generate complex path conditions)
-e
list of percentages to pick the equality relation for path conditions- [0 - 100]
-c
list of percentages to introduce cycles, i.e. the possibility to walk back from one cell to the previous cell- [0 - 100]
When the CVE
generation method is selected, olympia will use the content of the CVEs
folder to select a random SMT file.
To disable one of the provided files, move it into the CVEs/disable
folder.
To reliably reproduce benchmark generations, an integer seed can be provided by using the -s
flag.
Finally, for every generated solidity contract a compilation and bug-reachability check is performed.
However, these tests do NOT include gas or byte-size checks of the compiled binaries.
Since this task is very time consuming it can be turned off using the --disable-reachability-check
flag.
The following example command generates 10 solidity benchmark instances. From the 10 instances, 5 of dimension 10x10 and 5 of dimension 20x20. Moreover, it restricts the generation method to be of the type default
and equality
(with a 25% or 50% chance of picking the "==" relation if equality
is selected).
The cycle property is turned off as indicated by the provided value of 0%.
Finally, the used maze generation algorithm will be either Kruskal
or Prims
.
$ python olympia/olympia_wrapper.py \
-o benchmark \
-d 10 20 \
-i 5 \
-m default equality \
-e 25 50 \
-c 0 \
-a Kruskal Prims
To run fuzzers on the generated benchmark in a separate docker container:
$ python3 ./scripts/run_tools.py <CONFIG_FILE> <FUZZ_OUT>
An example of the configuration file (<CONFIG_FILE>
) is provided below.
{ "MazeList" : "benchmarks/programs.list"
, "Repeats" : 2
, "Workers" : 10
, "Seeds" : [4321, 1234]
, "Duration" : 30
, "MazeDir" : "benchmarks"
, "Tools" : ["echidna", "foundry", "ityfuzz", "medusa"]
}
MazeList
: path to the list of programs in the benchmarkRepeats
: number of repeats for each fuzzerWorkers
: number of available CPUs cores for the fuzzing run (each tool will get a dedicated core per campaign)Seeds
: list of seeds provided to the tools for a specific repetitionDuration
: length of fuzzing campaign in minutesMazeDir
: path to a directory that contains benchmark programs for each programming languageTools
: list of one or more of the available fuzzers (echidna
,foundry
,ityfuzz
,medusa
)
Note that all paths (MazeList
and MazeDir
) should be either absolute paths or relative paths from the configuration file.
The provided example.conf
is an example configuration file.
After the experiment is finished, the output directory (<FUZZ_OUT>
) will
contain generated reports on whether the bug was found and, if so, how long it took to find.
Once the fuzzing campaign is finished, the coverage and bug finding results can be summarized in csv format using the script as follows:
$ python scripts/save_results.py <FUZZ_OUT> <OUT_DIR>
<FUZZ_OUT>
: directory that contains fuzzing outputs<OUT_DIR>
: path to save the output file to
The script summarizes the fuzzing results found in the <FUZZ_OUT>
directory and stores them into a csv file inside of the <OUT_DIR>
.
This section explains how to generate a small benchmark and run experiments. To follow the examples, make sure to complete the installation first.
To run a simple benchmark on a single generated program, start by generating the program from the olympia
director.
$ python olympia/olympia.py -d 5 -o benchmark
This will construct a solidity program from a 5 by 5 maze using random generation methods (see the previous settings section for more information).
Additionally, edit the provided example.conf
file to point to the generated benchmark
, the
programs.list
file inside of the benchmark
folder, and define the fuzzers to be benchmarked:
{ "MazeList" : "benchmark/programs.list"
, "Repeats" : 2
, "Workers" : 2
, "Seeds" : [4321, 1234]
, "Duration" : 60
, "MazeDir" : "benchmark"
, "Tools" : ["echidna"]
}
The above setting runs the echidna
fuzzer with 2 repetitions, using the seeds 4312
and 1234
, and a 60 minute timeout on the benchmark files defined in benchmark/programs.list
.
The benchmark can then be started by running the following command from the scripts
directory:
$ python3 ./scripts/run_tools.py example.conf outputs
This will store the benchmark outputs of all fuzzing campaigns to outputs
.
A comprehensive report of the campaign's results will be stored in a report.txt
file of each campaign's directory while another file, with the ending .tc
will contain the fuzzer's output for the campaign (this can be used for debugging).
Finally, to collect and generate a single data file run following command
$ python3 ./scripts/save_results.py outputs ./
This will collect the benchmark results from output
and generate
a .csv
file inside of ./
(i.e. the current directory) for further processing.