WIP: merge
This commit is contained in:
commit
df7333b764
4
fuzzers/FRET/.gitignore
vendored
Normal file
4
fuzzers/FRET/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.qcow2
|
||||
corpus
|
||||
*.axf
|
||||
demo
|
65
fuzzers/FRET/Cargo.toml
Normal file
65
fuzzers/FRET/Cargo.toml
Normal file
@ -0,0 +1,65 @@
|
||||
[package]
|
||||
name = "fret"
|
||||
version = "0.8.2"
|
||||
authors = ["Alwin Berger <alwin.berger@tu-dortmund.de>"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["std", "snapshot_restore", "singlecore", "restarting", "do_hash_notify_state", "config_stg", "fuzz_int" ]
|
||||
std = []
|
||||
# Exec environemnt basics
|
||||
snapshot_restore = []
|
||||
snapshot_fast = [ "snapshot_restore" ]
|
||||
singlecore = []
|
||||
restarting = ['singlecore']
|
||||
run_until_saturation = []
|
||||
fuzz_int = []
|
||||
# information capture
|
||||
observe_edges = [] # observe cfg edges
|
||||
observe_hitcounts = [ "observe_edges" ] # reduces edge granularity
|
||||
observe_systemstate = []
|
||||
do_hash_notify_state = []
|
||||
trace_stg = [ "observe_systemstate" ]
|
||||
# feedbacks
|
||||
feed_stg = [ "trace_stg", "observe_systemstate" ]
|
||||
# feed_stg_edge = [ "feed_stg"]
|
||||
feed_stg_pathhash = [ "feed_stg"]
|
||||
feed_stg_abbhash = [ "feed_stg"]
|
||||
feed_stg_aggregatehash = [ "feed_stg"]
|
||||
mutate_stg = [ "observe_systemstate" ]
|
||||
feed_longest = [ ]
|
||||
feed_afl = [ "observe_edges" ]
|
||||
feed_genetic = []
|
||||
gensize_1 = [ ]
|
||||
gensize_10 = [ ]
|
||||
gensize_100 = [ ]
|
||||
# schedulers
|
||||
sched_genetic = []
|
||||
sched_afl = []
|
||||
sched_stg = []
|
||||
# sched_stg_edge = ['sched_stg'] # every edge in the stg
|
||||
sched_stg_pathhash = ['sched_stg'] # every path in the stg
|
||||
sched_stg_abbhash = ['sched_stg'] # every path of abbs
|
||||
sched_stg_aggregatehash = ['sched_stg'] # every aggregated path (order independent)
|
||||
# overall_configs
|
||||
config_genetic = ["gensize_100","feed_genetic","sched_genetic","trace_stg"]
|
||||
config_afl = ["feed_afl","sched_afl","observe_hitcounts","trace_stg"]
|
||||
config_frafl = ["feed_afl","sched_afl","feed_longest","trace_stg"]
|
||||
config_stg = ["feed_stg_aggregatehash","sched_stg_aggregatehash","mutate_stg"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
libafl = { path = "../../libafl/" }
|
||||
libafl_bolts = { path = "../../libafl_bolts/" }
|
||||
libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] }
|
||||
serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib
|
||||
hashbrown = { version = "0.14.0", features = ["serde"] } # A faster hashmap, nostd compatible
|
||||
petgraph = { version="0.6.4", features = ["serde-1"] }
|
||||
ron = "0.7" # write serialized data - including hashmaps
|
||||
rand = "0.5"
|
||||
clap = { version = "4.4.11", features = ["derive"] }
|
||||
csv = "1.3.0"
|
26
fuzzers/FRET/README.md
Normal file
26
fuzzers/FRET/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Qemu systemmode with launcher
|
||||
|
||||
This folder contains an example fuzzer for the qemu systemmode, using LLMP for fast multi-process fuzzing and crash detection.
|
||||
|
||||
## Build
|
||||
|
||||
To build this example, run
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
cd example; sh build.sh; cd ..
|
||||
```
|
||||
|
||||
This will build the the fuzzer (src/fuzzer.rs) and a small example binary based on FreeRTOS, which can run under a qemu emulation target.
|
||||
|
||||
## Run
|
||||
|
||||
Since the instrumentation is based on snapshtos QEMU needs a virtual drive (even if it is unused...).
|
||||
Create on and then run the fuzzer:
|
||||
```bash
|
||||
# create an image
|
||||
qemu-img create -f qcow2 dummy.qcow2 32M
|
||||
# run the fuzzer
|
||||
KERNEL=./example/example.elf target/release/qemu_systemmode -icount shift=auto,align=off,sleep=off -machine mps2-an385 -monitor null -kernel ./example/example.elf -serial null -nographic -snapshot -drive if=none,format=qcow2,file=dummy.qcow2 -S
|
||||
```
|
||||
Currently the ``KERNEL`` variable is needed because the fuzzer does not parse QEMUs arguments to find the binary.
|
12
fuzzers/FRET/benchmark/.gitignore
vendored
Normal file
12
fuzzers/FRET/benchmark/.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
*dump
|
||||
timedump*
|
||||
corpora
|
||||
build
|
||||
mnt
|
||||
.R*
|
||||
*.png
|
||||
*.pdf
|
||||
bins
|
||||
.snakemake
|
||||
*.zip
|
||||
*.tar.*
|
57
fuzzers/FRET/benchmark/Makefile
Normal file
57
fuzzers/FRET/benchmark/Makefile
Normal file
@ -0,0 +1,57 @@
|
||||
TIME=7200
|
||||
|
||||
corpora/%/seed:
|
||||
mkdir -p $$(dirname $@)
|
||||
LINE=$$(grep "^$$(basename $*)" target_symbols.csv); \
|
||||
export \
|
||||
KERNEL=benchmark/build/$*.elf \
|
||||
FUZZ_MAIN=$$(echo $$LINE | cut -d, -f2) \
|
||||
FUZZ_INPUT=$$(echo $$LINE | cut -d, -f3) \
|
||||
FUZZ_INPUT_LEN=$$(echo $$LINE | cut -d, -f4) \
|
||||
BREAKPOINT=$$(echo $$LINE | cut -d, -f5) \
|
||||
SEED_DIR=benchmark/corpora/$* \
|
||||
DUMP_SEED=seed; \
|
||||
../fuzzer.sh
|
||||
|
||||
timedump/%$(FUZZ_RANDOM)$(SUFFIX): corpora/%/seed
|
||||
mkdir -p $$(dirname $@)
|
||||
LINE=$$(grep "^$$(basename $*)" target_symbols.csv); \
|
||||
export \
|
||||
KERNEL=benchmark/build/$*.elf \
|
||||
FUZZ_MAIN=$$(echo $$LINE | cut -d, -f2) \
|
||||
FUZZ_INPUT=$$(echo $$LINE | cut -d, -f3) \
|
||||
FUZZ_INPUT_LEN=$$(echo $$LINE | cut -d, -f4) \
|
||||
BREAKPOINT=$$(echo $$LINE | cut -d, -f5) \
|
||||
SEED_RANDOM=1 \
|
||||
TIME_DUMP=benchmark/$@ \
|
||||
CASE_DUMP=benchmark/$@; \
|
||||
../fuzzer.sh + + + + + $(TIME) + + + > $@_log
|
||||
#SEED_DIR=benchmark/corpora/$*
|
||||
|
||||
all_sequential: timedump/sequential/mpeg2$(FUZZ_RANDOM) timedump/sequential/dijkstra$(FUZZ_RANDOM) timedump/sequential/epic$(FUZZ_RANDOM) \
|
||||
timedump/sequential/g723_enc$(FUZZ_RANDOM) timedump/sequential/audiobeam$(FUZZ_RANDOM) \
|
||||
timedump/sequential/gsm_enc$(FUZZ_RANDOM)
|
||||
|
||||
all_kernel: timedump/kernel/bsort$(FUZZ_RANDOM) timedump/kernel/insertsort$(FUZZ_RANDOM) #timedump/kernel/fft$(FUZZ_RANDOM)
|
||||
|
||||
all_app: timedump/app/lift$(FUZZ_RANDOM)
|
||||
|
||||
all_system: timedump/lift$(FUZZ_RANDOM)$(SUFFIX)
|
||||
|
||||
all_period: timedump/waters$(FUZZ_RANDOM)$(SUFFIX)
|
||||
|
||||
tacle_rtos: timedump/tacle_rtos$(FUZZ_RANDOM)
|
||||
|
||||
graphics:
|
||||
Rscript --vanilla plot_comparison.r mnt/timedump/sequential audiobeam
|
||||
Rscript --vanilla plot_comparison.r mnt/timedump/sequential dijkstra
|
||||
Rscript --vanilla plot_comparison.r mnt/timedump/sequential epic
|
||||
Rscript --vanilla plot_comparison.r mnt/timedump/sequential g723_enc
|
||||
# Rscript --vanilla plot_comparison.r mnt/timedump/sequential gsm_enc
|
||||
# Rscript --vanilla plot_comparison.r mnt/timedump/sequential huff_dec
|
||||
Rscript --vanilla plot_comparison.r mnt/timedump/sequential mpeg2
|
||||
# Rscript --vanilla plot_comparison.r mnt/timedump/sequential rijndael_dec
|
||||
# Rscript --vanilla plot_comparison.r mnt/timedump/sequential rijndael_enc
|
||||
|
||||
clean:
|
||||
rm -rf corpora timedump
|
272
fuzzers/FRET/benchmark/Snakefile
Normal file
272
fuzzers/FRET/benchmark/Snakefile
Normal file
@ -0,0 +1,272 @@
|
||||
import csv
|
||||
import os
|
||||
def_flags="--no-default-features --features std,snapshot_restore,singlecore,restarting,do_hash_notify_state"
|
||||
remote="remote/"
|
||||
RUNTIME=1800
|
||||
TARGET_REPS_A=2
|
||||
TARGET_REPS_B=2
|
||||
NUM_NODES=2
|
||||
REP_PER_NODE_A=int(TARGET_REPS_A/NUM_NODES)
|
||||
REP_PER_NODE_B=int(TARGET_REPS_B/NUM_NODES)
|
||||
NODE_ID= 0 if os.getenv('NODE_ID') == None else int(os.environ['NODE_ID'])
|
||||
MY_RANGE_A=range(NODE_ID*REP_PER_NODE_A,(NODE_ID+1)*REP_PER_NODE_A)
|
||||
MY_RANGE_B=range(NODE_ID*REP_PER_NODE_B,(NODE_ID+1)*REP_PER_NODE_B)
|
||||
|
||||
rule build_showmap:
|
||||
output:
|
||||
directory("bins/target_showmap")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},config_stg"
|
||||
|
||||
rule build_random:
|
||||
output:
|
||||
directory("bins/target_random")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},feed_longest"
|
||||
|
||||
rule build_frafl:
|
||||
output:
|
||||
directory("bins/target_frafl")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},config_frafl,feed_longest"
|
||||
|
||||
rule build_afl:
|
||||
output:
|
||||
directory("bins/target_afl")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},config_afl,observer_hitcounts"
|
||||
|
||||
rule build_stg:
|
||||
output:
|
||||
directory("bins/target_stg")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},config_stg"
|
||||
|
||||
rule build_stgpath:
|
||||
output:
|
||||
directory("bins/target_stgpath")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},feed_stg_abbhash,sched_stg_abbhash,mutate_stg"
|
||||
|
||||
rule build_showmap_int:
|
||||
output:
|
||||
directory("bins/target_showmap_int")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},config_stg,fuzz_int"
|
||||
|
||||
rule build_random_int:
|
||||
output:
|
||||
directory("bins/target_random_int")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},feed_longest,fuzz_int"
|
||||
|
||||
|
||||
rule build_frafl_int:
|
||||
output:
|
||||
directory("bins/target_frafl_int")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},config_frafl,fuzz_int"
|
||||
|
||||
rule build_afl_int:
|
||||
output:
|
||||
directory("bins/target_afl_int")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},config_afl,fuzz_int,"
|
||||
|
||||
rule build_stg_int:
|
||||
output:
|
||||
directory("bins/target_stg_int")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},config_stg,fuzz_int"
|
||||
|
||||
rule build_stgpath_int:
|
||||
output:
|
||||
directory("bins/target_stgpath_int")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},feed_stg_abbhash,sched_stg_abbhash,mutate_stg,fuzz_int"
|
||||
|
||||
rule build_feedgeneration1:
|
||||
output:
|
||||
directory("bins/target_feedgeneration1")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},feed_genetic,gensize_1"
|
||||
|
||||
rule build_feedgeneration1_int:
|
||||
output:
|
||||
directory("bins/target_feedgeneration1_int")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},feed_genetic,fuzz_int,gensize_1"
|
||||
|
||||
rule build_feedgeneration10:
|
||||
output:
|
||||
directory("bins/target_feedgeneration10")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},feed_genetic,gensize_10"
|
||||
|
||||
rule build_feedgeneration10_int:
|
||||
output:
|
||||
directory("bins/target_feedgeneration10_int")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},feed_genetic,fuzz_int,gensize_10"
|
||||
|
||||
rule build_feedgeneration100:
|
||||
output:
|
||||
directory("bins/target_feedgeneration100")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},config_genetic,gensize_100"
|
||||
|
||||
rule build_feedgeneration100_int:
|
||||
output:
|
||||
directory("bins/target_feedgeneration100_int")
|
||||
shell:
|
||||
"cargo build --target-dir {output} {def_flags},config_genetic,fuzz_int,gensize_100"
|
||||
|
||||
rule run_bench:
|
||||
input:
|
||||
"build/{target}.elf",
|
||||
"bins/target_{fuzzer}"
|
||||
output:
|
||||
multiext("timedump/{fuzzer}/{target}#{num}", ".time", ".log") # , ".case"
|
||||
run:
|
||||
with open('target_symbols.csv') as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
line = next((x for x in reader if x['kernel']==wildcards.target), None)
|
||||
if line == None:
|
||||
return False
|
||||
kernel=line['kernel']
|
||||
fuzz_main=line['main_function']
|
||||
fuzz_input=line['input_symbol']
|
||||
fuzz_len=line['input_size']
|
||||
bkp=line['return_function']
|
||||
if wildcards.fuzzer.find('random') >= 0:
|
||||
script="""
|
||||
export RUST_BACKTRACE=1
|
||||
mkdir -p $(dirname {output[0]})
|
||||
set +e
|
||||
echo $(pwd)/{input[1]}/debug/fret -n $(pwd)/timedump/{wildcards.fuzzer}/{wildcards.target}#{wildcards.num} -t -a -g -k {input[0]} -c ./target_symbols.csv fuzz --random -t {RUNTIME} -s {wildcards.num}
|
||||
$(pwd)/{input[1]}/debug/fret -n $(pwd)/timedump/{wildcards.fuzzer}/{wildcards.target}#{wildcards.num} -t -a -g -k {input[0]} -c ./target_symbols.csv fuzz --random -t {RUNTIME} -s {wildcards.num} > {output[1]} 2>&1
|
||||
exit 0
|
||||
"""
|
||||
else:
|
||||
script="""
|
||||
export RUST_BACKTRACE=1
|
||||
mkdir -p $(dirname {output[0]})
|
||||
set +e
|
||||
echo $(pwd)/{input[1]}/debug/fret -n $(pwd)/timedump/{wildcards.fuzzer}/{wildcards.target}#{wildcards.num} -t -a -g -k {input[0]} -c ./target_symbols.csv fuzz -t {RUNTIME} -s {wildcards.num}
|
||||
$(pwd)/{input[1]}/debug/fret -n $(pwd)/timedump/{wildcards.fuzzer}/{wildcards.target}#{wildcards.num} -t -a -g -k {input[0]} -c ./target_symbols.csv fuzz -t {RUNTIME} -s {wildcards.num} > {output[1]} 2>&1
|
||||
exit 0
|
||||
"""
|
||||
shell(script)
|
||||
|
||||
rule run_showmap:
|
||||
input:
|
||||
"{remote}build/{target}.elf",
|
||||
"bins/target_showmap",
|
||||
"bins/target_showmap_int",
|
||||
"{remote}timedump/{fuzzer}/{target}#{num}.case"
|
||||
output:
|
||||
"{remote}timedump/{fuzzer}/{target}#{num}_case.trace.ron",
|
||||
"{remote}timedump/{fuzzer}/{target}#{num}_case.time",
|
||||
run:
|
||||
with open('target_symbols.csv') as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
line = next((x for x in reader if x['kernel']==wildcards.target), None)
|
||||
if line == None:
|
||||
return False
|
||||
kernel=line['kernel']
|
||||
fuzz_main=line['main_function']
|
||||
fuzz_input=line['input_symbol']
|
||||
fuzz_len=line['input_size']
|
||||
bkp=line['return_function']
|
||||
script=""
|
||||
if wildcards.fuzzer.find('_int') > -1:
|
||||
script="export FUZZER=$(pwd)/{input[2]}/debug/fret\n"
|
||||
else:
|
||||
script="export FUZZER=$(pwd)/{input[1]}/debug/fret\n"
|
||||
script+="""
|
||||
mkdir -p $(dirname {output})
|
||||
set +e
|
||||
echo $(pwd)/{input[1]}/debug/fret -n $(pwd)/{remote}/timedump/{wildcards.fuzzer}/{wildcards.target}#{wildcards.num}_case -t -a -r -g -k {input[0]} -c ./target_symbols.csv showmap -i {input[3]}
|
||||
$(pwd)/{input[1]}/debug/fret -n $(pwd)/{remote}/timedump/{wildcards.fuzzer}/{wildcards.target}#{wildcards.num}_case -t -a -r -g -k {input[0]} -c ./target_symbols.csv showmap -i {input[3]}
|
||||
exit 0
|
||||
"""
|
||||
if wildcards.fuzzer.find('random') >= 0:
|
||||
script="export FUZZ_RANDOM=1\n"+script
|
||||
shell(script)
|
||||
|
||||
rule tarnsform_trace:
|
||||
input:
|
||||
"{remote}timedump/{fuzzer}/{target}.{num}.trace.ron"
|
||||
output:
|
||||
"{remote}timedump/{fuzzer}/{target}.{num}.trace.csv"
|
||||
shell:
|
||||
"$(pwd)/../../../../state2gantt/target/debug/state2gantt {input} > {output[0]}"
|
||||
|
||||
rule trace2gantt:
|
||||
input:
|
||||
"{remote}timedump/{fuzzer}/{target}.{num}.trace.csv"
|
||||
output:
|
||||
"{remote}timedump/{fuzzer}/{target}.{num}.trace.csv.png"
|
||||
shell:
|
||||
"Rscript --vanilla $(pwd)/../../../../state2gantt/gantt.R {input}"
|
||||
|
||||
rule all_main:
|
||||
input:
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['random','afl','feedgeneration10','state'], target=['waters','watersv2'],num=range(0,3))
|
||||
|
||||
rule all_main_int:
|
||||
input:
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['random_int','afl_int','feedgeneration10_int','state_int'], target=['waters_int','watersv2_int'],num=range(0,4))
|
||||
|
||||
rule all_compare_feedgeneration:
|
||||
input:
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['feedgeneration1','feedgeneration10','feedgeneration100'], target=['waters_int','watersv2'],num=range(0,10))
|
||||
|
||||
rule all_compare_feedgeneration_int:
|
||||
input:
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['feedgeneration1_int','feedgeneration10_int','feedgeneration100_int'], target=['waters_int','watersv2_int'],num=range(0,10))
|
||||
|
||||
rule all_compare_afl:
|
||||
input:
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['afl','frafl','feedlongest'], target=['waters','watersv2'],num=range(0,10))
|
||||
|
||||
rule all_compare_afl_int:
|
||||
input:
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['afl_int','frafl_int','feedlongest_int'], target=['waters_int','watersv2_int'],num=range(0,10))
|
||||
|
||||
rule all_images:
|
||||
input:
|
||||
expand("{remote}timedump/{fuzzer}/{target}.{num}.trace.csv.png",remote=remote, fuzzer=['afl','feedgeneration10','state'], target=['waters','watersv2'],num=range(0,3))
|
||||
|
||||
rule all_images_int:
|
||||
input:
|
||||
expand("{remote}timedump/{fuzzer}/{target}.{num}.trace.csv.png",remote=remote, fuzzer=['afl_int','feedgeneration10_int','state_int'], target=['waters_int','watersv2_int'],num=range(0,3))
|
||||
|
||||
rule clusterfuzz:
|
||||
input:
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['random','afl','feedgeneration10','state'], target=['waters','watersv2'],num=MY_RANGE_A),
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['random_int','afl_int','feedgeneration10_int','state_int'], target=['waters_int','watersv2_int'],num=MY_RANGE_A),
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['feedgeneration1','feedgeneration10','feedgeneration100'], target=['waters_int','watersv2'],num=MY_RANGE_B),
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['feedgeneration1_int','feedgeneration10_int','feedgeneration100_int'], target=['waters_int','watersv2_int'],num=MY_RANGE_B),
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['afl','frafl','feedlongest'], target=['waters','watersv2'],num=MY_RANGE_B),
|
||||
expand("timedump/{fuzzer}/{target}.{num}", fuzzer=['afl_int','frafl_int','feedlongest_int'], target=['waters_int','watersv2_int'],num=MY_RANGE_B),
|
||||
|
||||
rule all_new:
|
||||
input:
|
||||
expand("timedump/{fuzzer}/{target}#{num}.time", fuzzer=['feedgeneration100', 'frafl', 'stg'], target=['waters', 'watersv2'],num=range(0,3)),
|
||||
expand("timedump/{fuzzer}/{target}#{num}.time", fuzzer=['feedgeneration100_int', 'frafl_int', 'stg_int'], target=['waters_int', 'watersv2_int'],num=range(0,3)),
|
||||
expand("timedump/{fuzzer}/{target}#{num}.time", fuzzer=['random', 'stgpath'], target=['waters', 'watersv2'],num=range(0,3)),
|
||||
expand("timedump/{fuzzer}/{target}#{num}.time", fuzzer=['random_int', 'stgpath_int'], target=['waters_int', 'watersv2_int'],num=range(0,3))
|
||||
|
||||
rule all_showmap:
|
||||
input:
|
||||
expand("{remote}timedump/{fuzzer}/{target}#{num}_case.trace.ron",remote=remote, fuzzer=['frafl', 'stg'], target=['watersv2'],num=range(2,3)),
|
||||
expand("{remote}timedump/{fuzzer}/{target}#{num}_case.trace.ron",remote=remote, fuzzer=['frafl_int', 'stg_int'], target=['watersv2_int'],num=range(0,3)),
|
||||
expand("{remote}timedump/{fuzzer}/{target}#{num}_case.trace.ron",remote=remote, fuzzer=['random', 'stgpath'], target=['watersv2'],num=range(0,1)),
|
||||
expand("{remote}timedump/{fuzzer}/{target}#{num}_case.trace.ron",remote=remote, fuzzer=['random_int', 'stgpath_int'], target=['watersv2_int'],num=range(0,1))
|
||||
|
||||
|
||||
|
||||
rule all_bins:
|
||||
input:
|
||||
expand("bins/target_{target}{flag}",target=['random','frafl','stg','stgpath','feedgeneration100'],flag=['','_int'])
|
17
fuzzers/FRET/benchmark/build_all_demos.sh
Normal file
17
fuzzers/FRET/benchmark/build_all_demos.sh
Normal file
@ -0,0 +1,17 @@
|
||||
make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC clean && make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC WATERS_DEMO=1 INTERRUPT_ACTIVATION=1
|
||||
cp ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/build/RTOSDemo.axf build/waters_int.elf
|
||||
|
||||
make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC clean && make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC WATERS_DEMO=1 INTERRUPT_ACTIVATION=0
|
||||
cp ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/build/RTOSDemo.axf build/waters.elf
|
||||
|
||||
make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC clean && make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC WATERSV2_DEMO=1 INTERRUPT_ACTIVATION=1
|
||||
cp ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/build/RTOSDemo.axf build/watersv2_int.elf
|
||||
|
||||
make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC clean && make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC WATERSV2_DEMO=1 INTERRUPT_ACTIVATION=0
|
||||
cp ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/build/RTOSDemo.axf build/watersv2.elf
|
||||
|
||||
make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC clean && make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC INTERACT_DEMO=1 INTERRUPT_ACTIVATION=1
|
||||
cp ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/build/RTOSDemo.axf build/interact_int.elf
|
||||
|
||||
make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC clean && make -C ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC INTERACT_DEMO=1 INTERRUPT_ACTIVATION=0
|
||||
cp ../../../../FreeRTOS/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/build/RTOSDemo.axf build/interact.elf
|
8
fuzzers/FRET/benchmark/logtail.sh
Normal file
8
fuzzers/FRET/benchmark/logtail.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
find $1 -type 'f' -iname "${2}#*.log" | while IFS="" read -r p || [ -n "$p" ]
|
||||
do
|
||||
LINE=$(tail -n 100 $p | grep -io "run time: .* corpus: [0-9]*" | tail -n 1)
|
||||
echo $p: $LINE
|
||||
LINE=$(grep -i "interesting corpus elements" $p | tail -n 1)
|
||||
echo $p: $LINE
|
||||
done
|
7
fuzzers/FRET/benchmark/plot_all_benchmarks.sh
Normal file
7
fuzzers/FRET/benchmark/plot_all_benchmarks.sh
Normal file
@ -0,0 +1,7 @@
|
||||
Rscript plot_multi.r remote waters ~/code/FRET/LibAFL/fuzzers/FRET/benchmark/remote &
|
||||
Rscript plot_multi.r remote waters_int ~/code/FRET/LibAFL/fuzzers/FRET/benchmark/remote &
|
||||
Rscript plot_multi.r remote watersv2 ~/code/FRET/LibAFL/fuzzers/FRET/benchmark/remote &
|
||||
Rscript plot_multi.r remote watersv2_int ~/code/FRET/LibAFL/fuzzers/FRET/benchmark/remote &
|
||||
Rscript plot_multi.r remote interact ~/code/FRET/LibAFL/fuzzers/FRET/benchmark/remote &
|
||||
Rscript plot_multi.r remote interact_int ~/code/FRET/LibAFL/fuzzers/FRET/benchmark/remote &
|
||||
wait
|
14
fuzzers/FRET/benchmark/plot_all_traces.sh
Normal file
14
fuzzers/FRET/benchmark/plot_all_traces.sh
Normal file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
find ./remote/timedump -type 'f' -iregex '.*case' | while IFS="" read -r p || [ -n "$p" ]
|
||||
do
|
||||
N=$(dirname "$p")/$(basename -s .case "$p")
|
||||
T="${N}_case.trace.ron"
|
||||
P="${N}_case"
|
||||
echo $N
|
||||
if [ ! -f "$T" ]; then
|
||||
snakemake -c1 "$T"
|
||||
fi
|
||||
if [ ! -f "$P.html" ]; then
|
||||
~/code/FRET/state2gantt/driver.sh "$T"
|
||||
fi
|
||||
done
|
83
fuzzers/FRET/benchmark/plot_comparison.r
Normal file
83
fuzzers/FRET/benchmark/plot_comparison.r
Normal file
@ -0,0 +1,83 @@
|
||||
library("mosaic")
|
||||
args = commandArgs(trailingOnly=TRUE)
|
||||
|
||||
#myolors=c("#339933","#0066ff","#993300") # grün, balu, rot
|
||||
myolors=c("dark green","dark blue","dark red", "yellow") # grün, balu, rot
|
||||
|
||||
if (length(args)==0) {
|
||||
runtype="timedump"
|
||||
target="waters"
|
||||
filename_1=sprintf("%s.png",target)
|
||||
filename_2=sprintf("%s_maxline.png",target)
|
||||
filename_3=sprintf("%s_hist.png",target)
|
||||
} else {
|
||||
runtype=args[1]
|
||||
target=args[2]
|
||||
filename_1=sprintf("%s.png",args[2])
|
||||
filename_2=sprintf("%s_maxline.png",args[2])
|
||||
filename_3=sprintf("%s_hist.png",args[2])
|
||||
# filename_1=args[3]
|
||||
}
|
||||
|
||||
file_1=sprintf("~/code/FRET/LibAFL/fuzzers/FRET/benchmark/%s/%s_state",runtype,target)
|
||||
file_2=sprintf("~/code/FRET/LibAFL/fuzzers/FRET/benchmark/%s/%s_afl",runtype,target)
|
||||
file_3=sprintf("~/code/FRET/LibAFL/fuzzers/FRET/benchmark/%s/%s_random",runtype,target)
|
||||
file_4=sprintf("~/code/FRET/LibAFL/fuzzers/FRET/benchmark/%s/%s_graph",runtype,target)
|
||||
timetrace <- read.table(file_1, quote="\"", comment.char="")
|
||||
timetrace_afl <- read.table(file_2, quote="\"", comment.char="")
|
||||
timetrace_rand <- read.table(file_3, quote="\"", comment.char="")
|
||||
timetrace_graph <- read.table(file_4, quote="\"", comment.char="")
|
||||
timetrace[[2]]=seq_len(length(timetrace[[1]]))
|
||||
timetrace_afl[[2]]=seq_len(length(timetrace_afl[[1]]))
|
||||
timetrace_rand[[2]]=seq_len(length(timetrace_rand[[1]]))
|
||||
timetrace_graph[[2]]=seq_len(length(timetrace_graph[[1]]))
|
||||
names(timetrace)[1] <- "timetrace"
|
||||
names(timetrace)[2] <- "iter"
|
||||
names(timetrace_afl)[1] <- "timetrace"
|
||||
names(timetrace_afl)[2] <- "iter"
|
||||
names(timetrace_rand)[1] <- "timetrace"
|
||||
names(timetrace_rand)[2] <- "iter"
|
||||
names(timetrace_graph)[1] <- "timetrace"
|
||||
names(timetrace_graph)[2] <- "iter"
|
||||
|
||||
png(file=filename_1)
|
||||
# pdf(file=filename_1,width=8, height=8)
|
||||
plot(timetrace[[2]],timetrace[[1]], col=myolors[1], xlab="iters", ylab="wcet", pch='.')
|
||||
points(timetrace_afl[[2]],timetrace_afl[[1]], col=myolors[2], pch='.')
|
||||
points(timetrace_rand[[2]],timetrace_rand[[1]], col=myolors[3], pch='.')
|
||||
points(timetrace_graph[[2]],timetrace_graph[[1]], col=myolors[4], pch='.')
|
||||
abline(lm(timetrace ~ iter, data=timetrace),col=myolors[1])
|
||||
abline(lm(timetrace ~ iter, data=timetrace_afl),col=myolors[2])
|
||||
abline(lm(timetrace ~ iter, data=timetrace_rand),col=myolors[3])
|
||||
dev.off()
|
||||
|
||||
png(file=filename_3)
|
||||
gf_histogram(~ timetrace,data=timetrace, fill=myolors[1]) %>%
|
||||
gf_histogram(~ timetrace,data=timetrace_afl, fill=myolors[2]) %>%
|
||||
gf_histogram(~ timetrace,data=timetrace_rand, fill=myolors[3]) %>%
|
||||
gf_histogram(~ timetrace,data=timetrace_graph, fill=myolors[4])
|
||||
dev.off()
|
||||
|
||||
# Takes a flat list
|
||||
trace2maxline <- function(tr) {
|
||||
maxline = tr
|
||||
for (var in seq_len(length(maxline))[2:length(maxline)]) {
|
||||
maxline[var] = max(maxline[var],maxline[var-1])
|
||||
}
|
||||
#plot(seq_len(length(maxline)),maxline,"l",xlab="Index",ylab="WOET")
|
||||
return(maxline)
|
||||
}
|
||||
timetrace[[1]] <- trace2maxline(timetrace[[1]])
|
||||
timetrace_afl[[1]] <- trace2maxline(timetrace_afl[[1]])
|
||||
timetrace_rand[[1]] <- trace2maxline(timetrace_rand[[1]])
|
||||
timetrace_graph[[1]] <- trace2maxline(timetrace_graph[[1]])
|
||||
|
||||
png(file=filename_2)
|
||||
plot(timetrace[[2]],timetrace[[1]], col=myolors[1], xlab="iters", ylab="wcet", pch='.')
|
||||
points(timetrace_afl[[2]],timetrace_afl[[1]], col=myolors[2], pch='.')
|
||||
points(timetrace_rand[[2]],timetrace_rand[[1]], col=myolors[3], pch='.')
|
||||
points(timetrace_graph[[2]],timetrace_graph[[1]], col=myolors[4], pch='.')
|
||||
#abline(lm(timetrace ~ iter, data=timetrace),col=myolors[1])
|
||||
#abline(lm(timetrace ~ iter, data=timetrace_afl),col=myolors[2])
|
||||
#abline(lm(timetrace ~ iter, data=timetrace_rand),col=myolors[3])
|
||||
dev.off()
|
335
fuzzers/FRET/benchmark/plot_multi.r
Normal file
335
fuzzers/FRET/benchmark/plot_multi.r
Normal file
@ -0,0 +1,335 @@
|
||||
library("mosaic")
|
||||
library("dplyr")
|
||||
library("foreach")
|
||||
library("doParallel")
|
||||
|
||||
#setup parallel backend to use many processors
|
||||
cores=detectCores()
|
||||
cl <- makeCluster(cores[1]-1) #not to overload your computer
|
||||
registerDoParallel(cl)
|
||||
|
||||
args = commandArgs(trailingOnly=TRUE)
|
||||
|
||||
if (length(args)==0) {
|
||||
runtype="remote"
|
||||
#target="waters"
|
||||
target="watersv2"
|
||||
#target="waters_int"
|
||||
#target="watersv2_int"
|
||||
outputpath="~/code/FRET/LibAFL/fuzzers/FRET/benchmark/"
|
||||
#MY_SELECTION <- c('state', 'afl', 'graph', 'random')
|
||||
SAVE_FILE=TRUE
|
||||
} else {
|
||||
runtype=args[1]
|
||||
target=args[2]
|
||||
outputpath=args[3]
|
||||
#MY_SELECTION <- args[4:length(args)]
|
||||
#if (length(MY_SELECTION) == 0)
|
||||
# MY_SELECTION<-NULL
|
||||
SAVE_FILE=TRUE
|
||||
print(runtype)
|
||||
print(target)
|
||||
print(outputpath)
|
||||
}
|
||||
worst_cases <- list(waters=0, waters_int=0, tmr=405669, micro_longint=0, gen3=0)
|
||||
worst_case <- worst_cases[[target]]
|
||||
if (is.null(worst_case)) {
|
||||
worst_case = 0
|
||||
}
|
||||
|
||||
#MY_COLORS=c("green","blue","red", "orange", "pink", "black")
|
||||
MY_COLORS <- c("green", "blue", "red", "magenta", "orange", "cyan", "pink", "gray", "orange", "black", "yellow","brown")
|
||||
BENCHDIR=sprintf("~/code/FRET/LibAFL/fuzzers/FRET/benchmark/%s",runtype)
|
||||
BASENAMES=Filter(function(x) x!="" && substr(x,1,1)!='.',list.dirs(BENCHDIR,full.names=FALSE))
|
||||
PATTERNS="%s#[0-9]*.time$"
|
||||
#RIBBON='sd'
|
||||
#RIBBON='span'
|
||||
RIBBON='both'
|
||||
DRAW_WC = worst_case > 0
|
||||
LEGEND_POS="bottomright"
|
||||
#LEGEND_POS="bottomright"
|
||||
CONTINUE_LINE_TO_END=FALSE
|
||||
|
||||
# https://www.r-bloggers.com/2013/04/how-to-change-the-alpha-value-of-colours-in-r/
|
||||
alpha <- function(col, alpha=1){
|
||||
if(missing(col))
|
||||
stop("Please provide a vector of colours.")
|
||||
apply(sapply(col, col2rgb)/255, 2,
|
||||
function(x)
|
||||
rgb(x[1], x[2], x[3], alpha=alpha))
|
||||
}
|
||||
|
||||
# Trimm a list of data frames to common length
|
||||
trim_data <- function(input,len=NULL) {
|
||||
if (is.null(len)) {
|
||||
len <- min(sapply(input, function(v) dim(v)[1]))
|
||||
}
|
||||
return(lapply(input, function(d) slice_head(d,n=len)))
|
||||
}
|
||||
|
||||
length_of_data <- function(input) {
|
||||
min(sapply(input, function(v) dim(v)[1]))
|
||||
}
|
||||
|
||||
# Takes a flat list
|
||||
trace2maxline <- function(tr) {
|
||||
maxline = tr
|
||||
for (var in seq_len(length(maxline))[2:length(maxline)]) {
|
||||
#if (maxline[var]>1000000000) {
|
||||
# maxline[var]=maxline[var-1]
|
||||
#} else {
|
||||
maxline[var] = max(maxline[var],maxline[var-1])
|
||||
#}
|
||||
}
|
||||
#plot(seq_len(length(maxline)),maxline,"l",xlab="Index",ylab="WOET")
|
||||
return(maxline)
|
||||
}
|
||||
|
||||
# Take a list of data frames, output same form but maxlines
|
||||
data2maxlines <- function(tr) {
|
||||
min_length <- min(sapply(tr, function(v) dim(v)[1]))
|
||||
maxline <- tr
|
||||
for (var in seq_len(length(tr))) {
|
||||
maxline[[var]][[1]]=trace2maxline(tr[[var]][[1]])
|
||||
}
|
||||
return(maxline)
|
||||
}
|
||||
# Take a multi-column data frame, output same form but maxlines
|
||||
frame2maxlines <- function(tr) {
|
||||
for (var in seq_len(length(tr))) {
|
||||
tr[[var]]=trace2maxline(tr[[var]])
|
||||
}
|
||||
return(tr)
|
||||
}
|
||||
|
||||
trace2maxpoints <- function(tr) {
|
||||
minval = tr[1,1]
|
||||
collect = tr[1,]
|
||||
for (i in seq_len(dim(tr)[1])) {
|
||||
if (minval < tr[i,1]) {
|
||||
collect = rbind(collect,tr[i,])
|
||||
minval = tr[i,1]
|
||||
}
|
||||
}
|
||||
tmp = tr[dim(tr)[1],]
|
||||
tmp[1] = minval[1]
|
||||
collect = rbind(collect,tmp)
|
||||
return(collect)
|
||||
}
|
||||
|
||||
sample_maxpoints <- function(tr,po) {
|
||||
index = 1
|
||||
collect=NULL
|
||||
endpoint = dim(tr)[1]
|
||||
for (p in po) {
|
||||
if (p<=tr[1,2]) {
|
||||
tmp = tr[index,]
|
||||
tmp[2] = p
|
||||
collect = rbind(collect, tmp)
|
||||
} else if (p>=tr[endpoint,2]) {
|
||||
tmp = tr[endpoint,]
|
||||
tmp[2] = p
|
||||
collect = rbind(collect, tmp)
|
||||
} else {
|
||||
for (i in seq(index,endpoint)-1) {
|
||||
if (p >= tr[i,2] && p<tr[i+1,2]) {
|
||||
tmp = tr[i,]
|
||||
tmp[2] = p
|
||||
collect = rbind(collect, tmp)
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return(collect)
|
||||
}
|
||||
|
||||
#https://www.r-bloggers.com/2012/01/parallel-r-loops-for-windows-and-linux/
|
||||
all_runtypetables <- foreach (bn=BASENAMES) %do% {
|
||||
runtypefiles <- list.files(file.path(BENCHDIR,bn),pattern=sprintf(PATTERNS,target),full.names = TRUE)
|
||||
if (length(runtypefiles) > 0) {
|
||||
runtypetables_reduced <- foreach(i=seq_len(length(runtypefiles))) %dopar% {
|
||||
rtable = read.csv(runtypefiles[[i]], col.names=c(sprintf("%s%d",bn,i),sprintf("times%d",i)))
|
||||
trace2maxpoints(rtable)
|
||||
}
|
||||
#runtypetables <- lapply(seq_len(length(runtypefiles)),
|
||||
# function(i)read.csv(runtypefiles[[i]], col.names=c(sprintf("%s%d",bn,i),sprintf("times%d",i))))
|
||||
#runtypetables_reduced <- lapply(runtypetables, trace2maxpoints)
|
||||
runtypetables_reduced
|
||||
#all_runtypetables = c(all_runtypetables, list(runtypetables_reduced))
|
||||
}
|
||||
}
|
||||
all_runtypetables = all_runtypetables[lapply(all_runtypetables, length) > 0]
|
||||
all_min_points = foreach(rtt=all_runtypetables,.combine = cbind) %do% {
|
||||
bn = substr(names(rtt[[1]])[1],1,nchar(names(rtt[[1]])[1])-1)
|
||||
ret = data.frame(min(unlist(lapply(rtt, function(v) v[dim(v)[1],2]))))
|
||||
names(ret)[1] = bn
|
||||
ret/(3600 * 1000)
|
||||
}
|
||||
all_max_points = foreach(rtt=all_runtypetables,.combine = cbind) %do% {
|
||||
bn = substr(names(rtt[[1]])[1],1,nchar(names(rtt[[1]])[1])-1)
|
||||
ret = data.frame(max(unlist(lapply(rtt, function(v) v[dim(v)[1],2]))))
|
||||
names(ret)[1] = bn
|
||||
ret/(3600 * 1000)
|
||||
}
|
||||
all_points = sort(unique(Reduce(c, lapply(all_runtypetables, function(v) Reduce(c, lapply(v, function(w) w[[2]]))))))
|
||||
all_maxlines <- foreach (rtt=all_runtypetables) %do% {
|
||||
bn = substr(names(rtt[[1]])[1],1,nchar(names(rtt[[1]])[1])-1)
|
||||
runtypetables_sampled = foreach(v=rtt) %dopar% {
|
||||
sample_maxpoints(v, all_points)[1]
|
||||
}
|
||||
#runtypetables_sampled = lapply(rtt, function(v) sample_maxpoints(v, all_points)[1])
|
||||
tmp_frame <- Reduce(cbind, runtypetables_sampled)
|
||||
statframe <- data.frame(rowMeans(tmp_frame),apply(tmp_frame, 1, sd),apply(tmp_frame, 1, min),apply(tmp_frame, 1, max), apply(tmp_frame, 1, median))
|
||||
names(statframe) <- c(bn, sprintf("%s_sd",bn), sprintf("%s_min",bn), sprintf("%s_max",bn), sprintf("%s_med",bn))
|
||||
#statframe[sprintf("%s_times",bn)] = all_points
|
||||
round(statframe)
|
||||
#all_maxlines = c(all_maxlines, list(round(statframe)))
|
||||
}
|
||||
one_frame<-data.frame(all_maxlines)
|
||||
one_frame[length(one_frame)+1] <- all_points/(3600 * 1000)
|
||||
names(one_frame)[length(one_frame)] <- 'time'
|
||||
|
||||
typenames = names(one_frame)[which(names(one_frame) != 'time')]
|
||||
typenames = typenames[which(!endsWith(typenames, "_sd"))]
|
||||
typenames = typenames[which(!endsWith(typenames, "_med"))]
|
||||
ylow=min(one_frame[typenames])
|
||||
yhigh=max(one_frame[typenames],worst_case)
|
||||
typenames = typenames[which(!endsWith(typenames, "_min"))]
|
||||
typenames = typenames[which(!endsWith(typenames, "_max"))]
|
||||
|
||||
ml2lines <- function(ml,lim) {
|
||||
lines = NULL
|
||||
last = 0
|
||||
for (i in seq_len(dim(ml)[1])) {
|
||||
if (!CONTINUE_LINE_TO_END && lim<ml[i,2]) {
|
||||
break
|
||||
}
|
||||
lines = rbind(lines, cbind(X=last, Y=ml[i,1]))
|
||||
lines = rbind(lines, cbind(X=ml[i,2], Y=ml[i,1]))
|
||||
last = ml[i,2]
|
||||
}
|
||||
return(lines)
|
||||
}
|
||||
|
||||
plotting <- function(selection, filename, MY_COLORS_) {
|
||||
# filter out names of iters and sd cols
|
||||
typenames = names(one_frame)[which(names(one_frame) != 'times')]
|
||||
typenames = typenames[which(!endsWith(typenames, "_sd"))]
|
||||
typenames = typenames[which(!endsWith(typenames, "_med"))]
|
||||
typenames = typenames[which(!endsWith(typenames, "_min"))]
|
||||
typenames = typenames[which(!endsWith(typenames, "_max"))]
|
||||
typenames = selection[which(selection %in% typenames)]
|
||||
if (length(typenames) == 0) {return()}
|
||||
|
||||
h_ = 500
|
||||
w_ = h_*4/3
|
||||
|
||||
if (SAVE_FILE) {png(file=sprintf("%s/%s_%s.png",outputpath,target,filename), width=w_, height=h_)}
|
||||
par(mar=c(4,4,1,1))
|
||||
par(oma=c(0,0,0,0))
|
||||
|
||||
plot(c(0,max(one_frame['time'])),c(ylow,yhigh), col='white', xlab="Time [h]", ylab="WORT [insn]", pch='.')
|
||||
|
||||
for (t in seq_len(length(typenames))) {
|
||||
#proj = one_frame[seq(1, dim(one_frame)[1], by=max(1, length(one_frame[[1]])/(10*w_))),]
|
||||
#points(proj[c('iters',typenames[t])], col=MY_COLORS_[t], pch='.')
|
||||
avglines = ml2lines(one_frame[c(typenames[t],'time')],all_max_points[typenames[t]])
|
||||
#lines(avglines, col=MY_COLORS_[t])
|
||||
medlines = ml2lines(one_frame[c(sprintf("%s_med",typenames[t]),'time')],all_max_points[typenames[t]])
|
||||
lines(medlines, col=MY_COLORS_[t], lty='solid')
|
||||
milines = NULL
|
||||
malines = NULL
|
||||
milines = ml2lines(one_frame[c(sprintf("%s_min",typenames[t]),'time')],all_max_points[typenames[t]])
|
||||
malines = ml2lines(one_frame[c(sprintf("%s_max",typenames[t]),'time')],all_max_points[typenames[t]])
|
||||
if (exists("RIBBON") && ( RIBBON=='max' )) {
|
||||
#lines(milines, col=MY_COLORS_[t], lty='dashed')
|
||||
lines(malines, col=MY_COLORS_[t], lty='dashed')
|
||||
#points(proj[c('iters',sprintf("%s_min",typenames[t]))], col=MY_COLORS_[t], pch='.')
|
||||
#points(proj[c('iters',sprintf("%s_max",typenames[t]))], col=MY_COLORS_[t], pch='.')
|
||||
}
|
||||
if (exists("RIBBON") && RIBBON != '') {
|
||||
for (i in seq_len(dim(avglines)[1]-1)) {
|
||||
if (RIBBON=='both') {
|
||||
# draw boxes
|
||||
x_l <- milines[i,][['X']]
|
||||
x_r <- milines[i+1,][['X']]
|
||||
y_l <- milines[i,][['Y']]
|
||||
y_h <- malines[i,][['Y']]
|
||||
rect(x_l, y_l, x_r, y_h, col=alpha(MY_COLORS_[t], alpha=0.1), lwd=0)
|
||||
}
|
||||
if (FALSE && RIBBON=='span') {
|
||||
# draw boxes
|
||||
x_l <- milines[i,][['X']]
|
||||
x_r <- milines[i+1,][['X']]
|
||||
y_l <- milines[i,][['Y']]
|
||||
y_h <- malines[i,][['Y']]
|
||||
rect(x_l, y_l, x_r, y_h, col=alpha(MY_COLORS_[t], alpha=0.1), lwd=0)
|
||||
}
|
||||
#if (FALSE && RIBBON=='both' || RIBBON=='sd') {
|
||||
# # draw sd
|
||||
# x_l <- avglines[i,][['X']]
|
||||
# x_r <- avglines[i+1,][['X']]
|
||||
# y_l <- avglines[i,][['Y']]-one_frame[ceiling(i/2),][[sprintf("%s_sd",typenames[t])]]
|
||||
# y_h <- avglines[i,][['Y']]+one_frame[ceiling(i/2),][[sprintf("%s_sd",typenames[t])]]
|
||||
# if (x_r != x_l) {
|
||||
# rect(x_l, y_l, x_r, y_h, col=alpha(MY_COLORS_[t], alpha=0.1), lwd=0)
|
||||
# }
|
||||
#}
|
||||
#sd_ <- row[sprintf("%s_sd",typenames[t])][[1]]
|
||||
#min_ <- row[sprintf("%s_min",typenames[t])][[1]]
|
||||
#max_ <- row[sprintf("%s_max",typenames[t])][[1]]
|
||||
#if (exists("RIBBON")) {
|
||||
# switch (RIBBON,
|
||||
# 'sd' = arrows(x_, y_-sd_, x_, y_+sd_, length=0, angle=90, code=3, col=alpha(MY_COLORS_[t], alpha=0.03)),
|
||||
# 'both' = arrows(x_, y_-sd_, x_, y_+sd_, length=0, angle=90, code=3, col=alpha(MY_COLORS_[t], alpha=0.05)),
|
||||
# 'span' = #arrows(x_, min_, x_, max_, length=0, angle=90, code=3, col=alpha(MY_COLORS_[t], alpha=0.03))
|
||||
# )
|
||||
#}
|
||||
##arrows(x_, y_-sd_, x_, y_+sd_, length=0.05, angle=90, code=3, col=alpha(MY_COLORS[t], alpha=0.1))
|
||||
}
|
||||
}
|
||||
}
|
||||
leglines=typenames
|
||||
if (DRAW_WC) {
|
||||
lines(c(0,length(one_frame[[1]])),y=c(worst_case,worst_case), lty='dotted')
|
||||
leglines=c(typenames, 'worst observed')
|
||||
}
|
||||
legend(LEGEND_POS, legend=leglines,#"bottomright",
|
||||
col=c(MY_COLORS_[1:length(typenames)],"black"),
|
||||
lty=c(rep("solid",length(typenames)),"dotted"))
|
||||
|
||||
if (SAVE_FILE) {dev.off()}
|
||||
}
|
||||
stopCluster(cl)
|
||||
|
||||
par(mar=c(3.8,3.8,0,0))
|
||||
par(oma=c(0,0,0,0))
|
||||
|
||||
#RIBBON='both'
|
||||
#MY_SELECTION = c('state_int','generation100_int')
|
||||
#MY_SELECTION = c('state','frafl')
|
||||
|
||||
if (exists("MY_SELECTION")) {
|
||||
plotting(MY_SELECTION, 'custom', MY_COLORS[c(1,2)])
|
||||
} else {
|
||||
# MY_SELECTION=c('state', 'afl', 'random', 'feedlongest', 'feedgeneration', 'feedgeneration10')
|
||||
#MY_SELECTION=c('state_int', 'afl_int', 'random_int', 'feedlongest_int', 'feedgeneration_int', 'feedgeneration10_int')
|
||||
#MY_SELECTION=c('state', 'frAFL', 'statenohash', 'feedgeneration10')
|
||||
#MY_SELECTION=c('state_int', 'frAFL_int', 'statenohash_int', 'feedgeneration10_int')
|
||||
MY_SELECTION=typenames
|
||||
RIBBON='both'
|
||||
for (i in seq_len(length(MY_SELECTION))) {
|
||||
n <- MY_SELECTION[i]
|
||||
plotting(c(n), n, c(MY_COLORS[i]))
|
||||
}
|
||||
RIBBON='max'
|
||||
plotting(MY_SELECTION,'all', MY_COLORS)
|
||||
}
|
||||
|
||||
for (t in seq_len(length(typenames))) {
|
||||
li = one_frame[dim(one_frame)[1],]
|
||||
pear = (li[[typenames[[t]]]]-li[[sprintf("%s_med",typenames[[t]])]])/li[[sprintf("%s_sd",typenames[[t]])]]
|
||||
print(sprintf("%s pearson: %g",typenames[[t]],pear))
|
||||
}
|
28
fuzzers/FRET/benchmark/target_symbols.csv
Normal file
28
fuzzers/FRET/benchmark/target_symbols.csv
Normal file
@ -0,0 +1,28 @@
|
||||
kernel,main_function,input_symbol,input_size,return_function
|
||||
mpeg2,mpeg2_main,mpeg2_oldorgframe,90112,mpeg2_return
|
||||
audiobeam,audiobeam_main,audiobeam_input,11520,audiobeam_return
|
||||
epic,epic_main,epic_image,4096,epic_return
|
||||
dijkstra,dijkstra_main,dijkstra_AdjMatrix,10000,dijkstra_return
|
||||
fft,fft_main,fft_twidtable,2046,fft_return
|
||||
bsort,bsort_main,bsort_Array,400,bsort_return
|
||||
insertsort,insertsort_main,insertsort_a,400,insertsort_return
|
||||
g723_enc,g723_enc_main,g723_enc_INPUT,1024,g723_enc_return
|
||||
rijndael_dec,rijndael_dec_main,rijndael_dec_data,32768,rijndael_dec_return
|
||||
rijndael_enc,rijndael_enc_main,rijndael_enc_data,31369,rijndael_enc_return
|
||||
huff_dec,huff_dec_main,huff_dec_encoded,419,huff_dec_return
|
||||
huff_enc,huff_enc_main,huff_enc_plaintext,600,huff_enc_return
|
||||
gsm_enc,gsm_enc_main,gsm_enc_pcmdata,6400,gsm_enc_return
|
||||
tmr,main,FUZZ_INPUT,32,trigger_Qemu_break
|
||||
tacle_rtos,prvStage0,FUZZ_INPUT,604,trigger_Qemu_break
|
||||
lift,main_lift,FUZZ_INPUT,100,trigger_Qemu_break
|
||||
waters,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break
|
||||
watersv2,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break
|
||||
waters_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break
|
||||
watersv2_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break
|
||||
micro_branchless,main_branchless,FUZZ_INPUT,4,trigger_Qemu_break
|
||||
micro_int,main_int,FUZZ_INPUT,16,trigger_Qemu_break
|
||||
micro_longint,main_micro_longint,FUZZ_INPUT,16,trigger_Qemu_break
|
||||
minimal,main_minimal,FUZZ_INPUT,4096,trigger_Qemu_break
|
||||
gen3,main_minimal,FUZZ_INPUT,4096,trigger_Qemu_break
|
||||
interact,main_interact,FUZZ_INPUT,4096,trigger_Qemu_break
|
||||
interact_int,main_interact,FUZZ_INPUT,4096,trigger_Qemu_break
|
|
2
fuzzers/FRET/example/build.sh
Executable file
2
fuzzers/FRET/example/build.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
arm-none-eabi-gcc -ggdb -ffreestanding -nostartfiles -lgcc -T mps2_m3.ld -mcpu=cortex-m3 main.c startup.c -o example.elf
|
38
fuzzers/FRET/example/main.c
Normal file
38
fuzzers/FRET/example/main.c
Normal file
@ -0,0 +1,38 @@
|
||||
int BREAKPOINT() {
|
||||
for (;;)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(unsigned int* Data, unsigned int Size) {
|
||||
//if (Data[3] == 0) {while(1){}} // cause a timeout
|
||||
for (int i=0; i<Size; i++) {
|
||||
// if (Data[i] > 0xFFd0 && Data[i] < 0xFFFF) {return 1;} // cause qemu to crash
|
||||
for (int j=i+1; j<Size; j++) {
|
||||
if (Data[j] == 0) {continue;}
|
||||
if (Data[j]>Data[i]) {
|
||||
int tmp = Data[i];
|
||||
Data[i]=Data[j];
|
||||
Data[j]=tmp;
|
||||
if (Data[i] <= 100) {j--;}
|
||||
}
|
||||
}
|
||||
}
|
||||
return BREAKPOINT();
|
||||
}
|
||||
unsigned int FUZZ_INPUT[] = {
|
||||
101,201,700,230,860,
|
||||
234,980,200,340,678,
|
||||
230,134,900,236,900,
|
||||
123,800,123,658,607,
|
||||
246,804,567,568,207,
|
||||
407,246,678,457,892,
|
||||
834,456,878,246,699,
|
||||
854,234,844,290,125,
|
||||
324,560,852,928,910,
|
||||
790,853,345,234,586,
|
||||
};
|
||||
|
||||
int main() {
|
||||
LLVMFuzzerTestOneInput(FUZZ_INPUT, 50);
|
||||
}
|
143
fuzzers/FRET/example/mps2_m3.ld
Normal file
143
fuzzers/FRET/example/mps2_m3.ld
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* FreeRTOS V202112.00
|
||||
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* https://www.FreeRTOS.org
|
||||
* https://github.com/FreeRTOS
|
||||
*
|
||||
*/
|
||||
|
||||
MEMORY
|
||||
{
|
||||
RAM (xrw) : ORIGIN = 0x00000000, LENGTH = 4M
|
||||
/* Originally */
|
||||
/* FLASH (xr) : ORIGIN = 0x00000000, LENGTH = 4M */
|
||||
/* RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4M */
|
||||
}
|
||||
ENTRY(Reset_Handler)
|
||||
|
||||
_Min_Heap_Size = 0x300000 ; /* Required amount of heap. */
|
||||
_Min_Stack_Size = 0x4000 ; /* Required amount of stack. */
|
||||
M_VECTOR_RAM_SIZE = (16 + 48) * 4;
|
||||
_estack = ORIGIN(RAM) + LENGTH(RAM);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
|
||||
.isr_vector :
|
||||
{
|
||||
__vector_table = .;
|
||||
KEEP(*(.isr_vector))
|
||||
. = ALIGN(4);
|
||||
} > RAM /* FLASH */
|
||||
|
||||
.text :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
*(.text*)
|
||||
KEEP (*(.init))
|
||||
KEEP (*(.fini))
|
||||
KEEP(*(.eh_frame))
|
||||
*(.rodata*)
|
||||
. = ALIGN(4);
|
||||
_etext = .;
|
||||
} > RAM /* FLASH */
|
||||
|
||||
.ARM.extab :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
*(.ARM.extab* .gnu.linkonce.armextab.*)
|
||||
. = ALIGN(4);
|
||||
} >RAM /* FLASH */
|
||||
|
||||
.ARM :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
__exidx_start = .;
|
||||
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
|
||||
__exidx_end = .;
|
||||
. = ALIGN(4);
|
||||
} >RAM /* FLASH */
|
||||
|
||||
.interrupts_ram :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
__VECTOR_RAM__ = .;
|
||||
__interrupts_ram_start__ = .;
|
||||
. += M_VECTOR_RAM_SIZE;
|
||||
. = ALIGN(4);
|
||||
__interrupts_ram_end = .;
|
||||
} > RAM
|
||||
|
||||
_sidata = LOADADDR(.data);
|
||||
|
||||
.data : /* AT ( _sidata ) */
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_sdata = .;
|
||||
*(.data*)
|
||||
. = ALIGN(4);
|
||||
_edata = .;
|
||||
} > RAM /* RAM AT > FLASH */
|
||||
|
||||
.uninitialized (NOLOAD):
|
||||
{
|
||||
. = ALIGN(32);
|
||||
__uninitialized_start = .;
|
||||
*(.uninitialized)
|
||||
KEEP(*(.keep.uninitialized))
|
||||
. = ALIGN(32);
|
||||
__uninitialized_end = .;
|
||||
} > RAM
|
||||
|
||||
.bss :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_sbss = .;
|
||||
__bss_start__ = _sbss;
|
||||
*(.bss*)
|
||||
*(COMMON)
|
||||
. = ALIGN(4);
|
||||
_ebss = .;
|
||||
__bss_end__ = _ebss;
|
||||
} >RAM
|
||||
|
||||
.heap :
|
||||
{
|
||||
. = ALIGN(8);
|
||||
PROVIDE ( end = . );
|
||||
PROVIDE ( _end = . );
|
||||
_heap_bottom = .;
|
||||
. = . + _Min_Heap_Size;
|
||||
_heap_top = .;
|
||||
. = . + _Min_Stack_Size;
|
||||
. = ALIGN(8);
|
||||
} >RAM
|
||||
|
||||
/* Set stack top to end of RAM, and stack limit move down by
|
||||
* size of stack_dummy section */
|
||||
__StackTop = ORIGIN(RAM) + LENGTH(RAM);
|
||||
__StackLimit = __StackTop - _Min_Stack_Size;
|
||||
PROVIDE(__stack = __StackTop);
|
||||
|
||||
/* Check if data + heap + stack exceeds RAM limit */
|
||||
ASSERT(__StackLimit >= _heap_top, "region RAM overflowed with stack")
|
||||
}
|
||||
|
114
fuzzers/FRET/example/startup.c
Normal file
114
fuzzers/FRET/example/startup.c
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* FreeRTOS V202112.00
|
||||
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* https://www.FreeRTOS.org
|
||||
* https://github.com/FreeRTOS
|
||||
*
|
||||
*/
|
||||
|
||||
typedef unsigned int uint32_t;
|
||||
|
||||
extern int main();
|
||||
|
||||
extern uint32_t _estack, _sidata, _sdata, _edata, _sbss, _ebss;
|
||||
|
||||
/* Prevent optimization so gcc does not replace code with memcpy */
|
||||
__attribute__( ( optimize( "O0" ) ) )
|
||||
__attribute__( ( naked ) )
|
||||
void Reset_Handler( void )
|
||||
{
|
||||
/* set stack pointer */
|
||||
__asm volatile ( "ldr r0, =_estack" );
|
||||
__asm volatile ( "mov sp, r0" );
|
||||
|
||||
/* copy .data section from flash to RAM */
|
||||
// Not needed for this example, see linker script
|
||||
// for( uint32_t * src = &_sidata, * dest = &_sdata; dest < &_edata; )
|
||||
// {
|
||||
// *dest++ = *src++;
|
||||
// }
|
||||
|
||||
/* zero out .bss section */
|
||||
for( uint32_t * dest = &_sbss; dest < &_ebss; )
|
||||
{
|
||||
*dest++ = 0;
|
||||
}
|
||||
|
||||
/* jump to board initialisation */
|
||||
void _start( void );
|
||||
_start();
|
||||
}
|
||||
|
||||
const uint32_t * isr_vector[] __attribute__( ( section( ".isr_vector" ) ) ) =
|
||||
{
|
||||
( uint32_t * ) &_estack,
|
||||
( uint32_t * ) &Reset_Handler, /* Reset -15 */
|
||||
0, /* NMI_Handler -14 */
|
||||
0, /* HardFault_Handler -13 */
|
||||
0, /* MemManage_Handler -12 */
|
||||
0, /* BusFault_Handler -11 */
|
||||
0, /* UsageFault_Handler -10 */
|
||||
0, /* reserved */
|
||||
0, /* reserved */
|
||||
0, /* reserved */
|
||||
0, /* reserved -6 */
|
||||
0, /* SVC_Handler -5 */
|
||||
0, /* DebugMon_Handler -4 */
|
||||
0, /* reserved */
|
||||
0, /* PendSV handler -2 */
|
||||
0, /* SysTick_Handler -1 */
|
||||
0, /* uart0 receive 0 */
|
||||
0, /* uart0 transmit */
|
||||
0, /* uart1 receive */
|
||||
0, /* uart1 transmit */
|
||||
0, /* uart 2 receive */
|
||||
0, /* uart 2 transmit */
|
||||
0, /* GPIO 0 combined interrupt */
|
||||
0, /* GPIO 2 combined interrupt */
|
||||
0, /* Timer 0 */
|
||||
0, /* Timer 1 */
|
||||
0, /* Dial Timer */
|
||||
0, /* SPI0 SPI1 */
|
||||
0, /* uart overflow 1, 2,3 */
|
||||
0, /* Ethernet 13 */
|
||||
};
|
||||
|
||||
__attribute__( ( naked ) ) void exit(__attribute__((unused)) int status )
|
||||
{
|
||||
/* Force qemu to exit using ARM Semihosting */
|
||||
__asm volatile (
|
||||
"mov r1, r0\n"
|
||||
"cmp r1, #0\n"
|
||||
"bne .notclean\n"
|
||||
"ldr r1, =0x20026\n" /* ADP_Stopped_ApplicationExit, a clean exit */
|
||||
".notclean:\n"
|
||||
"movs r0, #0x18\n" /* SYS_EXIT */
|
||||
"bkpt 0xab\n"
|
||||
"end: b end\n"
|
||||
);
|
||||
}
|
||||
|
||||
void _start( void )
|
||||
{
|
||||
main( );
|
||||
exit( 0 );
|
||||
}
|
||||
|
357
fuzzers/FRET/src/clock.rs
Normal file
357
fuzzers/FRET/src/clock.rs
Normal file
@ -0,0 +1,357 @@
|
||||
use hashbrown::{hash_map::Entry, HashMap};
|
||||
use libafl_bolts::{
|
||||
current_nanos,
|
||||
rands::StdRand,
|
||||
tuples::{tuple_list,MatchName},
|
||||
impl_serdeany,
|
||||
Named,
|
||||
};
|
||||
use libafl::{
|
||||
executors::{ExitKind},
|
||||
fuzzer::{StdFuzzer},
|
||||
inputs::{BytesInput, HasTargetBytes},
|
||||
observers::{Observer,VariableMapObserver},
|
||||
state::{StdState, HasNamedMetadata},
|
||||
Error,
|
||||
observers::ObserversTuple, prelude::UsesInput,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cell::UnsafeCell, cmp::max, env, fs::OpenOptions, io::Write, time::Instant};
|
||||
|
||||
use libafl_qemu::{
|
||||
emu,
|
||||
emu::Emulator,
|
||||
executor::QemuExecutor,
|
||||
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
|
||||
};
|
||||
use libafl::events::EventFirer;
|
||||
use libafl::state::MaybeHasClientPerfMonitor;
|
||||
use libafl::prelude::State;
|
||||
use libafl::inputs::Input;
|
||||
use libafl::feedbacks::Feedback;
|
||||
use libafl::SerdeAny;
|
||||
use libafl::state::HasMetadata;
|
||||
use libafl::corpus::testcase::Testcase;
|
||||
use core::{fmt::Debug, time::Duration};
|
||||
// use libafl::feedbacks::FeedbackState;
|
||||
// use libafl::state::HasFeedbackStates;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub static mut FUZZ_START_TIMESTAMP : SystemTime = UNIX_EPOCH;
|
||||
|
||||
pub const QEMU_ICOUNT_SHIFT : u32 = 5;
|
||||
pub const QEMU_ISNS_PER_SEC : u32 = u32::pow(10, 9) / u32::pow(2, QEMU_ICOUNT_SHIFT);
|
||||
pub const QEMU_ISNS_PER_USEC : u32 = QEMU_ISNS_PER_SEC / 1000000;
|
||||
pub const QEMU_NS_PER_ISN : u32 = 1 << QEMU_ICOUNT_SHIFT;
|
||||
pub const TARGET_SYSCLK_FREQ : u32 = 25 * 1000 * 1000;
|
||||
pub const TARGET_MHZ_PER_MIPS : f32 = TARGET_SYSCLK_FREQ as f32 / QEMU_ISNS_PER_SEC as f32;
|
||||
pub const TARGET_MIPS_PER_MHZ : f32 = QEMU_ISNS_PER_SEC as f32 / TARGET_SYSCLK_FREQ as f32;
|
||||
pub const TARGET_SYSCLK_PER_QEMU_SEC : u32 = (TARGET_SYSCLK_FREQ as f32 * TARGET_MIPS_PER_MHZ) as u32;
|
||||
pub const QEMU_SYSCLK_PER_TARGET_SEC : u32 = (TARGET_SYSCLK_FREQ as f32 * TARGET_MHZ_PER_MIPS) as u32;
|
||||
|
||||
//========== Metadata
|
||||
#[derive(Debug, SerdeAny, Serialize, Deserialize)]
|
||||
pub struct QemuIcountMetadata {
|
||||
runtime: u64,
|
||||
}
|
||||
|
||||
/// Metadata for [`QemuClockIncreaseFeedback`]
|
||||
#[derive(Debug, Serialize, Deserialize, SerdeAny)]
|
||||
pub struct MaxIcountMetadata {
|
||||
pub max_icount_seen: u64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
// impl FeedbackState for MaxIcountMetadata
|
||||
// {
|
||||
// fn reset(&mut self) -> Result<(), Error> {
|
||||
// self.max_icount_seen = 0;
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Named for MaxIcountMetadata
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl MaxIcountMetadata
|
||||
{
|
||||
/// Create new `MaxIcountMetadata`
|
||||
#[must_use]
|
||||
pub fn new(name: &'static str) -> Self {
|
||||
Self {
|
||||
max_icount_seen: 0,
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MaxIcountMetadata {
|
||||
fn default() -> Self {
|
||||
Self::new("MaxClock")
|
||||
}
|
||||
}
|
||||
|
||||
/// A piece of metadata tracking all icounts
|
||||
#[derive(Debug, SerdeAny, Serialize, Deserialize)]
|
||||
pub struct IcHist (pub Vec<(u64, u128)>, pub (u64,u128));
|
||||
|
||||
//========== Observer
|
||||
|
||||
/// A simple observer, just overlooking the runtime of the target.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct QemuClockObserver {
|
||||
name: String,
|
||||
start_tick: u64,
|
||||
end_tick: u64,
|
||||
dump_path: Option<PathBuf>
|
||||
}
|
||||
|
||||
impl QemuClockObserver {
|
||||
/// Creates a new [`QemuClockObserver`] with the given name.
|
||||
#[must_use]
|
||||
pub fn new(name: &'static str, dump_path: Option<PathBuf>) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
start_tick: 0,
|
||||
end_tick: 0,
|
||||
dump_path
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the runtime for the last execution of this target.
|
||||
#[must_use]
|
||||
pub fn last_runtime(&self) -> u64 {
|
||||
self.end_tick - self.start_tick
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Observer<S> for QemuClockObserver
|
||||
where
|
||||
S: UsesInput + HasMetadata,
|
||||
{
|
||||
fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
|
||||
// Only remember the pre-run ticks if presistent mode ist used
|
||||
#[cfg(not(feature = "snapshot_restore"))]
|
||||
unsafe {
|
||||
self.start_tick=emu::icount_get_raw();
|
||||
self.end_tick=self.start_tick;
|
||||
}
|
||||
// unsafe {
|
||||
// println!("clock pre {}",emu::icount_get_raw());
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_exec(&mut self, _state: &mut S, _input: &S::Input, _exit_kind: &ExitKind) -> Result<(), Error> {
|
||||
unsafe { self.end_tick = emu::icount_get_raw() };
|
||||
if let Some(td) = &self.dump_path {
|
||||
// println!("clock post {}", self.end_tick);
|
||||
// println!("Number of Ticks: {} <- {} {}",self.end_tick - self.start_tick, self.end_tick, self.start_tick);
|
||||
let metadata =_state.metadata_map_mut();
|
||||
let hist = metadata.get_mut::<IcHist>();
|
||||
let timestamp = SystemTime::now().duration_since(unsafe {FUZZ_START_TIMESTAMP}).unwrap().as_millis();
|
||||
match hist {
|
||||
None => {
|
||||
metadata.insert(IcHist(vec![(self.end_tick - self.start_tick, timestamp)],
|
||||
(self.end_tick - self.start_tick, timestamp)));
|
||||
}
|
||||
Some(v) => {
|
||||
v.0.push((self.end_tick - self.start_tick, timestamp));
|
||||
if v.1.0 < self.end_tick-self.start_tick {
|
||||
v.1 = (self.end_tick - self.start_tick, timestamp);
|
||||
}
|
||||
if v.0.len() >= 100 {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(td).expect("Could not open timedump");
|
||||
let newv : Vec<(u64, u128)> = Vec::with_capacity(110);
|
||||
for i in std::mem::replace(&mut v.0, newv).into_iter() {
|
||||
writeln!(file, "{},{}", i.0, i.1).expect("Write to dump failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for QemuClockObserver {
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QemuClockObserver {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: String::from("clock"),
|
||||
start_tick: 0,
|
||||
end_tick: 0,
|
||||
dump_path: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//========== Feedback
|
||||
/// Nop feedback that annotates execution time in the new testcase, if any
|
||||
/// for this Feedback, the testcase is never interesting (use with an OR).
|
||||
/// It decides, if the given [`QemuClockObserver`] value of a run is interesting.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ClockTimeFeedback {
|
||||
exec_time: Option<Duration>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for ClockTimeFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor + HasMetadata,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
_state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &S::Input,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>,
|
||||
{
|
||||
// TODO Replace with match_name_type when stable
|
||||
let observer = observers.match_name::<QemuClockObserver>(self.name()).unwrap();
|
||||
self.exec_time = Some(Duration::from_nanos(observer.last_runtime() << QEMU_ICOUNT_SHIFT)); // Assume a somewhat realistic multiplier of clock, it does not matter
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Append to the testcase the generated metadata in case of a new corpus item
|
||||
#[inline]
|
||||
fn append_metadata<OT>(
|
||||
&mut self,
|
||||
_state: &mut S,
|
||||
observers: &OT,
|
||||
testcase: &mut Testcase<S::Input>,
|
||||
) -> Result<(), Error> {
|
||||
*testcase.exec_time_mut() = self.exec_time;
|
||||
self.exec_time = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Discard the stored metadata in case that the testcase is not added to the corpus
|
||||
#[inline]
|
||||
fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
|
||||
self.exec_time = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for ClockTimeFeedback {
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClockTimeFeedback {
|
||||
/// Creates a new [`ClockFeedback`], deciding if the value of a [`QemuClockObserver`] with the given `name` of a run is interesting.
|
||||
#[must_use]
|
||||
pub fn new(name: &'static str) -> Self {
|
||||
Self {
|
||||
exec_time: None,
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`ClockFeedback`], deciding if the given [`QemuClockObserver`] value of a run is interesting.
|
||||
#[must_use]
|
||||
pub fn new_with_observer(observer: &QemuClockObserver) -> Self {
|
||||
Self {
|
||||
exec_time: None,
|
||||
name: observer.name().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Feedback`] rewarding increasing the execution cycles on Qemu.
|
||||
#[derive(Debug)]
|
||||
pub struct QemuClockIncreaseFeedback {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for QemuClockIncreaseFeedback
|
||||
where
|
||||
S: State + UsesInput + HasNamedMetadata + MaybeHasClientPerfMonitor + Debug,
|
||||
{
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &S::Input,
|
||||
_observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>,
|
||||
{
|
||||
let observer = _observers.match_name::<QemuClockObserver>("clock")
|
||||
.expect("QemuClockObserver not found");
|
||||
let clock_state = state
|
||||
.named_metadata_map_mut()
|
||||
.get_mut::<MaxIcountMetadata>(&self.name)
|
||||
.unwrap();
|
||||
if observer.last_runtime() > clock_state.max_icount_seen {
|
||||
// println!("Clock improving {}",observer.last_runtime());
|
||||
clock_state.max_icount_seen = observer.last_runtime();
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Append to the testcase the generated metadata in case of a new corpus item
|
||||
#[inline]
|
||||
fn append_metadata<OT>(&mut self, _state: &mut S, observers: &OT, testcase: &mut Testcase<S::Input>) -> Result<(), Error> {
|
||||
// testcase.metadata_mut().insert(QemuIcountMetadata{runtime: self.last_runtime});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Discard the stored metadata in case that the testcase is not added to the corpus
|
||||
#[inline]
|
||||
fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Named for QemuClockIncreaseFeedback {
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl QemuClockIncreaseFeedback {
|
||||
/// Creates a new [`HitFeedback`]
|
||||
#[must_use]
|
||||
pub fn new(name: &'static str) -> Self {
|
||||
Self {name: String::from(name)}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QemuClockIncreaseFeedback {
|
||||
fn default() -> Self {
|
||||
Self::new("MaxClock")
|
||||
}
|
||||
}
|
816
fuzzers/FRET/src/fuzzer.rs
Normal file
816
fuzzers/FRET/src/fuzzer.rs
Normal file
@ -0,0 +1,816 @@
|
||||
//! A fuzzer using qemu in systemmode for binary-only coverage of kernels
|
||||
//!
|
||||
use core::time::Duration;
|
||||
use std::{env, path::PathBuf, process::{self, abort}, io::{Read, Write}, fs::{self, OpenOptions}, cmp::{min, max}, mem::transmute_copy, collections::btree_map::Range, ptr::addr_of_mut, ffi::OsStr};
|
||||
use hashbrown::HashMap;
|
||||
use libafl_bolts::{
|
||||
core_affinity::Cores, current_nanos, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, tuples::tuple_list, AsMutSlice, AsSlice
|
||||
};
|
||||
use libafl::{
|
||||
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, events::{launcher::Launcher, EventConfig}, executors::{ExitKind, TimeoutExecutor}, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::{BytesInput, HasTargetBytes}, monitors::MultiMonitor, observers::VariableMapObserver, prelude::{havoc_mutations, minimizer::TopRatedsMetadata, CorpusId, Generator, HasBytesVec, HitcountsMapObserver, RandBytesGenerator, SimpleEventManager, SimpleMonitor, SimpleRestartingEventManager, StdScheduledMutator}, schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, stages::StdMutationalStage, state::{HasCorpus, HasMetadata, HasNamedMetadata, StdState}, Error, Evaluator
|
||||
};
|
||||
use libafl_qemu::{
|
||||
edges::{self, edges_map_mut_slice, QemuEdgeCoverageHelper, MAX_EDGES_NUM}, elf::EasyElf, emu::{libafl_qemu_remove_native_breakpoint, libafl_qemu_set_native_breakpoint, Emulator}, GuestAddr, GuestPhysAddr, QemuExecutor, QemuHooks, QemuInstrumentationFilter, Regs
|
||||
};
|
||||
use rand::{SeedableRng, StdRng, Rng};
|
||||
use crate::{
|
||||
clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP}, mutational::{input_bytes_to_interrupt_times, InterruptShiftStage, MINIMUM_INTER_ARRIVAL_TIME}, qemustate::QemuStateRestoreHelper, systemstate::{self, feedbacks::{DumpSystraceFeedback, NovelSystemStateFeedback, SystraceErrorFeedback}, helpers::{QemuSystemStateHelper, ISR_SYMBOLS}, observers::QemuSystemStateObserver, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}}, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, TimeMaximizerCorpusScheduler, TimeStateMaximizerCorpusScheduler}
|
||||
};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use clap::{Parser, Subcommand};
|
||||
use csv::Reader;
|
||||
use petgraph::{dot::{Config, Dot}, Graph};
|
||||
use petgraph::graph::EdgeIndex;
|
||||
use petgraph::graph::NodeIndex;
|
||||
use petgraph::prelude::DiGraph;
|
||||
use crate::systemstate::stg::STGFeedbackState;
|
||||
use crate::clock::QEMU_ICOUNT_SHIFT;
|
||||
|
||||
// Constants ================================================================================
|
||||
|
||||
pub static mut RNG_SEED: u64 = 1;
|
||||
|
||||
pub static mut LIMIT : u32 = u32::MAX;
|
||||
pub const FIRST_INT : u32 = 500000;
|
||||
|
||||
pub const MAX_NUM_INTERRUPT: usize = 128;
|
||||
pub const DO_NUM_INTERRUPT: usize = 128;
|
||||
pub static mut MAX_INPUT_SIZE: usize = 32;
|
||||
/// Read ELF program headers to resolve physical load addresses.
|
||||
fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr {
|
||||
let ret;
|
||||
for i in &tab.goblin().program_headers {
|
||||
if i.vm_range().contains(&vaddr.try_into().unwrap()) {
|
||||
ret = vaddr - TryInto::<GuestPhysAddr>::try_into(i.p_vaddr).unwrap()
|
||||
+ TryInto::<GuestPhysAddr>::try_into(i.p_paddr).unwrap();
|
||||
return ret - (ret % 2);
|
||||
}
|
||||
}
|
||||
return vaddr;
|
||||
}
|
||||
|
||||
pub fn load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> GuestAddr {
|
||||
try_load_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol))
|
||||
}
|
||||
|
||||
pub fn try_load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> Option<GuestAddr> {
|
||||
let ret = elf
|
||||
.resolve_symbol(symbol, 0);
|
||||
if do_translation {
|
||||
Option::map_or(ret, None, |x| Some(virt2phys(x as GuestPhysAddr,&elf) as GuestAddr))
|
||||
} else {ret}
|
||||
}
|
||||
|
||||
pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option<std::ops::Range<GuestAddr>> {
|
||||
let gob = elf.goblin();
|
||||
|
||||
let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function()).collect();
|
||||
funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value));
|
||||
|
||||
for sym in &gob.syms {
|
||||
if let Some(sym_name) = gob.strtab.get_at(sym.st_name) {
|
||||
if sym_name == symbol {
|
||||
if sym.st_value == 0 {
|
||||
return None;
|
||||
} else {
|
||||
//#[cfg(cpu_target = "arm")]
|
||||
// Required because of arm interworking addresses aka bit(0) for thumb mode
|
||||
let addr = (sym.st_value as GuestAddr) & !(0x1 as GuestAddr);
|
||||
//#[cfg(not(cpu_target = "arm"))]
|
||||
//let addr = sym.st_value as GuestAddr;
|
||||
// look for first function after addr
|
||||
let sym_end = funcs.iter().find(|x| x.st_value > sym.st_value);
|
||||
if let Some(sym_end) = sym_end {
|
||||
// println!("{} {:#x}..{} {:#x}", gob.strtab.get_at(sym.st_name).unwrap_or(""),addr, gob.strtab.get_at(sym_end.st_name).unwrap_or(""),sym_end.st_value & !0x1);
|
||||
return Some(addr..((sym_end.st_value & !0x1) as GuestAddr));
|
||||
}
|
||||
return None;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn get_all_fn_symbol_ranges(elf: &EasyElf, api_range: std::ops::Range<GuestAddr>) -> HashMap<String,std::ops::Range<GuestAddr>> {
|
||||
let mut api_addreses : HashMap<String,std::ops::Range<GuestAddr>> = HashMap::new();
|
||||
|
||||
let gob = elf.goblin();
|
||||
|
||||
let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function() && api_range.contains(&x.st_value.try_into().unwrap())).collect();
|
||||
funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value));
|
||||
|
||||
for sym in &funcs {
|
||||
let sym_name = gob.strtab.get_at(sym.st_name);
|
||||
if let Some(sym_name) = sym_name {
|
||||
// if ISR_SYMBOLS.contains(&sym_name) {continue;}; // skip select symbols, which correspond to ISR-safe system calls
|
||||
if let Some(r) = get_function_range(elf, sym_name) {
|
||||
api_addreses.insert(sym_name.to_string(), r);
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in api_addreses.iter() {
|
||||
println!("{} {:#x}..{:#x}", i.0, i.1.start, i.1.end);
|
||||
}
|
||||
|
||||
return api_addreses;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
static mut libafl_interrupt_offsets : [u32; MAX_NUM_INTERRUPT];
|
||||
static mut libafl_num_interrupts : usize;
|
||||
}
|
||||
|
||||
// Argument parsing ================================================================================
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
/// Kernel Image
|
||||
#[arg(short, long, value_name = "FILE")]
|
||||
kernel: PathBuf,
|
||||
|
||||
/// Sets a custom config file
|
||||
#[arg(short, long, value_name = "FILE")]
|
||||
config: PathBuf,
|
||||
|
||||
/// Sets the prefix of dumed files
|
||||
#[arg(short='n', long, value_name = "FILENAME")]
|
||||
dump_name: Option<PathBuf>,
|
||||
|
||||
/// do time dumps
|
||||
#[arg(short='t', long)]
|
||||
dump_times: bool,
|
||||
|
||||
/// do worst-case dumps
|
||||
#[arg(short='a', long)]
|
||||
dump_cases: bool,
|
||||
|
||||
/// do trace dumps (if supported)
|
||||
#[arg(short='r', long)]
|
||||
dump_traces: bool,
|
||||
|
||||
/// do graph dumps (if supported)
|
||||
#[arg(short='g', long)]
|
||||
dump_graph: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
#[derive(Subcommand,Clone)]
|
||||
enum Commands {
|
||||
/// run a single input
|
||||
Showmap {
|
||||
/// take this input
|
||||
#[arg(short, long)]
|
||||
input: PathBuf,
|
||||
},
|
||||
/// start fuzzing campaign
|
||||
Fuzz {
|
||||
/// disable heuristic
|
||||
#[arg(short, long)]
|
||||
random: bool,
|
||||
/// seed for randomness
|
||||
#[arg(short, long)]
|
||||
seed: Option<u64>,
|
||||
/// runtime in seconds
|
||||
#[arg(short, long)]
|
||||
time: Option<u64>,
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes a state, cli and a suffix, writes out the current worst case
|
||||
macro_rules! do_dump_case {
|
||||
( $s:expr,$cli:expr, $c:expr) => {
|
||||
if ($cli.dump_cases) {
|
||||
let dump_path = $cli.dump_name.clone().unwrap().with_extension(if $c=="" {"case"} else {$c});
|
||||
println!("Dumping worst case to {:?}", &dump_path);
|
||||
let corpus = $s.corpus();
|
||||
let mut worst = Duration::new(0,0);
|
||||
let mut worst_input = None;
|
||||
for i in 0..corpus.count() {
|
||||
let tc = corpus.get(corpus.nth(i.into())).expect("Could not get element from corpus").borrow();
|
||||
if worst < tc.exec_time().expect("Testcase missing duration") {
|
||||
worst_input = Some(tc.input().as_ref().unwrap().bytes().to_owned());
|
||||
worst = tc.exec_time().expect("Testcase missing duration");
|
||||
}
|
||||
}
|
||||
if let Some(wi) = worst_input {
|
||||
fs::write(dump_path,wi).expect("Failed to write worst corpus element");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes a state, cli and a suffix, appends icount history
|
||||
macro_rules! do_dump_times {
|
||||
($state:expr, $cli:expr, $c:expr) => {
|
||||
if $cli.dump_times {
|
||||
let dump_path = $cli.dump_name.clone().unwrap().with_extension(if $c=="" {"time"} else {$c});
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(dump_path).expect("Could not open timedump");
|
||||
if let Ok(ichist) = $state.metadata_mut::<IcHist>() {
|
||||
for i in ichist.0.drain(..) {
|
||||
writeln!(file, "{},{}", i.0, i.1).expect("Write to dump failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Takes a state and a bool, writes out the current graph
|
||||
macro_rules! do_dump_stg {
|
||||
($state:expr, $cli:expr, $c:expr) => {
|
||||
#[cfg(feature = "trace_stg")]
|
||||
if $cli.dump_graph {
|
||||
let dump_path = $cli.dump_name.clone().unwrap().with_extension(if $c=="" {"stg"} else {$c});
|
||||
println!("Dumping graph to {:?}", &dump_path);
|
||||
if let Some(md) = $state.named_metadata_map_mut().get_mut::<STGFeedbackState>("stgfeedbackstate") {
|
||||
let out = md.graph.map(|_i,x| x.color_print(), |_i,x| x.color_print());
|
||||
let outs = Dot::with_config(&out, &[]).to_string();
|
||||
let outs = outs.replace("\\\"","\"");
|
||||
let outs = outs.replace(';',"\\n");
|
||||
fs::write(dump_path,outs).expect("Failed to write graph");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Takes a state and a bool, writes out top rated inputs
|
||||
macro_rules! do_dump_toprated {
|
||||
($state:expr, $cli:expr, $c:expr) => {
|
||||
if $cli.dump_cases {
|
||||
{
|
||||
let dump_path = $cli.dump_name.clone().unwrap().with_extension(if $c=="" {"toprated"} else {$c});
|
||||
println!("Dumping toprated to {:?}", &dump_path);
|
||||
if let Some(md) = $state.metadata_map_mut().get_mut::<TopRatedsMetadata>() {
|
||||
let mut uniq: Vec<CorpusId> = md.map.values().map(|x| x.clone()).collect();
|
||||
uniq.sort();
|
||||
uniq.dedup();
|
||||
fs::write(dump_path,ron::to_string(&md.map).expect("Failed to serialize metadata")).expect("Failed to write graph");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn env_from_config(kernel : &PathBuf, path : &PathBuf) {
|
||||
let is_csv = path.as_path().extension().map_or(false, |x| x=="csv");
|
||||
if !is_csv {
|
||||
let lines = std::fs::read_to_string(path).expect("Config file not found");
|
||||
let lines = lines.lines().filter(
|
||||
|x| x.len()>0
|
||||
);
|
||||
for l in lines {
|
||||
let pair = l.split_once('=').expect("Non VAR=VAL line in config");
|
||||
std::env::set_var(pair.0, pair.1);
|
||||
}
|
||||
} else {
|
||||
let mut reader = csv::Reader::from_path(path).expect("CSV read from config failed");
|
||||
let p = kernel.as_path();
|
||||
let stem = p.file_stem().expect("Kernel filename error").to_str().unwrap();
|
||||
for r in reader.records() {
|
||||
let rec = r.expect("CSV entry error");
|
||||
if stem == &rec[0] {
|
||||
std::env::set_var("FUZZ_MAIN", &rec[1]);
|
||||
std::env::set_var("FUZZ_INPUT", &rec[2]);
|
||||
std::env::set_var("FUZZ_INPUT_LEN", &rec[3]);
|
||||
std::env::set_var("BREAKPOINT", &rec[4]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fuzzer setup ================================================================================
|
||||
|
||||
pub fn fuzz() {
|
||||
let cli = Cli::parse();
|
||||
env_from_config(&cli.kernel, &cli.config);
|
||||
unsafe {FUZZ_START_TIMESTAMP = SystemTime::now();}
|
||||
if cli.dump_name.is_none() && (cli.dump_times || cli.dump_cases || cli.dump_traces || cli.dump_graph) {
|
||||
panic!("Dump name not give but dump is requested");
|
||||
}
|
||||
let mut starttime = std::time::Instant::now();
|
||||
// Hardcoded parameters
|
||||
let timeout = Duration::from_secs(10);
|
||||
let broker_port = 1337;
|
||||
let cores = Cores::from_cmdline("1").unwrap();
|
||||
let corpus_dirs = [PathBuf::from("./corpus")];
|
||||
let objective_dir = PathBuf::from("./crashes");
|
||||
|
||||
let mut elf_buffer = Vec::new();
|
||||
let elf = EasyElf::from_file(
|
||||
&cli.kernel,
|
||||
&mut elf_buffer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// the main address where the fuzzer starts
|
||||
// if this is set for freeRTOS it has an influence on where the data will have to be written,
|
||||
// since the startup routine copies the data segemnt to it's virtual address
|
||||
let main_addr = elf
|
||||
.resolve_symbol(&env::var("FUZZ_MAIN").unwrap_or_else(|_| "FUZZ_MAIN".to_owned()), 0);
|
||||
if let Some(main_addr) = main_addr {
|
||||
println!("main address = {:#x}", main_addr);
|
||||
}
|
||||
|
||||
let input_addr = load_symbol(&elf, &env::var("FUZZ_INPUT").unwrap_or_else(|_| "FUZZ_INPUT".to_owned()), true);
|
||||
println!("FUZZ_INPUT @ {:#x}", input_addr);
|
||||
|
||||
let input_length_ptr = try_load_symbol(&elf, &env::var("FUZZ_LENGTH").unwrap_or_else(|_| "FUZZ_LENGTH".to_owned()), true);
|
||||
let input_counter_ptr = try_load_symbol(&elf, &env::var("FUZZ_POINTER").unwrap_or_else(|_| "FUZZ_POINTER".to_owned()), true);
|
||||
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let curr_tcb_pointer = load_symbol(&elf, "pxCurrentTCB", false); // loads to the address specified in elf, without respecting program headers
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
println!("TCB pointer at {:#x}", curr_tcb_pointer);
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let task_queue_addr = load_symbol(&elf, "pxReadyTasksLists", false);
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let task_delay_addr = load_symbol(&elf, "pxDelayedTaskList", false);
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let task_delay_overflow_addr = load_symbol(&elf, "pxOverflowDelayedTaskList", false);
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let scheduler_lock = load_symbol(&elf, "uxSchedulerSuspended", false);
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let scheduler_running = load_symbol(&elf, "xSchedulerRunning", false);
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let critical_section = load_symbol(&elf, "uxCriticalNesting", false);
|
||||
let app_start = load_symbol(&elf, "__APP_CODE_START__", false);
|
||||
let app_end = load_symbol(&elf, "__APP_CODE_END__", false);
|
||||
let app_range = app_start..app_end;
|
||||
let api_start = load_symbol(&elf, "__API_CODE_START__", false);
|
||||
let api_end = load_symbol(&elf, "__API_CODE_END__", false);
|
||||
let api_range = api_start..api_end;
|
||||
|
||||
let breakpoint = elf
|
||||
.resolve_symbol(
|
||||
&env::var("BREAKPOINT").unwrap_or_else(|_| "BREAKPOINT".to_owned()),
|
||||
0,
|
||||
)
|
||||
.expect("Symbol or env BREAKPOINT not found");
|
||||
println!("Breakpoint address = {:#x}", breakpoint);
|
||||
unsafe {
|
||||
libafl_num_interrupts = 0;
|
||||
}
|
||||
|
||||
if let Ok(input_len) = env::var("FUZZ_INPUT_LEN") {
|
||||
unsafe {MAX_INPUT_SIZE = str::parse::<usize>(&input_len).expect("FUZZ_INPUT_LEN was not a number");}
|
||||
}
|
||||
unsafe {dbg!(MAX_INPUT_SIZE);}
|
||||
|
||||
if let Ok(seed) = env::var("SEED_RANDOM") {
|
||||
unsafe {RNG_SEED = str::parse::<u64>(&seed).expect("SEED_RANDOM must be an integer.");}
|
||||
}
|
||||
|
||||
let mut api_ranges = get_all_fn_symbol_ranges(&elf, api_range);
|
||||
let app_fn_ranges = get_all_fn_symbol_ranges(&elf, app_range.clone());
|
||||
|
||||
let mut isr_ranges : HashMap<String,std::ops::Range<GuestAddr>> = systemstate::helpers::ISR_SYMBOLS.iter().filter_map(|x| (api_ranges.get(&x.to_string()).map(|y| (x.to_string(),y.clone())))).collect();
|
||||
systemstate::helpers::ISR_SYMBOLS.iter().for_each(|x| {let _ =(app_fn_ranges.get(&x.to_string()).map(|y| (x.to_string(),y.clone()))).map(|y| isr_ranges.insert(y.0,y.1));}); // add used defined isr
|
||||
let denylist=isr_ranges.values().map(|x| x.clone()).collect();
|
||||
let denylist = QemuInstrumentationFilter::DenyList(denylist); // do not count isr jumps, which are useless
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let mut isr_addreses : HashMap<GuestAddr, String> = systemstate::helpers::ISR_SYMBOLS.iter().filter_map(|x| (api_ranges.remove(&x.to_string()).map(|y| (y.start,x.to_string())))).collect();
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
systemstate::helpers::ISR_SYMBOLS.iter().for_each(|x| {let _ =(app_fn_ranges.get(&x.to_string()).map(|y| (x.to_string(),y.clone()))).map(|y| isr_addreses.insert(y.1.start, y.0));}); // add used defined isr
|
||||
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
for i in systemstate::helpers::ISR_SYMBOLS {
|
||||
if isr_ranges.get(&i.to_string()).is_none() {
|
||||
if let Some(fr) = get_function_range(&elf, i) {
|
||||
isr_addreses.insert(fr.start, i.to_string());
|
||||
isr_ranges.insert(i.to_string(), fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let api_addreses : HashMap<GuestAddr, String> = api_ranges.iter().map(|(k,v)| (v.start,k.clone())).collect();
|
||||
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let api_ranges : Vec<_> = api_ranges.into_iter().collect();
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let isr_ranges : Vec<_> = isr_ranges.into_iter().collect();
|
||||
|
||||
// Client setup ================================================================================
|
||||
|
||||
let mut run_client = |state: Option<_>, mut mgr, _core_id| {
|
||||
// Initialize QEMU
|
||||
let args: Vec<String> = vec![
|
||||
"target/debug/fret",
|
||||
"-icount",
|
||||
&format!("shift={},align=off,sleep=off", QEMU_ICOUNT_SHIFT),
|
||||
"-machine",
|
||||
"mps2-an385",
|
||||
"-cpu",
|
||||
"cortex-m3",
|
||||
"-monitor",
|
||||
"null",
|
||||
"-kernel",
|
||||
&cli.kernel.as_os_str().to_str().expect("kernel path is not a string"),
|
||||
"-serial",
|
||||
"null",
|
||||
"-nographic",
|
||||
"-S",
|
||||
// "-semihosting",
|
||||
// "--semihosting-config",
|
||||
// "enable=on,target=native",
|
||||
"-snapshot",
|
||||
"-drive",
|
||||
"if=none,format=qcow2,file=dummy.qcow2",
|
||||
].into_iter().map(String::from).collect();
|
||||
let env: Vec<(String, String)> = env::vars().collect();
|
||||
let emu = Emulator::new(&args, &env).expect("Emulator creation failed");
|
||||
|
||||
if let Some(main_addr) = main_addr {
|
||||
unsafe {
|
||||
libafl_qemu_set_native_breakpoint(main_addr);
|
||||
emu.run();
|
||||
libafl_qemu_remove_native_breakpoint(main_addr);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { libafl_qemu_set_native_breakpoint(breakpoint); }// BREAKPOINT
|
||||
|
||||
// The wrapped harness function, calling out to the LLVM-style harness
|
||||
let mut harness = |input: &BytesInput| {
|
||||
let target = input.target_bytes();
|
||||
let mut buf = target.as_slice();
|
||||
let mut len = buf.len();
|
||||
unsafe {
|
||||
#[cfg(feature = "fuzz_int")]
|
||||
{
|
||||
let t = input_bytes_to_interrupt_times(buf);
|
||||
for i in 0..t.len() {libafl_interrupt_offsets[i]=t[i];}
|
||||
libafl_num_interrupts=t.len();
|
||||
|
||||
if buf.len() > libafl_num_interrupts*4 {
|
||||
buf = &buf[libafl_num_interrupts*4..];
|
||||
len = buf.len();
|
||||
}
|
||||
// for i in 0 .. libafl_num_interrupts {
|
||||
// libafl_interrupt_offsets[i] = FIRST_INT+TryInto::<u32>::try_into(i).unwrap()*MINIMUM_INTER_ARRIVAL_TIME;
|
||||
// }
|
||||
// println!("Load: {:?}", libafl_interrupt_offsets[0..libafl_num_interrupts].to_vec());
|
||||
}
|
||||
if len > MAX_INPUT_SIZE {
|
||||
buf = &buf[0..MAX_INPUT_SIZE];
|
||||
len = MAX_INPUT_SIZE;
|
||||
}
|
||||
|
||||
// Note: I could not find a difference between write_mem and write_phys_mem for my usecase
|
||||
emu.write_mem(input_addr, buf);
|
||||
if let Some(s) = input_length_ptr {
|
||||
emu.write_mem(s, &len.to_le_bytes())
|
||||
}
|
||||
|
||||
emu.run();
|
||||
|
||||
// If the execution stops at any point other then the designated breakpoint (e.g. a breakpoint on a panic method) we consider it a crash
|
||||
let mut pcs = (0..emu.num_cpus())
|
||||
.map(|i| emu.cpu_from_index(i))
|
||||
.map(|cpu| -> Result<u32, String> { cpu.read_reg(Regs::Pc) });
|
||||
match pcs
|
||||
.find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0)))
|
||||
{
|
||||
Some(_) => ExitKind::Ok,
|
||||
None => ExitKind::Crash,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create an observation channel to keep track of the execution time
|
||||
let clock_time_observer = QemuClockObserver::new("clocktime", if cli.dump_times {cli.dump_name.clone().map(|x| x.with_extension("time"))} else {None} );
|
||||
|
||||
// Create an observation channel using the coverage map
|
||||
#[cfg(feature = "observe_edges")]
|
||||
let edges_observer = unsafe { VariableMapObserver::from_mut_slice(
|
||||
"edges",
|
||||
edges_map_mut_slice(),
|
||||
addr_of_mut!(MAX_EDGES_NUM)
|
||||
)};
|
||||
#[cfg(feature = "observer_hitcounts")]
|
||||
let edges_observer = HitcountsMapObserver::new(edges_observer);
|
||||
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let stg_coverage_observer = unsafe { VariableMapObserver::from_mut_slice(
|
||||
"stg",
|
||||
stg_map_mut_slice(),
|
||||
addr_of_mut!(MAX_STG_NUM)
|
||||
)};
|
||||
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let systemstate_observer = QemuSystemStateObserver::new();
|
||||
|
||||
// Feedback to rate the interestingness of an input
|
||||
// This one is composed by two Feedbacks in OR
|
||||
let mut feedback = feedback_or!(
|
||||
// Time feedback, this one does not need a feedback state
|
||||
ClockTimeFeedback::new_with_observer(&clock_time_observer)
|
||||
);
|
||||
#[cfg(feature = "feed_genetic")]
|
||||
let mut feedback = feedback_or!(
|
||||
feedback,
|
||||
AlwaysTrueFeedback::new()
|
||||
);
|
||||
#[cfg(feature = "feed_afl")]
|
||||
let mut feedback = feedback_or!(
|
||||
feedback,
|
||||
// New maximization map feedback linked to the edges observer and the feedback state
|
||||
MaxMapFeedback::tracking(&edges_observer, true, true)
|
||||
);
|
||||
#[cfg(feature = "feed_longest")]
|
||||
let mut feedback = feedback_or!(
|
||||
// afl feedback needs to be activated first for MapIndexesMetadata
|
||||
feedback,
|
||||
// Feedback to reward any input which increses the execution time
|
||||
ExecTimeIncFeedback::new()
|
||||
);
|
||||
#[cfg(all(feature = "observe_systemstate"))]
|
||||
let mut feedback = feedback_or!(
|
||||
feedback,
|
||||
DumpSystraceFeedback::with_dump(if cli.dump_traces {cli.dump_name.clone().map(|x| x.with_extension("trace.ron"))} else {None})
|
||||
);
|
||||
#[cfg(feature = "trace_stg")]
|
||||
let mut feedback = feedback_or!(
|
||||
feedback,
|
||||
StgFeedback::new(if cli.dump_graph {cli.dump_name.clone()} else {None})
|
||||
);
|
||||
#[cfg(feature = "feed_stg_edge")]
|
||||
let mut feedback = feedback_or!(
|
||||
feedback,
|
||||
MaxMapFeedback::tracking(&stg_coverage_observer, true, true)
|
||||
);
|
||||
|
||||
// A feedback to choose if an input is a solution or not
|
||||
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new(), SystraceErrorFeedback::new(cli.dump_cases || matches!(cli.command, Commands::Fuzz{..})));
|
||||
|
||||
// If not restarting, create a State from scratch
|
||||
let mut state = state.unwrap_or_else(|| {
|
||||
StdState::new(
|
||||
// RNG
|
||||
unsafe {StdRand::with_seed(RNG_SEED) },
|
||||
// Corpus that will be evolved, we keep it in memory for performance
|
||||
InMemoryCorpus::new(),
|
||||
// Corpus in which we store solutions (crashes in this example),
|
||||
// on disk so the user can get them after stopping the fuzzer
|
||||
OnDiskCorpus::new(objective_dir.clone()).unwrap(),
|
||||
// States of the feedbacks.
|
||||
// The feedbacks can report the data that should persist in the State.
|
||||
&mut feedback,
|
||||
// Same for objective feedbacks
|
||||
&mut objective,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
// A minimization+queue policy to get testcasess from the corpus
|
||||
#[cfg(not(any(feature = "sched_afl", feature = "sched_stg", feature = "sched_genetic")))]
|
||||
let scheduler = QueueScheduler::new(); // fallback
|
||||
#[cfg(feature = "sched_afl",)]
|
||||
let scheduler = TimeMaximizerCorpusScheduler::new(QueueScheduler::new());
|
||||
#[cfg(feature = "sched_stg")]
|
||||
let scheduler = LongestTraceScheduler::new(GraphMaximizerCorpusScheduler::new(QueueScheduler::new()));
|
||||
#[cfg(feature = "sched_genetic")]
|
||||
let scheduler = GenerationScheduler::new();
|
||||
|
||||
// A fuzzer with feedbacks and a corpus scheduler
|
||||
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
|
||||
|
||||
let qhelpers = tuple_list!();
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let qhelpers = (QemuSystemStateHelper::new(api_addreses,api_ranges,isr_addreses,isr_ranges,curr_tcb_pointer,task_queue_addr,task_delay_addr,task_delay_overflow_addr,scheduler_lock,scheduler_running, critical_section,input_counter_ptr,app_range.clone()), qhelpers);
|
||||
#[cfg(feature = "observe_edges")]
|
||||
let qhelpers = (QemuEdgeCoverageHelper::new(denylist), qhelpers);
|
||||
let qhelpers = (QemuStateRestoreHelper::new(), qhelpers);
|
||||
|
||||
let mut hooks = QemuHooks::new(emu.clone(),qhelpers);
|
||||
|
||||
let observer_list = tuple_list!();
|
||||
#[cfg(feature = "observe_systemstate")]
|
||||
let observer_list = (systemstate_observer, (stg_coverage_observer, observer_list)); // must come after clock
|
||||
#[cfg(feature = "observe_edges")]
|
||||
let observer_list = (edges_observer, observer_list);
|
||||
let observer_list = (clock_time_observer, observer_list);
|
||||
|
||||
// Create a QEMU in-process executor
|
||||
let executor = QemuExecutor::new(
|
||||
&mut hooks,
|
||||
&mut harness,
|
||||
observer_list,
|
||||
&mut fuzzer,
|
||||
&mut state,
|
||||
&mut mgr,
|
||||
)
|
||||
.expect("Failed to create QemuExecutor");
|
||||
|
||||
// Wrap the executor to keep track of the timeout
|
||||
let mut executor = TimeoutExecutor::new(executor, timeout);
|
||||
|
||||
let mutations = havoc_mutations();
|
||||
// Setup an havoc mutator with a mutational stage
|
||||
let mutator = StdScheduledMutator::new(mutations);
|
||||
|
||||
let stages = ();
|
||||
let mut stages = (StdMutationalStage::new(mutator), stages);
|
||||
#[cfg(feature = "fuzz_int")]
|
||||
let mut stages = (InterruptShiftStage::new(), stages);
|
||||
|
||||
if let Commands::Showmap { input } = cli.command.clone() {
|
||||
let s = input.as_os_str();
|
||||
let show_input = if s=="-" {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
std::io::stdin().read_to_end(&mut buf).expect("Could not read Stdin");
|
||||
buf
|
||||
} else if s=="$" {
|
||||
env::var("SHOWMAP_TEXTINPUT").expect("SHOWMAP_TEXTINPUT not set").as_bytes().to_owned()
|
||||
} else {
|
||||
fs::read(s).expect("Input file for DO_SHOWMAP can not be read")
|
||||
};
|
||||
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, BytesInput::new(show_input))
|
||||
.unwrap();
|
||||
do_dump_times!(state, &cli, "");
|
||||
do_dump_stg!(state, &cli, "");
|
||||
} else if let Commands::Fuzz { random, time, seed } = cli.command {
|
||||
if let Some(se) = seed {
|
||||
unsafe {
|
||||
let mut rng = StdRng::seed_from_u64(se);
|
||||
for i in 0..1000 {
|
||||
let inp = BytesInput::new(vec![rng.gen::<u8>(); MAX_INPUT_SIZE]);
|
||||
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if let Ok(sf) = env::var("SEED_DIR") {
|
||||
state
|
||||
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[PathBuf::from(&sf)])
|
||||
.unwrap_or_else(|_| {
|
||||
println!("Failed to load initial corpus at {:?}", &corpus_dirs);
|
||||
process::exit(0);
|
||||
});
|
||||
println!("We imported {} inputs from seedfile.", state.corpus().count());
|
||||
} else if state.corpus().count() < 1 {
|
||||
state
|
||||
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs)
|
||||
.unwrap_or_else(|_| {
|
||||
println!("Failed to load initial corpus at {:?}", &corpus_dirs);
|
||||
process::exit(0);
|
||||
});
|
||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||
}
|
||||
|
||||
match time {
|
||||
None => {
|
||||
fuzzer
|
||||
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
|
||||
.unwrap();
|
||||
},
|
||||
Some(t) => {
|
||||
println!("Iterations {}",t);
|
||||
let num = t;
|
||||
if random { unsafe {
|
||||
println!("Random Fuzzing, ignore corpus");
|
||||
// let mut generator = RandBytesGenerator::new(MAX_INPUT_SIZE);
|
||||
let target_duration = Duration::from_secs(num);
|
||||
let start_time = std::time::Instant::now();
|
||||
let mut rng = StdRng::seed_from_u64(RNG_SEED);
|
||||
while start_time.elapsed() < target_duration {
|
||||
// let inp = generator.generate(&mut state).unwrap();
|
||||
// libafl's generator is too slow
|
||||
let inp = BytesInput::new(vec![rng.gen::<u8>(); MAX_INPUT_SIZE]);
|
||||
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap();
|
||||
}
|
||||
}} else {
|
||||
// fuzzer
|
||||
// .fuzz_loop_for_duration(&mut stages, &mut executor, &mut state, &mut mgr, Duration::from_secs(num))
|
||||
// .unwrap();
|
||||
fuzzer
|
||||
.fuzz_loop_until(&mut stages, &mut executor, &mut state, &mut mgr, starttime.checked_add(Duration::from_secs(num)).unwrap())
|
||||
.unwrap();
|
||||
#[cfg(feature = "run_until_saturation")]
|
||||
{
|
||||
let mut dumper = |marker : String| {
|
||||
let d = format!("{}.case",marker);
|
||||
do_dump_case!(state, &cli, &d);
|
||||
let _d = format!("{}.stg",marker);
|
||||
do_dump_stg!(state, &cli, &_d);
|
||||
let d = format!("{}.toprated",marker);
|
||||
do_dump_toprated!(state, &cli, &d);
|
||||
};
|
||||
|
||||
dumper(format!(".iter_{}",t));
|
||||
do_dump_times!(state, &cli, "");
|
||||
|
||||
println!("Start running until saturation");
|
||||
let mut last = state.metadata_map().get::<IcHist>().unwrap().1;
|
||||
while SystemTime::now().duration_since(unsafe {FUZZ_START_TIMESTAMP}).unwrap().as_millis() < last.1 + Duration::from_secs(10800).as_millis() {
|
||||
starttime=starttime.checked_add(Duration::from_secs(30)).unwrap();
|
||||
fuzzer
|
||||
.fuzz_loop_until(&mut stages, &mut executor, &mut state, &mut mgr, starttime)
|
||||
.unwrap();
|
||||
let after = state.metadata_map().get::<IcHist>().unwrap().1;
|
||||
if after.0 > last.0 {
|
||||
last=after;
|
||||
}
|
||||
do_dump_case!(state, &cli, "");
|
||||
do_dump_stg!(state, &cli, "");
|
||||
do_dump_toprated!(state, &cli, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
do_dump_times!(state, &cli, "");
|
||||
do_dump_case!(state, &cli, "");
|
||||
do_dump_stg!(state, &cli, "");
|
||||
do_dump_toprated!(state, &cli, "");
|
||||
},
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "singlecore"))]
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Special case where no fuzzing happens, but standard input is dumped
|
||||
if let Ok(input_dump) = env::var("DUMP_SEED") {
|
||||
// Initialize QEMU
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let env: Vec<(String, String)> = env::vars().collect();
|
||||
let emu = Emulator::new(&args, &env).expect("Emu creation failed");
|
||||
|
||||
if let Some(main_addr) = main_addr {
|
||||
unsafe { libafl_qemu_set_native_breakpoint(main_addr); }// BREAKPOINT
|
||||
}
|
||||
unsafe {
|
||||
emu.run();
|
||||
|
||||
let mut buf = [0u8].repeat(MAX_INPUT_SIZE);
|
||||
emu.read_mem(input_addr, buf.as_mut_slice());
|
||||
|
||||
let dir = env::var("SEED_DIR").map_or("./corpus".to_string(), |x| x);
|
||||
let filename = if input_dump == "" {"input"} else {&input_dump};
|
||||
println!("Dumping input to: {}/{}",&dir,filename);
|
||||
fs::write(format!("{}/{}",&dir,filename), buf).expect("could not write input dump");
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
#[cfg(feature = "singlecore")]
|
||||
{
|
||||
let monitor = SimpleMonitor::new(|s| println!("{}", s));
|
||||
#[cfg(not(feature = "restarting"))]
|
||||
{
|
||||
let mgr = SimpleEventManager::new(monitor);
|
||||
run_client(None, mgr, 0);
|
||||
}
|
||||
|
||||
#[cfg(feature = "restarting")]
|
||||
{
|
||||
let mut shmem_provider = StdShMemProvider::new().unwrap();
|
||||
let (state, mut mgr) = match SimpleRestartingEventManager::launch(monitor, &mut shmem_provider)
|
||||
{
|
||||
// The restarting state will spawn the same process again as child, then restarted it each time it crashes.
|
||||
Ok(res) => res,
|
||||
Err(err) => match err {
|
||||
Error::ShuttingDown => {
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
panic!("Failed to setup the restarter: {}", err);
|
||||
}
|
||||
},
|
||||
};
|
||||
run_client(state, mgr, 0);
|
||||
}
|
||||
}
|
||||
// else -> multicore
|
||||
#[cfg(not(feature = "singlecore"))]
|
||||
{
|
||||
// The shared memory allocator
|
||||
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
|
||||
|
||||
// The stats reporter for the broker
|
||||
let monitor = MultiMonitor::new(|s| println!("{}", s));
|
||||
|
||||
// Build and run a Launcher
|
||||
match Launcher::builder()
|
||||
.shmem_provider(shmem_provider)
|
||||
.broker_port(broker_port)
|
||||
.configuration(EventConfig::from_build_id())
|
||||
.monitor(monitor)
|
||||
.run_client(&mut run_client)
|
||||
.cores(&cores)
|
||||
// .stdout_file(Some("/dev/null"))
|
||||
.build()
|
||||
.launch()
|
||||
{
|
||||
Ok(()) => (),
|
||||
Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Good bye."),
|
||||
Err(err) => panic!("Failed to run launcher: {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
12
fuzzers/FRET/src/lib.rs
Normal file
12
fuzzers/FRET/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
mod fuzzer;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod clock;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod qemustate;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod systemstate;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod mutational;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod worst;
|
23
fuzzers/FRET/src/main.rs
Normal file
23
fuzzers/FRET/src/main.rs
Normal file
@ -0,0 +1,23 @@
|
||||
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
|
||||
#[cfg(target_os = "linux")]
|
||||
mod fuzzer;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod clock;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod qemustate;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod systemstate;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod worst;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod mutational;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn main() {
|
||||
fuzzer::fuzz();
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn main() {
|
||||
panic!("qemu-user and libafl_qemu is only supported on linux!");
|
||||
}
|
346
fuzzers/FRET/src/mutational.rs
Normal file
346
fuzzers/FRET/src/mutational.rs
Normal file
@ -0,0 +1,346 @@
|
||||
//| The [`MutationalStage`] is the default stage used during fuzzing.
|
||||
//! For the current input, it will perform a range of random mutations, and then run them in the executor.
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use std::cmp::{max, min};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use libafl_bolts::rands::{
|
||||
StdRand, RandomSeed,
|
||||
Rand
|
||||
};
|
||||
use libafl::{
|
||||
corpus::{self, Corpus}, fuzzer::Evaluator, mark_feature_time, prelude::{new_hash_feedback, CorpusId, HasBytesVec, MutationResult, Mutator, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasMetadata, HasNamedMetadata, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error
|
||||
};
|
||||
use libafl::prelude::State;
|
||||
use crate::{clock::{IcHist, QEMU_ISNS_PER_USEC}, fuzzer::{DO_NUM_INTERRUPT, FIRST_INT, MAX_NUM_INTERRUPT}, systemstate::{stg::{STGFeedbackState, STGNodeMetadata}, CaptureEvent, ExecInterval, FreeRTOSSystemStateMetadata, ReducedFreeRTOSSystemState}};
|
||||
|
||||
pub static mut MINIMUM_INTER_ARRIVAL_TIME : u32 = 1000 /*us*/ * QEMU_ISNS_PER_USEC;
|
||||
// one isn per 2**4 ns
|
||||
// virtual insn/sec 62500000 = 1/16 GHz
|
||||
// 1ms = 62500 insn
|
||||
// 1us = 62.5 insn
|
||||
|
||||
pub fn input_bytes_to_interrupt_times(buf: &[u8]) -> Vec<u32> {
|
||||
let len = buf.len();
|
||||
let mut start_tick : u32 = 0;
|
||||
let mut ret = Vec::with_capacity(DO_NUM_INTERRUPT);
|
||||
for i in 0..DO_NUM_INTERRUPT {
|
||||
let mut t : [u8; 4] = [0,0,0,0];
|
||||
if len > (i+1)*4 {
|
||||
for j in 0usize..4usize {
|
||||
t[j]=buf[i*4+j];
|
||||
}
|
||||
start_tick = u32::from_le_bytes(t);
|
||||
if start_tick < FIRST_INT {start_tick=0;}
|
||||
ret.push(start_tick);
|
||||
} else {break;}
|
||||
}
|
||||
ret.sort_unstable();
|
||||
// obey the minimum inter arrival time while maintaining the sort
|
||||
for i in 0..ret.len() {
|
||||
if ret[i]==0 {continue;}
|
||||
for j in i+1..ret.len()-1 {
|
||||
if ret[j]-ret[i] < unsafe{MINIMUM_INTER_ARRIVAL_TIME} {
|
||||
ret[j] = u32::saturating_add(ret[i],unsafe{MINIMUM_INTER_ARRIVAL_TIME});
|
||||
} else {break;}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
|
||||
//======================= Custom mutator
|
||||
|
||||
/// The default mutational stage
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct InterruptShiftStage<E, EM, Z> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
phantom: PhantomData<(E, EM, Z)>,
|
||||
}
|
||||
|
||||
impl<E, EM, Z> InterruptShiftStage<E, EM, Z>
|
||||
where
|
||||
E: UsesState<State = Z::State>,
|
||||
EM: UsesState<State = Z::State>,
|
||||
Z: Evaluator<E, EM>,
|
||||
Z::State: MaybeHasClientPerfMonitor + HasCorpus + HasRand,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self { phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, EM, Z> Stage<E, EM, Z> for InterruptShiftStage<E, EM, Z>
|
||||
where
|
||||
E: UsesState<State = Z::State>,
|
||||
EM: UsesState<State = Z::State>,
|
||||
Z: Evaluator<E, EM>,
|
||||
Z::State: MaybeHasClientPerfMonitor + HasCorpus + HasRand + HasMetadata + HasNamedMetadata,
|
||||
<Z::State as UsesInput>::Input: HasBytesVec
|
||||
{
|
||||
fn perform(
|
||||
&mut self,
|
||||
fuzzer: &mut Z,
|
||||
executor: &mut E,
|
||||
state: &mut Self::State,
|
||||
manager: &mut EM,
|
||||
corpus_idx: CorpusId,
|
||||
) -> Result<(), Error> {
|
||||
let mut _input = state
|
||||
.corpus()
|
||||
.get(corpus_idx)?
|
||||
.borrow_mut().clone();
|
||||
let mut newinput = _input.input_mut().as_mut().unwrap().clone();
|
||||
let mut do_rerun = false;
|
||||
// if state.rand_mut().between(1, 100) <= 50 // only attempt the mutation half of the time
|
||||
{
|
||||
// need our own random generator, because borrowing rules
|
||||
let mut myrand = StdRand::new();
|
||||
let mut target_bytes : Vec<u8> = vec![];
|
||||
{
|
||||
let input = _input.input_mut().as_ref().unwrap();
|
||||
target_bytes = input.bytes().to_vec();
|
||||
let tmp = &mut state.rand_mut();
|
||||
myrand.set_seed(tmp.next());
|
||||
}
|
||||
|
||||
// produce a slice of absolute interrupt times
|
||||
let mut interrupt_offsets : [u32; MAX_NUM_INTERRUPT] = [u32::MAX; MAX_NUM_INTERRUPT];
|
||||
let mut num_interrupts : usize = 0;
|
||||
{
|
||||
let t = input_bytes_to_interrupt_times(&target_bytes);
|
||||
for i in 0..t.len() {interrupt_offsets[i]=t[i];}
|
||||
num_interrupts=t.len();
|
||||
}
|
||||
interrupt_offsets.sort_unstable();
|
||||
|
||||
// println!("Vor Mutator: {:?}", interrupt_offsets[0..num_interrupts].to_vec());
|
||||
// let num_i = min(target_bytes.len() / 4, DO_NUM_INTERRUPT);
|
||||
let mut suffix = target_bytes.split_off(4 * num_interrupts);
|
||||
let mut prefix : Vec<[u8; 4]> = vec![];
|
||||
// let mut suffix : Vec<u8> = vec![];
|
||||
#[cfg(feature = "mutate_stg")]
|
||||
{
|
||||
let metadata = state.metadata_map();
|
||||
let hist = metadata.get::<IcHist>().unwrap();
|
||||
let maxtick : u64 = hist.1.0;
|
||||
drop(hist);
|
||||
if interrupt_offsets[0] as u64 > maxtick { // place interrupt in reachable range
|
||||
do_rerun = true;
|
||||
for _ in 0..num_interrupts {
|
||||
prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64)).try_into().expect("ticks > u32")));
|
||||
}
|
||||
} else {
|
||||
// let choice = myrand.between(1,100);
|
||||
// if choice <= 25 { // 0.5*0.25 = 12.5% of the time fully randomize all interrupts
|
||||
// do_rerun = true;
|
||||
// // let metadata = state.metadata_map();
|
||||
// let hist = metadata.get::<IcHist>().unwrap();
|
||||
// let maxtick : u64 = hist.1.0;
|
||||
// // let maxtick : u64 = (_input.exec_time().expect("No duration found").as_nanos() >> 4).try_into().unwrap();
|
||||
// let mut numbers : Vec<u32> = vec![];
|
||||
// for i in 0..num_interrupts {
|
||||
// prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64)).try_into().expect("ticks > u32")));
|
||||
// }
|
||||
// }
|
||||
// else if choice <= 75 { // 0.5 * 0.25 = 12.5% of cases
|
||||
// let feedbackstate = match state
|
||||
// .named_metadata_map_mut()
|
||||
// .get_mut::<STGFeedbackState>("stgfeedbackstate") {
|
||||
// Some(s) => s,
|
||||
// None => {
|
||||
// panic!("STGfeedbackstate not visible")
|
||||
// }
|
||||
// };
|
||||
// let tmp = _input.metadata_map().get::<STGNodeMetadata>();
|
||||
// if tmp.is_some() {
|
||||
// let trace = tmp.expect("STGNodeMetadata not found");
|
||||
// let mut node_indices = vec![];
|
||||
// for i in (0..trace.intervals.len()).into_iter() {
|
||||
// if let Some(abb) = &trace.intervals[i].abb {
|
||||
// if let Some(idx) = feedbackstate.state_abb_hash_index.get(&(abb.get_hash(), trace.intervals[i].start_state)) {
|
||||
// node_indices.push(Some(idx));
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// node_indices.push(None);
|
||||
// }
|
||||
// // let mut marks : HashMap<u32, usize>= HashMap::new(); // interrupt -> block hit
|
||||
// // for i in 0..trace.intervals.len() {
|
||||
// // let curr = &trace.intervals[i];
|
||||
// // let m = interrupt_offsets[0..num_interrupts].iter().filter(|x| (curr.start_tick..curr.end_tick).contains(&((**x) as u64)));
|
||||
// // for k in m {
|
||||
// // marks.insert(*k,i);
|
||||
// // }
|
||||
// // }
|
||||
// // walk backwards trough the trace and try moving the interrupt to a block that does not have an outgoing interrupt edge or ist already hit by a predecessor
|
||||
// for i in (0..num_interrupts).rev() {
|
||||
// let mut lb = FIRST_INT;
|
||||
// let mut ub : u32 = trace.intervals[trace.intervals.len()-1].end_tick.try_into().expect("ticks > u32");
|
||||
// if i > 0 {
|
||||
// lb = u32::saturating_add(interrupt_offsets[i-1],unsafe{MINIMUM_INTER_ARRIVAL_TIME});
|
||||
// }
|
||||
// if i < num_interrupts-1 {
|
||||
// ub = u32::saturating_sub(interrupt_offsets[i+1],unsafe{MINIMUM_INTER_ARRIVAL_TIME});
|
||||
// }
|
||||
// let alternatives : Vec<_> = (0..trace.intervals.len()).filter(|x|
|
||||
// node_indices[*x].is_some() &&
|
||||
// (trace.intervals[*x].start_tick < (lb as u64) && (lb as u64) < trace.intervals[*x].end_tick
|
||||
// || trace.intervals[*x].start_tick > (lb as u64) && trace.intervals[*x].start_tick < (ub as u64))
|
||||
// ).collect();
|
||||
// let not_yet_hit : Vec<_> = alternatives.iter().filter(
|
||||
// |x| feedbackstate.graph.edges_directed(*node_indices[**x].unwrap(), petgraph::Direction::Outgoing).any(|y| y.weight().event != CaptureEvent::ISRStart)).collect();
|
||||
// if not_yet_hit.len() > 0 {
|
||||
// let replacement = &trace.intervals[*myrand.choose(not_yet_hit)];
|
||||
// interrupt_offsets[i] = (myrand.between(replacement.start_tick,
|
||||
// replacement.end_tick)).try_into().expect("ticks > u32");
|
||||
// // println!("chose new alternative, i: {} {} -> {}",i,tmp, interrupt_offsets[i]);
|
||||
// do_rerun = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else { // old version of the alternative search
|
||||
{
|
||||
let tmp = _input.metadata_map().get::<STGNodeMetadata>();
|
||||
if tmp.is_some() {
|
||||
let trace = tmp.expect("STGNodeMetadata not found");
|
||||
|
||||
// calculate hits and identify snippets
|
||||
let mut last_m = false;
|
||||
let mut marks : Vec<(&ExecInterval, usize, usize)>= vec![]; // 1: got interrupted, 2: interrupt handler
|
||||
for i in 0..trace.intervals.len() {
|
||||
let curr = &trace.intervals[i];
|
||||
let m = interrupt_offsets[0..num_interrupts].iter().any(|x| (curr.start_tick..curr.end_tick).contains(&(*x as u64)));
|
||||
if m {
|
||||
marks.push((curr, i, 1));
|
||||
// println!("1: {}",curr.current_task.0.task_name);
|
||||
} else if last_m {
|
||||
marks.push((curr, i, 2));
|
||||
// println!("2: {}",curr.current_task.0.task_name);
|
||||
} else {
|
||||
marks.push((curr, i, 0));
|
||||
}
|
||||
last_m = m;
|
||||
}
|
||||
for i in 0..num_interrupts {
|
||||
// bounds based on minimum inter-arrival time
|
||||
let mut lb = FIRST_INT;
|
||||
let mut ub : u32 = marks[marks.len()-1].0.end_tick.try_into().expect("ticks > u32");
|
||||
if i > 0 {
|
||||
lb = u32::saturating_add(interrupt_offsets[i-1],unsafe{MINIMUM_INTER_ARRIVAL_TIME});
|
||||
}
|
||||
if i < num_interrupts-1 {
|
||||
ub = u32::saturating_sub(interrupt_offsets[i+1],unsafe{MINIMUM_INTER_ARRIVAL_TIME});
|
||||
}
|
||||
// get old hit and handler
|
||||
let old_hit = marks.iter().filter(
|
||||
|x| x.0.start_tick < (interrupt_offsets[i] as u64) && (interrupt_offsets[i] as u64) < x.0.end_tick
|
||||
).next();
|
||||
let old_handler = match old_hit {
|
||||
Some(s) => if s.1 < num_interrupts-1 && s.1 < marks.len()-1 {
|
||||
Some(marks[s.1+1])
|
||||
} else {None},
|
||||
None => None
|
||||
};
|
||||
// find reachable alternatives
|
||||
let alternatives : Vec<_> = marks.iter().filter(|x|
|
||||
x.2 != 2 &&
|
||||
(
|
||||
x.0.start_tick < (lb as u64) && (lb as u64) < x.0.end_tick
|
||||
|| x.0.start_tick > (lb as u64) && x.0.start_tick < (ub as u64))
|
||||
).collect();
|
||||
// in cases there are no alternatives
|
||||
if alternatives.len() == 0 {
|
||||
if old_hit.is_none() {
|
||||
// choose something random
|
||||
let untouched : Vec<_> = marks.iter().filter(
|
||||
|x| x.2 == 0
|
||||
).collect();
|
||||
if untouched.len() > 0 {
|
||||
let tmp = interrupt_offsets[i];
|
||||
let choice = myrand.choose(untouched);
|
||||
interrupt_offsets[i] = myrand.between(choice.0.start_tick, choice.0.end_tick)
|
||||
.try_into().expect("tick > u32");
|
||||
do_rerun = true;
|
||||
}
|
||||
// println!("no alternatives, choose random i: {} {} -> {}",i,tmp,interrupt_offsets[i]);
|
||||
continue;
|
||||
} else {
|
||||
// do nothing
|
||||
// println!("no alternatives, do nothing i: {} {}",i,interrupt_offsets[i]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let replacement = myrand.choose(alternatives);
|
||||
if (old_hit.map_or(false, |x| x == replacement)) {
|
||||
// use the old value
|
||||
// println!("chose old value, do nothing i: {} {}",i,interrupt_offsets[i]);
|
||||
continue;
|
||||
} else {
|
||||
let extra = if (old_hit.map_or(false, |x| x.1 < replacement.1)) {
|
||||
// move futher back, respect old_handler
|
||||
old_handler.map_or(0, |x| x.0.end_tick - x.0.start_tick)
|
||||
} else { 0 };
|
||||
let tmp = interrupt_offsets[i];
|
||||
interrupt_offsets[i] = (myrand.between(replacement.0.start_tick,
|
||||
replacement.0.end_tick) + extra).try_into().expect("ticks > u32");
|
||||
// println!("chose new alternative, i: {} {} -> {}",i,tmp, interrupt_offsets[i]);
|
||||
do_rerun = true;
|
||||
}
|
||||
}
|
||||
let mut numbers : Vec<u32> = interrupt_offsets[0..num_interrupts].to_vec();
|
||||
numbers.sort();
|
||||
// println!("Mutator: {:?}", numbers);
|
||||
// let mut start : u32 = 0;
|
||||
// for i in 0..numbers.len() {
|
||||
// let tmp = numbers[i];
|
||||
// numbers[i] = numbers[i]-start;
|
||||
// start = tmp;
|
||||
// }
|
||||
for i in 0..numbers.len() {
|
||||
prefix.push(u32::to_le_bytes(numbers[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "trace_stg"))]
|
||||
{
|
||||
if myrand.between(1,100) <= 25 { // we have no hint if interrupt times will change anything
|
||||
do_rerun = true;
|
||||
let metadata = state.metadata_map();
|
||||
let hist = metadata.get::<IcHist>().unwrap();
|
||||
let maxtick : u64 = hist.1.0;
|
||||
// let maxtick : u64 = (_input.exec_time().expect("No duration found").as_nanos() >> 4).try_into().unwrap();
|
||||
let mut numbers : Vec<u32> = vec![];
|
||||
for i in 0..num_interrupts {
|
||||
prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64)).try_into().expect("ticks > u32")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut n : Vec<u8> = vec![];
|
||||
n = [prefix.concat(), suffix].concat();
|
||||
newinput.bytes_mut().clear();
|
||||
newinput.bytes_mut().append(&mut n);
|
||||
}
|
||||
// InterruptShifterMutator::mutate(&mut mymut, state, &mut input, 0)?;
|
||||
if do_rerun {
|
||||
let (_, corpus_idx) = fuzzer.evaluate_input(state, executor, manager, newinput)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, EM, Z> UsesState for InterruptShiftStage<E, EM, Z>
|
||||
where
|
||||
E: UsesState<State = Z::State>,
|
||||
EM: UsesState<State = Z::State>,
|
||||
Z: Evaluator<E, EM>,
|
||||
Z::State: MaybeHasClientPerfMonitor + HasCorpus + HasRand,
|
||||
{
|
||||
type State = Z::State;
|
||||
}
|
96
fuzzers/FRET/src/qemustate.rs
Normal file
96
fuzzers/FRET/src/qemustate.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use libafl::prelude::UsesInput;
|
||||
use libafl_qemu::CPUArchState;
|
||||
use libafl_qemu::Emulator;
|
||||
use libafl_qemu::FastSnapshot;
|
||||
use libafl_qemu::QemuExecutor;
|
||||
use libafl_qemu::QemuHelper;
|
||||
use libafl_qemu::QemuHelperTuple;
|
||||
use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata};
|
||||
use libafl_qemu::QemuHooks;
|
||||
|
||||
use libafl_qemu::{
|
||||
emu,
|
||||
};
|
||||
// TODO be thread-safe maybe with https://amanieu.github.io/thread_local-rs/thread_local/index.html
|
||||
#[derive(Debug)]
|
||||
pub struct QemuStateRestoreHelper {
|
||||
has_snapshot: bool,
|
||||
use_snapshot: bool,
|
||||
saved_cpu_states: Vec<CPUArchState>,
|
||||
fastsnap: Option<FastSnapshot>
|
||||
}
|
||||
|
||||
impl QemuStateRestoreHelper {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
has_snapshot: false,
|
||||
use_snapshot: true,
|
||||
saved_cpu_states: vec![],
|
||||
fastsnap: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QemuStateRestoreHelper {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> QemuHelper<S> for QemuStateRestoreHelper
|
||||
where
|
||||
S: UsesInput,
|
||||
{
|
||||
const HOOKS_DO_SIDE_EFFECTS: bool = true;
|
||||
|
||||
fn init_hooks<QT>(&self, _hooks: &QemuHooks<QT, S>)
|
||||
where
|
||||
QT: QemuHelperTuple<S>,
|
||||
{
|
||||
}
|
||||
|
||||
fn first_exec<QT>(&self, _hooks: &QemuHooks<QT, S>)
|
||||
where
|
||||
QT: QemuHelperTuple<S>,
|
||||
{
|
||||
}
|
||||
|
||||
fn post_exec<OT>(&mut self, emulator: &Emulator, _input: &S::Input, _observers: &mut OT, _exit_kind: &mut ExitKind) {
|
||||
// unsafe { println!("snapshot post {}",emu::icount_get_raw()) };
|
||||
}
|
||||
|
||||
fn pre_exec(&mut self, emulator: &Emulator, _input: &S::Input) {
|
||||
// only restore in pre-exec, to preserve the post-execution state for inspection
|
||||
#[cfg(feature = "snapshot_restore")]
|
||||
{
|
||||
#[cfg(feature = "snapshot_fast")]
|
||||
match self.fastsnap {
|
||||
Some(s) => emulator.restore_fast_snapshot(s),
|
||||
None => {self.fastsnap = Some(emulator.create_fast_snapshot(true));},
|
||||
}
|
||||
#[cfg(not(feature = "snapshot_fast"))]
|
||||
if !self.has_snapshot {
|
||||
emulator.save_snapshot("Start", true);
|
||||
self.has_snapshot = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
emulator.load_snapshot("Start", true);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "snapshot_restore"))]
|
||||
if !self.has_snapshot {
|
||||
self.saved_cpu_states = (0..emulator.num_cpus())
|
||||
.map(|i| emulator.cpu_from_index(i).save_state())
|
||||
.collect();
|
||||
self.has_snapshot = true;
|
||||
} else {
|
||||
for (i, s) in self.saved_cpu_states.iter().enumerate() {
|
||||
emulator.cpu_from_index(i).restore_state(s);
|
||||
}
|
||||
}
|
||||
|
||||
// unsafe { println!("snapshot pre {}",emu::icount_get_raw()) };
|
||||
}
|
||||
}
|
6
fuzzers/FRET/src/systemstate/ARCH.md
Normal file
6
fuzzers/FRET/src/systemstate/ARCH.md
Normal file
@ -0,0 +1,6 @@
|
||||
# System-state heuristics
|
||||
## Information flow
|
||||
- ``fuzzer.rs`` resolves symbols and creates ``api_ranges`` and ``isr_ranges``
|
||||
- ``helpers::QemuSystemStateHelper`` captures a series of ``RawFreeRTOSSystemState``
|
||||
- ``observers::QemuSystemStateObserver`` divides this into ``ReducedFreeRTOSSystemState`` and ``ExecInterval``, the first contains the raw states and the second contains information about the flow between states
|
||||
- ``stg::StgFeedback`` builds an stg from the intervals
|
283
fuzzers/FRET/src/systemstate/feedbacks.rs
Normal file
283
fuzzers/FRET/src/systemstate/feedbacks.rs
Normal file
@ -0,0 +1,283 @@
|
||||
use libafl::SerdeAny;
|
||||
use libafl_bolts::ownedref::OwnedSlice;
|
||||
use libafl::inputs::BytesInput;
|
||||
use libafl::prelude::UsesInput;
|
||||
use libafl::state::HasNamedMetadata;
|
||||
use std::path::PathBuf;
|
||||
use crate::clock::QemuClockObserver;
|
||||
use libafl::corpus::Testcase;
|
||||
use libafl_bolts::tuples::MatchName;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
use std::hash::Hash;
|
||||
use libafl::events::EventFirer;
|
||||
use libafl::state::MaybeHasClientPerfMonitor;
|
||||
use libafl::prelude::State;
|
||||
use libafl::feedbacks::Feedback;
|
||||
use libafl_bolts::Named;
|
||||
use libafl::Error;
|
||||
use hashbrown::HashMap;
|
||||
use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::ExecInterval;
|
||||
use super::ReducedFreeRTOSSystemState;
|
||||
use super::FreeRTOSSystemStateMetadata;
|
||||
use super::observers::QemuSystemStateObserver;
|
||||
use petgraph::prelude::DiGraph;
|
||||
use petgraph::graph::NodeIndex;
|
||||
use petgraph::Direction;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
//============================= Feedback
|
||||
|
||||
/// Shared Metadata for a systemstateFeedback
|
||||
#[derive(Debug, Serialize, Deserialize, SerdeAny, Clone, Default)]
|
||||
pub struct SystemStateFeedbackState
|
||||
{
|
||||
known_traces: HashMap<u64,(u64,u64,usize)>, // encounters,ticks,length
|
||||
longest: Vec<ReducedFreeRTOSSystemState>,
|
||||
}
|
||||
impl Named for SystemStateFeedbackState
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"systemstate"
|
||||
}
|
||||
}
|
||||
|
||||
/// A Feedback reporting novel System-State Transitions. Depends on [`QemuSystemStateObserver`]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct NovelSystemStateFeedback
|
||||
{
|
||||
last_trace: Option<Vec<ReducedFreeRTOSSystemState>>,
|
||||
// known_traces: HashMap<u64,(u64,usize)>,
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for NovelSystemStateFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor + HasNamedMetadata,
|
||||
{
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
manager: &mut EM,
|
||||
input: &S::Input,
|
||||
observers: &OT,
|
||||
exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>
|
||||
{
|
||||
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
|
||||
.expect("QemuSystemStateObserver not found");
|
||||
let clock_observer = observers.match_name::<QemuClockObserver>("clocktime") //TODO not fixed
|
||||
.expect("QemuClockObserver not found");
|
||||
let feedbackstate = match state
|
||||
.named_metadata_map_mut()
|
||||
.get_mut::<SystemStateFeedbackState>("systemstate") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
let n=SystemStateFeedbackState::default();
|
||||
state.named_metadata_map_mut().insert(n, "systemstate");
|
||||
state.named_metadata_map_mut().get_mut::<SystemStateFeedbackState>("systemstate").unwrap()
|
||||
}
|
||||
};
|
||||
// let feedbackstate = state
|
||||
// .feedback_states_mut()
|
||||
// .match_name_mut::<systemstateFeedbackState>("systemstate")
|
||||
// .unwrap();
|
||||
// Do Stuff
|
||||
let mut hasher = DefaultHasher::new();
|
||||
observer.last_run.hash(&mut hasher);
|
||||
let somehash = hasher.finish();
|
||||
let mut is_novel = false;
|
||||
let mut takes_longer = false;
|
||||
match feedbackstate.known_traces.get_mut(&somehash) {
|
||||
None => {
|
||||
is_novel = true;
|
||||
feedbackstate.known_traces.insert(somehash,(1,clock_observer.last_runtime(),observer.last_run.len()));
|
||||
}
|
||||
Some(s) => {
|
||||
s.0+=1;
|
||||
if s.1 < clock_observer.last_runtime() {
|
||||
s.1 = clock_observer.last_runtime();
|
||||
takes_longer = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if observer.last_run.len() > feedbackstate.longest.len() {
|
||||
feedbackstate.longest=observer.last_run.clone();
|
||||
}
|
||||
self.last_trace = Some(observer.last_run.clone());
|
||||
// if (!is_novel) { println!("not novel") };
|
||||
Ok(is_novel | takes_longer)
|
||||
}
|
||||
|
||||
/// Append to the testcase the generated metadata in case of a new corpus item
|
||||
#[inline]
|
||||
fn append_metadata<OT>(&mut self, _state: &mut S, observers: &OT, testcase: &mut Testcase<S::Input>) -> Result<(), Error> {
|
||||
let a = self.last_trace.take();
|
||||
match a {
|
||||
Some(s) => testcase.metadata_map_mut().insert(FreeRTOSSystemStateMetadata::new(s)),
|
||||
None => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Discard the stored metadata in case that the testcase is not added to the corpus
|
||||
#[inline]
|
||||
fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
|
||||
self.last_trace = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for NovelSystemStateFeedback
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"systemstate"
|
||||
}
|
||||
}
|
||||
|
||||
//=========================== Debugging Feedback
|
||||
/// A [`Feedback`] meant to dump the system-traces for debugging. Depends on [`QemuSystemStateObserver`]
|
||||
#[derive(Debug)]
|
||||
pub struct DumpSystraceFeedback
|
||||
{
|
||||
dumpfile: Option<PathBuf>,
|
||||
dump_metadata: bool,
|
||||
last_states: Option<HashMap<u64, ReducedFreeRTOSSystemState>>,
|
||||
last_trace: Option<Vec<ExecInterval>>,
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for DumpSystraceFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor,
|
||||
{
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
manager: &mut EM,
|
||||
input: &S::Input,
|
||||
observers: &OT,
|
||||
exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>
|
||||
{
|
||||
if self.dumpfile.is_none() {return Ok(false)};
|
||||
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
|
||||
.expect("QemuSystemStateObserver not found");
|
||||
let names : Vec<String> = observer.last_run.iter().map(|x| x.current_task.task_name.clone()).collect();
|
||||
match &self.dumpfile {
|
||||
Some(s) => {
|
||||
std::fs::write(s,ron::to_string(&(&observer.last_trace,&observer.last_states)).expect("Error serializing hashmap")).expect("Can not dump to file");
|
||||
self.dumpfile = None
|
||||
},
|
||||
None => if self.dump_metadata {println!("{:?}\n{:?}",observer.last_run,names);}
|
||||
};
|
||||
// if self.dump_metadata {self.last_trace=Some(observer.last_trace.clone());}
|
||||
Ok(false)
|
||||
}
|
||||
/// Append to the testcase the generated metadata in case of a new corpus item
|
||||
#[inline]
|
||||
fn append_metadata<OT>(&mut self, _state: &mut S, observers: &OT, testcase: &mut Testcase<S::Input>) -> Result<(), Error> {
|
||||
if !self.dump_metadata {return Ok(());}
|
||||
// let a = self.last_trace.take();
|
||||
// match a {
|
||||
// Some(s) => testcase.metadata_map_mut().insert(FreeRTOSSystemStateMetadata::new(s)),
|
||||
// None => (),
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Discard the stored metadata in case that the testcase is not added to the corpus
|
||||
#[inline]
|
||||
fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
|
||||
self.last_trace = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for DumpSystraceFeedback
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"Dumpsystemstate"
|
||||
}
|
||||
}
|
||||
|
||||
impl DumpSystraceFeedback
|
||||
{
|
||||
/// Creates a new [`DumpSystraceFeedback`]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {dumpfile: None, dump_metadata: false, last_trace: None, last_states: None }
|
||||
}
|
||||
pub fn with_dump(dumpfile: Option<PathBuf>) -> Self {
|
||||
Self {dumpfile: dumpfile, dump_metadata: false, last_trace: None, last_states: None}
|
||||
}
|
||||
pub fn metadata_only() -> Self {
|
||||
Self {dumpfile: None, dump_metadata: true, last_trace: None, last_states: None}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SystraceErrorFeedback
|
||||
{
|
||||
dump_case: bool
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for SystraceErrorFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor,
|
||||
{
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
_state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &S::Input,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>
|
||||
{
|
||||
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
|
||||
.expect("QemuSystemStateObserver not found");
|
||||
Ok(self.dump_case&&!observer.success)
|
||||
}
|
||||
/// Append to the testcase the generated metadata in case of a new corpus item
|
||||
#[inline]
|
||||
fn append_metadata<OT>(&mut self, _state: &mut S, _observers: &OT, _testcase: &mut Testcase<S::Input>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Discard the stored metadata in case that the testcase is not added to the corpus
|
||||
#[inline]
|
||||
fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for SystraceErrorFeedback
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"SystraceErrorFeedback"
|
||||
}
|
||||
}
|
||||
|
||||
impl SystraceErrorFeedback
|
||||
{
|
||||
#[must_use]
|
||||
pub fn new(dump_case: bool) -> Self {
|
||||
Self {dump_case}
|
||||
}
|
||||
}
|
122
fuzzers/FRET/src/systemstate/freertos.rs
Normal file
122
fuzzers/FRET/src/systemstate/freertos.rs
Normal file
@ -0,0 +1,122 @@
|
||||
#![allow(non_camel_case_types,non_snake_case,non_upper_case_globals,deref_nullptr)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
// Manual Types
|
||||
use libafl_qemu::Emulator;
|
||||
|
||||
/*========== Start of generated Code =============*/
|
||||
pub type char_ptr = ::std::os::raw::c_uint;
|
||||
pub type ListItem_t_ptr = ::std::os::raw::c_uint;
|
||||
pub type StackType_t_ptr = ::std::os::raw::c_uint;
|
||||
pub type void_ptr = ::std::os::raw::c_uint;
|
||||
pub type tskTaskControlBlock_ptr = ::std::os::raw::c_uint;
|
||||
pub type xLIST_ptr = ::std::os::raw::c_uint;
|
||||
pub type xLIST_ITEM_ptr = ::std::os::raw::c_uint;
|
||||
/* automatically generated by rust-bindgen 0.59.2 */
|
||||
|
||||
pub type __uint8_t = ::std::os::raw::c_uchar;
|
||||
pub type __uint16_t = ::std::os::raw::c_ushort;
|
||||
pub type __uint32_t = ::std::os::raw::c_uint;
|
||||
pub type StackType_t = u32;
|
||||
pub type UBaseType_t = ::std::os::raw::c_uint;
|
||||
pub type TickType_t = u32;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct xLIST_ITEM {
|
||||
pub xItemValue: TickType_t,
|
||||
pub pxNext: xLIST_ITEM_ptr,
|
||||
pub pxPrevious: xLIST_ITEM_ptr,
|
||||
pub pvOwner: void_ptr,
|
||||
pub pvContainer: xLIST_ptr,
|
||||
}
|
||||
pub type ListItem_t = xLIST_ITEM;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct xMINI_LIST_ITEM {
|
||||
pub xItemValue: TickType_t,
|
||||
pub pxNext: xLIST_ITEM_ptr,
|
||||
pub pxPrevious: xLIST_ITEM_ptr,
|
||||
}
|
||||
pub type MiniListItem_t = xMINI_LIST_ITEM;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct xLIST {
|
||||
pub uxNumberOfItems: UBaseType_t,
|
||||
pub pxIndex: ListItem_t_ptr,
|
||||
pub xListEnd: MiniListItem_t,
|
||||
}
|
||||
pub type List_t = xLIST;
|
||||
pub type TaskHandle_t = tskTaskControlBlock_ptr;
|
||||
pub const eTaskState_eRunning: eTaskState = 0;
|
||||
pub const eTaskState_eReady: eTaskState = 1;
|
||||
pub const eTaskState_eBlocked: eTaskState = 2;
|
||||
pub const eTaskState_eSuspended: eTaskState = 3;
|
||||
pub const eTaskState_eDeleted: eTaskState = 4;
|
||||
pub const eTaskState_eInvalid: eTaskState = 5;
|
||||
pub type eTaskState = ::std::os::raw::c_uint;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct xTASK_STATUS {
|
||||
pub xHandle: TaskHandle_t,
|
||||
pub pcTaskName: char_ptr,
|
||||
pub xTaskNumber: UBaseType_t,
|
||||
pub eCurrentState: eTaskState,
|
||||
pub uxCurrentPriority: UBaseType_t,
|
||||
pub uxBasePriority: UBaseType_t,
|
||||
pub ulRunTimeCounter: u32,
|
||||
pub pxStackBase: StackType_t_ptr,
|
||||
pub usStackHighWaterMark: u16,
|
||||
}
|
||||
pub type TaskStatus_t = xTASK_STATUS;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct tskTaskControlBlock {
|
||||
pub pxTopOfStack: StackType_t_ptr,
|
||||
pub xStateListItem: ListItem_t,
|
||||
pub xEventListItem: ListItem_t,
|
||||
pub uxPriority: UBaseType_t,
|
||||
pub pxStack: StackType_t_ptr,
|
||||
pub pcTaskName: [::std::os::raw::c_char; 10usize],
|
||||
pub uxBasePriority: UBaseType_t,
|
||||
pub uxMutexesHeld: UBaseType_t,
|
||||
pub ulNotifiedValue: [u32; 1usize],
|
||||
pub ucNotifyState: [u8; 1usize],
|
||||
pub ucStaticallyAllocated: u8,
|
||||
pub ucDelayAborted: u8,
|
||||
}
|
||||
pub type tskTCB = tskTaskControlBlock;
|
||||
pub type TCB_t = tskTCB;
|
||||
/*========== End of generated Code =============*/
|
||||
|
||||
pub trait emu_lookup {
|
||||
fn lookup(emu: &Emulator, addr: ::std::os::raw::c_uint) -> Self;
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum rtos_struct {
|
||||
TCB_struct(TCB_t),
|
||||
List_struct(List_t),
|
||||
List_Item_struct(ListItem_t),
|
||||
List_MiniItem_struct(MiniListItem_t),
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_emu_lookup {
|
||||
($struct_name:ident) => {
|
||||
impl $crate::systemstate::freertos::emu_lookup for $struct_name {
|
||||
fn lookup(emu: &Emulator, addr: ::std::os::raw::c_uint) -> $struct_name {
|
||||
let mut tmp : [u8; std::mem::size_of::<$struct_name>()] = [0u8; std::mem::size_of::<$struct_name>()];
|
||||
unsafe {
|
||||
emu.read_mem(addr.into(), &mut tmp);
|
||||
std::mem::transmute::<[u8; std::mem::size_of::<$struct_name>()], $struct_name>(tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_emu_lookup!(TCB_t);
|
||||
impl_emu_lookup!(List_t);
|
||||
impl_emu_lookup!(ListItem_t);
|
||||
impl_emu_lookup!(MiniListItem_t);
|
||||
impl_emu_lookup!(void_ptr);
|
||||
impl_emu_lookup!(TaskStatus_t);
|
631
fuzzers/FRET/src/systemstate/graph.rs
Normal file
631
fuzzers/FRET/src/systemstate/graph.rs
Normal file
@ -0,0 +1,631 @@
|
||||
|
||||
use libafl::SerdeAny;
|
||||
/// Feedbacks organizing SystemStates as a graph
|
||||
use libafl::inputs::HasBytesVec;
|
||||
use libafl_bolts::rands::RandomSeed;
|
||||
use libafl_bolts::rands::StdRand;
|
||||
use libafl::mutators::Mutator;
|
||||
use libafl::mutators::MutationResult;
|
||||
use libafl::prelude::HasTargetBytes;
|
||||
use libafl::prelude::UsesInput;
|
||||
use libafl::state::HasNamedMetadata;
|
||||
use libafl::state::UsesState;
|
||||
use libafl::prelude::State;
|
||||
use core::marker::PhantomData;
|
||||
use libafl::state::HasCorpus;
|
||||
use libafl::state::HasSolutions;
|
||||
use libafl::state::HasRand;
|
||||
use crate::worst::MaxExecsLenFavFactor;
|
||||
use crate::worst::MaxTimeFavFactor;
|
||||
use libafl::schedulers::MinimizerScheduler;
|
||||
use libafl_bolts::HasRefCnt;
|
||||
use libafl_bolts::AsSlice;
|
||||
use libafl_bolts::ownedref::OwnedSlice;
|
||||
use libafl::inputs::BytesInput;
|
||||
use std::path::PathBuf;
|
||||
use crate::clock::QemuClockObserver;
|
||||
use libafl::corpus::Testcase;
|
||||
use libafl_bolts::tuples::MatchName;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
use std::hash::Hash;
|
||||
use libafl::events::EventFirer;
|
||||
use libafl::state::MaybeHasClientPerfMonitor;
|
||||
use libafl::feedbacks::Feedback;
|
||||
use libafl_bolts::Named;
|
||||
use libafl::Error;
|
||||
use hashbrown::HashMap;
|
||||
use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::ExecInterval;
|
||||
use super::ReducedFreeRTOSSystemState;
|
||||
use super::FreeRTOSSystemStateMetadata;
|
||||
use super::observers::QemuSystemStateObserver;
|
||||
use petgraph::prelude::DiGraph;
|
||||
use petgraph::graph::NodeIndex;
|
||||
use petgraph::Direction;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use libafl_bolts::rands::Rand;
|
||||
|
||||
//============================= Data Structures
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
|
||||
pub struct VariantTuple
|
||||
{
|
||||
pub start_tick: u64,
|
||||
pub end_tick: u64,
|
||||
input_counter: u32,
|
||||
pub input: Vec<u8>, // in the end any kind of input are bytes, regardless of type and lifetime
|
||||
}
|
||||
impl VariantTuple {
|
||||
fn from(other: &ReducedFreeRTOSSystemState,input: Vec<u8>) -> Self {
|
||||
// VariantTuple{
|
||||
// start_tick: other.tick,
|
||||
// end_tick: other.end_tick,
|
||||
// input_counter: other.input_counter,
|
||||
// input: input,
|
||||
// }
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct SysGraphNode
|
||||
{
|
||||
base: ReducedFreeRTOSSystemState,
|
||||
pub variants: Vec<VariantTuple>,
|
||||
}
|
||||
impl SysGraphNode {
|
||||
fn from(base: ReducedFreeRTOSSystemState, input: Vec<u8>) -> Self {
|
||||
SysGraphNode{variants: vec![VariantTuple::from(&base, input)], base:base }
|
||||
}
|
||||
/// unites the variants of this value with another, draining the other if the bases are equal
|
||||
fn unite(&mut self, other: &mut SysGraphNode) -> bool {
|
||||
if self!=other {return false;}
|
||||
self.variants.append(&mut other.variants);
|
||||
self.variants.dedup();
|
||||
return true;
|
||||
}
|
||||
/// add a Varint from a [`RefinedFreeRTOSSystemState`]
|
||||
fn unite_raw(&mut self, other: &ReducedFreeRTOSSystemState, input: &Vec<u8>) -> bool {
|
||||
if &self.base!=other {return false;}
|
||||
self.variants.push(VariantTuple::from(other, input.clone()));
|
||||
self.variants.dedup();
|
||||
return true;
|
||||
}
|
||||
/// add a Varint from a [`RefinedFreeRTOSSystemState`], if it's interesting
|
||||
fn unite_interesting(&mut self, other: &ReducedFreeRTOSSystemState, input: &Vec<u8>) -> bool {
|
||||
// if &self.base!=other {return false;}
|
||||
// let interesting =
|
||||
// self.variants.iter().all(|x| x.end_tick-x.start_tick<other.end_tick-other.tick) || // longest variant
|
||||
// self.variants.iter().all(|x| x.end_tick-x.start_tick>other.end_tick-other.tick) || // shortest variant
|
||||
// self.variants.iter().all(|x| x.input_counter>other.input_counter) || // longest input
|
||||
// self.variants.iter().all(|x| x.input_counter<other.input_counter); // shortest input
|
||||
// if interesting {
|
||||
// let var = VariantTuple::from(other, input.clone());
|
||||
// self.variants.push(var);
|
||||
// }
|
||||
// return interesting;
|
||||
todo!()
|
||||
}
|
||||
pub fn get_taskname(&self) -> &str {
|
||||
&self.base.current_task.task_name
|
||||
}
|
||||
pub fn get_input_counts(&self) -> Vec<u32> {
|
||||
self.variants.iter().map(|x| x.input_counter).collect()
|
||||
}
|
||||
pub fn pretty_print(&self) -> String {
|
||||
let mut ret = String::new();
|
||||
ret.push_str(&format!("{}",&self.base.current_task.task_name));
|
||||
ret.push_str(";Rl:");
|
||||
for i in &self.base.ready_list_after {
|
||||
ret.push_str(&format!(";{}",i.task_name));
|
||||
}
|
||||
ret.push_str(";Dl:");
|
||||
for i in &self.base.delay_list_after {
|
||||
ret.push_str(&format!(";{}",i.task_name));
|
||||
}
|
||||
// println!("{}",ret);
|
||||
ret
|
||||
}
|
||||
}
|
||||
impl PartialEq for SysGraphNode {
|
||||
fn eq(&self, other: &SysGraphNode) -> bool {
|
||||
self.base==other.base
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper around Vec<RefinedFreeRTOSSystemState> to attach as Metadata
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct SysGraphMetadata {
|
||||
pub inner: Vec<NodeIndex>,
|
||||
indices: Vec<usize>,
|
||||
tcref: isize,
|
||||
}
|
||||
impl SysGraphMetadata {
|
||||
pub fn new(inner: Vec<NodeIndex>) -> Self{
|
||||
Self {indices: inner.iter().map(|x| x.index()).collect(), inner: inner, tcref: 0}
|
||||
}
|
||||
}
|
||||
impl AsSlice for SysGraphMetadata {
|
||||
/// Convert the slice of system-states to a slice of hashes over enumerated states
|
||||
fn as_slice(&self) -> &[usize] {
|
||||
self.indices.as_slice()
|
||||
}
|
||||
|
||||
type Entry = usize;
|
||||
}
|
||||
|
||||
impl HasRefCnt for SysGraphMetadata {
|
||||
fn refcnt(&self) -> isize {
|
||||
self.tcref
|
||||
}
|
||||
|
||||
fn refcnt_mut(&mut self) -> &mut isize {
|
||||
&mut self.tcref
|
||||
}
|
||||
}
|
||||
|
||||
libafl_bolts::impl_serdeany!(SysGraphMetadata);
|
||||
|
||||
pub type GraphMaximizerCorpusScheduler<CS> =
|
||||
MinimizerScheduler<CS, MaxTimeFavFactor<<CS as UsesState>::State>,SysGraphMetadata>;
|
||||
|
||||
//============================= Graph Feedback
|
||||
|
||||
/// Improved System State Graph
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, SerdeAny)]
|
||||
pub struct SysGraphFeedbackState
|
||||
{
|
||||
pub graph: DiGraph<SysGraphNode, ()>,
|
||||
entrypoint: NodeIndex,
|
||||
exit: NodeIndex,
|
||||
name: String,
|
||||
}
|
||||
impl SysGraphFeedbackState
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
let mut graph = DiGraph::<SysGraphNode, ()>::new();
|
||||
let mut entry = SysGraphNode::default();
|
||||
entry.base.current_task.task_name="Start".to_string();
|
||||
let mut exit = SysGraphNode::default();
|
||||
exit.base.current_task.task_name="End".to_string();
|
||||
let entry = graph.add_node(entry);
|
||||
let exit = graph.add_node(exit);
|
||||
Self {graph: graph, entrypoint: entry, exit: exit, name: String::from("SysMap")}
|
||||
}
|
||||
fn insert(&mut self, list: Vec<ReducedFreeRTOSSystemState>, input: &Vec<u8>) {
|
||||
let mut current_index = self.entrypoint;
|
||||
for n in list {
|
||||
let mut done = false;
|
||||
for i in self.graph.neighbors_directed(current_index, Direction::Outgoing) {
|
||||
if n == self.graph[i].base {
|
||||
done = true;
|
||||
current_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !done {
|
||||
let j = self.graph.add_node(SysGraphNode::from(n,input.clone()));
|
||||
self.graph.add_edge(current_index, j, ());
|
||||
current_index = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Try adding a system state path from a [Vec<RefinedFreeRTOSSystemState>], return true if the path was interesting
|
||||
fn update(&mut self, list: &Vec<ReducedFreeRTOSSystemState>, input: &Vec<u8>) -> (bool, Vec<NodeIndex>) {
|
||||
let mut current_index = self.entrypoint;
|
||||
let mut novel = false;
|
||||
let mut trace : Vec<NodeIndex> = vec![current_index];
|
||||
for n in list {
|
||||
let mut matching : Option<NodeIndex> = None;
|
||||
for i in self.graph.node_indices() {
|
||||
let tmp = &self.graph[i];
|
||||
if n == &tmp.base {
|
||||
matching = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
match matching {
|
||||
None => {
|
||||
novel = true;
|
||||
let j = self.graph.add_node(SysGraphNode::from(n.clone(),input.clone()));
|
||||
self.graph.update_edge(current_index, j, ());
|
||||
current_index = j;
|
||||
},
|
||||
Some(i) => {
|
||||
novel |= self.graph[i].unite_interesting(&n, input);
|
||||
self.graph.update_edge(current_index, i, ());
|
||||
current_index = i;
|
||||
}
|
||||
}
|
||||
trace.push(current_index);
|
||||
}
|
||||
if current_index != self.entrypoint {
|
||||
self.graph.update_edge(current_index, self.exit, ()); // every path ends in the exit noded
|
||||
}
|
||||
return (novel, trace);
|
||||
}
|
||||
}
|
||||
impl Named for SysGraphFeedbackState
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
impl Default for SysGraphFeedbackState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
impl SysGraphFeedbackState
|
||||
{
|
||||
fn reset(&mut self) -> Result<(), Error> {
|
||||
self.graph.clear();
|
||||
let mut entry = SysGraphNode::default();
|
||||
entry.base.current_task.task_name="Start".to_string();
|
||||
let mut exit = SysGraphNode::default();
|
||||
exit.base.current_task.task_name="End".to_string();
|
||||
self.entrypoint = self.graph.add_node(entry);
|
||||
self.exit = self.graph.add_node(exit);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A Feedback reporting novel System-State Transitions. Depends on [`QemuSystemStateObserver`]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct SysMapFeedback
|
||||
{
|
||||
name: String,
|
||||
last_trace: Option<Vec<NodeIndex>>,
|
||||
}
|
||||
impl SysMapFeedback {
|
||||
pub fn new() -> Self {
|
||||
Self {name: String::from("SysMapFeedback"), last_trace: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for SysMapFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor + HasNamedMetadata,
|
||||
S::Input: HasTargetBytes,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &S::Input,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>,
|
||||
{
|
||||
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
|
||||
.expect("QemuSystemStateObserver not found");
|
||||
let feedbackstate = match state
|
||||
.named_metadata_map_mut()
|
||||
.get_mut::<SysGraphFeedbackState>("SysMap") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
let n=SysGraphFeedbackState::default();
|
||||
state.named_metadata_map_mut().insert(n, "SysMap");
|
||||
state.named_metadata_map_mut().get_mut::<SysGraphFeedbackState>("SysMap").unwrap()
|
||||
}
|
||||
};
|
||||
let ret = feedbackstate.update(&observer.last_run, &observer.last_input);
|
||||
self.last_trace = Some(ret.1);
|
||||
Ok(ret.0)
|
||||
}
|
||||
|
||||
/// Append to the testcase the generated metadata in case of a new corpus item
|
||||
#[inline]
|
||||
fn append_metadata<OT>(&mut self, _state: &mut S, _observers: &OT, testcase: &mut Testcase<S::Input>) -> Result<(), Error> {
|
||||
let a = self.last_trace.take();
|
||||
match a {
|
||||
Some(s) => testcase.metadata_map_mut().insert(SysGraphMetadata::new(s)),
|
||||
None => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Discard the stored metadata in case that the testcase is not added to the corpus
|
||||
#[inline]
|
||||
fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
|
||||
self.last_trace = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Named for SysMapFeedback
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
//============================= Mutators
|
||||
//=============================== Snippets
|
||||
// pub struct RandGraphSnippetMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// phantom: PhantomData<(I, S)>,
|
||||
// }
|
||||
// impl<I, S> RandGraphSnippetMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// pub fn new() -> Self {
|
||||
// RandGraphSnippetMutator{phantom: PhantomData}
|
||||
// }
|
||||
// }
|
||||
// impl<I, S> Mutator<I, S> for RandGraphSnippetMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// fn mutate(
|
||||
// &mut self,
|
||||
// state: &mut S,
|
||||
// input: &mut I,
|
||||
// _stage_idx: i32
|
||||
// ) -> Result<MutationResult, Error>
|
||||
// {
|
||||
// // need our own random generator, because borrowing rules
|
||||
// let mut myrand = StdRand::new();
|
||||
// let tmp = &mut state.rand_mut();
|
||||
// myrand.set_seed(tmp.next());
|
||||
// drop(tmp);
|
||||
|
||||
// let feedbackstate = state
|
||||
// .feedback_states()
|
||||
// .match_name::<SysGraphFeedbackState>("SysMap")
|
||||
// .unwrap();
|
||||
// let g = &feedbackstate.graph;
|
||||
// let tmp = state.metadata().get::<SysGraphMetadata>();
|
||||
// if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
|
||||
// return Ok(MutationResult::Skipped);
|
||||
// }
|
||||
// let trace =tmp.expect("SysGraphMetadata not found");
|
||||
// // follow the path, extract snippets from last reads, find common snippets.
|
||||
// // those are likley keys parts. choose random parts from other sibling traces
|
||||
// let sibling_inputs : Vec<&Vec<u8>>= g[*trace.inner.last().unwrap()].variants.iter().map(|x| &x.input).collect();
|
||||
// let mut snippet_collector = vec![];
|
||||
// let mut per_input_counters = HashMap::<&Vec<u8>,usize>::new(); // ugly workaround to track multiple inputs
|
||||
// for t in &trace.inner {
|
||||
// let node = &g[*t];
|
||||
// let mut per_node_snippets = HashMap::<&Vec<u8>,&[u8]>::new();
|
||||
// for v in &node.variants {
|
||||
// match per_input_counters.get_mut(&v.input) {
|
||||
// None => {
|
||||
// if sibling_inputs.iter().any(|x| *x==&v.input) { // only collect info about siblin inputs from target
|
||||
// per_input_counters.insert(&v.input, v.input_counter.try_into().unwrap());
|
||||
// }
|
||||
// },
|
||||
// Some(x) => {
|
||||
// let x_u = *x;
|
||||
// if x_u<v.input_counter as usize {
|
||||
// *x=v.input_counter as usize;
|
||||
// per_node_snippets.insert(&v.input,&v.input[x_u..v.input_counter as usize]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// snippet_collector.push(per_node_snippets);
|
||||
// }
|
||||
// let mut new_input : Vec<u8> = vec![];
|
||||
// for c in snippet_collector {
|
||||
// new_input.extend_from_slice(myrand.choose(c).1);
|
||||
// }
|
||||
// for i in new_input.iter().enumerate() {
|
||||
// input.bytes_mut()[i.0]=*i.1;
|
||||
// }
|
||||
|
||||
// Ok(MutationResult::Mutated)
|
||||
// }
|
||||
|
||||
// fn post_exec(
|
||||
// &mut self,
|
||||
// _state: &mut S,
|
||||
// _stage_idx: i32,
|
||||
// _corpus_idx: Option<usize>
|
||||
// ) -> Result<(), Error> {
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<I, S> Named for RandGraphSnippetMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// fn name(&self) -> &str {
|
||||
// "RandGraphSnippetMutator"
|
||||
// }
|
||||
// }
|
||||
// //=============================== Snippets
|
||||
// pub struct RandInputSnippetMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// phantom: PhantomData<(I, S)>,
|
||||
// }
|
||||
// impl<I, S> RandInputSnippetMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// pub fn new() -> Self {
|
||||
// RandInputSnippetMutator{phantom: PhantomData}
|
||||
// }
|
||||
// }
|
||||
// impl<I, S> Mutator<I, S> for RandInputSnippetMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// fn mutate(
|
||||
// &mut self,
|
||||
// state: &mut S,
|
||||
// input: &mut I,
|
||||
// _stage_idx: i32
|
||||
// ) -> Result<MutationResult, Error>
|
||||
// {
|
||||
// // need our own random generator, because borrowing rules
|
||||
// let mut myrand = StdRand::new();
|
||||
// let tmp = &mut state.rand_mut();
|
||||
// myrand.set_seed(tmp.next());
|
||||
// drop(tmp);
|
||||
|
||||
// let feedbackstate = state
|
||||
// .feedback_states()
|
||||
// .match_name::<SysGraphFeedbackState>("SysMap")
|
||||
// .unwrap();
|
||||
// let g = &feedbackstate.graph;
|
||||
// let tmp = state.metadata().get::<SysGraphMetadata>();
|
||||
// if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
|
||||
// return Ok(MutationResult::Skipped);
|
||||
// }
|
||||
// let trace = tmp.expect("SysGraphMetadata not found");
|
||||
|
||||
// let mut collection : Vec<Vec<u8>> = Vec::new();
|
||||
// let mut current_pointer : usize = 0;
|
||||
// for t in &trace.inner {
|
||||
// let node = &g[*t];
|
||||
// for v in &node.variants {
|
||||
// if v.input == input.bytes() {
|
||||
// if v.input_counter > current_pointer.try_into().unwrap() {
|
||||
// collection.push(v.input[current_pointer..v.input_counter as usize].to_owned());
|
||||
// current_pointer = v.input_counter as usize;
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// let index_to_mutate = myrand.below(collection.len() as u64) as usize;
|
||||
// for i in 0..collection[index_to_mutate].len() {
|
||||
// collection[index_to_mutate][i] = myrand.below(0xFF) as u8;
|
||||
// }
|
||||
// for i in collection.concat().iter().enumerate() {
|
||||
// input.bytes_mut()[i.0]=*i.1;
|
||||
// }
|
||||
|
||||
// Ok(MutationResult::Mutated)
|
||||
// }
|
||||
|
||||
// fn post_exec(
|
||||
// &mut self,
|
||||
// _state: &mut S,
|
||||
// _stage_idx: i32,
|
||||
// _corpus_idx: Option<usize>
|
||||
// ) -> Result<(), Error> {
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<I, S> Named for RandInputSnippetMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// fn name(&self) -> &str {
|
||||
// "RandInputSnippetMutator"
|
||||
// }
|
||||
// }
|
||||
// //=============================== Suffix
|
||||
// pub struct RandGraphSuffixMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// phantom: PhantomData<(I, S)>,
|
||||
// }
|
||||
// impl<I, S> RandGraphSuffixMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// pub fn new() -> Self {
|
||||
// RandGraphSuffixMutator{phantom: PhantomData}
|
||||
// }
|
||||
// }
|
||||
// impl<I, S> Mutator<I, S> for RandGraphSuffixMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// fn mutate(
|
||||
// &mut self,
|
||||
// state: &mut S,
|
||||
// input: &mut I,
|
||||
// _stage_idx: i32
|
||||
// ) -> Result<MutationResult, Error>
|
||||
// {
|
||||
// // need our own random generator, because borrowing rules
|
||||
// let mut myrand = StdRand::new();
|
||||
// let tmp = &mut state.rand_mut();
|
||||
// myrand.set_seed(tmp.next());
|
||||
// drop(tmp);
|
||||
|
||||
// let feedbackstate = state
|
||||
// .feedback_states()
|
||||
// .match_name::<SysGraphFeedbackState>("SysMap")
|
||||
// .unwrap();
|
||||
// let g = &feedbackstate.graph;
|
||||
// let tmp = state.metadata().get::<SysGraphMetadata>();
|
||||
// if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
|
||||
// return Ok(MutationResult::Skipped);
|
||||
// }
|
||||
// let trace =tmp.expect("SysGraphMetadata not found");
|
||||
// // follow the path, extract snippets from last reads, find common snippets.
|
||||
// // those are likley keys parts. choose random parts from other sibling traces
|
||||
// let inp_c_end = g[*trace.inner.last().unwrap()].base.input_counter;
|
||||
// let mut num_to_reverse = myrand.below(trace.inner.len().try_into().unwrap());
|
||||
// for t in trace.inner.iter().rev() {
|
||||
// let int_c_prefix = g[*t].base.input_counter;
|
||||
// if int_c_prefix < inp_c_end {
|
||||
// num_to_reverse-=1;
|
||||
// if num_to_reverse<=0 {
|
||||
// let mut new_input=input.bytes()[..(int_c_prefix as usize)].to_vec();
|
||||
// let mut ext : Vec<u8> = (int_c_prefix..inp_c_end).map(|_| myrand.next().to_le_bytes()).flatten().collect();
|
||||
// new_input.append(&mut ext);
|
||||
// for i in new_input.iter().enumerate() {
|
||||
// if input.bytes_mut().len()>i.0 {
|
||||
// input.bytes_mut()[i.0]=*i.1;
|
||||
// }
|
||||
// else { break };
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Ok(MutationResult::Mutated)
|
||||
// }
|
||||
|
||||
// fn post_exec(
|
||||
// &mut self,
|
||||
// _state: &mut S,
|
||||
// _stage_idx: i32,
|
||||
// _corpus_idx: Option<usize>
|
||||
// ) -> Result<(), Error> {
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<I, S> Named for RandGraphSuffixMutator<I, S>
|
||||
// where
|
||||
// I: Input + HasBytesVec,
|
||||
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
|
||||
// {
|
||||
// fn name(&self) -> &str {
|
||||
// "RandGraphSuffixMutator"
|
||||
// }
|
||||
// }
|
393
fuzzers/FRET/src/systemstate/helpers.rs
Normal file
393
fuzzers/FRET/src/systemstate/helpers.rs
Normal file
@ -0,0 +1,393 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::io::Write;
|
||||
use std::ops::Range;
|
||||
use hashbrown::HashMap;
|
||||
use libafl::prelude::ExitKind;
|
||||
use libafl::prelude::UsesInput;
|
||||
use libafl_qemu::read_user_reg_unchecked;
|
||||
use libafl_qemu::Emulator;
|
||||
use libafl_qemu::GuestAddr;
|
||||
use libafl_qemu::GuestPhysAddr;
|
||||
use libafl_qemu::GuestReg;
|
||||
use libafl_qemu::QemuHooks;
|
||||
use libafl_qemu::edges::QemuEdgesMapMetadata;
|
||||
use libafl_qemu::emu;
|
||||
use libafl_qemu::hooks;
|
||||
use libafl_qemu::Hook;
|
||||
// use crate::systemstate::extract_abbs_from_trace;
|
||||
use crate::systemstate::RawFreeRTOSSystemState;
|
||||
use crate::systemstate::CURRENT_SYSTEMSTATE_VEC;
|
||||
use crate::systemstate::NUM_PRIOS;
|
||||
use super::freertos::void_ptr;
|
||||
use super::freertos::TCB_t;
|
||||
use super::freertos::rtos_struct::List_Item_struct;
|
||||
use super::freertos::rtos_struct::*;
|
||||
use super::freertos;
|
||||
use super::CaptureEvent;
|
||||
|
||||
use libafl_qemu::{
|
||||
helper::{QemuHelper, QemuHelperTuple},
|
||||
// edges::SAVED_JUMP,
|
||||
};
|
||||
|
||||
//============================= Struct definitions
|
||||
|
||||
pub static mut INTR_OFFSET : Option<u64> = None;
|
||||
pub static mut INTR_DONE : bool = true;
|
||||
|
||||
// only used when inputs are injected
|
||||
pub static mut NEXT_INPUT : Vec<u8> = Vec::new();
|
||||
|
||||
//============================= API symbols
|
||||
|
||||
pub const ISR_SYMBOLS : &'static [&'static str] = &[
|
||||
// ISRs
|
||||
"Reset_Handler","Default_Handler","Default_Handler2","Default_Handler3","Default_Handler4","Default_Handler5","Default_Handler6","vPortSVCHandler","xPortPendSVHandler","xPortSysTickHandler","isr_starter"//,"vTaskGenericNotifyGiveFromISR"
|
||||
];
|
||||
|
||||
//============================= Qemu Helper
|
||||
|
||||
/// A Qemu Helper with reads FreeRTOS specific structs from Qemu whenever certain syscalls occur, also inject inputs
|
||||
#[derive(Debug)]
|
||||
pub struct QemuSystemStateHelper {
|
||||
// Address of API functions
|
||||
api_fn_addrs: HashMap<GuestAddr, String>,
|
||||
api_fn_ranges: Vec<(String, std::ops::Range<GuestAddr>)>,
|
||||
// Address of interrupt routines
|
||||
isr_addrs: HashMap<GuestAddr, String>,
|
||||
isr_ranges: Vec<(String, std::ops::Range<GuestAddr>)>,
|
||||
tcb_addr: GuestAddr,
|
||||
ready_queues: GuestAddr,
|
||||
delay_queue: GuestAddr,
|
||||
delay_queue_overflow: GuestAddr,
|
||||
scheduler_lock_addr: GuestAddr,
|
||||
scheduler_running_addr: GuestAddr,
|
||||
critical_addr: GuestAddr,
|
||||
input_counter: Option<GuestAddr>,
|
||||
app_range: Range<GuestAddr>,
|
||||
}
|
||||
|
||||
impl QemuSystemStateHelper {
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
api_fn_addrs: HashMap<GuestAddr, String>,
|
||||
api_fn_ranges: Vec<(String, std::ops::Range<GuestAddr>)>,
|
||||
isr_addrs: HashMap<GuestAddr, String>,
|
||||
isr_ranges: Vec<(String, std::ops::Range<GuestAddr>)>,
|
||||
tcb_addr: GuestAddr,
|
||||
ready_queues: GuestAddr,
|
||||
delay_queue: GuestAddr,
|
||||
delay_queue_overflow: GuestAddr,
|
||||
scheduler_lock_addr: GuestAddr,
|
||||
scheduler_running_addr: GuestAddr,
|
||||
critical_addr: GuestAddr,
|
||||
input_counter: Option<GuestAddr>,
|
||||
app_range: Range<GuestAddr>,
|
||||
) -> Self {
|
||||
QemuSystemStateHelper {
|
||||
api_fn_addrs,
|
||||
api_fn_ranges,
|
||||
isr_addrs,
|
||||
isr_ranges,
|
||||
tcb_addr: tcb_addr,
|
||||
ready_queues: ready_queues,
|
||||
delay_queue,
|
||||
delay_queue_overflow,
|
||||
scheduler_lock_addr,
|
||||
scheduler_running_addr,
|
||||
critical_addr,
|
||||
input_counter: input_counter,
|
||||
app_range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> QemuHelper<S> for QemuSystemStateHelper
|
||||
where
|
||||
S: UsesInput,
|
||||
{
|
||||
fn first_exec<QT>(&self, _hooks: &QemuHooks<QT, S>)
|
||||
where
|
||||
QT: QemuHelperTuple<S>,
|
||||
{
|
||||
// for wp in self.api_fn_addrs.keys() {
|
||||
// _hooks.instruction(*wp, Hook::Function(exec_syscall_hook::<QT, S>), false);
|
||||
// }
|
||||
for wp in self.isr_addrs.keys() {
|
||||
_hooks.instruction(*wp, Hook::Function(exec_isr_hook::<QT, S>), false);
|
||||
}
|
||||
_hooks.jmps(Hook::Function(gen_jmp_is_syscall::<QT, S>), Hook::Function(trace_jmp::<QT, S>));
|
||||
}
|
||||
|
||||
// TODO: refactor duplicate code
|
||||
fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {
|
||||
unsafe {
|
||||
CURRENT_SYSTEMSTATE_VEC.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn post_exec<OT>(&mut self, emulator: &Emulator, _input: &S::Input, _observers: &mut OT, _exit_kind: &mut ExitKind) {
|
||||
trigger_collection(emulator,(0, 0), CaptureEvent::End, self);
|
||||
unsafe {
|
||||
let c = emulator.cpu_from_index(0);
|
||||
let pc = c.read_reg::<i32, u32>(15).unwrap();
|
||||
CURRENT_SYSTEMSTATE_VEC[CURRENT_SYSTEMSTATE_VEC.len()-1].edge = (pc,0);
|
||||
CURRENT_SYSTEMSTATE_VEC[CURRENT_SYSTEMSTATE_VEC.len()-1].capture_point = (CaptureEvent::End,"Breakpoint".to_string());
|
||||
}
|
||||
// Find the first ISREnd of vPortSVCHandler and drop anything before
|
||||
unsafe {
|
||||
let mut index = 0;
|
||||
while index < CURRENT_SYSTEMSTATE_VEC.len() {
|
||||
if CaptureEvent::ISREnd == CURRENT_SYSTEMSTATE_VEC[index].capture_point.0 && CURRENT_SYSTEMSTATE_VEC[index].capture_point.1 == "xPortPendSVHandler" {
|
||||
break;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
CURRENT_SYSTEMSTATE_VEC.drain(..index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_freertos_list(systemstate : &mut RawFreeRTOSSystemState, emulator: &Emulator, target: GuestAddr) -> freertos::List_t {
|
||||
let read : freertos::List_t = freertos::emu_lookup::lookup(emulator, target);
|
||||
let listbytes : GuestAddr = GuestAddr::try_from(std::mem::size_of::<freertos::List_t>()).unwrap();
|
||||
|
||||
let mut next_index = read.pxIndex;
|
||||
for _j in 0..read.uxNumberOfItems {
|
||||
// always jump over the xListEnd marker
|
||||
if (target..target+listbytes).contains(&next_index) {
|
||||
let next_item : freertos::MiniListItem_t = freertos::emu_lookup::lookup(emulator, next_index);
|
||||
let new_next_index=next_item.pxNext;
|
||||
systemstate.dumping_ground.insert(next_index,List_MiniItem_struct(next_item));
|
||||
next_index = new_next_index;
|
||||
}
|
||||
let next_item : freertos::ListItem_t = freertos::emu_lookup::lookup(emulator, next_index);
|
||||
// println!("Item at {}: {:?}",next_index,next_item);
|
||||
if next_item.pvContainer != target {
|
||||
// the list is being modified, abort by setting the list empty
|
||||
eprintln!("Warning: attempted to read a list that is being modified");
|
||||
let mut read=read;
|
||||
read.uxNumberOfItems = 0;
|
||||
return read;
|
||||
}
|
||||
// assert_eq!(next_item.pvContainer,target);
|
||||
let new_next_index=next_item.pxNext;
|
||||
let next_tcb : TCB_t= freertos::emu_lookup::lookup(emulator,next_item.pvOwner);
|
||||
// println!("TCB at {}: {:?}",next_item.pvOwner,next_tcb);
|
||||
systemstate.dumping_ground.insert(next_item.pvOwner,TCB_struct(next_tcb.clone()));
|
||||
systemstate.dumping_ground.insert(next_index,List_Item_struct(next_item));
|
||||
next_index=new_next_index;
|
||||
}
|
||||
// Handle edge case where the end marker was not included yet
|
||||
if (target..target+listbytes).contains(&next_index) {
|
||||
let next_item : freertos::MiniListItem_t = freertos::emu_lookup::lookup(emulator, next_index);
|
||||
systemstate.dumping_ground.insert(next_index,List_MiniItem_struct(next_item));
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn trigger_collection(emulator: &Emulator, edge: (GuestAddr, GuestAddr), event: CaptureEvent, h: &QemuSystemStateHelper) {
|
||||
let listbytes : GuestAddr = GuestAddr::try_from(std::mem::size_of::<freertos::List_t>()).unwrap();
|
||||
let mut systemstate = RawFreeRTOSSystemState::default();
|
||||
|
||||
match event {
|
||||
CaptureEvent::APIStart => {
|
||||
let s = h.api_fn_addrs.get(&edge.1).unwrap();
|
||||
systemstate.capture_point=(CaptureEvent::APIStart, s.to_string());
|
||||
},
|
||||
CaptureEvent::APIEnd => {
|
||||
let s = h.api_fn_addrs.get(&edge.0).unwrap();
|
||||
systemstate.capture_point=(CaptureEvent::APIEnd, s.to_string());
|
||||
},
|
||||
CaptureEvent::ISRStart => {
|
||||
let s = h.isr_addrs.get(&edge.1).unwrap();
|
||||
systemstate.capture_point=(CaptureEvent::ISRStart, s.to_string());
|
||||
},
|
||||
CaptureEvent::ISREnd => {
|
||||
let s = h.isr_addrs.get(&edge.0).unwrap();
|
||||
systemstate.capture_point=(CaptureEvent::ISREnd, s.to_string());
|
||||
},
|
||||
CaptureEvent::End => {systemstate.capture_point=(CaptureEvent::End, "".to_string());},
|
||||
CaptureEvent::Undefined => (),
|
||||
}
|
||||
|
||||
if systemstate.capture_point.0 == CaptureEvent::Undefined {
|
||||
// println!("Not found: {:#x} {:#x}", edge.0.unwrap_or(0), edge.1.unwrap_or(0));
|
||||
}
|
||||
systemstate.edge = ((edge.0),(edge.1));
|
||||
|
||||
systemstate.qemu_tick = get_icount(emulator);
|
||||
|
||||
let mut buf : [u8; 4] = [0,0,0,0];
|
||||
match h.input_counter {
|
||||
Some(s) => unsafe { emulator.read_mem(s, &mut buf); },
|
||||
None => (),
|
||||
};
|
||||
systemstate.input_counter = GuestAddr::from_le_bytes(buf);
|
||||
|
||||
let curr_tcb_addr : freertos::void_ptr = freertos::emu_lookup::lookup(emulator, h.tcb_addr);
|
||||
if curr_tcb_addr == 0 {
|
||||
return;
|
||||
};
|
||||
|
||||
// println!("{:?}",std::str::from_utf8(¤t_tcb.pcTaskName));
|
||||
let critical : void_ptr = freertos::emu_lookup::lookup(emulator, h.critical_addr);
|
||||
let suspended : void_ptr = freertos::emu_lookup::lookup(emulator, h.scheduler_lock_addr);
|
||||
let running : void_ptr = freertos::emu_lookup::lookup(emulator, h.scheduler_running_addr);
|
||||
|
||||
systemstate.current_tcb = freertos::emu_lookup::lookup(emulator,curr_tcb_addr);
|
||||
// During ISRs it is only safe to extract structs if they are not currently being modified
|
||||
if systemstate.capture_point.0==CaptureEvent::APIStart || systemstate.capture_point.0==CaptureEvent::APIEnd || (critical == 0 && suspended == 0 ) {
|
||||
// Extract delay list
|
||||
let mut target : GuestAddr = h.delay_queue;
|
||||
target = freertos::emu_lookup::lookup(emulator, target);
|
||||
systemstate.delay_list = read_freertos_list(&mut systemstate, emulator, target);
|
||||
|
||||
// Extract delay list overflow
|
||||
let mut target : GuestAddr = h.delay_queue_overflow;
|
||||
target = freertos::emu_lookup::lookup(emulator, target);
|
||||
systemstate.delay_list_overflow = read_freertos_list(&mut systemstate, emulator, target);
|
||||
|
||||
// Extract priority lists
|
||||
for i in 0..NUM_PRIOS {
|
||||
let target : GuestAddr = listbytes*GuestAddr::try_from(i).unwrap()+h.ready_queues;
|
||||
systemstate.prio_ready_lists[i] = read_freertos_list(&mut systemstate, emulator, target);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
unsafe { CURRENT_SYSTEMSTATE_VEC.push(systemstate); }
|
||||
}
|
||||
|
||||
//============================= Trace interrupt service routines
|
||||
|
||||
pub fn exec_isr_hook<QT, S>(
|
||||
hooks: &mut QemuHooks<QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
pc: GuestAddr,
|
||||
)
|
||||
where
|
||||
S: UsesInput,
|
||||
QT: QemuHelperTuple<S>,
|
||||
{
|
||||
let emulator = hooks.emulator();
|
||||
let h = hooks.helpers().match_first_type::<QemuSystemStateHelper>().expect("QemuSystemHelper not found in helper tupel");
|
||||
let src = read_rec_return_stackframe(emulator, 0xfffffffc);
|
||||
trigger_collection(emulator, (src, pc), CaptureEvent::ISRStart, h);
|
||||
// println!("Exec ISR Call {:#x} {:#x} {}", src, pc, get_icount(emulator));
|
||||
}
|
||||
|
||||
//============================= Trace syscalls and returns
|
||||
|
||||
pub fn gen_jmp_is_syscall<QT, S>(
|
||||
hooks: &mut QemuHooks<QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
src: GuestAddr,
|
||||
dest: GuestAddr,
|
||||
) -> Option<u64>
|
||||
where
|
||||
S: UsesInput,
|
||||
QT: QemuHelperTuple<S>,
|
||||
{
|
||||
if let Some(h) = hooks.helpers().match_first_type::<QemuSystemStateHelper>() {
|
||||
if h.app_range.contains(&src) && !h.app_range.contains(&dest) && in_any_range(&h.isr_ranges,src).is_none() {
|
||||
if let Some(_) = in_any_range(&h.api_fn_ranges,dest) {
|
||||
// println!("New jmp {:x} {:x}", src, dest);
|
||||
// println!("API Call Edge {:x} {:x}", src, dest);
|
||||
return Some(1);
|
||||
// TODO: trigger collection right here
|
||||
// otherwise there can be a race-condition, where LAST_API_CALL is set before the api starts, if the interrupt handler calls an api function, it will misidentify the callsite of that api call
|
||||
}
|
||||
} else if dest == 0 { // !h.app_range.contains(&src) &&
|
||||
if let Some(_) = in_any_range(&h.api_fn_ranges, src) {
|
||||
// println!("API Return Edge {:#x}", src);
|
||||
return Some(2);
|
||||
}
|
||||
if let Some(_) = in_any_range(&h.isr_ranges, src) {
|
||||
// println!("ISR Return Edge {:#x}", src);
|
||||
return Some(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn trace_jmp<QT, S>(
|
||||
hooks: &mut QemuHooks<QT, S>,
|
||||
_state: Option<&mut S>,
|
||||
src: GuestAddr, mut dest: GuestAddr, id: u64
|
||||
)
|
||||
where
|
||||
S: UsesInput,
|
||||
QT: QemuHelperTuple<S>,
|
||||
{
|
||||
let h = hooks.helpers().match_first_type::<QemuSystemStateHelper>().expect("QemuSystemHelper not found in helper tupel");
|
||||
let emulator = hooks.emulator();
|
||||
if id == 1 { // API call
|
||||
trigger_collection(emulator, (src, dest), CaptureEvent::APIStart, h);
|
||||
// println!("Exec API Call {:#x} {:#x} {}", src, dest, get_icount(emulator));
|
||||
} else if id == 2 { // API return
|
||||
// Ignore returns to other APIs or ISRs. We only account for the first call depth of API calls from user space.
|
||||
if in_any_range(&h.api_fn_ranges, dest).is_none() && in_any_range(&h.isr_ranges, dest).is_none() {
|
||||
|
||||
let mut edge = (0, 0);
|
||||
edge.0=in_any_range(&h.api_fn_ranges, src).unwrap().start;
|
||||
edge.1=dest;
|
||||
|
||||
trigger_collection(emulator, edge, CaptureEvent::APIEnd, h);
|
||||
// println!("Exec API Return Edge {:#x} {:#x} {}", src, dest, get_icount(emulator));
|
||||
}
|
||||
} else if id == 3 { // ISR return
|
||||
dest = read_rec_return_stackframe(emulator, dest);
|
||||
|
||||
let mut edge = (0, 0);
|
||||
edge.0=in_any_range(&h.isr_ranges, src).unwrap().start;
|
||||
edge.1=dest;
|
||||
|
||||
trigger_collection(emulator, edge, CaptureEvent::ISREnd, h);
|
||||
// println!("Exec ISR Return Edge {:#x} {:#x} {}", src, dest, get_icount(emulator));
|
||||
}
|
||||
}
|
||||
|
||||
//============================= Utility functions
|
||||
|
||||
fn get_icount(emulator : &Emulator) -> u64 {
|
||||
unsafe {
|
||||
// TODO: investigate why can_do_io is not set sometimes, as this is just a workaround
|
||||
let c = emulator.cpu_from_index(0);
|
||||
let can_do_io = (*c.raw_ptr()).neg.can_do_io;
|
||||
(*c.raw_ptr()).neg.can_do_io = true;
|
||||
let r = emu::icount_get_raw();
|
||||
(*c.raw_ptr()).neg.can_do_io = can_do_io;
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
fn read_rec_return_stackframe(emu : &Emulator, lr : GuestAddr) -> GuestAddr {
|
||||
let lr_ = lr & u32::MAX-1;
|
||||
if lr_ == 0xfffffffc || lr_ == 0xFFFFFFF8 || lr_ == 0xFFFFFFF0 {
|
||||
unsafe {
|
||||
// if 0xFFFFFFF0/1 0xFFFFFFF8/9 -> "main stack" MSP
|
||||
let mut buf = [0u8; 4];
|
||||
let sp : GuestAddr = if lr_ == 0xfffffffc || lr_ == 0xFFFFFFF0 { // PSP
|
||||
read_user_reg_unchecked(emu) as u32
|
||||
} else {
|
||||
emu.read_reg(13).unwrap()
|
||||
};
|
||||
let ret_pc = sp+0x18; // https://developer.arm.com/documentation/dui0552/a/the-cortex-m3-processor/exception-model/exception-entry-and-return
|
||||
emu.read_mem(ret_pc, buf.as_mut_slice());
|
||||
return u32::from_le_bytes(buf);
|
||||
// elseif 0xfffffffc/d
|
||||
}} else {
|
||||
return lr;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn in_any_range<'a>(ranges: &'a Vec<(String, Range<u32>)>, addr : GuestAddr) -> Option<&'a std::ops::Range<GuestAddr>> {
|
||||
for (_,r) in ranges {
|
||||
if r.contains(&addr) {return Some(r);}
|
||||
}
|
||||
return None;
|
||||
}
|
355
fuzzers/FRET/src/systemstate/mod.rs
Normal file
355
fuzzers/FRET/src/systemstate/mod.rs
Normal file
@ -0,0 +1,355 @@
|
||||
//! systemstate referes to the State of a FreeRTOS fuzzing target
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fmt;
|
||||
use hashbrown::HashSet;
|
||||
use libafl_bolts::HasRefCnt;
|
||||
use libafl_bolts::AsSlice;
|
||||
use libafl_qemu::GuestAddr;
|
||||
use std::hash::Hasher;
|
||||
use std::hash::Hash;
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::rc::Rc;
|
||||
|
||||
use freertos::TCB_t;
|
||||
|
||||
pub mod freertos;
|
||||
pub mod helpers;
|
||||
pub mod observers;
|
||||
pub mod feedbacks;
|
||||
pub mod graph;
|
||||
pub mod schedulers;
|
||||
pub mod stg;
|
||||
|
||||
// Constants
|
||||
const NUM_PRIOS: usize = 5;
|
||||
|
||||
//============================= Struct definitions
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum CaptureEvent {
|
||||
APIStart, /// src,dst
|
||||
APIEnd, /// src,dst
|
||||
ISRStart, /// _,dst
|
||||
ISREnd, /// src,_
|
||||
End, /// src,_
|
||||
#[default]
|
||||
Undefined,
|
||||
}
|
||||
|
||||
|
||||
/// Raw info Dump from Qemu
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RawFreeRTOSSystemState {
|
||||
qemu_tick: u64,
|
||||
current_tcb: TCB_t,
|
||||
prio_ready_lists: [freertos::List_t; NUM_PRIOS],
|
||||
delay_list: freertos::List_t,
|
||||
delay_list_overflow: freertos::List_t,
|
||||
dumping_ground: HashMap<u32,freertos::rtos_struct>,
|
||||
input_counter: u32,
|
||||
edge: (GuestAddr,GuestAddr),
|
||||
capture_point: (CaptureEvent,String)
|
||||
}
|
||||
/// List of system state dumps from QemuHelpers
|
||||
static mut CURRENT_SYSTEMSTATE_VEC: Vec<RawFreeRTOSSystemState> = vec![];
|
||||
|
||||
/// A reduced version of freertos::TCB_t
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct RefinedTCB {
|
||||
pub task_name: String,
|
||||
pub priority: u32,
|
||||
pub base_priority: u32,
|
||||
mutexes_held: u32,
|
||||
// notify_value: u32,
|
||||
notify_state: u8,
|
||||
}
|
||||
|
||||
impl PartialEq for RefinedTCB {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let ret = self.task_name == other.task_name &&
|
||||
self.priority == other.priority &&
|
||||
self.base_priority == other.base_priority;
|
||||
#[cfg(feature = "do_hash_notify_state")]
|
||||
let ret = ret && self.notify_state == other.notify_state;
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for RefinedTCB {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.task_name.hash(state);
|
||||
self.priority.hash(state);
|
||||
self.mutexes_held.hash(state);
|
||||
#[cfg(feature = "do_hash_notify_state")]
|
||||
self.notify_state.hash(state);
|
||||
// self.notify_value.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl RefinedTCB {
|
||||
pub fn from_tcb(input: &TCB_t) -> Self {
|
||||
unsafe {
|
||||
let tmp = std::mem::transmute::<[i8; 10],[u8; 10]>(input.pcTaskName);
|
||||
let name : String = std::str::from_utf8(&tmp).expect("TCB name was not utf8").chars().filter(|x| *x != '\0').collect::<String>();
|
||||
Self {
|
||||
task_name: name,
|
||||
priority: input.uxPriority,
|
||||
base_priority: input.uxBasePriority,
|
||||
mutexes_held: input.uxMutexesHeld,
|
||||
// notify_value: input.ulNotifiedValue[0],
|
||||
notify_state: input.ucNotifyState[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn from_tcb_owned(input: TCB_t) -> Self {
|
||||
unsafe {
|
||||
let tmp = std::mem::transmute::<[i8; 10],[u8; 10]>(input.pcTaskName);
|
||||
let name : String = std::str::from_utf8(&tmp).expect("TCB name was not utf8").chars().filter(|x| *x != '\0').collect::<String>();
|
||||
Self {
|
||||
task_name: name,
|
||||
priority: input.uxPriority,
|
||||
base_priority: input.uxBasePriority,
|
||||
mutexes_held: input.uxMutexesHeld,
|
||||
// notify_value: input.ulNotifiedValue[0],
|
||||
notify_state: input.ucNotifyState[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reduced information about a systems state, without any execution context
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct ReducedFreeRTOSSystemState {
|
||||
// pub tick: u64,
|
||||
pub current_task: RefinedTCB,
|
||||
ready_list_after: Vec<RefinedTCB>,
|
||||
delay_list_after: Vec<RefinedTCB>,
|
||||
// edge: (Option<GuestAddr>,Option<GuestAddr>),
|
||||
// pub capture_point: (CaptureEvent,String),
|
||||
// input_counter: u32
|
||||
}
|
||||
impl PartialEq for ReducedFreeRTOSSystemState {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.current_task == other.current_task && self.ready_list_after == other.ready_list_after &&
|
||||
self.delay_list_after == other.delay_list_after
|
||||
// && self.edge == other.edge
|
||||
// && self.capture_point == other.capture_point
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for ReducedFreeRTOSSystemState {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.current_task.hash(state);
|
||||
self.ready_list_after.hash(state);
|
||||
self.delay_list_after.hash(state);
|
||||
}
|
||||
}
|
||||
impl ReducedFreeRTOSSystemState {
|
||||
// fn get_tick(&self) -> u64 {
|
||||
// self.tick
|
||||
// }
|
||||
|
||||
pub fn print_lists(&self) -> String {
|
||||
let mut ret = String::from("+");
|
||||
for j in self.ready_list_after.iter() {
|
||||
ret.push_str(format!(" {}", j.task_name).as_str());
|
||||
}
|
||||
ret.push_str("\n-");
|
||||
for j in self.delay_list_after.iter() {
|
||||
ret.push_str(format!(" {}", j.task_name).as_str());
|
||||
}
|
||||
ret
|
||||
}
|
||||
pub fn get_hash(&self) -> u64 {
|
||||
let mut h = DefaultHasher::new();
|
||||
self.hash(&mut h);
|
||||
h.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||
// pub enum ExecLevel {
|
||||
// APP = 0,
|
||||
// API = 1,
|
||||
// ISR = 2,
|
||||
// }
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ExecInterval {
|
||||
pub start_tick: u64,
|
||||
pub end_tick: u64,
|
||||
pub start_state: u64,
|
||||
pub end_state: u64,
|
||||
pub start_capture: (CaptureEvent, String),
|
||||
pub end_capture: (CaptureEvent, String),
|
||||
pub level: u8,
|
||||
tick_spend_preempted: u64,
|
||||
pub abb: Option<AtomicBasicBlock>
|
||||
}
|
||||
|
||||
impl ExecInterval {
|
||||
pub fn get_exec_time(&self) -> u64 {
|
||||
self.end_tick-self.start_tick-self.tick_spend_preempted
|
||||
}
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.start_tick != 0 || self.end_tick != 0
|
||||
}
|
||||
pub fn invaildate(&mut self) {
|
||||
self.start_tick = 0;
|
||||
self.end_tick = 0;
|
||||
}
|
||||
|
||||
/// Attach this interval to the later one, keep a record of the time spend preempted
|
||||
pub fn try_unite_with_later_interval(&mut self, later_interval : &mut Self) -> bool {
|
||||
if self.end_state!=later_interval.start_state || self.abb!=later_interval.abb || !self.is_valid() || !later_interval.is_valid() {
|
||||
return false;
|
||||
}
|
||||
// assert_eq!(self.end_state, later_interval.start_state);
|
||||
// assert_eq!(self.abb, later_interval.abb);
|
||||
later_interval.tick_spend_preempted += self.tick_spend_preempted + (later_interval.start_tick-self.end_tick);
|
||||
later_interval.start_tick = self.start_tick;
|
||||
later_interval.start_state = self.start_state;
|
||||
self.invaildate();
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn get_hash_index(&self) -> (u64, u64) {
|
||||
return (self.start_state, self.abb.as_ref().expect("ABB not set").get_hash())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper around Vec<RefinedFreeRTOSSystemState> to attach as Metadata
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct FreeRTOSSystemStateMetadata {
|
||||
pub inner: Vec<ReducedFreeRTOSSystemState>,
|
||||
trace_length: usize,
|
||||
indices: Vec<usize>, // Hashed enumeration of States
|
||||
tcref: isize,
|
||||
}
|
||||
impl FreeRTOSSystemStateMetadata {
|
||||
pub fn new(inner: Vec<ReducedFreeRTOSSystemState>) -> Self{
|
||||
let tmp = inner.iter().enumerate().map(|x| compute_hash(x) as usize).collect();
|
||||
Self {trace_length: inner.len(), inner: inner, indices: tmp, tcref: 0}
|
||||
}
|
||||
}
|
||||
pub fn compute_hash<T>(obj: T) -> u64
|
||||
where
|
||||
T: Hash
|
||||
{
|
||||
let mut s = DefaultHasher::new();
|
||||
obj.hash(&mut s);
|
||||
s.finish()
|
||||
}
|
||||
|
||||
impl AsSlice for FreeRTOSSystemStateMetadata {
|
||||
/// Convert the slice of system-states to a slice of hashes over enumerated states
|
||||
fn as_slice(&self) -> &[usize] {
|
||||
self.indices.as_slice()
|
||||
}
|
||||
|
||||
type Entry = usize;
|
||||
}
|
||||
|
||||
impl HasRefCnt for FreeRTOSSystemStateMetadata {
|
||||
fn refcnt(&self) -> isize {
|
||||
self.tcref
|
||||
}
|
||||
|
||||
fn refcnt_mut(&mut self) -> &mut isize {
|
||||
&mut self.tcref
|
||||
}
|
||||
}
|
||||
|
||||
libafl_bolts::impl_serdeany!(FreeRTOSSystemStateMetadata);
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct AtomicBasicBlock {
|
||||
start: GuestAddr,
|
||||
ends: HashSet<GuestAddr>,
|
||||
level: u8,
|
||||
}
|
||||
|
||||
impl Hash for AtomicBasicBlock {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
// Use a combination of the start address and the set of ending addresses to compute the hash value
|
||||
self.start.hash(state);
|
||||
let mut keys : Vec<_> = self.ends.iter().collect();
|
||||
keys.sort();
|
||||
self.level.hash(state);
|
||||
keys.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AtomicBasicBlock {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut ends_str = String::new();
|
||||
for end in &self.ends {
|
||||
ends_str.push_str(&format!("0x{:#x}, ", end));
|
||||
}
|
||||
write!(f, "ABB {{ level: {}, start: 0x{:#x}, ends: [{}]}}", self.level, self.start, ends_str.trim().trim_matches(','))
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for AtomicBasicBlock {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut ends_str = String::new();
|
||||
for end in &self.ends {
|
||||
ends_str.push_str(&format!("{:#x}, ", end));
|
||||
}
|
||||
write!(f, "ABB {{ level: {}, start: {:#x}, ends: [{}]}}", self.level, self.start, ends_str.trim().trim_matches(','))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for AtomicBasicBlock {
|
||||
fn partial_cmp(&self, other: &AtomicBasicBlock) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for AtomicBasicBlock {
|
||||
fn cmp(&self, other: &AtomicBasicBlock) -> std::cmp::Ordering {
|
||||
if self.start.cmp(&other.start) == std::cmp::Ordering::Equal {
|
||||
if self.level.cmp(&other.level) != std::cmp::Ordering::Equal {
|
||||
return self.level.cmp(&other.level);
|
||||
}
|
||||
// If the start addresses are equal, compare by 'ends'
|
||||
let end1 = if self.ends.len() == 1 { *self.ends.iter().next().unwrap() as u64 } else {
|
||||
let mut temp = self.ends.iter().collect::<Vec<_>>().into_iter().collect::<Vec<&GuestAddr>>();
|
||||
temp.sort_unstable();
|
||||
let mut h = DefaultHasher::new();
|
||||
temp.hash(&mut h);
|
||||
h.finish()
|
||||
};
|
||||
let end2 = if other.ends.len() == 1 { *self.ends.iter().next().unwrap() as u64 } else {
|
||||
let mut temp = other.ends.iter().collect::<Vec<_>>().into_iter().collect::<Vec<&GuestAddr>>();
|
||||
temp.sort_unstable();
|
||||
let mut h = DefaultHasher::new();
|
||||
temp.hash(&mut h);
|
||||
h.finish()
|
||||
};
|
||||
end1.cmp(&end2)
|
||||
} else {
|
||||
// If the start addresses are not equal, compare by 'start'
|
||||
self.start.cmp(&other.start)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AtomicBasicBlock {
|
||||
pub fn get_hash(&self) -> u64 {
|
||||
let mut s = DefaultHasher::new();
|
||||
self.hash(&mut s);
|
||||
s.finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn get_task_names(trace: &Vec<ReducedFreeRTOSSystemState>) -> HashSet<String> {
|
||||
let mut ret: HashSet<_, _> = HashSet::new();
|
||||
for state in trace {
|
||||
ret.insert(state.current_task.task_name.to_string());
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
libafl_bolts::impl_serdeany!(AtomicBasicBlock);
|
388
fuzzers/FRET/src/systemstate/observers.rs
Normal file
388
fuzzers/FRET/src/systemstate/observers.rs
Normal file
@ -0,0 +1,388 @@
|
||||
// use crate::systemstate::IRQ_INPUT_BYTES_NUMBER;
|
||||
use libafl::prelude::ExitKind;
|
||||
use libafl::{inputs::HasTargetBytes, prelude::UsesInput};
|
||||
use libafl_bolts::HasLen;
|
||||
use libafl_bolts::Named;
|
||||
use libafl_bolts::AsSlice;
|
||||
use libafl::Error;
|
||||
use libafl::observers::Observer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use crate::systemstate::CaptureEvent;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::{ AtomicBasicBlock, ExecInterval};
|
||||
use super::{
|
||||
CURRENT_SYSTEMSTATE_VEC,
|
||||
RawFreeRTOSSystemState,
|
||||
RefinedTCB,
|
||||
ReducedFreeRTOSSystemState,
|
||||
freertos::{List_t, TCB_t, rtos_struct, rtos_struct::*},
|
||||
};
|
||||
|
||||
//============================= Observer
|
||||
|
||||
/// The Qemusystemstate Observer retrieves the systemstate
|
||||
/// that will get updated by the target.
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
#[allow(clippy::unsafe_derive_deserialize)]
|
||||
pub struct QemuSystemStateObserver
|
||||
{
|
||||
pub last_run: Vec<ReducedFreeRTOSSystemState>,
|
||||
pub last_states: HashMap<u64, ReducedFreeRTOSSystemState>,
|
||||
pub last_trace: Vec<ExecInterval>,
|
||||
pub last_input: Vec<u8>,
|
||||
pub success: bool,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl<S> Observer<S> for QemuSystemStateObserver
|
||||
where
|
||||
S: UsesInput,
|
||||
S::Input : HasTargetBytes,
|
||||
{
|
||||
#[inline]
|
||||
fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
|
||||
unsafe {CURRENT_SYSTEMSTATE_VEC.clear(); }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn post_exec(&mut self, _state: &mut S, _input: &S::Input, _exit_kind: &ExitKind) -> Result<(), Error> {
|
||||
// unsafe {self.last_run = invalidate_ineffective_isr(refine_system_states(&mut CURRENT_SYSTEMSTATE_VEC));}
|
||||
unsafe {
|
||||
let mut temp = refine_system_states(&mut CURRENT_SYSTEMSTATE_VEC);
|
||||
// fix_broken_trace(&mut temp.1);
|
||||
self.last_run = temp.0.clone();
|
||||
// println!("{:?}",temp);
|
||||
let mut temp = states2intervals(temp.0, temp.1);
|
||||
self.last_trace = temp.0;
|
||||
self.last_states = temp.1;
|
||||
self.success = temp.2;
|
||||
// println!("{:?}",temp);
|
||||
}
|
||||
// let abbs = extract_abbs_from_trace(&self.last_run);
|
||||
// println!("{:?}",abbs);
|
||||
// let abbs = trace_to_state_abb(&self.last_run);
|
||||
// println!("{:?}",abbs);
|
||||
self.last_input=_input.target_bytes().as_slice().to_owned();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for QemuSystemStateObserver
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasLen for QemuSystemStateObserver
|
||||
{
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.last_run.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl QemuSystemStateObserver {
|
||||
pub fn new() -> Self {
|
||||
Self{last_run: vec![], last_trace: vec![], last_input: vec![], name: "systemstate".to_string(), last_states: HashMap::new(), success: false }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//============================= Parsing helpers
|
||||
|
||||
/// Parse a List_t containing TCB_t into Vec<TCB_t> from cache. Consumes the elements from cache
|
||||
fn tcb_list_to_vec_cached(list: List_t, dump: &mut HashMap<u32,rtos_struct>) -> Vec<TCB_t>
|
||||
{
|
||||
let mut ret : Vec<TCB_t> = Vec::new();
|
||||
if list.uxNumberOfItems == 0 {return ret;}
|
||||
let last_list_item = match dump.remove(&list.pxIndex).expect("List_t entry was not in Hashmap") {
|
||||
List_Item_struct(li) => li,
|
||||
List_MiniItem_struct(mli) => match dump.remove(&mli.pxNext).expect("MiniListItem pointer invaild") {
|
||||
List_Item_struct(li) => li,
|
||||
_ => panic!("MiniListItem of a non empty List does not point to ListItem"),
|
||||
},
|
||||
_ => panic!("List_t entry was not a ListItem"),
|
||||
};
|
||||
let mut next_index = last_list_item.pxNext;
|
||||
let last_tcb = match dump.remove(&last_list_item.pvOwner).expect("ListItem Owner not in Hashmap") {
|
||||
TCB_struct(t) => t,
|
||||
_ => panic!("List content does not equal type"),
|
||||
};
|
||||
for _ in 0..list.uxNumberOfItems-1 {
|
||||
let next_list_item = match dump.remove(&next_index).expect("List_t entry was not in Hashmap") {
|
||||
List_Item_struct(li) => li,
|
||||
List_MiniItem_struct(mli) => match dump.remove(&mli.pxNext).expect("MiniListItem pointer invaild") {
|
||||
List_Item_struct(li) => li,
|
||||
_ => panic!("MiniListItem of a non empty List does not point to ListItem"),
|
||||
},
|
||||
_ => panic!("List_t entry was not a ListItem"),
|
||||
};
|
||||
match dump.remove(&next_list_item.pvOwner).expect("ListItem Owner not in Hashmap") {
|
||||
TCB_struct(t) => {ret.push(t)},
|
||||
_ => panic!("List content does not equal type"),
|
||||
}
|
||||
next_index=next_list_item.pxNext;
|
||||
}
|
||||
ret.push(last_tcb);
|
||||
ret
|
||||
}
|
||||
/// Drains a List of raw SystemStates to produce a refined trace
|
||||
fn refine_system_states(input: &mut Vec<RawFreeRTOSSystemState>) -> (Vec<ReducedFreeRTOSSystemState>, Vec<(u64, CaptureEvent, String, (u32, u32))>) {
|
||||
let mut ret = (Vec::<_>::new(), Vec::<_>::new());
|
||||
for mut i in input.drain(..) {
|
||||
let cur = RefinedTCB::from_tcb_owned(i.current_tcb);
|
||||
// println!("Refine: {} {:?} {:?} {:x}-{:x}", cur.task_name, i.capture_point.0, i.capture_point.1.to_string(), i.edge.0, i.edge.1);
|
||||
// collect ready list
|
||||
let mut collector = Vec::<RefinedTCB>::new();
|
||||
for j in i.prio_ready_lists.into_iter().rev() {
|
||||
let mut tmp = tcb_list_to_vec_cached(j,&mut i.dumping_ground).iter().map(|x| RefinedTCB::from_tcb(x)).collect();
|
||||
collector.append(&mut tmp);
|
||||
}
|
||||
// collect delay list
|
||||
let mut delay_list : Vec::<RefinedTCB> = tcb_list_to_vec_cached(i.delay_list, &mut i.dumping_ground).iter().map(|x| RefinedTCB::from_tcb(x)).collect();
|
||||
let mut delay_list_overflow : Vec::<RefinedTCB> = tcb_list_to_vec_cached(i.delay_list_overflow, &mut i.dumping_ground).iter().map(|x| RefinedTCB::from_tcb(x)).collect();
|
||||
delay_list.append(&mut delay_list_overflow);
|
||||
delay_list.sort_by(|a,b| a.task_name.cmp(&b.task_name));
|
||||
|
||||
ret.0.push(ReducedFreeRTOSSystemState {
|
||||
current_task: cur,
|
||||
ready_list_after: collector,
|
||||
delay_list_after: delay_list,
|
||||
// input_counter: i.input_counter,//+IRQ_INPUT_BYTES_NUMBER,
|
||||
});
|
||||
ret.1.push((i.qemu_tick, i.capture_point.0, i.capture_point.1.to_string(), i.edge));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Transform the states and metadata into a list of ExecIntervals
|
||||
fn states2intervals(trace: Vec<ReducedFreeRTOSSystemState>, meta: Vec<(u64, CaptureEvent, String, (u32, u32))>) -> (Vec<ExecInterval>, HashMap<u64, ReducedFreeRTOSSystemState>, bool) {
|
||||
if trace.len() == 0 {return (Vec::new(), HashMap::new(), true);}
|
||||
let mut isr_stack : VecDeque<u8> = VecDeque::from([]); // 2+ = ISR, 1 = systemcall, 0 = APP. Trace starts with an ISREnd and executes the app
|
||||
|
||||
|
||||
let mut level_of_task : HashMap<&str, u8> = HashMap::new();
|
||||
|
||||
let mut ret: Vec<ExecInterval> = vec![];
|
||||
let mut edges: Vec<(u32, u32)> = vec![];
|
||||
let mut last_hash : u64 = trace[0].get_hash();
|
||||
let mut table : HashMap<u64, ReducedFreeRTOSSystemState> = HashMap::new();
|
||||
table.insert(last_hash, trace[0].clone());
|
||||
for i in 0..trace.len()-1 {
|
||||
let curr_name = trace[i].current_task.task_name.as_str();
|
||||
let level = match meta[i].1 {
|
||||
CaptureEvent::APIEnd => { // API end always exits towards the app
|
||||
if !level_of_task.contains_key(curr_name) {
|
||||
level_of_task.insert(curr_name, 0);
|
||||
}
|
||||
*level_of_task.get_mut(curr_name).unwrap()=0;
|
||||
0
|
||||
},
|
||||
CaptureEvent::APIStart => { // API start can only be called in the app
|
||||
if !level_of_task.contains_key(curr_name) { // Should not happen, apps start from an ISR End. Some input exibited this behavior for unknown reasons
|
||||
level_of_task.insert(curr_name, 0);
|
||||
}
|
||||
*level_of_task.get_mut(curr_name).unwrap()=1;
|
||||
1
|
||||
},
|
||||
CaptureEvent::ISREnd => {
|
||||
// special case where the next block is an app start
|
||||
if !level_of_task.contains_key(curr_name) {
|
||||
level_of_task.insert(curr_name, 0);
|
||||
}
|
||||
// nested isr, TODO: Test level > 2
|
||||
if isr_stack.len() > 1 {
|
||||
isr_stack.pop_back().unwrap();
|
||||
*isr_stack.back().unwrap()
|
||||
} else {
|
||||
isr_stack.pop_back();
|
||||
// possibly go back to an api call that is still running for this task
|
||||
*level_of_task.get(curr_name).unwrap()
|
||||
}
|
||||
},
|
||||
CaptureEvent::ISRStart => {
|
||||
// special case for isrs which do not capture their end
|
||||
// if meta[i].2 == "isr_starter" {
|
||||
// &2
|
||||
// } else {
|
||||
// regular case
|
||||
if isr_stack.len() > 0 {
|
||||
let l = *isr_stack.back().unwrap();
|
||||
isr_stack.push_back(l+1);
|
||||
l+1
|
||||
} else {
|
||||
isr_stack.push_back(2);
|
||||
2
|
||||
}
|
||||
// }
|
||||
}
|
||||
_ => 100
|
||||
};
|
||||
// if trace[i].2 == CaptureEvent::End {break;}
|
||||
let next_hash=trace[i+1].get_hash();
|
||||
if !table.contains_key(&next_hash) {
|
||||
table.insert(next_hash, trace[i+1].clone());
|
||||
}
|
||||
ret.push(ExecInterval{
|
||||
start_tick: meta[i].0,
|
||||
end_tick: meta[i+1].0,
|
||||
start_state: last_hash,
|
||||
end_state: next_hash,
|
||||
start_capture: (meta[i].1, meta[i].2.clone()),
|
||||
end_capture: (meta[i+1].1, meta[i+1].2.clone()),
|
||||
level: level,
|
||||
tick_spend_preempted: 0,
|
||||
abb: None
|
||||
});
|
||||
last_hash = next_hash;
|
||||
edges.push((meta[i].3.1, meta[i+1].3.0));
|
||||
}
|
||||
let t = add_abb_info(&mut ret, &table, &edges);
|
||||
(ret, table, t)
|
||||
}
|
||||
|
||||
fn add_abb_info(trace: &mut Vec<ExecInterval>, table: &HashMap<u64, ReducedFreeRTOSSystemState>, edges: &Vec<(u32, u32)>) -> bool {
|
||||
let mut ret = true;
|
||||
let mut task_has_started : HashSet<String> = HashSet::new();
|
||||
let mut wip_abb_trace : Vec<Rc<RefCell<AtomicBasicBlock>>> = vec![];
|
||||
// let mut open_abb_at_this_task_or_level : HashMap<(u8,&str),usize> = HashMap::new();
|
||||
let mut open_abb_at_this_ret_addr_and_task : HashMap<(u32,&str),usize> = HashMap::new();
|
||||
|
||||
for i in 0..trace.len() {
|
||||
let curr_name = &table[&trace[i].start_state].current_task.task_name;
|
||||
// let last : Option<&usize> = last_abb_start_of_task.get(&curr_name);
|
||||
|
||||
// let open_abb = open_abb_at_this_task_or_level.get(&(trace[i].level, if trace[i].level<2 {&curr_name} else {""})).to_owned(); // apps/apis are differentiated by task name, isrs by nested level
|
||||
let open_abb = open_abb_at_this_ret_addr_and_task.get(&(edges[i].0, if trace[i].level<2 {&curr_name} else {""})).to_owned(); // apps/apis are differentiated by task name, isrs by nested level
|
||||
|
||||
// println!("Edge {:x}-{:x}", edges[i].0.unwrap_or(0xffff), edges[i].1.unwrap_or(0xffff));
|
||||
|
||||
match trace[i].start_capture.0 {
|
||||
// generic api abb start
|
||||
CaptureEvent::APIStart => {
|
||||
// assert_eq!(open_abb, None);
|
||||
ret &= open_abb.is_none();
|
||||
open_abb_at_this_ret_addr_and_task.insert((edges[i].1, if trace[i].level<2 {&curr_name} else {""}), i);
|
||||
wip_abb_trace.push(Rc::new(RefCell::new(AtomicBasicBlock{start: edges[i].0, ends: HashSet::new(), level: if trace[i].level<2 {trace[i].level} else {2}})));
|
||||
},
|
||||
// generic isr abb start
|
||||
CaptureEvent::ISRStart => {
|
||||
// assert_eq!(open_abb, None);
|
||||
ret &= open_abb.is_none();
|
||||
open_abb_at_this_ret_addr_and_task.insert((edges[i].1, if trace[i].level<2 {&curr_name} else {""}), i);
|
||||
wip_abb_trace.push(Rc::new(RefCell::new(AtomicBasicBlock{start: edges[i].0, ends: HashSet::new(), level: if trace[i].level<2 {trace[i].level} else {2}})));
|
||||
},
|
||||
// generic app abb start
|
||||
CaptureEvent::APIEnd => {
|
||||
// assert_eq!(open_abb, None);
|
||||
ret &= open_abb.is_none();
|
||||
open_abb_at_this_ret_addr_and_task.insert((edges[i].1, if trace[i].level<2 {&curr_name} else {""}), i);
|
||||
wip_abb_trace.push(Rc::new(RefCell::new(AtomicBasicBlock{start: edges[i].0, ends: HashSet::new(), level: if trace[i].level<2 {trace[i].level} else {2}})));
|
||||
},
|
||||
// generic continued blocks
|
||||
CaptureEvent::ISREnd => {
|
||||
// special case app abb start
|
||||
if trace[i].start_capture.1=="xPortPendSVHandler" && !task_has_started.contains(curr_name) {
|
||||
// assert_eq!(open_abb, None);
|
||||
ret &= open_abb.is_none();
|
||||
wip_abb_trace.push(Rc::new(RefCell::new(AtomicBasicBlock{start: 0, ends: HashSet::new(), level: if trace[i].level<2 {trace[i].level} else {2}})));
|
||||
open_abb_at_this_ret_addr_and_task.insert((edges[i].1, if trace[i].level<2 {&curr_name} else {""}), i);
|
||||
task_has_started.insert(curr_name.clone());
|
||||
} else {
|
||||
if let Some(last) = open_abb_at_this_ret_addr_and_task.get(&(edges[i].0, if trace[i].level<2 {&curr_name} else {""})) {
|
||||
let last = last.clone(); // required to drop immutable reference
|
||||
wip_abb_trace.push(wip_abb_trace[last].clone());
|
||||
// if the abb is interrupted again, it will need to continue at edge[i].1
|
||||
open_abb_at_this_ret_addr_and_task.remove(&(edges[i].0, if trace[i].level<2 {&curr_name} else {""}));
|
||||
open_abb_at_this_ret_addr_and_task.insert((edges[i].1, if trace[i].level<2 {&curr_name} else {""}), last); // order matters!
|
||||
} else {
|
||||
// panic!();
|
||||
// println!("Continued block with no start {} {} {:?} {:?} {:x}-{:x} {} {}", curr_name, trace[i].start_tick, trace[i].start_capture, trace[i].end_capture, edges[i].0, edges[i].1, task_has_started.contains(curr_name),trace[i].level);
|
||||
// println!("{:x?}", open_abb_at_this_ret_addr_and_task);
|
||||
ret = false;
|
||||
wip_abb_trace.push(Rc::new(RefCell::new(AtomicBasicBlock{start: edges[i].1, ends: HashSet::new(), level: if trace[i].level<2 {trace[i].level} else {2}})))
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => panic!("Undefined block start")
|
||||
}
|
||||
match trace[i].end_capture.0 {
|
||||
// generic app abb end
|
||||
CaptureEvent::APIStart => {
|
||||
let _t = &wip_abb_trace[i];
|
||||
RefCell::borrow_mut(&*wip_abb_trace[i]).ends.insert(edges[i].1);
|
||||
open_abb_at_this_ret_addr_and_task.remove(&(edges[i].1, if trace[i].level<2 {&curr_name} else {""}));
|
||||
},
|
||||
// generic api abb end
|
||||
CaptureEvent::APIEnd => {
|
||||
RefCell::borrow_mut(&*wip_abb_trace[i]).ends.insert(edges[i].1);
|
||||
open_abb_at_this_ret_addr_and_task.remove(&(edges[i].1, if trace[i].level<2 {&curr_name} else {""}));
|
||||
},
|
||||
// generic isr abb end
|
||||
CaptureEvent::ISREnd => {
|
||||
RefCell::borrow_mut(&*wip_abb_trace[i]).ends.insert(edges[i].1);
|
||||
open_abb_at_this_ret_addr_and_task.remove(&(edges[i].1, if trace[i].level<2 {&curr_name} else {""}));
|
||||
},
|
||||
// end anything
|
||||
CaptureEvent::End => {
|
||||
RefCell::borrow_mut(&*wip_abb_trace[i]).ends.insert(edges[i].1);
|
||||
open_abb_at_this_ret_addr_and_task.remove(&(edges[i].1, if trace[i].level<2 {&curr_name} else {""}));
|
||||
},
|
||||
CaptureEvent::ISRStart => (),
|
||||
_ => panic!("Undefined block end")
|
||||
}
|
||||
// println!("{} {} {:x}-{:x} {:x}-{:x} {:?} {:?} {}",curr_name, trace[i].level, edges[i].0, edges[i].1, ((*wip_abb_trace[i])).borrow().start, ((*wip_abb_trace[i])).borrow().ends.iter().next().unwrap_or(&0xffff), trace[i].start_capture, trace[i].end_capture, trace[i].start_tick);
|
||||
// println!("{:x?}", open_abb_at_this_ret_addr_and_task);
|
||||
}
|
||||
// drop(open_abb_at_this_task_or_level);
|
||||
|
||||
for i in 0..trace.len() {
|
||||
trace[i].abb = Some((*wip_abb_trace[i]).borrow().clone());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/// restore the isr/api begin/end invariant
|
||||
fn fix_broken_trace(meta: &mut Vec<(u64, CaptureEvent, String, (Option<u32>, Option<u32>))>) {
|
||||
for i in meta.iter_mut() {
|
||||
if i.1 == CaptureEvent::APIStart && i.2.ends_with("FromISR") {
|
||||
i.1 = CaptureEvent::ISREnd;
|
||||
i.2 = "isr_starter".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// invalidate subsequent intervals of equal states where an ISREnd follows an ISRStart. If the interrupt had no effect on the system we, are not interested.
|
||||
fn invalidate_ineffective_isr(trace: &mut Vec<ExecInterval>) {
|
||||
let mut i = 0;
|
||||
while i < trace.len() - 1 {
|
||||
if trace[i].is_valid() &&
|
||||
matches!(trace[i].start_capture.0, CaptureEvent::ISRStart) && matches!(trace[i].end_capture.0, CaptureEvent::ISREnd) &&
|
||||
trace[i].start_capture.1 == trace[i].end_capture.1 && trace[i].start_state == trace[i].end_state
|
||||
{
|
||||
trace[i].invaildate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// merge a sequence of intervals of the same state+abb. jump over all invalid blocks.
|
||||
fn merge_subsequent_abbs(trace: &mut Vec<ExecInterval>) {
|
||||
let mut i = 1;
|
||||
let mut lst_valid=0;
|
||||
while i < trace.len() - 1 {
|
||||
if trace[i].is_valid() {
|
||||
let mut temp = trace[i].clone();
|
||||
trace[lst_valid].try_unite_with_later_interval(&mut temp);
|
||||
trace[i] = temp;
|
||||
lst_valid = i;
|
||||
}
|
||||
}
|
||||
}
|
275
fuzzers/FRET/src/systemstate/schedulers.rs
Normal file
275
fuzzers/FRET/src/systemstate/schedulers.rs
Normal file
@ -0,0 +1,275 @@
|
||||
//! The Minimizer schedulers are a family of corpus schedulers that feed the fuzzer
|
||||
//! with testcases only from a subset of the total corpus.
|
||||
|
||||
use core::{marker::PhantomData};
|
||||
use std::{cmp::{max, min}, mem::swap, borrow::BorrowMut};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use libafl_bolts::{rands::Rand, serdeany::SerdeAny, AsSlice, HasRefCnt, HasLen};
|
||||
use libafl::{
|
||||
corpus::{Corpus, Testcase},
|
||||
inputs::UsesInput,
|
||||
schedulers::{Scheduler, TestcaseScore, minimizer::DEFAULT_SKIP_NON_FAVORED_PROB },
|
||||
state::{HasCorpus, HasMetadata, HasRand, UsesState, State},
|
||||
Error, SerdeAny, prelude::CorpusId,
|
||||
|
||||
};
|
||||
|
||||
use crate::worst::MaxTimeFavFactor;
|
||||
|
||||
use super::{stg::STGNodeMetadata, FreeRTOSSystemStateMetadata};
|
||||
|
||||
/// A state metadata holding a map of favoreds testcases for each map entry
|
||||
#[derive(Debug, Serialize, Deserialize, SerdeAny, Default)]
|
||||
pub struct LongestTracesMetadata {
|
||||
/// map index -> corpus index
|
||||
pub max_trace_length: usize,
|
||||
}
|
||||
|
||||
impl LongestTracesMetadata {
|
||||
fn new(l : usize) -> Self {
|
||||
Self {max_trace_length: l}
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`MinimizerScheduler`] employs a genetic algorithm to compute a subset of the
|
||||
/// corpus that exercise all the requested features (e.g. all the coverage seen so far)
|
||||
/// prioritizing [`Testcase`]`s` using [`TestcaseScore`]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LongestTraceScheduler<CS> {
|
||||
base: CS,
|
||||
skip_non_favored_prob: u64,
|
||||
}
|
||||
|
||||
impl<CS> UsesState for LongestTraceScheduler<CS>
|
||||
where
|
||||
CS: UsesState,
|
||||
{
|
||||
type State = CS::State;
|
||||
}
|
||||
|
||||
impl<CS> Scheduler for LongestTraceScheduler<CS>
|
||||
where
|
||||
CS: Scheduler,
|
||||
CS::State: HasCorpus + HasMetadata + HasRand,
|
||||
{
|
||||
/// Add an entry to the corpus and return its index
|
||||
fn on_add(&mut self, state: &mut CS::State, idx: CorpusId) -> Result<(), Error> {
|
||||
let l = state.corpus()
|
||||
.get(idx)?
|
||||
.borrow()
|
||||
.metadata_map()
|
||||
.get::<FreeRTOSSystemStateMetadata>().map_or(0, |x| x.trace_length);
|
||||
self.get_update_trace_length(state,l);
|
||||
self.base.on_add(state, idx)
|
||||
}
|
||||
|
||||
/// Replaces the testcase at the given idx
|
||||
// fn on_replace(
|
||||
// &mut self,
|
||||
// state: &mut CS::State,
|
||||
// idx: CorpusId,
|
||||
// testcase: &Testcase<<CS::State as UsesInput>::Input>,
|
||||
// ) -> Result<(), Error> {
|
||||
// let l = state.corpus()
|
||||
// .get(idx)?
|
||||
// .borrow()
|
||||
// .metadata()
|
||||
// .get::<FreeRTOSSystemStateMetadata>().map_or(0, |x| x.trace_length);
|
||||
// self.get_update_trace_length(state, l);
|
||||
// self.base.on_replace(state, idx, testcase)
|
||||
// }
|
||||
|
||||
/// Removes an entry from the corpus, returning M if M was present.
|
||||
// fn on_remove(
|
||||
// &self,
|
||||
// state: &mut CS::State,
|
||||
// idx: usize,
|
||||
// testcase: &Option<Testcase<<CS::State as UsesInput>::Input>>,
|
||||
// ) -> Result<(), Error> {
|
||||
// self.base.on_remove(state, idx, testcase)?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
/// Gets the next entry
|
||||
fn next(&mut self, state: &mut CS::State) -> Result<CorpusId, Error> {
|
||||
let mut idx = self.base.next(state)?;
|
||||
while {
|
||||
let l = state.corpus()
|
||||
.get(idx)?
|
||||
.borrow()
|
||||
.metadata_map()
|
||||
.get::<STGNodeMetadata>().map_or(0, |x| x.nodes.len());
|
||||
let m = self.get_update_trace_length(state,l);
|
||||
state.rand_mut().below(m) > l as u64
|
||||
} && state.rand_mut().below(100) < self.skip_non_favored_prob
|
||||
{
|
||||
idx = self.base.next(state)?;
|
||||
}
|
||||
Ok(idx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS> LongestTraceScheduler<CS>
|
||||
where
|
||||
CS: Scheduler,
|
||||
CS::State: HasCorpus + HasMetadata + HasRand,
|
||||
{
|
||||
pub fn get_update_trace_length(&self, state: &mut CS::State, par: usize) -> u64 {
|
||||
// Create a new top rated meta if not existing
|
||||
if let Some(td) = state.metadata_map_mut().get_mut::<LongestTracesMetadata>() {
|
||||
let m = max(td.max_trace_length, par);
|
||||
td.max_trace_length = m;
|
||||
m as u64
|
||||
} else {
|
||||
state.add_metadata(LongestTracesMetadata::new(par));
|
||||
par as u64
|
||||
}
|
||||
}
|
||||
pub fn new(base: CS) -> Self {
|
||||
Self {
|
||||
base,
|
||||
skip_non_favored_prob: DEFAULT_SKIP_NON_FAVORED_PROB,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================================
|
||||
|
||||
/// A state metadata holding a map of favoreds testcases for each map entry
|
||||
#[derive(Debug, Serialize, Deserialize, SerdeAny, Default)]
|
||||
pub struct GeneticMetadata {
|
||||
pub current_gen: Vec<(usize, f64)>,
|
||||
pub current_cursor: usize,
|
||||
pub next_gen: Vec<(usize, f64)>,
|
||||
pub gen: usize
|
||||
}
|
||||
|
||||
impl GeneticMetadata {
|
||||
fn new(current_gen: Vec<(usize, f64)>, next_gen: Vec<(usize, f64)>) -> Self {
|
||||
Self {current_gen, current_cursor: 0, next_gen, gen: 0}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GenerationScheduler<S> {
|
||||
phantom: PhantomData<S>,
|
||||
gen_size: usize,
|
||||
}
|
||||
|
||||
impl<S> UsesState for GenerationScheduler<S>
|
||||
where
|
||||
S: State + UsesInput,
|
||||
{
|
||||
type State = S;
|
||||
}
|
||||
|
||||
impl<S> Scheduler for GenerationScheduler<S>
|
||||
where
|
||||
S: State + HasCorpus + HasMetadata,
|
||||
S::Input: HasLen,
|
||||
{
|
||||
/// get first element in current gen,
|
||||
/// if current_gen is empty, swap lists, sort by FavFactor, take top k and return first
|
||||
fn next(&mut self, state: &mut Self::State) -> Result<CorpusId, Error> {
|
||||
let mut to_remove : Vec<(usize, f64)> = vec![];
|
||||
let mut to_return : usize = 0;
|
||||
let corpus_len = state.corpus().count();
|
||||
let mut current_len = 0;
|
||||
let gm = state.metadata_map_mut().get_mut::<GeneticMetadata>().expect("Corpus Scheduler empty");
|
||||
// println!("index: {} curr: {:?} next: {:?} gen: {} corp: {}", gm.current_cursor, gm.current_gen.len(), gm.next_gen.len(), gm.gen,
|
||||
// c);
|
||||
match gm.current_gen.get(gm.current_cursor) {
|
||||
Some(c) => {
|
||||
current_len = gm.current_gen.len();
|
||||
gm.current_cursor+=1;
|
||||
// println!("normal next: {}", (*c).0);
|
||||
return Ok((*c).0.into())
|
||||
},
|
||||
None => {
|
||||
swap(&mut to_remove, &mut gm.current_gen);
|
||||
swap(&mut gm.next_gen, &mut gm.current_gen);
|
||||
gm.current_gen.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
||||
// gm.current_gen.reverse();
|
||||
if gm.current_gen.len() == 0 {panic!("Corpus is empty");}
|
||||
let d : Vec<(usize, f64)> = gm.current_gen.drain(min(gm.current_gen.len(), self.gen_size)..).collect();
|
||||
to_remove.extend(d);
|
||||
// move all indices to the left, since all other indices will be deleted
|
||||
gm.current_gen.sort_by(|a,b| a.0.cmp(&(*b).0)); // in order of the corpus index
|
||||
// for i in 0..gm.current_gen.len() {
|
||||
// gm.current_gen[i] = (i, gm.current_gen[i].1);
|
||||
// }
|
||||
to_return = gm.current_gen.get(0).unwrap().0;
|
||||
// assert_eq!(to_return, 0);
|
||||
gm.current_cursor=1;
|
||||
gm.gen+=1;
|
||||
current_len = gm.current_gen.len();
|
||||
}
|
||||
};
|
||||
// removing these elements will move all indices left by to_remove.len()
|
||||
// to_remove.sort_by(|x,y| x.0.cmp(&(*y).0));
|
||||
// to_remove.reverse();
|
||||
let cm = state.corpus_mut();
|
||||
assert_eq!(corpus_len-to_remove.len(), current_len);
|
||||
assert_ne!(current_len,0);
|
||||
for i in to_remove {
|
||||
cm.remove(i.0.into()).unwrap();
|
||||
}
|
||||
assert_eq!(cm.get(to_return.into()).is_ok(),true);
|
||||
// println!("switch next: {to_return}");
|
||||
return Ok(to_return.into());
|
||||
}
|
||||
|
||||
/// Add the new input to the next generation
|
||||
fn on_add(
|
||||
&mut self,
|
||||
state: &mut Self::State,
|
||||
idx: CorpusId
|
||||
) -> Result<(), Error> {
|
||||
// println!("On Add {idx}");
|
||||
let mut tc = state.corpus_mut().get(idx).expect("Newly added testcase not found by index").borrow_mut().clone();
|
||||
let ff = MaxTimeFavFactor::compute(state, &mut tc).unwrap();
|
||||
if let Some(gm) = state.metadata_map_mut().get_mut::<GeneticMetadata>() {
|
||||
gm.next_gen.push((idx.into(),ff));
|
||||
} else {
|
||||
state.add_metadata(GeneticMetadata::new(vec![], vec![(idx.into(),ff)]));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
// fn on_replace(
|
||||
// &self,
|
||||
// _state: &mut Self::State,
|
||||
// _idx: usize,
|
||||
// _prev: &Testcase<<Self::State as UsesInput>::Input>
|
||||
// ) -> Result<(), Error> {
|
||||
// // println!("On Replace {_idx}");
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// fn on_remove(
|
||||
// &self,
|
||||
// state: &mut Self::State,
|
||||
// idx: usize,
|
||||
// _testcase: &Option<Testcase<<Self::State as UsesInput>::Input>>
|
||||
// ) -> Result<(), Error> {
|
||||
// // println!("On Remove {idx}");
|
||||
// if let Some(gm) = state.metadata_mut().get_mut::<GeneticMetadata>() {
|
||||
// gm.next_gen = gm.next_gen.drain(..).into_iter().filter(|x| (*x).0 != idx).collect::<Vec<(usize, f64)>>();
|
||||
// gm.current_gen = gm.current_gen.drain(..).into_iter().filter(|x| (*x).0 != idx).collect::<Vec<(usize, f64)>>();
|
||||
// } else {
|
||||
// state.add_metadata(GeneticMetadata::new(vec![], vec![]));
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
}
|
||||
|
||||
impl<S> GenerationScheduler<S>
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
phantom: PhantomData,
|
||||
gen_size: 100,
|
||||
}
|
||||
}
|
||||
}
|
590
fuzzers/FRET/src/systemstate/stg.rs
Normal file
590
fuzzers/FRET/src/systemstate/stg.rs
Normal file
@ -0,0 +1,590 @@
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use libafl::SerdeAny;
|
||||
/// Feedbacks organizing SystemStates as a graph
|
||||
use libafl::inputs::HasBytesVec;
|
||||
use libafl_bolts::ownedref::OwnedMutSlice;
|
||||
use petgraph::graph::EdgeIndex;
|
||||
use std::fs;
|
||||
use libafl_bolts::rands::RandomSeed;
|
||||
use libafl_bolts::rands::StdRand;
|
||||
use libafl::mutators::Mutator;
|
||||
use libafl::mutators::MutationResult;
|
||||
use libafl::prelude::HasTargetBytes;
|
||||
use libafl::prelude::UsesInput;
|
||||
use libafl::state::HasNamedMetadata;
|
||||
use libafl::state::UsesState;
|
||||
use libafl::prelude::State;
|
||||
use petgraph::dot::Config;
|
||||
use petgraph::dot::Dot;
|
||||
use core::marker::PhantomData;
|
||||
use libafl::state::HasCorpus;
|
||||
use libafl::state::HasSolutions;
|
||||
use libafl::state::HasRand;
|
||||
use crate::worst::MaxExecsLenFavFactor;
|
||||
use crate::worst::MaxTimeFavFactor;
|
||||
use libafl::schedulers::MinimizerScheduler;
|
||||
use libafl_bolts::HasRefCnt;
|
||||
use libafl_bolts::AsSlice;
|
||||
use libafl_bolts::ownedref::OwnedSlice;
|
||||
use libafl::inputs::BytesInput;
|
||||
use std::path::PathBuf;
|
||||
use crate::clock::QemuClockObserver;
|
||||
use libafl::corpus::Testcase;
|
||||
use libafl_bolts::tuples::MatchName;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
use std::hash::Hash;
|
||||
use libafl::events::EventFirer;
|
||||
use libafl::state::MaybeHasClientPerfMonitor;
|
||||
use libafl::feedbacks::Feedback;
|
||||
use libafl_bolts::Named;
|
||||
use libafl::Error;
|
||||
use libafl_qemu::edges::EDGES_MAP_SIZE;
|
||||
use hashbrown::HashMap;
|
||||
use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::feedbacks::SystemStateFeedbackState;
|
||||
use super::AtomicBasicBlock;
|
||||
use super::CaptureEvent;
|
||||
use super::ExecInterval;
|
||||
use super::ReducedFreeRTOSSystemState;
|
||||
use super::FreeRTOSSystemStateMetadata;
|
||||
use super::observers::QemuSystemStateObserver;
|
||||
use petgraph::prelude::DiGraph;
|
||||
use petgraph::graph::NodeIndex;
|
||||
use petgraph::Direction;
|
||||
use std::cmp::Ordering;
|
||||
use std::rc::Rc;
|
||||
|
||||
use libafl_bolts::rands::Rand;
|
||||
|
||||
use crate::clock::FUZZ_START_TIMESTAMP;
|
||||
use std::time::SystemTime;
|
||||
use std::{fs::OpenOptions, io::Write};
|
||||
|
||||
//============================= Data Structures
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default, Hash)]
|
||||
pub struct STGNode
|
||||
{
|
||||
base: ReducedFreeRTOSSystemState,
|
||||
abb: AtomicBasicBlock,
|
||||
}
|
||||
impl STGNode {
|
||||
pub fn pretty_print(&self) -> String {
|
||||
format!("{}\nl{} {:x}-{:x}\n{}", self.base.current_task.task_name, self.abb.level, self.abb.start, self.abb.ends.iter().next().unwrap_or_else(||&0xFFFF), self.base.print_lists())
|
||||
}
|
||||
pub fn color_print(&self) -> String {
|
||||
let color = match self.abb.level {
|
||||
1 => "\", shape=box, style=filled, fillcolor=\"lightblue",
|
||||
2 => "\", shape=box, style=filled, fillcolor=\"yellow",
|
||||
0 => "\", shape=box, style=filled, fillcolor=\"white",
|
||||
_ => "\", style=filled, fillcolor=\"lightgray",
|
||||
};
|
||||
let message = match self.abb.level {
|
||||
1 => format!("API Call"),
|
||||
2 => format!("ISR"),
|
||||
0 => format!("Task: {}",self.base.current_task.task_name),
|
||||
_ => format!(""),
|
||||
};
|
||||
let mut label = format!("{}\nABB: {:x}-{:x}\n{}", message, self.abb.start, self.abb.ends.iter().next().unwrap_or_else(||&0xFFFF), self.base.print_lists());
|
||||
label.push_str(color);
|
||||
label
|
||||
}
|
||||
fn get_hash(&self) -> u64 {
|
||||
let mut s = DefaultHasher::new();
|
||||
self.base.hash(&mut s);
|
||||
self.abb.hash(&mut s);
|
||||
s.finish()
|
||||
}
|
||||
}
|
||||
impl PartialEq for STGNode {
|
||||
fn eq(&self, other: &STGNode) -> bool {
|
||||
self.base==other.base
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default, Hash, PartialEq, Eq)]
|
||||
pub struct STGEdge
|
||||
{
|
||||
// is_interrupt: bool,
|
||||
pub event: CaptureEvent,
|
||||
pub name: String
|
||||
}
|
||||
|
||||
impl STGEdge {
|
||||
pub fn pretty_print(&self) -> String {
|
||||
let mut short = match self.event {
|
||||
CaptureEvent::APIStart => "Call: ",
|
||||
CaptureEvent::APIEnd => "Ret: ",
|
||||
CaptureEvent::ISRStart => "Int: ",
|
||||
CaptureEvent::ISREnd => "IRet: ",
|
||||
CaptureEvent::End => "End: ",
|
||||
CaptureEvent::Undefined => "",
|
||||
}.to_string();
|
||||
short.push_str(&self.name);
|
||||
short
|
||||
}
|
||||
pub fn color_print(&self) -> String {
|
||||
let mut short = self.name.clone();
|
||||
short.push_str(match self.event {
|
||||
CaptureEvent::APIStart => "\", color=\"blue",
|
||||
CaptureEvent::APIEnd => "\", color=\"black",
|
||||
CaptureEvent::ISRStart => "\", color=red, style=\"dashed",
|
||||
CaptureEvent::ISREnd => "\", color=red, style=\"solid",
|
||||
CaptureEvent::End => "",
|
||||
CaptureEvent::Undefined => "",
|
||||
});
|
||||
short
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared Metadata for a systemstateFeedback
|
||||
#[derive(Debug, Serialize, Deserialize, SerdeAny, Clone)]
|
||||
pub struct STGFeedbackState
|
||||
{
|
||||
// aggregated traces as a graph
|
||||
pub graph: DiGraph<STGNode, STGEdge>,
|
||||
systemstate_index: HashMap<u64, ReducedFreeRTOSSystemState>,
|
||||
pub state_abb_hash_index: HashMap<(u64, u64), NodeIndex>,
|
||||
stgnode_index: HashMap<u64, NodeIndex>,
|
||||
entrypoint: NodeIndex,
|
||||
exitpoint: NodeIndex,
|
||||
// Metadata about aggregated traces. aggegated meaning, order has been removed
|
||||
worst_observed_per_aggegated_path: HashMap<Vec<AtomicBasicBlock>,u64>,
|
||||
worst_observed_per_abb_path: HashMap<u64,u64>,
|
||||
worst_observed_per_stg_path: HashMap<u64,u64>,
|
||||
worst_abb_exec_count: HashMap<AtomicBasicBlock, usize>
|
||||
}
|
||||
|
||||
impl Default for STGFeedbackState {
|
||||
fn default() -> STGFeedbackState {
|
||||
let mut graph = DiGraph::new();
|
||||
let mut entry = STGNode::default();
|
||||
entry.base.current_task.task_name="Start".to_string();
|
||||
let mut exit = STGNode::default();
|
||||
exit.base.current_task.task_name="End".to_string();
|
||||
|
||||
let systemstate_index = HashMap::from([(entry.base.get_hash(), entry.base.clone()), (exit.base.get_hash(), exit.base.clone())]);
|
||||
|
||||
let h_entry = entry.get_hash();
|
||||
let h_exit = exit.get_hash();
|
||||
|
||||
let entrypoint = graph.add_node(entry.clone());
|
||||
let exitpoint = graph.add_node(exit.clone());
|
||||
|
||||
let state_abb_hash_index = HashMap::from([((entry.base.get_hash(), entry.abb.get_hash()), entrypoint), ((exit.base.get_hash(), exit.abb.get_hash()), exitpoint)]);
|
||||
|
||||
let index = HashMap::from([(h_entry, entrypoint), (h_exit, exitpoint)]);
|
||||
|
||||
STGFeedbackState {
|
||||
graph,
|
||||
stgnode_index: index,
|
||||
entrypoint,
|
||||
exitpoint,
|
||||
worst_observed_per_aggegated_path: HashMap::new(),
|
||||
worst_observed_per_abb_path: HashMap::new(),
|
||||
worst_observed_per_stg_path: HashMap::new(),
|
||||
worst_abb_exec_count: HashMap::new(),
|
||||
systemstate_index,
|
||||
state_abb_hash_index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for STGFeedbackState
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"stgfeedbackstate"
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper around Vec<RefinedFreeRTOSSystemState> to attach as Metadata
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct STGNodeMetadata {
|
||||
pub nodes: Vec<NodeIndex>,
|
||||
pub edges: Vec<EdgeIndex>,
|
||||
pub abbs: u64,
|
||||
pub aggregate: u64,
|
||||
pub top_abb_counts: Vec<u64>,
|
||||
pub intervals: Vec<ExecInterval>,
|
||||
indices: Vec<usize>,
|
||||
tcref: isize,
|
||||
}
|
||||
impl STGNodeMetadata {
|
||||
pub fn new(nodes: Vec<NodeIndex>, edges: Vec<EdgeIndex>, abbs: u64, aggregate: u64, top_abb_counts: Vec<u64>, intervals: Vec<ExecInterval>) -> Self{
|
||||
let mut indices : Vec<_> = vec![];
|
||||
#[cfg(all(feature = "sched_stg",not(any(feature = "sched_stg_pathhash",feature = "sched_stg_abbhash",feature = "sched_stg_aggregatehash"))))]
|
||||
{
|
||||
indices = edges.iter().map(|x| x.index()).collect();
|
||||
indices.sort_unstable();
|
||||
indices.dedup();
|
||||
}
|
||||
#[cfg(feature = "sched_stg_pathhash")]
|
||||
{
|
||||
indices.push(get_generic_hash(&edges) as usize);
|
||||
}
|
||||
#[cfg(feature = "sched_stg_abbhash")]
|
||||
{
|
||||
indices.push(abbs as usize);
|
||||
}
|
||||
#[cfg(feature = "sched_stg_aggregatehash")]
|
||||
{
|
||||
// indices.push(aggregate as usize);
|
||||
indices = top_abb_counts.iter().map(|x| (*x) as usize).collect();
|
||||
}
|
||||
Self {indices, intervals, nodes, abbs, aggregate, top_abb_counts, edges, tcref: 0}
|
||||
}
|
||||
}
|
||||
impl AsSlice for STGNodeMetadata {
|
||||
/// Convert the slice of system-states to a slice of hashes over enumerated states
|
||||
fn as_slice(&self) -> &[usize] {
|
||||
self.indices.as_slice()
|
||||
}
|
||||
|
||||
type Entry = usize;
|
||||
}
|
||||
|
||||
impl HasRefCnt for STGNodeMetadata {
|
||||
fn refcnt(&self) -> isize {
|
||||
self.tcref
|
||||
}
|
||||
|
||||
fn refcnt_mut(&mut self) -> &mut isize {
|
||||
&mut self.tcref
|
||||
}
|
||||
}
|
||||
|
||||
libafl_bolts::impl_serdeany!(STGNodeMetadata);
|
||||
|
||||
pub type GraphMaximizerCorpusScheduler<CS> =
|
||||
MinimizerScheduler<CS, MaxTimeFavFactor<<CS as UsesState>::State>,STGNodeMetadata>;
|
||||
|
||||
// AI generated, human verified
|
||||
fn count_occurrences<T>(vec: &Vec<T>) -> HashMap<&T, usize>
|
||||
where
|
||||
T: PartialEq + Eq + Hash + Clone,
|
||||
{
|
||||
let mut counts = HashMap::new();
|
||||
|
||||
if vec.is_empty() {
|
||||
return counts;
|
||||
}
|
||||
|
||||
let mut current_obj = &vec[0];
|
||||
let mut current_count = 1;
|
||||
|
||||
for obj in vec.iter().skip(1) {
|
||||
if obj == current_obj {
|
||||
current_count += 1;
|
||||
} else {
|
||||
counts.insert(current_obj, current_count);
|
||||
current_obj = obj;
|
||||
current_count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the count of the last object
|
||||
counts.insert(current_obj, current_count);
|
||||
|
||||
counts
|
||||
}
|
||||
|
||||
//============================= Graph Feedback
|
||||
|
||||
pub static mut STG_MAP: [u16; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE];
|
||||
pub static mut MAX_STG_NUM: usize = 0;
|
||||
pub unsafe fn stg_map_mut_slice<'a>() -> OwnedMutSlice<'a, u16> {
|
||||
OwnedMutSlice::from_raw_parts_mut(STG_MAP.as_mut_ptr(), STG_MAP.len())
|
||||
}
|
||||
|
||||
/// A Feedback reporting novel System-State Transitions. Depends on [`QemuSystemStateObserver`]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct StgFeedback
|
||||
{
|
||||
name: String,
|
||||
last_node_trace: Option<Vec<NodeIndex>>,
|
||||
last_edge_trace: Option<Vec<EdgeIndex>>,
|
||||
last_intervals: Option<Vec<ExecInterval>>,
|
||||
last_abbs_hash: Option<u64>, // only set, if it was interesting
|
||||
last_aggregate_hash: Option<u64>, // only set, if it was interesting
|
||||
last_top_abb_hashes: Option<Vec<u64>>, // only set, if it was interesting
|
||||
dump_path: Option<PathBuf>
|
||||
}
|
||||
#[cfg(feature = "feed_stg")]
|
||||
const INTEREST_EDGE : bool = true;
|
||||
#[cfg(feature = "feed_stg")]
|
||||
const INTEREST_NODE : bool = true;
|
||||
#[cfg(feature = "feed_stg_pathhash")]
|
||||
const INTEREST_PATH : bool = true;
|
||||
#[cfg(feature = "feed_stg_abbhash")]
|
||||
const INTEREST_ABBPATH : bool = true;
|
||||
#[cfg(feature = "feed_stg_aggregatehash")]
|
||||
const INTEREST_AGGREGATE : bool = true;
|
||||
|
||||
#[cfg(not(feature = "feed_stg"))]
|
||||
const INTEREST_EDGE : bool = false;
|
||||
#[cfg(not(feature = "feed_stg"))]
|
||||
const INTEREST_NODE : bool = false;
|
||||
#[cfg(not(feature = "feed_stg_pathhash"))]
|
||||
const INTEREST_PATH : bool = false;
|
||||
#[cfg(not(feature = "feed_stg_abbhash"))]
|
||||
const INTEREST_ABBPATH : bool = false;
|
||||
#[cfg(not(feature = "feed_stg_aggregatehash"))]
|
||||
const INTEREST_AGGREGATE : bool = false;
|
||||
fn set_observer_map(trace : &Vec<EdgeIndex>) {
|
||||
unsafe {
|
||||
for i in 0..MAX_STG_NUM {
|
||||
STG_MAP[i] = 0;
|
||||
}
|
||||
for i in trace {
|
||||
if MAX_STG_NUM < i.index() {
|
||||
MAX_STG_NUM = i.index();
|
||||
}
|
||||
STG_MAP[i.index()]+=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_generic_hash<H>(input: &H) -> u64
|
||||
where
|
||||
H: Hash,
|
||||
{
|
||||
let mut s = DefaultHasher::new();
|
||||
input.hash(&mut s);
|
||||
s.finish()
|
||||
}
|
||||
|
||||
impl StgFeedback {
|
||||
pub fn new(dump_name: Option<PathBuf>) -> Self {
|
||||
// Self {name: String::from("STGFeedback"), last_node_trace: None, last_edge_trace: None, last_intervals: None }
|
||||
let mut s = Self::default();
|
||||
s.dump_path = dump_name.map(|x| x.with_extension("stgsize"));
|
||||
s
|
||||
}
|
||||
|
||||
/// params:
|
||||
/// tarce of intervals
|
||||
/// hashtable of states
|
||||
/// feedbackstate
|
||||
/// produces:
|
||||
/// tarce of node indexes representing the path trough the graph
|
||||
/// newly discovered node?
|
||||
/// side effect:
|
||||
/// the graph gets new nodes and edge
|
||||
fn update_stg_interval(trace: &Vec<ExecInterval>, table: &HashMap<u64, ReducedFreeRTOSSystemState>, fbs: &mut STGFeedbackState) -> (Vec<NodeIndex>, Vec<EdgeIndex>, bool, bool) {
|
||||
let mut return_node_trace = vec![fbs.entrypoint];
|
||||
let mut return_edge_trace = vec![];
|
||||
let mut interesting = false;
|
||||
let mut updated = false;
|
||||
// add all missing state+abb combinations to the graph
|
||||
for (i,interval) in trace.iter().enumerate() { // Iterate intervals
|
||||
let node = STGNode {base: table[&interval.start_state].clone(), abb: interval.abb.as_ref().unwrap().clone()};
|
||||
let h_node = node.get_hash();
|
||||
let next_idx = if let Some(idx) = fbs.stgnode_index.get(&h_node) {
|
||||
// alredy present
|
||||
*idx
|
||||
} else {
|
||||
// not present
|
||||
let h = (node.base.get_hash(), node.abb.get_hash());
|
||||
let idx = fbs.graph.add_node(node);
|
||||
fbs.stgnode_index.insert(h_node, idx);
|
||||
fbs.state_abb_hash_index.insert(h, idx);
|
||||
interesting |= INTEREST_NODE;
|
||||
updated = true;
|
||||
idx
|
||||
};
|
||||
// connect in graph if edge not present
|
||||
let e = fbs.graph.edges_directed(return_node_trace[return_node_trace.len()-1],Direction::Outgoing).find(|x| petgraph::visit::EdgeRef::target(x) == next_idx);
|
||||
if let Some(e_) = e {
|
||||
return_edge_trace.push(petgraph::visit::EdgeRef::id(&e_));
|
||||
} else {
|
||||
let e_ = fbs.graph.add_edge(return_node_trace[return_node_trace.len()-1], next_idx, STGEdge{event: interval.start_capture.0, name: interval.start_capture.1.clone()});
|
||||
return_edge_trace.push(e_);
|
||||
interesting |= INTEREST_EDGE;
|
||||
updated = true;
|
||||
}
|
||||
return_node_trace.push(next_idx);
|
||||
/*
|
||||
Ideas:
|
||||
Mark edges triggered by interrupts
|
||||
Specify path with edges instead of nodes?
|
||||
Form a coverage map over edges?
|
||||
Sum up execution time per ABB
|
||||
*/
|
||||
}
|
||||
// every path terminates at the end
|
||||
if !fbs.graph.neighbors_directed(return_node_trace[return_node_trace.len()-1],Direction::Outgoing).any(|x| x == fbs.exitpoint) {
|
||||
let e_ = fbs.graph.add_edge(return_node_trace[return_node_trace.len()-1], fbs.exitpoint, STGEdge { event: CaptureEvent::End, name: String::from("End") });
|
||||
return_edge_trace.push(e_);
|
||||
interesting |= INTEREST_EDGE;
|
||||
updated = true;
|
||||
}
|
||||
return_node_trace.push(fbs.exitpoint);
|
||||
#[cfg(feature = "feed_stg")]
|
||||
set_observer_map(&return_edge_trace);
|
||||
(return_node_trace, return_edge_trace, interesting, updated)
|
||||
}
|
||||
|
||||
fn abbs_in_exec_order(trace: &Vec<ExecInterval>) -> Vec<AtomicBasicBlock> {
|
||||
let mut ret = Vec::new();
|
||||
for i in 0..trace.len() {
|
||||
if trace[i].abb != None &&
|
||||
(trace[i].end_capture.0 == CaptureEvent::APIStart || trace[i].end_capture.0 == CaptureEvent::APIEnd || trace[i].end_capture.0 == CaptureEvent::End || trace[i].end_capture.0 == CaptureEvent::ISREnd) {
|
||||
ret.push(trace[i].abb.as_ref().unwrap().clone());
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for StgFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor + HasNamedMetadata,
|
||||
S::Input: HasTargetBytes,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &S::Input,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>,
|
||||
{
|
||||
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
|
||||
.expect("QemuSystemStateObserver not found");
|
||||
let clock_observer = observers.match_name::<QemuClockObserver>("clocktime")
|
||||
.expect("QemuClockObserver not found");
|
||||
let feedbackstate = match state
|
||||
.named_metadata_map_mut()
|
||||
.get_mut::<STGFeedbackState>("stgfeedbackstate") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
let n=STGFeedbackState::default();
|
||||
state.named_metadata_map_mut().insert(n, "stgfeedbackstate");
|
||||
state.named_metadata_map_mut().get_mut::<STGFeedbackState>("stgfeedbackstate").unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
let (nodetrace, edgetrace, mut interesting, mut updated) = StgFeedback::update_stg_interval(&observer.last_trace, &observer.last_states, feedbackstate);
|
||||
|
||||
{
|
||||
let h = get_generic_hash(&edgetrace);
|
||||
if let Some(x) = feedbackstate.worst_observed_per_stg_path.get_mut(&h) {
|
||||
let t = clock_observer.last_runtime();
|
||||
if t > *x {
|
||||
*x = t;
|
||||
interesting |= INTEREST_PATH;
|
||||
}
|
||||
} else {
|
||||
feedbackstate.worst_observed_per_stg_path.insert(h, clock_observer.last_runtime());
|
||||
updated = true;
|
||||
interesting |= INTEREST_PATH;
|
||||
}
|
||||
}
|
||||
|
||||
let mut tmp = StgFeedback::abbs_in_exec_order(&observer.last_trace);
|
||||
if INTEREST_AGGREGATE || INTEREST_ABBPATH {
|
||||
if INTEREST_ABBPATH {
|
||||
let h = get_generic_hash(&tmp);
|
||||
self.last_abbs_hash = Some(h);
|
||||
// order of execution is relevant
|
||||
if let Some(x) = feedbackstate.worst_observed_per_abb_path.get_mut(&h) {
|
||||
let t = clock_observer.last_runtime();
|
||||
if t > *x {
|
||||
*x = t;
|
||||
interesting |= INTEREST_ABBPATH;
|
||||
}
|
||||
} else {
|
||||
feedbackstate.worst_observed_per_abb_path.insert(h, clock_observer.last_runtime());
|
||||
interesting |= INTEREST_ABBPATH;
|
||||
}
|
||||
}
|
||||
if INTEREST_AGGREGATE {
|
||||
// aggegation by sorting, order of states is not relevant
|
||||
let mut _tmp = tmp.clone();
|
||||
_tmp.sort();
|
||||
let counts = count_occurrences(&_tmp);
|
||||
let mut top_indices = Vec::new();
|
||||
for (k,c) in counts {
|
||||
if let Some(reference) = feedbackstate.worst_abb_exec_count.get_mut(k) {
|
||||
if *reference < c {
|
||||
*reference = c;
|
||||
top_indices.push(get_generic_hash(k));
|
||||
}
|
||||
} else {
|
||||
top_indices.push(get_generic_hash(k));
|
||||
feedbackstate.worst_abb_exec_count.insert(k.clone(), c);
|
||||
}
|
||||
}
|
||||
self.last_top_abb_hashes = Some(top_indices);
|
||||
|
||||
self.last_aggregate_hash = Some(get_generic_hash(&_tmp));
|
||||
if let Some(x) = feedbackstate.worst_observed_per_aggegated_path.get_mut(&_tmp) {
|
||||
let t = clock_observer.last_runtime();
|
||||
if t > *x {
|
||||
*x = t;
|
||||
interesting |= INTEREST_AGGREGATE;
|
||||
}
|
||||
} else {
|
||||
feedbackstate.worst_observed_per_aggegated_path.insert(_tmp, clock_observer.last_runtime());
|
||||
interesting |= INTEREST_AGGREGATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let out = feedbackstate.graph.map(|i,x| x.pretty_print(), |_,_| "");
|
||||
// let outs = Dot::with_config(&out, &[Config::EdgeNoLabel]).to_string();
|
||||
// let outs = outs.replace(';',"\\n");
|
||||
// fs::write("./mystg.dot",outs).expect("Failed to write graph");
|
||||
self.last_node_trace = Some(nodetrace);
|
||||
self.last_edge_trace = Some(edgetrace);
|
||||
self.last_intervals = Some(observer.last_trace.clone());
|
||||
|
||||
if let Some(dp) = &self.dump_path {
|
||||
if updated {
|
||||
let timestamp = SystemTime::now().duration_since(unsafe {FUZZ_START_TIMESTAMP}).unwrap().as_millis();
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(dp).expect("Could not open stgsize");
|
||||
writeln!(file, "{},{},{},{},{}", feedbackstate.graph.edge_count(), feedbackstate.graph.node_count(), feedbackstate.worst_observed_per_aggegated_path.len(),feedbackstate.worst_observed_per_stg_path.len(), timestamp).expect("Write to dump failed");
|
||||
}
|
||||
}
|
||||
Ok(interesting)
|
||||
}
|
||||
|
||||
/// Append to the testcase the generated metadata in case of a new corpus item
|
||||
#[inline]
|
||||
fn append_metadata<OT>(&mut self, _state: &mut S, _observers: &OT, testcase: &mut Testcase<S::Input>) -> Result<(), Error> {
|
||||
let nodes = self.last_node_trace.take();
|
||||
let edges = self.last_edge_trace.take();
|
||||
match nodes {
|
||||
Some(s) => testcase.metadata_map_mut().insert(STGNodeMetadata::new(s, edges.unwrap(), self.last_abbs_hash.take().unwrap_or_default(), self.last_aggregate_hash.take().unwrap_or_default(), self.last_top_abb_hashes.take().unwrap_or_default(), self.last_intervals.take().unwrap())),
|
||||
None => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Discard the stored metadata in case that the testcase is not added to the corpus
|
||||
#[inline]
|
||||
fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Named for StgFeedback
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
396
fuzzers/FRET/src/worst.rs
Normal file
396
fuzzers/FRET/src/worst.rs
Normal file
@ -0,0 +1,396 @@
|
||||
use core::fmt::Debug;
|
||||
use core::cmp::Ordering::{Greater,Less,Equal};
|
||||
use libafl::inputs::BytesInput;
|
||||
use libafl::inputs::HasTargetBytes;
|
||||
use libafl::feedbacks::MapIndexesMetadata;
|
||||
use libafl::corpus::Testcase;
|
||||
use libafl::prelude::{UsesInput};
|
||||
use core::marker::PhantomData;
|
||||
use libafl::schedulers::{MinimizerScheduler, TestcaseScore};
|
||||
use std::path::PathBuf;
|
||||
use std::fs;
|
||||
use hashbrown::{HashMap};
|
||||
use libafl::observers::ObserversTuple;
|
||||
use libafl::executors::ExitKind;
|
||||
use libafl::events::EventFirer;
|
||||
use libafl::state::{MaybeHasClientPerfMonitor, HasCorpus, UsesState};
|
||||
use libafl::prelude::State;
|
||||
use libafl::inputs::Input;
|
||||
use libafl::feedbacks::Feedback;
|
||||
use libafl::state::HasMetadata;
|
||||
use libafl_qemu::edges::QemuEdgesMapMetadata;
|
||||
use libafl::observers::MapObserver;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp;
|
||||
|
||||
use libafl_bolts::{
|
||||
Named,
|
||||
HasLen,
|
||||
AsSlice,
|
||||
};
|
||||
use libafl::{
|
||||
observers::Observer,
|
||||
Error,
|
||||
};
|
||||
|
||||
use crate::clock::QemuClockObserver;
|
||||
use crate::systemstate::FreeRTOSSystemStateMetadata;
|
||||
//=========================== Scheduler
|
||||
|
||||
pub type TimeMaximizerCorpusScheduler<CS> =
|
||||
MinimizerScheduler<CS, MaxTimeFavFactor<<CS as UsesState>::State>, MapIndexesMetadata>;
|
||||
|
||||
/// Multiply the testcase size with the execution time.
|
||||
/// This favors small and quick testcases.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MaxTimeFavFactor<S>
|
||||
where
|
||||
S: HasCorpus + HasMetadata,
|
||||
S::Input: HasLen,
|
||||
{
|
||||
phantom: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S> TestcaseScore<S> for MaxTimeFavFactor<S>
|
||||
where
|
||||
S: HasCorpus + HasMetadata,
|
||||
S::Input: HasLen,
|
||||
{
|
||||
fn compute(state: &S, entry: &mut Testcase<<S as UsesInput>::Input>) -> Result<f64, Error> {
|
||||
// TODO maybe enforce entry.exec_time().is_some()
|
||||
let et = entry.exec_time().expect("testcase.exec_time is needed for scheduler");
|
||||
let tns : i64 = et.as_nanos().try_into().expect("failed to convert time");
|
||||
Ok(-tns as f64)
|
||||
}
|
||||
}
|
||||
|
||||
pub type LenTimeMaximizerCorpusScheduler<CS> =
|
||||
MinimizerScheduler<CS, MaxExecsLenFavFactor<<CS as UsesState>::State>, MapIndexesMetadata>;
|
||||
|
||||
pub type TimeStateMaximizerCorpusScheduler<CS> =
|
||||
MinimizerScheduler<CS, MaxTimeFavFactor<<CS as UsesState>::State>, FreeRTOSSystemStateMetadata>;
|
||||
|
||||
/// Multiply the testcase size with the execution time.
|
||||
/// This favors small and quick testcases.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MaxExecsLenFavFactor<S>
|
||||
where
|
||||
S: HasCorpus + HasMetadata,
|
||||
S::Input: HasLen,
|
||||
{
|
||||
phantom: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S> TestcaseScore<S> for MaxExecsLenFavFactor<S>
|
||||
where
|
||||
S: HasCorpus + HasMetadata,
|
||||
S::Input: HasLen,
|
||||
{
|
||||
fn compute( state: &S, entry: &mut Testcase<S::Input>) -> Result<f64, Error> {
|
||||
let execs_per_hour = (3600.0/entry.exec_time().expect("testcase.exec_time is needed for scheduler").as_secs_f64());
|
||||
let execs_times_length_per_hour = execs_per_hour*entry.load_len(state.corpus()).unwrap() as f64;
|
||||
Ok(execs_times_length_per_hour)
|
||||
}
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
|
||||
/// A Feedback reporting if the Input consists of strictly decreasing bytes.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct SortedFeedback {
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for SortedFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor,
|
||||
S::Input: HasTargetBytes,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
_state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &S::Input,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>,
|
||||
{
|
||||
let t = _input.target_bytes();
|
||||
let tmp = t.as_slice();
|
||||
if tmp.len()<32 {return Ok(false);}
|
||||
let tmp = Vec::<u8>::from(&tmp[0..32]);
|
||||
// tmp.reverse();
|
||||
// if tmp.is_sorted_by(|a,b| match a.partial_cmp(b).unwrap_or(Less) {
|
||||
// Less => Some(Greater),
|
||||
// Equal => Some(Greater),
|
||||
// Greater => Some(Less),
|
||||
// }) {return Ok(true)};
|
||||
let mut is_sorted = true;
|
||||
if tmp[0]<tmp[1] {
|
||||
for i in 1..tmp.len() {
|
||||
is_sorted &= tmp[i-1]<=tmp[i];
|
||||
if !is_sorted {break;}
|
||||
}
|
||||
} else {
|
||||
for i in 1..tmp.len() {
|
||||
is_sorted &= tmp[i-1]>=tmp[i];
|
||||
if !is_sorted {break;}
|
||||
}
|
||||
}
|
||||
return Ok(is_sorted);
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for SortedFeedback {
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"Sorted"
|
||||
}
|
||||
}
|
||||
|
||||
impl SortedFeedback {
|
||||
/// Creates a new [`HitFeedback`]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SortedFeedback {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
/// A Feedback which expects a certain minimum execution time
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ExecTimeReachedFeedback
|
||||
{
|
||||
target_time: u64,
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for ExecTimeReachedFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
_state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &S::Input,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>,
|
||||
{
|
||||
let observer = observers.match_name::<QemuClockObserver>("clock")
|
||||
.expect("QemuClockObserver not found");
|
||||
Ok(observer.last_runtime() >= self.target_time)
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for ExecTimeReachedFeedback
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"ExecTimeReachedFeedback"
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecTimeReachedFeedback
|
||||
where
|
||||
{
|
||||
/// Creates a new [`ExecTimeReachedFeedback`]
|
||||
#[must_use]
|
||||
pub fn new(target_time : u64) -> Self {
|
||||
Self {target_time: target_time}
|
||||
}
|
||||
}
|
||||
|
||||
pub static mut EXEC_TIME_COLLECTION : Vec<u32> = Vec::new();
|
||||
|
||||
/// A Noop Feedback which records a list of all execution times
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ExecTimeCollectorFeedback
|
||||
{
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for ExecTimeCollectorFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
_state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &S::Input,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>,
|
||||
{
|
||||
let observer = observers.match_name::<QemuClockObserver>("clock")
|
||||
.expect("QemuClockObserver not found");
|
||||
unsafe { EXEC_TIME_COLLECTION.push(observer.last_runtime().try_into().unwrap()); }
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for ExecTimeCollectorFeedback
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"ExecTimeCollectorFeedback"
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecTimeCollectorFeedback
|
||||
where
|
||||
{
|
||||
/// Creates a new [`ExecTimeCollectorFeedback`]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared Metadata for a SysStateFeedback
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct ExecTimeCollectorFeedbackState
|
||||
{
|
||||
collection: Vec<u32>,
|
||||
}
|
||||
impl Named for ExecTimeCollectorFeedbackState
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"ExecTimeCollectorFeedbackState"
|
||||
}
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
/// A Feedback which expects a certain minimum execution time
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ExecTimeIncFeedback
|
||||
{
|
||||
longest_time: u64,
|
||||
last_is_longest: bool
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for ExecTimeIncFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
_state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &S::Input,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>,
|
||||
{
|
||||
let observer = observers.match_name::<QemuClockObserver>("clocktime")
|
||||
.expect("QemuClockObserver not found");
|
||||
if observer.last_runtime() > self.longest_time {
|
||||
self.longest_time = observer.last_runtime();
|
||||
self.last_is_longest = true;
|
||||
Ok(true)
|
||||
} else {
|
||||
self.last_is_longest = false;
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
fn append_metadata<OT>(
|
||||
&mut self,
|
||||
_state: &mut S,
|
||||
observers: &OT,
|
||||
testcase: &mut Testcase<<S as UsesInput>::Input>,
|
||||
) -> Result<(), Error> {
|
||||
#[cfg(feature = "feed_afl")]
|
||||
if self.last_is_longest {
|
||||
let mim : Option<&mut MapIndexesMetadata>= testcase.metadata_map_mut().get_mut();
|
||||
// pretend that the longest input alone excercises some non-existing edge, to keep it relevant
|
||||
mim.unwrap().list.push(usize::MAX);
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for ExecTimeIncFeedback
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"ExecTimeReachedFeedback"
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecTimeIncFeedback
|
||||
where
|
||||
{
|
||||
/// Creates a new [`ExecTimeReachedFeedback`]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {longest_time: 0, last_is_longest: false}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Noop Feedback which records a list of all execution times
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct AlwaysTrueFeedback
|
||||
{
|
||||
}
|
||||
|
||||
impl<S> Feedback<S> for AlwaysTrueFeedback
|
||||
where
|
||||
S: State + UsesInput + MaybeHasClientPerfMonitor,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
_state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &S::Input,
|
||||
_observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<State = S>,
|
||||
OT: ObserversTuple<S>,
|
||||
{
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for AlwaysTrueFeedback
|
||||
{
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"AlwaysTrueFeedback"
|
||||
}
|
||||
}
|
||||
|
||||
impl AlwaysTrueFeedback
|
||||
where
|
||||
{
|
||||
/// Creates a new [`ExecTimeCollectorFeedback`]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
}
|
||||
}
|
||||
}
|
5
fuzzers/FRET/tests/.gitignore
vendored
Normal file
5
fuzzers/FRET/tests/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
dump
|
||||
demo*
|
||||
*.dot
|
||||
*.time
|
||||
*.case
|
27
fuzzers/FRET/tests/run_test.sh
Normal file
27
fuzzers/FRET/tests/run_test.sh
Normal file
@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
|
||||
# cargo build --no-default-features --features std,snapshot_restore,singlecore,feed_afl,observer_hitcounts
|
||||
|
||||
# Test basic fuzzing loop
|
||||
../target/debug/fret -k ../benchmark/build/waters.elf -c ../benchmark/target_symbols.csv -n ./dump/waters -tar fuzz -t 10 -s 123
|
||||
|
||||
# Test reprodcibility
|
||||
rm -f ./dump/demo.case.time
|
||||
../target/debug/fret -k ../benchmark/build/waters.elf -c ../benchmark/target_symbols.csv -n ./dump/demo -tr showmap -i ./demo.case
|
||||
if [[ $(cut -d, -f1 ./dump/demo.case.time) != $(cut -d, -f1 ./demo.example.time) ]]; then echo "Not reproducible!" && exit 1; else echo "Reproducible"; fi
|
||||
|
||||
# Test state dump
|
||||
# cargo build --no-default-features --features std,snapshot_restore,singlecore,feed_afl,observer_hitcounts,systemstate
|
||||
if [[ -n "$(diff -q demo.example.state.ron dump/demo.trace.ron)" ]]; then echo "State not reproducible!"; else echo "State Reproducible"; fi
|
||||
|
||||
# Test abb traces
|
||||
# cargo build --no-default-features --features std,snapshot_restore,singlecore,feed_afl,observer_hitcounts,systemstate,trace_abbs
|
||||
if [[ -n "$(diff -q demo.example.abb.ron dump/demo.trace.ron)" ]]; then echo "ABB not reproducible!"; else echo "ABB Reproducible"; fi
|
||||
|
||||
# ../target/debug/fret -k ../benchmark/build/minimal.elf -c ../benchmark/target_symbols.csv -n ./dump/minimal -tar fuzz -t 20 -s 123
|
||||
# ../target/debug/fret -k ../benchmark/build/minimal.elf -c ../benchmark/target_symbols.csv -n ./dump/minimal_worst -tr showmap -i ./dump/minimal.case
|
||||
|
||||
# Test fuzzing using systemtraces
|
||||
cargo build --no-default-features --features std,snapshot_restore,singlecore,feed_systemtrace
|
||||
|
||||
../target/debug/fret -k ../benchmark/build/waters.elf -c ../benchmark/target_symbols.csv -n ./dump/waters -tar fuzz -t 10 -s 123
|
@ -261,6 +261,86 @@ where
|
||||
|
||||
Ok(ret.unwrap())
|
||||
}
|
||||
|
||||
/// Fuzz for n iterations.
|
||||
/// Returns the index of the last fuzzed corpus item.
|
||||
/// (Note: An iteration represents a complete run of every stage.
|
||||
/// therefore the number n is not always equal to the number of the actual harness executions,
|
||||
/// because each stage could run the harness for multiple times)
|
||||
///
|
||||
/// If you use this fn in a restarting scenario to only run for `n` iterations,
|
||||
/// before exiting, make sure you call `event_mgr.on_restart(&mut state)?;`.
|
||||
/// This way, the state will be available in the next, respawned, iteration.
|
||||
fn fuzz_loop_for_duration(
|
||||
&mut self,
|
||||
stages: &mut ST,
|
||||
executor: &mut E,
|
||||
state: &mut EM::State,
|
||||
manager: &mut EM,
|
||||
time: Duration
|
||||
) -> Result<CorpusId, Error> {
|
||||
if time==Duration::ZERO {
|
||||
return Err(Error::illegal_argument(
|
||||
"Cannot fuzz for 0 duration!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut ret = None;
|
||||
let mut last = current_time();
|
||||
let monitor_timeout = STATS_TIMEOUT_DEFAULT;
|
||||
|
||||
let starttime = std::time::Instant::now();
|
||||
|
||||
while std::time::Instant::now().duration_since(starttime) < time {
|
||||
ret = Some(self.fuzz_one(stages, executor, state, manager)?);
|
||||
manager.maybe_report_progress(state, monitor_timeout)?;
|
||||
}
|
||||
|
||||
// If we would assume the fuzzer loop will always exit after this, we could do this here:
|
||||
// manager.on_restart(state)?;
|
||||
// But as the state may grow to a few megabytes,
|
||||
// for now we won' and the user has to do it (unless we find a way to do this on `Drop`).
|
||||
|
||||
Ok(ret.unwrap())
|
||||
}
|
||||
|
||||
/// Fuzz for n iterations.
|
||||
/// Returns the index of the last fuzzed corpus item.
|
||||
/// (Note: An iteration represents a complete run of every stage.
|
||||
/// therefore the number n is not always equal to the number of the actual harness executions,
|
||||
/// because each stage could run the harness for multiple times)
|
||||
///
|
||||
/// If you use this fn in a restarting scenario to only run for `n` iterations,
|
||||
/// before exiting, make sure you call `event_mgr.on_restart(&mut state)?;`.
|
||||
/// This way, the state will be available in the next, respawned, iteration.
|
||||
fn fuzz_loop_until(
|
||||
&mut self,
|
||||
stages: &mut ST,
|
||||
executor: &mut E,
|
||||
state: &mut EM::State,
|
||||
manager: &mut EM,
|
||||
time: std::time::Instant
|
||||
) -> Result<CorpusId, Error> {
|
||||
let mut ret = None;
|
||||
let mut last = current_time();
|
||||
let monitor_timeout = STATS_TIMEOUT_DEFAULT;
|
||||
|
||||
while std::time::Instant::now() < time {
|
||||
ret = Some(self.fuzz_one(stages, executor, state, manager)?);
|
||||
manager.maybe_report_progress(state, monitor_timeout)?;
|
||||
}
|
||||
|
||||
// If we would assume the fuzzer loop will always exit after this, we could do this here:
|
||||
// manager.on_restart(state)?;
|
||||
// But as the state may grow to a few megabytes,
|
||||
// for now we won' and the user has to do it (unless we find a way to do this on `Drop`).
|
||||
|
||||
if let None = ret {
|
||||
eprintln!("Warning: fuzzing loop ended with no last element");
|
||||
ret = Some(crate::corpus::CorpusId(0));
|
||||
}
|
||||
Ok(ret.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// The corpus this input should be added to
|
||||
|
@ -59,6 +59,15 @@ impl TopRatedsMetadata {
|
||||
pub fn map(&self) -> &HashMap<usize, CorpusId> {
|
||||
&self.map
|
||||
}
|
||||
|
||||
/// Retruns the number of inices that are considered interesting
|
||||
pub fn get_number(&self) -> usize {
|
||||
let mut tmp = HashSet::new();
|
||||
for i in self.map.values() {
|
||||
tmp.insert(*i);
|
||||
}
|
||||
tmp.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TopRatedsMetadata {
|
||||
@ -302,8 +311,8 @@ where
|
||||
old_meta.refcnt() <= 0
|
||||
};
|
||||
|
||||
if must_remove && self.remove_metadata {
|
||||
drop(old.metadata_map_mut().remove::<M>());
|
||||
if must_remove {
|
||||
// drop(old.metadata_map_mut().remove::<M>());
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,6 +342,7 @@ where
|
||||
.map
|
||||
.insert(elem, idx);
|
||||
}
|
||||
println!("Number of interesting corpus elements: {}", state.metadata_map_mut().get::<TopRatedsMetadata>().unwrap().get_number());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -13,4 +13,12 @@ extern_c_checked! {
|
||||
pub fn libafl_load_qemu_snapshot(name: *const u8, sync: bool);
|
||||
|
||||
pub fn libafl_qemu_current_paging_id(cpu: CPUStatePtr) -> GuestPhysAddr;
|
||||
|
||||
pub fn icount_get_raw() -> u64;
|
||||
pub fn libafl_start_int_timer();
|
||||
pub fn libafl_add_jmp_hook(
|
||||
gen: Option<extern "C" fn(u64, GuestAddr, GuestAddr) -> u64>, // data,src,dest
|
||||
exec: Option<extern "C" fn(u64, GuestAddr, GuestAddr, u64)>, // data,src,dest,id
|
||||
data: u64
|
||||
) -> usize;
|
||||
}
|
||||
|
@ -8,7 +8,15 @@ use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
pub use syscall_numbers::arm::*;
|
||||
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind, sys::CPUStatePtr, Qemu};
|
||||
extern "C" {
|
||||
fn libafl_qemu_read_user_sp_unchecked(cpu: CPUStatePtr) -> i32;
|
||||
}
|
||||
|
||||
pub fn read_user_reg_unchecked(emu : &Qemu) -> i32
|
||||
{
|
||||
unsafe {libafl_qemu_read_user_sp_unchecked(emu.current_cpu().unwrap().ptr)}.into()
|
||||
}
|
||||
|
||||
/// Registers for the ARM instruction set.
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
|
||||
|
@ -370,6 +370,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
static mut JMP_HOOKS: Vec<HookState<1>> = vec![];
|
||||
create_gen_wrapper!(jmp, (src: GuestAddr, dest: GuestAddr), u64, 1);
|
||||
create_exec_wrapper!(jmp, (src: GuestAddr, dst: GuestAddr, id: u64), 0, 1);
|
||||
|
||||
|
||||
|
||||
static mut HOOKS_IS_INITIALIZED: bool = false;
|
||||
static mut FIRST_EXEC: bool = true;
|
||||
|
||||
@ -1384,4 +1390,49 @@ where
|
||||
CRASH_HOOKS.push(HookRepr::Closure(transmute(hook)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jmps(
|
||||
&self,
|
||||
generation_hook: Hook<
|
||||
fn(&mut Self, Option<&mut S>, src: GuestAddr, dest: GuestAddr) -> Option<u64>,
|
||||
Box<
|
||||
dyn for<'a> FnMut(
|
||||
&'a mut Self,
|
||||
Option<&'a mut S>,
|
||||
GuestAddr,
|
||||
GuestAddr,
|
||||
) -> Option<u64>,
|
||||
>,
|
||||
extern "C" fn(*const (), src: GuestAddr, dest: GuestAddr) -> u64,
|
||||
>,
|
||||
execution_hook: Hook<
|
||||
fn(&mut Self, Option<&mut S>, src: GuestAddr, dest: GuestAddr, id: u64),
|
||||
Box<dyn for<'a> FnMut(&'a mut Self, Option<&'a mut S>, GuestAddr, GuestAddr, u64)>,
|
||||
extern "C" fn(*const (), src: GuestAddr, dest: GuestAddr, id: u64),
|
||||
>,
|
||||
) -> HookId {
|
||||
unsafe {
|
||||
let gen = get_raw_hook!(
|
||||
generation_hook,
|
||||
jmp_gen_hook_wrapper::<QT, S>,
|
||||
extern "C" fn(&mut HookState<1>, src: GuestAddr, dest: GuestAddr) -> u64
|
||||
);
|
||||
let exec = get_raw_hook!(
|
||||
execution_hook,
|
||||
jmp_0_exec_hook_wrapper::<QT, S>,
|
||||
extern "C" fn(&mut HookState<1>, src: GuestAddr, dest: GuestAddr, id: u64)
|
||||
);
|
||||
JMP_HOOKS.push(HookState {
|
||||
id: HookId(0),
|
||||
gen: hook_to_repr!(generation_hook),
|
||||
post_gen: HookRepr::Empty,
|
||||
execs: [hook_to_repr!(execution_hook)],
|
||||
});
|
||||
let id = self
|
||||
.emulator
|
||||
.add_jmp_hooks(JMP_HOOKS.last_mut().unwrap(), gen, exec);
|
||||
JMP_HOOKS.last_mut().unwrap().id = id;
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +86,22 @@ create_hook_id!(PreSyscall, libafl_qemu_remove_pre_syscall_hook, false);
|
||||
create_hook_id!(PostSyscall, libafl_qemu_remove_post_syscall_hook, false);
|
||||
create_hook_id!(NewThread, libafl_qemu_remove_new_thread_hook, false);
|
||||
|
||||
pub fn add_jmp_hooks<T: Into<HookData>>(
|
||||
&self,
|
||||
data: T,
|
||||
gen: Option<extern "C" fn(T, GuestAddr, GuestAddr) -> u64>,
|
||||
exec: Option<extern "C" fn(T, GuestAddr, GuestAddr, u64)>,
|
||||
) -> HookId {
|
||||
unsafe {
|
||||
let data: u64 = data.into().0;
|
||||
let gen: Option<extern "C" fn(u64, GuestAddr, GuestAddr) -> u64> =
|
||||
core::mem::transmute(gen);
|
||||
let exec: Option<extern "C" fn(u64, GuestAddr, GuestAddr, u64)> = core::mem::transmute(exec);
|
||||
let num = libafl_add_jmp_hook(gen, exec, data);
|
||||
HookId(num)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QemuInitError {
|
||||
MultipleInstances,
|
||||
|
@ -207,6 +207,7 @@ impl Qemu {
|
||||
/// Should, in general, be safe to call.
|
||||
/// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system.
|
||||
pub unsafe fn run(&self) -> Result<QemuExitReason, QemuExitError> {
|
||||
libafl_start_int_timer();
|
||||
vm_start();
|
||||
qemu_main_loop();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user