diff --git a/fuzzers/FRET/.gitignore b/fuzzers/FRET/.gitignore new file mode 100644 index 0000000000..114ccddabc --- /dev/null +++ b/fuzzers/FRET/.gitignore @@ -0,0 +1,4 @@ +*.qcow2 +corpus +*.axf +demo diff --git a/fuzzers/FRET/Cargo.toml b/fuzzers/FRET/Cargo.toml new file mode 100644 index 0000000000..2770b993df --- /dev/null +++ b/fuzzers/FRET/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "fret" +version = "0.8.2" +authors = ["Alwin Berger "] +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" diff --git a/fuzzers/FRET/README.md b/fuzzers/FRET/README.md new file mode 100644 index 0000000000..14098dc09c --- /dev/null +++ b/fuzzers/FRET/README.md @@ -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. \ No newline at end of file diff --git a/fuzzers/FRET/benchmark/.gitignore b/fuzzers/FRET/benchmark/.gitignore new file mode 100644 index 0000000000..52eedad332 --- /dev/null +++ b/fuzzers/FRET/benchmark/.gitignore @@ -0,0 +1,12 @@ +*dump +timedump* +corpora +build +mnt +.R* +*.png +*.pdf +bins +.snakemake +*.zip +*.tar.* diff --git a/fuzzers/FRET/benchmark/Makefile b/fuzzers/FRET/benchmark/Makefile new file mode 100644 index 0000000000..6a479038e8 --- /dev/null +++ b/fuzzers/FRET/benchmark/Makefile @@ -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 \ No newline at end of file diff --git a/fuzzers/FRET/benchmark/Snakefile b/fuzzers/FRET/benchmark/Snakefile new file mode 100644 index 0000000000..bc2340f68e --- /dev/null +++ b/fuzzers/FRET/benchmark/Snakefile @@ -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']) \ No newline at end of file diff --git a/fuzzers/FRET/benchmark/build_all_demos.sh b/fuzzers/FRET/benchmark/build_all_demos.sh new file mode 100644 index 0000000000..7a79349f90 --- /dev/null +++ b/fuzzers/FRET/benchmark/build_all_demos.sh @@ -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 diff --git a/fuzzers/FRET/benchmark/logtail.sh b/fuzzers/FRET/benchmark/logtail.sh new file mode 100644 index 0000000000..56bc049ae6 --- /dev/null +++ b/fuzzers/FRET/benchmark/logtail.sh @@ -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 diff --git a/fuzzers/FRET/benchmark/plot_all_benchmarks.sh b/fuzzers/FRET/benchmark/plot_all_benchmarks.sh new file mode 100644 index 0000000000..e5ffadd510 --- /dev/null +++ b/fuzzers/FRET/benchmark/plot_all_benchmarks.sh @@ -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 \ No newline at end of file diff --git a/fuzzers/FRET/benchmark/plot_all_traces.sh b/fuzzers/FRET/benchmark/plot_all_traces.sh new file mode 100644 index 0000000000..001451e833 --- /dev/null +++ b/fuzzers/FRET/benchmark/plot_all_traces.sh @@ -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 diff --git a/fuzzers/FRET/benchmark/plot_comparison.r b/fuzzers/FRET/benchmark/plot_comparison.r new file mode 100644 index 0000000000..53d6ae4604 --- /dev/null +++ b/fuzzers/FRET/benchmark/plot_comparison.r @@ -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() \ No newline at end of file diff --git a/fuzzers/FRET/benchmark/plot_multi.r b/fuzzers/FRET/benchmark/plot_multi.r new file mode 100644 index 0000000000..337e9a2ef5 --- /dev/null +++ b/fuzzers/FRET/benchmark/plot_multi.r @@ -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 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 0xFFd0 && Data[i] < 0xFFFF) {return 1;} // cause qemu to crash + for (int j=i+1; jData[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); +} diff --git a/fuzzers/FRET/example/mps2_m3.ld b/fuzzers/FRET/example/mps2_m3.ld new file mode 100644 index 0000000000..adfc15ed78 --- /dev/null +++ b/fuzzers/FRET/example/mps2_m3.ld @@ -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") +} + diff --git a/fuzzers/FRET/example/startup.c b/fuzzers/FRET/example/startup.c new file mode 100644 index 0000000000..3b3acb56b8 --- /dev/null +++ b/fuzzers/FRET/example/startup.c @@ -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 ); +} + diff --git a/fuzzers/FRET/src/clock.rs b/fuzzers/FRET/src/clock.rs new file mode 100644 index 0000000000..fc965d67dd --- /dev/null +++ b/fuzzers/FRET/src/clock.rs @@ -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 +} + +impl QemuClockObserver { + /// Creates a new [`QemuClockObserver`] with the given name. + #[must_use] + pub fn new(name: &'static str, dump_path: Option) -> 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 Observer 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::(); + 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, + name: String, +} + +impl Feedback for ClockTimeFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor + HasMetadata, +{ + #[allow(clippy::wrong_self_convention)] + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + // TODO Replace with match_name_type when stable + let observer = observers.match_name::(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( + &mut self, + _state: &mut S, + observers: &OT, + testcase: &mut Testcase, + ) -> 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 Feedback for QemuClockIncreaseFeedback +where + S: State + UsesInput + HasNamedMetadata + MaybeHasClientPerfMonitor + Debug, +{ + fn is_interesting( + &mut self, + state: &mut S, + _manager: &mut EM, + _input: &S::Input, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let observer = _observers.match_name::("clock") + .expect("QemuClockObserver not found"); + let clock_state = state + .named_metadata_map_mut() + .get_mut::(&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(&mut self, _state: &mut S, observers: &OT, testcase: &mut Testcase) -> 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") + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/fuzzer.rs b/fuzzers/FRET/src/fuzzer.rs new file mode 100644 index 0000000000..ec199f1fca --- /dev/null +++ b/fuzzers/FRET/src/fuzzer.rs @@ -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::::try_into(i.p_vaddr).unwrap() + + TryInto::::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 { +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> { +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) -> HashMap> { +let mut api_addreses : HashMap> = 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, + +/// 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, + /// runtime in seconds + #[arg(short, long)] + time: Option, +} +} + +/// 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::() { + 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") { + 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::() { + let mut uniq: Vec = 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::(&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::(&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> = 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 = 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 = 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 = 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::::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 { 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::::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::(); 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::(); 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::().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::().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 = 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), + } + } +} diff --git a/fuzzers/FRET/src/lib.rs b/fuzzers/FRET/src/lib.rs new file mode 100644 index 0000000000..62aea9374f --- /dev/null +++ b/fuzzers/FRET/src/lib.rs @@ -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; \ No newline at end of file diff --git a/fuzzers/FRET/src/main.rs b/fuzzers/FRET/src/main.rs new file mode 100644 index 0000000000..7575ece678 --- /dev/null +++ b/fuzzers/FRET/src/main.rs @@ -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!"); +} diff --git a/fuzzers/FRET/src/mutational.rs b/fuzzers/FRET/src/mutational.rs new file mode 100644 index 0000000000..4a3d088368 --- /dev/null +++ b/fuzzers/FRET/src/mutational.rs @@ -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 { + 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 { + #[allow(clippy::type_complexity)] + phantom: PhantomData<(E, EM, Z)>, +} + +impl InterruptShiftStage +where + E: UsesState, + EM: UsesState, + Z: Evaluator, + Z::State: MaybeHasClientPerfMonitor + HasCorpus + HasRand, +{ + pub fn new() -> Self { + Self { phantom: PhantomData } + } +} + +impl Stage for InterruptShiftStage +where + E: UsesState, + EM: UsesState, + Z: Evaluator, + Z::State: MaybeHasClientPerfMonitor + HasCorpus + HasRand + HasMetadata + HasNamedMetadata, + ::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 = 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 = vec![]; + #[cfg(feature = "mutate_stg")] + { + let metadata = state.metadata_map(); + let hist = metadata.get::().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::().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 = 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") { + // Some(s) => s, + // None => { + // panic!("STGfeedbackstate not visible") + // } + // }; + // let tmp = _input.metadata_map().get::(); + // 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= 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::(); + 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 = 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::().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 = 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 = 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 UsesState for InterruptShiftStage +where + E: UsesState, + EM: UsesState, + Z: Evaluator, + Z::State: MaybeHasClientPerfMonitor + HasCorpus + HasRand, +{ + type State = Z::State; +} \ No newline at end of file diff --git a/fuzzers/FRET/src/qemustate.rs b/fuzzers/FRET/src/qemustate.rs new file mode 100644 index 0000000000..d815059f8f --- /dev/null +++ b/fuzzers/FRET/src/qemustate.rs @@ -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, + fastsnap: Option +} + +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 QemuHelper for QemuStateRestoreHelper +where + S: UsesInput, +{ + const HOOKS_DO_SIDE_EFFECTS: bool = true; + + fn init_hooks(&self, _hooks: &QemuHooks) + where + QT: QemuHelperTuple, + { + } + + fn first_exec(&self, _hooks: &QemuHooks) + where + QT: QemuHelperTuple, + { + } + + fn post_exec(&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()) }; + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/ARCH.md b/fuzzers/FRET/src/systemstate/ARCH.md new file mode 100644 index 0000000000..62ffe8981d --- /dev/null +++ b/fuzzers/FRET/src/systemstate/ARCH.md @@ -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 \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/feedbacks.rs b/fuzzers/FRET/src/systemstate/feedbacks.rs new file mode 100644 index 0000000000..b9a481da94 --- /dev/null +++ b/fuzzers/FRET/src/systemstate/feedbacks.rs @@ -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, // encounters,ticks,length + longest: Vec, +} +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>, + // known_traces: HashMap, +} + +impl Feedback for NovelSystemStateFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor + HasNamedMetadata, +{ + fn is_interesting( + &mut self, + state: &mut S, + manager: &mut EM, + input: &S::Input, + observers: &OT, + exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple + { + let observer = observers.match_name::("systemstate") + .expect("QemuSystemStateObserver not found"); + let clock_observer = observers.match_name::("clocktime") //TODO not fixed + .expect("QemuClockObserver not found"); + let feedbackstate = match state + .named_metadata_map_mut() + .get_mut::("systemstate") { + Some(s) => s, + None => { + let n=SystemStateFeedbackState::default(); + state.named_metadata_map_mut().insert(n, "systemstate"); + state.named_metadata_map_mut().get_mut::("systemstate").unwrap() + } + }; + // let feedbackstate = state + // .feedback_states_mut() + // .match_name_mut::("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(&mut self, _state: &mut S, observers: &OT, testcase: &mut Testcase) -> 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, + dump_metadata: bool, + last_states: Option>, + last_trace: Option>, +} + +impl Feedback for DumpSystraceFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor, +{ + fn is_interesting( + &mut self, + state: &mut S, + manager: &mut EM, + input: &S::Input, + observers: &OT, + exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple + { + if self.dumpfile.is_none() {return Ok(false)}; + let observer = observers.match_name::("systemstate") + .expect("QemuSystemStateObserver not found"); + let names : Vec = 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(&mut self, _state: &mut S, observers: &OT, testcase: &mut Testcase) -> 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) -> 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 Feedback for SystraceErrorFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor, +{ + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple + { + let observer = observers.match_name::("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(&mut self, _state: &mut S, _observers: &OT, _testcase: &mut Testcase) -> 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} + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/freertos.rs b/fuzzers/FRET/src/systemstate/freertos.rs new file mode 100644 index 0000000000..04e4338b5c --- /dev/null +++ b/fuzzers/FRET/src/systemstate/freertos.rs @@ -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); diff --git a/fuzzers/FRET/src/systemstate/graph.rs b/fuzzers/FRET/src/systemstate/graph.rs new file mode 100644 index 0000000000..2d59be87c4 --- /dev/null +++ b/fuzzers/FRET/src/systemstate/graph.rs @@ -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, // in the end any kind of input are bytes, regardless of type and lifetime +} +impl VariantTuple { + fn from(other: &ReducedFreeRTOSSystemState,input: Vec) -> 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, +} +impl SysGraphNode { + fn from(base: ReducedFreeRTOSSystemState, input: Vec) -> 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) -> 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) -> bool { + // if &self.base!=other {return false;} + // let interesting = + // self.variants.iter().all(|x| x.end_tick-x.start_tickother.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 &str { + &self.base.current_task.task_name + } + pub fn get_input_counts(&self) -> Vec { + 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 to attach as Metadata +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct SysGraphMetadata { + pub inner: Vec, + indices: Vec, + tcref: isize, +} +impl SysGraphMetadata { + pub fn new(inner: Vec) -> 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 = + MinimizerScheduler::State>,SysGraphMetadata>; + +//============================= Graph Feedback + +/// Improved System State Graph +#[derive(Serialize, Deserialize, Clone, Debug, SerdeAny)] +pub struct SysGraphFeedbackState +{ + pub graph: DiGraph, + entrypoint: NodeIndex, + exit: NodeIndex, + name: String, +} +impl SysGraphFeedbackState +{ + pub fn new() -> Self { + let mut graph = DiGraph::::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, input: &Vec) { + 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], return true if the path was interesting + fn update(&mut self, list: &Vec, input: &Vec) -> (bool, Vec) { + let mut current_index = self.entrypoint; + let mut novel = false; + let mut trace : Vec = vec![current_index]; + for n in list { + let mut matching : Option = 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>, +} +impl SysMapFeedback { + pub fn new() -> Self { + Self {name: String::from("SysMapFeedback"), last_trace: None } + } +} + +impl Feedback for SysMapFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor + HasNamedMetadata, + S::Input: HasTargetBytes, +{ + #[allow(clippy::wrong_self_convention)] + fn is_interesting( + &mut self, + state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let observer = observers.match_name::("systemstate") + .expect("QemuSystemStateObserver not found"); + let feedbackstate = match state + .named_metadata_map_mut() + .get_mut::("SysMap") { + Some(s) => s, + None => { + let n=SysGraphFeedbackState::default(); + state.named_metadata_map_mut().insert(n, "SysMap"); + state.named_metadata_map_mut().get_mut::("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(&mut self, _state: &mut S, _observers: &OT, testcase: &mut Testcase) -> 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 +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// phantom: PhantomData<(I, S)>, +// } +// impl RandGraphSnippetMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// pub fn new() -> Self { +// RandGraphSnippetMutator{phantom: PhantomData} +// } +// } +// impl Mutator for RandGraphSnippetMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// fn mutate( +// &mut self, +// state: &mut S, +// input: &mut I, +// _stage_idx: i32 +// ) -> Result +// { +// // 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::("SysMap") +// .unwrap(); +// let g = &feedbackstate.graph; +// let tmp = state.metadata().get::(); +// 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>= g[*trace.inner.last().unwrap()].variants.iter().map(|x| &x.input).collect(); +// let mut snippet_collector = vec![]; +// let mut per_input_counters = HashMap::<&Vec,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]>::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 = 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 +// ) -> Result<(), Error> { +// Ok(()) +// } +// } + +// impl Named for RandGraphSnippetMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// fn name(&self) -> &str { +// "RandGraphSnippetMutator" +// } +// } +// //=============================== Snippets +// pub struct RandInputSnippetMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// phantom: PhantomData<(I, S)>, +// } +// impl RandInputSnippetMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// pub fn new() -> Self { +// RandInputSnippetMutator{phantom: PhantomData} +// } +// } +// impl Mutator for RandInputSnippetMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// fn mutate( +// &mut self, +// state: &mut S, +// input: &mut I, +// _stage_idx: i32 +// ) -> Result +// { +// // 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::("SysMap") +// .unwrap(); +// let g = &feedbackstate.graph; +// let tmp = state.metadata().get::(); +// 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::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 +// ) -> Result<(), Error> { +// Ok(()) +// } +// } + +// impl Named for RandInputSnippetMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// fn name(&self) -> &str { +// "RandInputSnippetMutator" +// } +// } +// //=============================== Suffix +// pub struct RandGraphSuffixMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// phantom: PhantomData<(I, S)>, +// } +// impl RandGraphSuffixMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// pub fn new() -> Self { +// RandGraphSuffixMutator{phantom: PhantomData} +// } +// } +// impl Mutator for RandGraphSuffixMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// fn mutate( +// &mut self, +// state: &mut S, +// input: &mut I, +// _stage_idx: i32 +// ) -> Result +// { +// // 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::("SysMap") +// .unwrap(); +// let g = &feedbackstate.graph; +// let tmp = state.metadata().get::(); +// 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 = (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 +// ) -> Result<(), Error> { +// Ok(()) +// } +// } + +// impl Named for RandGraphSuffixMutator +// where +// I: Input + HasBytesVec, +// S: HasRand + HasMetadata + HasCorpus + HasSolutions, +// { +// fn name(&self) -> &str { +// "RandGraphSuffixMutator" +// } +// } \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/helpers.rs b/fuzzers/FRET/src/systemstate/helpers.rs new file mode 100644 index 0000000000..759e19e98b --- /dev/null +++ b/fuzzers/FRET/src/systemstate/helpers.rs @@ -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 = None; +pub static mut INTR_DONE : bool = true; + +// only used when inputs are injected +pub static mut NEXT_INPUT : Vec = 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, + api_fn_ranges: Vec<(String, std::ops::Range)>, + // Address of interrupt routines + isr_addrs: HashMap, + isr_ranges: Vec<(String, std::ops::Range)>, + 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, + app_range: Range, +} + +impl QemuSystemStateHelper { + #[must_use] + pub fn new( + api_fn_addrs: HashMap, + api_fn_ranges: Vec<(String, std::ops::Range)>, + isr_addrs: HashMap, + isr_ranges: Vec<(String, std::ops::Range)>, + 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, + app_range: Range, + ) -> 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 QemuHelper for QemuSystemStateHelper +where + S: UsesInput, +{ + fn first_exec(&self, _hooks: &QemuHooks) + where + QT: QemuHelperTuple, + { + // for wp in self.api_fn_addrs.keys() { + // _hooks.instruction(*wp, Hook::Function(exec_syscall_hook::), false); + // } + for wp in self.isr_addrs.keys() { + _hooks.instruction(*wp, Hook::Function(exec_isr_hook::), false); + } + _hooks.jmps(Hook::Function(gen_jmp_is_syscall::), Hook::Function(trace_jmp::)); + } + + // TODO: refactor duplicate code + fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) { + unsafe { + CURRENT_SYSTEMSTATE_VEC.clear(); + } + } + + fn post_exec(&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::(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::()).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::()).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( + hooks: &mut QemuHooks, + _state: Option<&mut S>, + pc: GuestAddr, +) +where + S: UsesInput, + QT: QemuHelperTuple, +{ + let emulator = hooks.emulator(); + let h = hooks.helpers().match_first_type::().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( + hooks: &mut QemuHooks, + _state: Option<&mut S>, + src: GuestAddr, + dest: GuestAddr, +) -> Option +where + S: UsesInput, + QT: QemuHelperTuple, +{ + if let Some(h) = hooks.helpers().match_first_type::() { + 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( + hooks: &mut QemuHooks, + _state: Option<&mut S>, + src: GuestAddr, mut dest: GuestAddr, id: u64 +) +where + S: UsesInput, + QT: QemuHelperTuple, +{ + let h = hooks.helpers().match_first_type::().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)>, addr : GuestAddr) -> Option<&'a std::ops::Range> { + for (_,r) in ranges { + if r.contains(&addr) {return Some(r);} + } + return None; +} \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/mod.rs b/fuzzers/FRET/src/systemstate/mod.rs new file mode 100644 index 0000000000..a18c14be22 --- /dev/null +++ b/fuzzers/FRET/src/systemstate/mod.rs @@ -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, + input_counter: u32, + edge: (GuestAddr,GuestAddr), + capture_point: (CaptureEvent,String) +} +/// List of system state dumps from QemuHelpers +static mut CURRENT_SYSTEMSTATE_VEC: Vec = 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(&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::(); + 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::(); + 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, + delay_list_after: Vec, + // edge: (Option,Option), + // 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(&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 +} + +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 to attach as Metadata +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct FreeRTOSSystemStateMetadata { + pub inner: Vec, + trace_length: usize, + indices: Vec, // Hashed enumeration of States + tcref: isize, +} +impl FreeRTOSSystemStateMetadata { + pub fn new(inner: Vec) -> 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(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, + level: u8, +} + +impl Hash for AtomicBasicBlock { + fn hash(&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 { + 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::>().into_iter().collect::>(); + 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::>().into_iter().collect::>(); + 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) -> HashSet { + 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); \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/observers.rs b/fuzzers/FRET/src/systemstate/observers.rs new file mode 100644 index 0000000000..ab4c3cc933 --- /dev/null +++ b/fuzzers/FRET/src/systemstate/observers.rs @@ -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, + pub last_states: HashMap, + pub last_trace: Vec, + pub last_input: Vec, + pub success: bool, + name: String, +} + +impl Observer 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 from cache. Consumes the elements from cache +fn tcb_list_to_vec_cached(list: List_t, dump: &mut HashMap) -> Vec +{ + let mut ret : Vec = 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) -> (Vec, 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::::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:: = 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:: = 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, meta: Vec<(u64, CaptureEvent, String, (u32, u32))>) -> (Vec, HashMap, bool) { + if trace.len() == 0 {return (Vec::new(), HashMap::new(), true);} + let mut isr_stack : VecDeque = 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 = vec![]; + let mut edges: Vec<(u32, u32)> = vec![]; + let mut last_hash : u64 = trace[0].get_hash(); + let mut table : HashMap = 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, table: &HashMap, edges: &Vec<(u32, u32)>) -> bool { + let mut ret = true; + let mut task_has_started : HashSet = HashSet::new(); + let mut wip_abb_trace : Vec>> = 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, Option))>) { + 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) { + 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) { + 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; + } + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/systemstate/schedulers.rs b/fuzzers/FRET/src/systemstate/schedulers.rs new file mode 100644 index 0000000000..d7b56e5aa6 --- /dev/null +++ b/fuzzers/FRET/src/systemstate/schedulers.rs @@ -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 { + base: CS, + skip_non_favored_prob: u64, +} + +impl UsesState for LongestTraceScheduler +where + CS: UsesState, +{ + type State = CS::State; +} + +impl Scheduler for LongestTraceScheduler +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::().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<::Input>, + // ) -> Result<(), Error> { + // let l = state.corpus() + // .get(idx)? + // .borrow() + // .metadata() + // .get::().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::Input>>, + // ) -> Result<(), Error> { + // self.base.on_remove(state, idx, testcase)?; + // Ok(()) + // } + + /// Gets the next entry + fn next(&mut self, state: &mut CS::State) -> Result { + let mut idx = self.base.next(state)?; + while { + let l = state.corpus() + .get(idx)? + .borrow() + .metadata_map() + .get::().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 LongestTraceScheduler +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::() { + 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 { + phantom: PhantomData, + gen_size: usize, +} + +impl UsesState for GenerationScheduler +where + S: State + UsesInput, +{ + type State = S; +} + +impl Scheduler for GenerationScheduler +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 { + 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::().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::() { + 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<::Input> + // ) -> Result<(), Error> { + // // println!("On Replace {_idx}"); + // Ok(()) + // } + + // fn on_remove( + // &self, + // state: &mut Self::State, + // idx: usize, + // _testcase: &Option::Input>> + // ) -> Result<(), Error> { + // // println!("On Remove {idx}"); + // if let Some(gm) = state.metadata_mut().get_mut::() { + // gm.next_gen = gm.next_gen.drain(..).into_iter().filter(|x| (*x).0 != idx).collect::>(); + // gm.current_gen = gm.current_gen.drain(..).into_iter().filter(|x| (*x).0 != idx).collect::>(); + // } else { + // state.add_metadata(GeneticMetadata::new(vec![], vec![])); + // } + // Ok(()) + // } +} + +impl GenerationScheduler +{ + pub fn new() -> Self { + Self { + phantom: PhantomData, + gen_size: 100, + } + } +} diff --git a/fuzzers/FRET/src/systemstate/stg.rs b/fuzzers/FRET/src/systemstate/stg.rs new file mode 100644 index 0000000000..dd4c2d5fcf --- /dev/null +++ b/fuzzers/FRET/src/systemstate/stg.rs @@ -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, + systemstate_index: HashMap, + pub state_abb_hash_index: HashMap<(u64, u64), NodeIndex>, + stgnode_index: HashMap, + entrypoint: NodeIndex, + exitpoint: NodeIndex, + // Metadata about aggregated traces. aggegated meaning, order has been removed + worst_observed_per_aggegated_path: HashMap,u64>, + worst_observed_per_abb_path: HashMap, + worst_observed_per_stg_path: HashMap, + worst_abb_exec_count: HashMap +} + +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 to attach as Metadata +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct STGNodeMetadata { + pub nodes: Vec, + pub edges: Vec, + pub abbs: u64, + pub aggregate: u64, + pub top_abb_counts: Vec, + pub intervals: Vec, + indices: Vec, + tcref: isize, +} +impl STGNodeMetadata { + pub fn new(nodes: Vec, edges: Vec, abbs: u64, aggregate: u64, top_abb_counts: Vec, intervals: Vec) -> 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 = + MinimizerScheduler::State>,STGNodeMetadata>; + +// AI generated, human verified +fn count_occurrences(vec: &Vec) -> 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>, + last_edge_trace: Option>, + last_intervals: Option>, + last_abbs_hash: Option, // only set, if it was interesting + last_aggregate_hash: Option, // only set, if it was interesting + last_top_abb_hashes: Option>, // only set, if it was interesting + dump_path: Option +} +#[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) { + 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(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) -> 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, table: &HashMap, fbs: &mut STGFeedbackState) -> (Vec, Vec, 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) -> Vec { + 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 Feedback for StgFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor + HasNamedMetadata, + S::Input: HasTargetBytes, +{ + #[allow(clippy::wrong_self_convention)] + fn is_interesting( + &mut self, + state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let observer = observers.match_name::("systemstate") + .expect("QemuSystemStateObserver not found"); + let clock_observer = observers.match_name::("clocktime") + .expect("QemuClockObserver not found"); + let feedbackstate = match state + .named_metadata_map_mut() + .get_mut::("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").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(&mut self, _state: &mut S, _observers: &OT, testcase: &mut Testcase) -> 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 + } +} \ No newline at end of file diff --git a/fuzzers/FRET/src/worst.rs b/fuzzers/FRET/src/worst.rs new file mode 100644 index 0000000000..b40062d232 --- /dev/null +++ b/fuzzers/FRET/src/worst.rs @@ -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 = + MinimizerScheduler::State>, MapIndexesMetadata>; + +/// Multiply the testcase size with the execution time. +/// This favors small and quick testcases. +#[derive(Debug, Clone)] +pub struct MaxTimeFavFactor +where + S: HasCorpus + HasMetadata, + S::Input: HasLen, +{ + phantom: PhantomData, +} + +impl TestcaseScore for MaxTimeFavFactor +where + S: HasCorpus + HasMetadata, + S::Input: HasLen, +{ + fn compute(state: &S, entry: &mut Testcase<::Input>) -> Result { + // 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 = + MinimizerScheduler::State>, MapIndexesMetadata>; + +pub type TimeStateMaximizerCorpusScheduler = + MinimizerScheduler::State>, FreeRTOSSystemStateMetadata>; + +/// Multiply the testcase size with the execution time. +/// This favors small and quick testcases. +#[derive(Debug, Clone)] +pub struct MaxExecsLenFavFactor +where + S: HasCorpus + HasMetadata, + S::Input: HasLen, +{ + phantom: PhantomData, +} + +impl TestcaseScore for MaxExecsLenFavFactor +where + S: HasCorpus + HasMetadata, + S::Input: HasLen, +{ + fn compute( state: &S, entry: &mut Testcase) -> Result { + 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 Feedback for SortedFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor, + S::Input: HasTargetBytes, +{ + #[allow(clippy::wrong_self_convention)] + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let t = _input.target_bytes(); + let tmp = t.as_slice(); + if tmp.len()<32 {return Ok(false);} + let tmp = Vec::::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[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 Feedback for ExecTimeReachedFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor, +{ + #[allow(clippy::wrong_self_convention)] + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let observer = observers.match_name::("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 = Vec::new(); + +/// A Noop Feedback which records a list of all execution times +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ExecTimeCollectorFeedback +{ +} + +impl Feedback for ExecTimeCollectorFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor, +{ + #[allow(clippy::wrong_self_convention)] + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let observer = observers.match_name::("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, +} +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 Feedback for ExecTimeIncFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor, +{ + #[allow(clippy::wrong_self_convention)] + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let observer = observers.match_name::("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( + &mut self, + _state: &mut S, + observers: &OT, + testcase: &mut Testcase<::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 Feedback for AlwaysTrueFeedback +where + S: State + UsesInput + MaybeHasClientPerfMonitor, +{ + #[allow(clippy::wrong_self_convention)] + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + 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 { + } + } +} \ No newline at end of file diff --git a/fuzzers/FRET/tests/.gitignore b/fuzzers/FRET/tests/.gitignore new file mode 100644 index 0000000000..db7327fa5a --- /dev/null +++ b/fuzzers/FRET/tests/.gitignore @@ -0,0 +1,5 @@ +dump +demo* +*.dot +*.time +*.case diff --git a/fuzzers/FRET/tests/run_test.sh b/fuzzers/FRET/tests/run_test.sh new file mode 100644 index 0000000000..11c5bf3a33 --- /dev/null +++ b/fuzzers/FRET/tests/run_test.sh @@ -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 \ No newline at end of file diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index e8a8c26b50..8a4d86cea9 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -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 { + 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 { + 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 diff --git a/libafl/src/schedulers/minimizer.rs b/libafl/src/schedulers/minimizer.rs index 6ba37cdbc0..71ca7f0763 100644 --- a/libafl/src/schedulers/minimizer.rs +++ b/libafl/src/schedulers/minimizer.rs @@ -59,6 +59,15 @@ impl TopRatedsMetadata { pub fn map(&self) -> &HashMap { &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::()); + if must_remove { + // drop(old.metadata_map_mut().remove::()); } } @@ -333,6 +342,7 @@ where .map .insert(elem, idx); } + println!("Number of interesting corpus elements: {}", state.metadata_map_mut().get::().unwrap().get_number()); Ok(()) } diff --git a/libafl_qemu/libafl_qemu_sys/src/systemmode.rs b/libafl_qemu/libafl_qemu_sys/src/systemmode.rs index 85b02d58d4..bb0a8a56fb 100644 --- a/libafl_qemu/libafl_qemu_sys/src/systemmode.rs +++ b/libafl_qemu/libafl_qemu_sys/src/systemmode.rs @@ -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 u64>, // data,src,dest + exec: Option, // data,src,dest,id + data: u64 + ) -> usize; } diff --git a/libafl_qemu/src/arch/arm.rs b/libafl_qemu/src/arch/arm.rs index 3b46323040..c34eb25e00 100644 --- a/libafl_qemu/src/arch/arm.rs +++ b/libafl_qemu/src/arch/arm.rs @@ -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)] diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs index 95a8193987..503d0e484a 100644 --- a/libafl_qemu/src/hooks.rs +++ b/libafl_qemu/src/hooks.rs @@ -370,6 +370,12 @@ where } } +static mut JMP_HOOKS: Vec> = 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, + Box< + dyn for<'a> FnMut( + &'a mut Self, + Option<&'a mut S>, + GuestAddr, + GuestAddr, + ) -> Option, + >, + 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 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::, + extern "C" fn(&mut HookState<1>, src: GuestAddr, dest: GuestAddr) -> u64 + ); + let exec = get_raw_hook!( + execution_hook, + jmp_0_exec_hook_wrapper::, + 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 + } + } } diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index 7e338aad57..b102e721a8 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -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>( + &self, + data: T, + gen: Option u64>, + exec: Option, +) -> HookId { + unsafe { + let data: u64 = data.into().0; + let gen: Option u64> = + core::mem::transmute(gen); + let exec: Option = core::mem::transmute(exec); + let num = libafl_add_jmp_hook(gen, exec, data); + HookId(num) + } +} + #[derive(Debug)] pub enum QemuInitError { MultipleInstances, diff --git a/libafl_qemu/src/qemu/systemmode.rs b/libafl_qemu/src/qemu/systemmode.rs index 5cdb2f8366..e21fe03c86 100644 --- a/libafl_qemu/src/qemu/systemmode.rs +++ b/libafl_qemu/src/qemu/systemmode.rs @@ -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 { + libafl_start_int_timer(); vm_start(); qemu_main_loop();