Compare commits

..

84 Commits

Author SHA1 Message Date
52d927dc61 incomplete merge 2022-12-08 09:15:27 +01:00
8a661de371 add missing benchmark script 2022-09-07 21:28:40 +02:00
e8ff45b205 update evaluation scripts 2022-07-10 16:21:50 +02:00
aa7d78affe finish eval scripts 2022-06-14 23:54:47 +02:00
948ce540e8 parallel benchmarks with semaphores 2022-06-08 02:21:47 +02:00
6969b5de5a add random generator fuzzer 2022-06-08 02:21:17 +02:00
4dabfc5f00 Add InterruptShifterMutator
Also add features fuzz_interrupt and muta_interrupt,
which dictate if interrupts are used during fuzzing

Also allow makefile to skip finished iterations
2022-06-07 01:50:21 +02:00
eb3914e5c1 introduce INPUT_BYTES_OFFSET constant 2022-06-06 00:42:45 +02:00
2466fc5cb6 add a testcase minimizer 2022-06-01 23:46:23 +02:00
eabfe020b6 update info duming and makefile 2022-06-01 22:09:39 +02:00
2e81f386ee change u8 to u16 2022-06-01 22:02:19 +02:00
5b4f730187 add feed_known_edges, fix TimeMaximizerCorpusScheduler 2022-06-01 15:55:17 +02:00
d1c685ccf9 prepare for benchmarking
new features for mutators
fuzzer corpus in memory for benchmarking
showmap single file input
2022-05-29 00:45:17 +02:00
6a18fa75f4 add RandInputSnippetMutator 2022-05-29 00:43:02 +02:00
ee4056a008 add objective selection, dump exec times 2022-05-27 01:10:12 +02:00
14e8a81d17 fixup cargo.toml 2022-05-27 00:36:25 +02:00
f994bc62bc add jmp_as_edge cfg, extend libafl_exec_jmp_hook 2022-05-26 23:55:53 +02:00
517b3d3da7 add instrumentation call after breakpoint 2022-05-26 16:11:30 +02:00
86ca7863ca add RandGraphSuffixMutator 2022-05-08 13:45:10 +02:00
5d28754e1b add Makefile for benchmarks 2022-05-04 22:56:40 +02:00
70dec21c8c add feature flags for fuzz components 2022-05-04 22:55:26 +02:00
18a592d763 update clock to time calc 2022-05-04 21:11:39 +02:00
865e97cca7 fuzz for sulution with timeout 2022-05-04 21:11:19 +02:00
01e40ded1d fix interrupt input, graph output 2022-05-01 22:07:37 +02:00
0b638d9020 new setup for interrupts 2022-04-26 00:35:11 +02:00
67165640c5 fuzz until first solution 2022-04-24 20:47:30 +02:00
6551fc31f4 add interrupts 2022-04-19 23:43:58 +02:00
5c4238e0ee remove output from clock observer 2022-04-11 22:59:06 +02:00
865f2786a8 add jmp instrumentaion 2022-04-11 22:58:43 +02:00
fc4b72274e remember branches from app to os 2022-04-08 00:53:05 +02:00
47a4bc19d4 save last post interrupt edge in app 2022-04-06 21:03:21 +02:00
e8b24b0c7a remove broken imports 2022-04-05 19:05:36 +02:00
4e3acf85ad add RandGraphSnippetMutator 2022-03-23 01:37:04 +01:00
66babddb02 add graph trace metadata, scheduler 2022-03-21 00:00:32 +01:00
c92cbe78d8 add interesting metrics, reorganize 2022-03-20 21:29:25 +01:00
1d0c43081a add graph nodes 2022-03-17 22:37:14 +01:00
89cf096b9d add input_counter to system state 2022-03-15 16:47:00 +01:00
0693422e44 add SysMapFeedback 2022-03-09 12:30:23 +01:00
8691c77eb2 small fixes 2022-02-27 23:15:59 +01:00
5176828dbb add TimeStateMaximizerCorpusScheduler 2022-02-27 23:14:04 +01:00
71280b89af add multicore feature 2022-02-27 23:03:10 +01:00
5d08f3a9d7 make multicore optional 2022-02-27 22:16:55 +01:00
a6294af2c3 add HitSysStateFeedback 2022-02-22 23:14:05 +01:00
5df99365f6 switch to simple stages 2022-02-22 16:50:17 +01:00
f5bf5605f1 split system_state module, add tracedump 2022-02-22 16:49:16 +01:00
53bd755647 add clock count to systemstate feedback 2022-02-22 00:10:17 +01:00
ffdaf22b1d fix byte input reading 2022-02-22 00:09:15 +01:00
b73a971c51 add sysstate feedback 2022-02-17 19:47:18 +01:00
b85e0a6d5b parse raw freertos system state 2022-02-16 23:36:23 +01:00
5bf3269c8f slim down generated code 2022-02-16 15:18:45 +01:00
1650545424 emu lookup as macro 2022-02-15 23:39:48 +01:00
44faa80556 dump ready lists 2022-02-15 23:23:52 +01:00
be1ead84f4 fix some types 2022-02-13 20:20:09 +01:00
c3f398a315 prepare system state observer+feedback 2022-02-13 20:19:50 +01:00
819f759be9 fix GEN_BLOCK_HOOK_PTR 2022-02-13 20:18:27 +01:00
bec4743978 make generic edge-map post-processor 2022-02-11 19:44:12 +01:00
c252d6cad0 redirect log 2022-02-11 15:15:01 +01:00
1713824c3b fuzzer parallelization 2022-02-11 15:15:01 +01:00
5c5f1f77bd add feedback for decreasing bytes 2022-02-11 15:14:54 +01:00
d7c0193a5e add debug prints 2022-02-10 14:41:24 +01:00
2c3e1a4e4d no target map -> false 2022-02-10 14:41:04 +01:00
64dfd6a642 showmap debug options 2022-02-10 14:40:42 +01:00
99bd30c233 add snapshot path, debug prints 2022-02-09 22:17:27 +01:00
9369eae37b crate structure 2022-02-09 12:55:21 +01:00
dbd6391b59 switch scheduler 2022-02-09 11:56:20 +01:00
06d382bff8 Various improvements
Replace TimeObserver with ClockObserver
Add a Maximization Scheduler for Clocks
Factor out icount Metadata from Feedback
Allow Breakpoint removal in systemmode
2022-02-06 19:42:07 +01:00
2ead941419 remove some unecessary stuff 2022-02-03 21:53:04 +01:00
1edc70c6f1 limit to 32 bytes 2022-02-03 21:43:09 +01:00
9a1251875f small improvements 2022-02-03 21:21:14 +01:00
b3b8e81190 add QemuClockObserver 2022-01-31 19:39:22 +01:00
ba85877ab4 fix elf path 2022-01-28 11:35:05 +01:00
8a82ecac9d fix missing insert 2022-01-26 23:17:12 +01:00
8676342776 generalize system state hook 2022-01-26 23:14:38 +01:00
44a32398d9 proof of concept syscall-hook tcb tracer 2022-01-23 22:23:14 +01:00
29f89b4b29 disable syscall hooks in systemmode 2022-01-23 22:21:08 +01:00
e0f197424c wrap emu memory lookup 2022-01-21 20:09:36 +01:00
45bdab5000 struct parsing experiment 2022-01-21 00:17:17 +01:00
d4d86927b7 add feedback for improving msd 2022-01-17 20:51:52 +01:00
d2d2862727 showmap dumps edges to file 2022-01-17 18:45:53 +01:00
ac181eb99d add some basic fuzzer 2022-01-16 11:29:45 +01:00
c1db0752c1 remove hardcoded addresses 2022-01-16 11:29:45 +01:00
de4481e70d add a basic demo 2022-01-16 11:29:45 +01:00
edff095401 Add systemmode to libafl_qemu
Use the new configuration feature systemmode
2022-01-16 11:29:45 +01:00
d460bab404 TO_REMOVE relative qemu_path 2022-01-16 11:29:45 +01:00
122 changed files with 4102 additions and 5388 deletions

4
.gitignore vendored
View File

@ -18,7 +18,6 @@ vendor
*.dll
*.exe
*.dSYM
*.obj
.cur_input
.venv
@ -51,6 +50,3 @@ __pycache__
**/libxml2
**/corpus_discovered
**/libxml2-*.tar.gz
libafl_nyx/QEMU-Nyx
libafl_nyx/packer

View File

@ -6,7 +6,6 @@ members = [
"libafl_targets",
"libafl_frida",
"libafl_qemu",
"libafl_tinyinst",
"libafl_sugar",
"libafl_nyx",
"libafl_concolic/symcc_runtime",

View File

@ -72,9 +72,6 @@ COPY scripts/dummy.rs libafl_concolic/symcc_libafl/src/lib.rs
COPY libafl_nyx/Cargo.toml libafl_nyx/build.rs libafl_nyx/
COPY scripts/dummy.rs libafl_nyx/src/lib.rs
COPY libafl_tinyinst/Cargo.toml libafl_tinyinst/
COPY scripts/dummy.rs libafl_tinyinst/src/lib.rs
COPY utils utils
RUN cargo build && cargo build --release

View File

@ -34,7 +34,6 @@ LibAFL offers integrations with popular instrumentation frameworks. At the momen
+ SanitizerCoverage, in [libafl_targets](./libafl_targets)
+ Frida, in [libafl_frida](./libafl_frida)
+ QEMU user-mode, in [libafl_qemu](./libafl_qemu)
+ TinyInst, in [libafl_tinyinst](./libafl_tinyinst) by [elbiazo](https://github.com/elbiazo)
## Getting started

View File

@ -1,41 +0,0 @@
[package]
name = "fret"
version = "0.8.2"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
edition = "2021"
[features]
default = ["std", "snapshot_restore", "singlecore", "restarting", "feed_systemtrace", "fuzz_int" ]
std = []
snapshot_restore = []
snapshot_fast = [ "snapshot_restore" ]
singlecore = []
restarting = ['singlecore']
trace_abbs = []
systemstate = []
feed_systemgraph = [ "systemstate" ]
feed_systemtrace = [ "systemstate" ]
feed_longest = [ ]
feed_afl = [ ]
feed_genetic = [ ]
fuzz_int = [ ]
gensize_1 = [ ]
gensize_10 = [ ]
gensize_100 = [ ]
observer_hitcounts = []
no_hash_state = []
run_until_saturation = []
[profile.release]
lto = true
codegen-units = 1
debug = true
[dependencies]
libafl = { path = "../../libafl/" }
libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] }
serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib
hashbrown = { version = "0.12", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible
petgraph = { version="0.6.0", features = ["serde-1"] }
ron = "0.7" # write serialized data - including hashmaps
rand = "0.5"

View File

@ -1,26 +0,0 @@
# 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.

View File

@ -1,12 +0,0 @@
*dump
timedump*
corpora
build
mnt
.R*
*.png
*.pdf
bins
.snakemake
*.zip
*.tar.*

View File

@ -1,57 +0,0 @@
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

View File

@ -1,281 +0,0 @@
import csv
import os
def_flags="--no-default-features --features std,snapshot_restore,singlecore,restarting,run_until_saturation"
remote="timedump_253048_1873f6_all/"
RUNTIME=10
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},systemstate"
rule build_random:
output:
directory("bins/target_random")
shell:
"cargo build --target-dir {output} {def_flags},feed_longest"
rule build_feedlongest:
output:
directory("bins/target_feedlongest")
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},feed_afl,feed_longest"
rule build_afl:
output:
directory("bins/target_afl")
shell:
"cargo build --target-dir {output} {def_flags},feed_afl,observer_hitcounts"
rule build_state:
output:
directory("bins/target_state")
shell:
"cargo build --target-dir {output} {def_flags},feed_systemtrace"
rule build_nohashstate:
output:
directory("bins/target_nohashstate")
shell:
"cargo build --target-dir {output} {def_flags},feed_systemtrace,no_hash_state"
rule build_graph:
output:
directory("bins/target_graph")
shell:
"cargo build --target-dir {output} {def_flags},feed_systemgraph"
rule build_showmap_int:
output:
directory("bins/target_showmap_int")
shell:
"cargo build --target-dir {output} {def_flags},systemstate,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_state_int:
output:
directory("bins/target_state_int")
shell:
"cargo build --target-dir {output} {def_flags},feed_systemtrace,fuzz_int"
rule build_nohashstate_int:
output:
directory("bins/target_nohashstate_int")
shell:
"cargo build --target-dir {output} {def_flags},feed_systemtrace,fuzz_int,no_hash_state"
rule build_frafl_int:
output:
directory("bins/target_frafl_int")
shell:
"cargo build --target-dir {output} {def_flags},feed_afl,feed_longest,fuzz_int"
rule build_afl_int:
output:
directory("bins/target_afl_int")
shell:
"cargo build --target-dir {output} {def_flags},feed_afl,fuzz_int,observer_hitcounts"
rule build_feedlongest_int:
output:
directory("bins/target_feedlongest_int")
shell:
"cargo build --target-dir {output} {def_flags},feed_longest,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},feed_genetic,gensize_100"
rule build_feedgeneration100_int:
output:
directory("bins/target_feedgeneration100_int")
shell:
"cargo build --target-dir {output} {def_flags},feed_genetic,fuzz_int,gensize_100"
rule run_bench:
input:
"build/{target}.elf",
"bins/target_{fuzzer}"
output:
multiext("timedump/{fuzzer}/{target}.{num}", "", ".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']
script="""
mkdir -p $(dirname {output[0]})
export KERNEL=$(pwd)/{input[0]}
export FUZZ_MAIN={fuzz_main}
export FUZZ_INPUT={fuzz_input}
export FUZZ_INPUT_LEN={fuzz_len}
export BREAKPOINT={bkp}
export SEED_RANDOM={wildcards.num}
export TIME_DUMP=$(pwd)/{output[0]}
export CASE_DUMP=$(pwd)/{output[0]}.case
export TRACE_DUMP=$(pwd)/{output[0]}.trace
export FUZZ_ITERS={RUNTIME}
export FUZZER=$(pwd)/{input[1]}/debug/fret
set +e
../fuzzer.sh > {output[1]} 2>&1
exit 0
"""
if wildcards.fuzzer.find('random') >= 0:
script="export FUZZ_RANDOM={output[1]}\n"+script
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}.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})
export KERNEL=$(pwd)/{input[0]}
export FUZZ_MAIN={fuzz_main}
export FUZZ_INPUT={fuzz_input}
export FUZZ_INPUT_LEN={fuzz_len}
export BREAKPOINT={bkp}
export TRACE_DUMP=$(pwd)/{output[0]}
export DO_SHOWMAP=$(pwd)/{input[3]}
export TIME_DUMP=$(pwd)/{output[1]}
set +e
../fuzzer.sh
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_bins:
input:
expand("bins/target_{target}{flag}",target=['random','afl','frafl','state','feedgeneration100'],flag=['','_int'])

View File

@ -1,83 +0,0 @@
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()

View File

@ -1,327 +0,0 @@
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="timedump_253048_1873f6_all/timedump"
target="waters_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)]
SAVE_FILE=TRUE
}
worst_cases <- list(waters=0, waters_int=0, tmr=405669, micro_longint=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]*$"
#RIBBON='sd'
#RIBBON='span'
RIBBON='both'
DRAW_WC = worst_case > 0
LEGEND_POS="topright"
#LEGEND_POS="bottomright"
CONTINUE_LINE_TO_END=FALSE
# https://www.r-bloggers.com/2013/04/how-to-change-the-alpha-value-of-colours-in-r/
alpha <- function(col, alpha=1){
if(missing(col))
stop("Please provide a vector of colours.")
apply(sapply(col, col2rgb)/255, 2,
function(x)
rgb(x[1], x[2], x[3], alpha=alpha))
}
# Trimm a list of data frames to common length
trim_data <- function(input,len=NULL) {
if (is.null(len)) {
len <- min(sapply(input, function(v) dim(v)[1]))
}
return(lapply(input, function(d) slice_head(d,n=len)))
}
length_of_data <- function(input) {
min(sapply(input, function(v) dim(v)[1]))
}
# Takes a flat list
trace2maxline <- function(tr) {
maxline = tr
for (var in seq_len(length(maxline))[2:length(maxline)]) {
#if (maxline[var]>1000000000) {
# maxline[var]=maxline[var-1]
#} else {
maxline[var] = max(maxline[var],maxline[var-1])
#}
}
#plot(seq_len(length(maxline)),maxline,"l",xlab="Index",ylab="WOET")
return(maxline)
}
# Take a list of data frames, output same form but maxlines
data2maxlines <- function(tr) {
min_length <- min(sapply(tr, function(v) dim(v)[1]))
maxline <- tr
for (var in seq_len(length(tr))) {
maxline[[var]][[1]]=trace2maxline(tr[[var]][[1]])
}
return(maxline)
}
# Take a multi-column data frame, output same form but maxlines
frame2maxlines <- function(tr) {
for (var in seq_len(length(tr))) {
tr[[var]]=trace2maxline(tr[[var]])
}
return(tr)
}
trace2maxpoints <- function(tr) {
minval = tr[1,1]
collect = tr[1,]
for (i in seq_len(dim(tr)[1])) {
if (minval < tr[i,1]) {
collect = rbind(collect,tr[i,])
minval = tr[i,1]
}
}
tmp = tr[dim(tr)[1],]
tmp[1] = minval[1]
collect = rbind(collect,tmp)
return(collect)
}
sample_maxpoints <- function(tr,po) {
index = 1
collect=NULL
endpoint = dim(tr)[1]
for (p in po) {
if (p<=tr[1,2]) {
tmp = tr[index,]
tmp[2] = p
collect = rbind(collect, tmp)
} else if (p>=tr[endpoint,2]) {
tmp = tr[endpoint,]
tmp[2] = p
collect = rbind(collect, tmp)
} else {
for (i in seq(index,endpoint)-1) {
if (p >= tr[i,2] && p<tr[i+1,2]) {
tmp = tr[i,]
tmp[2] = p
collect = rbind(collect, tmp)
index = i
break
}
}
}
}
return(collect)
}
#https://www.r-bloggers.com/2012/01/parallel-r-loops-for-windows-and-linux/
all_runtypetables <- foreach (bn=BASENAMES) %do% {
runtypefiles <- list.files(file.path(BENCHDIR,bn),pattern=sprintf(PATTERNS,target),full.names = TRUE)
if (length(runtypefiles) > 0) {
runtypetables_reduced <- foreach(i=seq_len(length(runtypefiles))) %dopar% {
rtable = read.csv(runtypefiles[[i]], col.names=c(sprintf("%s%d",bn,i),sprintf("times%d",i)))
trace2maxpoints(rtable)
}
#runtypetables <- lapply(seq_len(length(runtypefiles)),
# function(i)read.csv(runtypefiles[[i]], col.names=c(sprintf("%s%d",bn,i),sprintf("times%d",i))))
#runtypetables_reduced <- lapply(runtypetables, trace2maxpoints)
runtypetables_reduced
#all_runtypetables = c(all_runtypetables, list(runtypetables_reduced))
}
}
all_runtypetables = all_runtypetables[lapply(all_runtypetables, length) > 0]
all_min_points = foreach(rtt=all_runtypetables,.combine = cbind) %do% {
bn = substr(names(rtt[[1]])[1],1,nchar(names(rtt[[1]])[1])-1)
ret = data.frame(min(unlist(lapply(rtt, function(v) v[dim(v)[1],2]))))
names(ret)[1] = bn
ret/(3600 * 1000)
}
all_max_points = foreach(rtt=all_runtypetables,.combine = cbind) %do% {
bn = substr(names(rtt[[1]])[1],1,nchar(names(rtt[[1]])[1])-1)
ret = data.frame(max(unlist(lapply(rtt, function(v) v[dim(v)[1],2]))))
names(ret)[1] = bn
ret/(3600 * 1000)
}
all_points = sort(unique(Reduce(c, lapply(all_runtypetables, function(v) Reduce(c, lapply(v, function(w) w[[2]]))))))
all_maxlines <- foreach (rtt=all_runtypetables) %do% {
bn = substr(names(rtt[[1]])[1],1,nchar(names(rtt[[1]])[1])-1)
runtypetables_sampled = foreach(v=rtt) %dopar% {
sample_maxpoints(v, all_points)[1]
}
#runtypetables_sampled = lapply(rtt, function(v) sample_maxpoints(v, all_points)[1])
tmp_frame <- Reduce(cbind, runtypetables_sampled)
statframe <- data.frame(rowMeans(tmp_frame),apply(tmp_frame, 1, sd),apply(tmp_frame, 1, min),apply(tmp_frame, 1, max), apply(tmp_frame, 1, median))
names(statframe) <- c(bn, sprintf("%s_sd",bn), sprintf("%s_min",bn), sprintf("%s_max",bn), sprintf("%s_med",bn))
#statframe[sprintf("%s_times",bn)] = all_points
round(statframe)
#all_maxlines = c(all_maxlines, list(round(statframe)))
}
one_frame<-data.frame(all_maxlines)
one_frame[length(one_frame)+1] <- all_points/(3600 * 1000)
names(one_frame)[length(one_frame)] <- 'time'
typenames = names(one_frame)[which(names(one_frame) != 'time')]
typenames = typenames[which(!endsWith(typenames, "_sd"))]
typenames = typenames[which(!endsWith(typenames, "_med"))]
ylow=min(one_frame[typenames])
yhigh=max(one_frame[typenames],worst_case)
typenames = typenames[which(!endsWith(typenames, "_min"))]
typenames = typenames[which(!endsWith(typenames, "_max"))]
ml2lines <- function(ml,lim) {
lines = NULL
last = 0
for (i in seq_len(dim(ml)[1])) {
if (!CONTINUE_LINE_TO_END && lim<ml[i,2]) {
break
}
lines = rbind(lines, cbind(X=last, Y=ml[i,1]))
lines = rbind(lines, cbind(X=ml[i,2], Y=ml[i,1]))
last = ml[i,2]
}
return(lines)
}
plotting <- function(selection, filename, MY_COLORS_) {
# filter out names of iters and sd cols
typenames = names(one_frame)[which(names(one_frame) != 'times')]
typenames = typenames[which(!endsWith(typenames, "_sd"))]
typenames = typenames[which(!endsWith(typenames, "_med"))]
typenames = typenames[which(!endsWith(typenames, "_min"))]
typenames = typenames[which(!endsWith(typenames, "_max"))]
typenames = selection[which(selection %in% typenames)]
if (length(typenames) == 0) {return()}
h_ = 500
w_ = h_*4/3
if (SAVE_FILE) {png(file=sprintf("%s%s_%s.png",outputpath,target,filename), width=w_, height=h_)}
par(mar=c(4,4,1,1))
par(oma=c(0,0,0,0))
plot(c(1,max(one_frame['time'])),c(ylow,yhigh), col='white', xlab="Time [h]", ylab="WORT [insn]", pch='.')
for (t in seq_len(length(typenames))) {
#proj = one_frame[seq(1, dim(one_frame)[1], by=max(1, length(one_frame[[1]])/(10*w_))),]
#points(proj[c('iters',typenames[t])], col=MY_COLORS_[t], pch='.')
avglines = ml2lines(one_frame[c(typenames[t],'time')],all_max_points[typenames[t]])
#lines(avglines, col=MY_COLORS_[t])
medlines = ml2lines(one_frame[c(sprintf("%s_med",typenames[t]),'time')],all_max_points[typenames[t]])
lines(medlines, col=MY_COLORS_[t], lty='solid')
milines = NULL
malines = NULL
milines = ml2lines(one_frame[c(sprintf("%s_min",typenames[t]),'time')],all_max_points[typenames[t]])
malines = ml2lines(one_frame[c(sprintf("%s_max",typenames[t]),'time')],all_max_points[typenames[t]])
if (exists("RIBBON") && ( RIBBON=='max' )) {
#lines(milines, col=MY_COLORS_[t], lty='dashed')
lines(malines, col=MY_COLORS_[t], lty='dashed')
#points(proj[c('iters',sprintf("%s_min",typenames[t]))], col=MY_COLORS_[t], pch='.')
#points(proj[c('iters',sprintf("%s_max",typenames[t]))], col=MY_COLORS_[t], pch='.')
}
if (exists("RIBBON") && RIBBON != '') {
for (i in seq_len(dim(avglines)[1]-1)) {
if (RIBBON=='both') {
# draw boxes
x_l <- milines[i,][['X']]
x_r <- milines[i+1,][['X']]
y_l <- milines[i,][['Y']]
y_h <- malines[i,][['Y']]
rect(x_l, y_l, x_r, y_h, col=alpha(MY_COLORS_[t], alpha=0.1), lwd=0)
}
if (FALSE && RIBBON=='span') {
# draw boxes
x_l <- milines[i,][['X']]
x_r <- milines[i+1,][['X']]
y_l <- milines[i,][['Y']]
y_h <- malines[i,][['Y']]
rect(x_l, y_l, x_r, y_h, col=alpha(MY_COLORS_[t], alpha=0.1), lwd=0)
}
#if (FALSE && RIBBON=='both' || RIBBON=='sd') {
# # draw sd
# x_l <- avglines[i,][['X']]
# x_r <- avglines[i+1,][['X']]
# y_l <- avglines[i,][['Y']]-one_frame[ceiling(i/2),][[sprintf("%s_sd",typenames[t])]]
# y_h <- avglines[i,][['Y']]+one_frame[ceiling(i/2),][[sprintf("%s_sd",typenames[t])]]
# if (x_r != x_l) {
# rect(x_l, y_l, x_r, y_h, col=alpha(MY_COLORS_[t], alpha=0.1), lwd=0)
# }
#}
#sd_ <- row[sprintf("%s_sd",typenames[t])][[1]]
#min_ <- row[sprintf("%s_min",typenames[t])][[1]]
#max_ <- row[sprintf("%s_max",typenames[t])][[1]]
#if (exists("RIBBON")) {
# switch (RIBBON,
# 'sd' = arrows(x_, y_-sd_, x_, y_+sd_, length=0, angle=90, code=3, col=alpha(MY_COLORS_[t], alpha=0.03)),
# 'both' = arrows(x_, y_-sd_, x_, y_+sd_, length=0, angle=90, code=3, col=alpha(MY_COLORS_[t], alpha=0.05)),
# 'span' = #arrows(x_, min_, x_, max_, length=0, angle=90, code=3, col=alpha(MY_COLORS_[t], alpha=0.03))
# )
#}
##arrows(x_, y_-sd_, x_, y_+sd_, length=0.05, angle=90, code=3, col=alpha(MY_COLORS[t], alpha=0.1))
}
}
}
leglines=typenames
if (DRAW_WC) {
lines(c(0,length(one_frame[[1]])),y=c(worst_case,worst_case), lty='dotted')
leglines=c(typenames, 'worst observed')
}
legend(LEGEND_POS, legend=leglines,#"topleft"
col=c(MY_COLORS_[1:length(typenames)],"black"),
lty=c(rep("solid",length(typenames)),"dotted"))
if (SAVE_FILE) {dev.off()}
}
stopCluster(cl)
par(mar=c(3.8,3.8,0,0))
par(oma=c(0,0,0,0))
#RIBBON='both'
#MY_SELECTION = c('state_int','generation100_int')
#MY_SELECTION = c('state_int','frafl_int')
if (exists("MY_SELECTION")) {
plotting(MY_SELECTION, 'custom', MY_COLORS[c(1,2)])
} else {
# MY_SELECTION=c('state', 'afl', 'random', 'feedlongest', 'feedgeneration', 'feedgeneration10')
#MY_SELECTION=c('state_int', 'afl_int', 'random_int', 'feedlongest_int', 'feedgeneration_int', 'feedgeneration10_int')
#MY_SELECTION=c('state', 'frAFL', 'statenohash', 'feedgeneration10')
#MY_SELECTION=c('state_int', 'frAFL_int', 'statenohash_int', 'feedgeneration10_int')
MY_SELECTION=typenames
RIBBON='both'
for (i in seq_len(length(MY_SELECTION))) {
n <- MY_SELECTION[i]
plotting(c(n), n, c(MY_COLORS[i]))
}
RIBBON='max'
plotting(MY_SELECTION,'all', MY_COLORS)
}
for (t in seq_len(length(typenames))) {
li = one_frame[dim(one_frame)[1],]
pear = (li[[typenames[[t]]]]-li[[sprintf("%s_med",typenames[[t]])]])/li[[sprintf("%s_sd",typenames[[t]])]]
print(sprintf("%s pearson: %g",typenames[[t]],pear))
}

View File

@ -1,24 +0,0 @@
kernel,main_function,input_symbol,input_size,return_function
mpeg2,mpeg2_main,mpeg2_oldorgframe,90112,mpeg2_return
audiobeam,audiobeam_main,audiobeam_input,11520,audiobeam_return
epic,epic_main,epic_image,4096,epic_return
dijkstra,dijkstra_main,dijkstra_AdjMatrix,10000,dijkstra_return
fft,fft_main,fft_twidtable,2046,fft_return
bsort,bsort_main,bsort_Array,400,bsort_return
insertsort,insertsort_main,insertsort_a,400,insertsort_return
g723_enc,g723_enc_main,g723_enc_INPUT,1024,g723_enc_return
rijndael_dec,rijndael_dec_main,rijndael_dec_data,32768,rijndael_dec_return
rijndael_enc,rijndael_enc_main,rijndael_enc_data,31369,rijndael_enc_return
huff_dec,huff_dec_main,huff_dec_encoded,419,huff_dec_return
huff_enc,huff_enc_main,huff_enc_plaintext,600,huff_enc_return
gsm_enc,gsm_enc_main,gsm_enc_pcmdata,6400,gsm_enc_return
tmr,main,FUZZ_INPUT,32,trigger_Qemu_break
tacle_rtos,prvStage0,FUZZ_INPUT,604,trigger_Qemu_break
lift,main_lift,FUZZ_INPUT,100,trigger_Qemu_break
waters,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break
watersv2,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break
waters_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break
watersv2_int,main_waters,FUZZ_INPUT,4096,trigger_Qemu_break
micro_branchless,main_branchless,FUZZ_INPUT,4,trigger_Qemu_break
micro_int,main_int,FUZZ_INPUT,16,trigger_Qemu_break
micro_longint,main_micro_longint,FUZZ_INPUT,16,trigger_Qemu_break
1 kernel main_function input_symbol input_size return_function
2 mpeg2 mpeg2_main mpeg2_oldorgframe 90112 mpeg2_return
3 audiobeam audiobeam_main audiobeam_input 11520 audiobeam_return
4 epic epic_main epic_image 4096 epic_return
5 dijkstra dijkstra_main dijkstra_AdjMatrix 10000 dijkstra_return
6 fft fft_main fft_twidtable 2046 fft_return
7 bsort bsort_main bsort_Array 400 bsort_return
8 insertsort insertsort_main insertsort_a 400 insertsort_return
9 g723_enc g723_enc_main g723_enc_INPUT 1024 g723_enc_return
10 rijndael_dec rijndael_dec_main rijndael_dec_data 32768 rijndael_dec_return
11 rijndael_enc rijndael_enc_main rijndael_enc_data 31369 rijndael_enc_return
12 huff_dec huff_dec_main huff_dec_encoded 419 huff_dec_return
13 huff_enc huff_enc_main huff_enc_plaintext 600 huff_enc_return
14 gsm_enc gsm_enc_main gsm_enc_pcmdata 6400 gsm_enc_return
15 tmr main FUZZ_INPUT 32 trigger_Qemu_break
16 tacle_rtos prvStage0 FUZZ_INPUT 604 trigger_Qemu_break
17 lift main_lift FUZZ_INPUT 100 trigger_Qemu_break
18 waters main_waters FUZZ_INPUT 4096 trigger_Qemu_break
19 watersv2 main_waters FUZZ_INPUT 4096 trigger_Qemu_break
20 waters_int main_waters FUZZ_INPUT 4096 trigger_Qemu_break
21 watersv2_int main_waters FUZZ_INPUT 4096 trigger_Qemu_break
22 micro_branchless main_branchless FUZZ_INPUT 4 trigger_Qemu_break
23 micro_int main_int FUZZ_INPUT 16 trigger_Qemu_break
24 micro_longint main_micro_longint FUZZ_INPUT 16 trigger_Qemu_break

View File

@ -1,2 +0,0 @@
#!/bin/sh
arm-none-eabi-gcc -ggdb -ffreestanding -nostartfiles -lgcc -T mps2_m3.ld -mcpu=cortex-m3 main.c startup.c -o example.elf

View File

@ -1,38 +0,0 @@
int BREAKPOINT() {
for (;;)
{
}
}
int LLVMFuzzerTestOneInput(unsigned int* Data, unsigned int Size) {
//if (Data[3] == 0) {while(1){}} // cause a timeout
for (int i=0; i<Size; i++) {
// if (Data[i] > 0xFFd0 && Data[i] < 0xFFFF) {return 1;} // cause qemu to crash
for (int j=i+1; j<Size; j++) {
if (Data[j] == 0) {continue;}
if (Data[j]>Data[i]) {
int tmp = Data[i];
Data[i]=Data[j];
Data[j]=tmp;
if (Data[i] <= 100) {j--;}
}
}
}
return BREAKPOINT();
}
unsigned int FUZZ_INPUT[] = {
101,201,700,230,860,
234,980,200,340,678,
230,134,900,236,900,
123,800,123,658,607,
246,804,567,568,207,
407,246,678,457,892,
834,456,878,246,699,
854,234,844,290,125,
324,560,852,928,910,
790,853,345,234,586,
};
int main() {
LLVMFuzzerTestOneInput(FUZZ_INPUT, 50);
}

View File

@ -1,143 +0,0 @@
/*
* 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")
}

View File

@ -1,114 +0,0 @@
/*
* 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 );
}

View File

@ -1,25 +0,0 @@
#!/usr/bin/env bash
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
cd "$parent_path"
[ -n "$1" -a "$1" != "+" -a -z "$KERNEL" ] && export KERNEL="$1"
[ -n "$2" -a "$2" != "+" -a -z "$FUZZ_MAIN" ] && export FUZZ_MAIN="$2"
[ -n "$3" -a "$3" != "+" -a -z "$FUZZ_INPUT" ] && export FUZZ_INPUT="$3"
[ -n "$4" -a "$4" != "+" -a -z "$FUZZ_INPUT_LEN" ] && export FUZZ_INPUT_LEN="$4"
[ -n "$5" -a "$5" != "+" -a -z "$BREAKPOINT" ] && export BREAKPOINT="$5"
[ -n "$6" -a "$6" != "+" -a -z "$FUZZ_ITERS" ] && export FUZZ_ITERS="$6"
[ -n "$7" -a "$7" != "+" -a -z "$TIME_DUMP" ] && export TIME_DUMP="$7"
[ -n "$8" -a "$8" != "+" -a -z "$CASE_DUMP" ] && export CASE_DUMP="$8"
[ -n "$9" -a "$9" != "+" -a -z "$DO_SHOWMAP" ] && export DO_SHOWMAP="$9"
[ -n "${10}" -a "${10}" != "+" -a -z "$SHOWMAP_TEXTINPUT" ] && export SHOWMAP_TEXTINPUT="${10}"
[ -n "${11}" -a "${11}" != "+" -a -z "$TRACE_DUMP" ] && export TRACE_DUMP="${11}"
[ -z "$FUZZER" ] && export FUZZER=target/debug/fret
set +e
$FUZZER -icount shift=4,align=off,sleep=off -machine mps2-an385 -monitor null -kernel $KERNEL -serial null -nographic -S -semihosting --semihosting-config enable=on,target=native -snapshot -drive if=none,format=qcow2,file=dummy.qcow2
if [ "$exitcode" = "101" ]
then
exit 101
else
exit 0
fi

View File

@ -1,715 +0,0 @@
//! 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};
use libafl::{
bolts::{
core_affinity::Cores,
current_nanos,
launcher::Launcher,
rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider},
tuples::tuple_list,
AsSlice,
},
corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
events::EventConfig,
executors::{ExitKind, TimeoutExecutor},
feedback_or,
feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes},
monitors::MultiMonitor,
observers::{VariableMapObserver},
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
state::{HasCorpus, StdState, HasMetadata, HasNamedMetadata},
Error,
prelude::{SimpleMonitor, SimpleEventManager, AsMutSlice, RandBytesGenerator, Generator, SimpleRestartingEventManager, HasBytesVec, minimizer::TopRatedsMetadata, havoc_mutations, StdScheduledMutator, HitcountsMapObserver}, Evaluator, stages::StdMutationalStage,
};
use libafl_qemu::{
edges, edges::QemuEdgeCoverageHelper, elf::EasyElf, emu::Emulator, GuestPhysAddr, QemuExecutor,
QemuHooks, Regs, QemuInstrumentationFilter, GuestAddr,
emu::libafl_qemu_set_native_breakpoint, emu::libafl_qemu_remove_native_breakpoint,
};
use rand::{SeedableRng, StdRng, Rng};
use crate::{
clock::{QemuClockObserver, ClockTimeFeedback, QemuClockIncreaseFeedback, IcHist, FUZZ_START_TIMESTAMP},
qemustate::QemuStateRestoreHelper,
systemstate::{helpers::QemuSystemStateHelper, observers::QemuSystemStateObserver, feedbacks::{DumpSystraceFeedback, NovelSystemStateFeedback}, graph::{SysMapFeedback, SysGraphFeedbackState, GraphMaximizerCorpusScheduler}, schedulers::{LongestTraceScheduler, GenerationScheduler}}, worst::{TimeMaximizerCorpusScheduler, ExecTimeIncFeedback, TimeStateMaximizerCorpusScheduler, AlwaysTrueFeedback},
mutational::MyStateStage,
mutational::{MINIMUM_INTER_ARRIVAL_TIME},
};
use std::time::{SystemTime, UNIX_EPOCH};
pub static mut RNG_SEED: u64 = 1;
pub static mut LIMIT : u32 = u32::MAX;
pub const MAX_NUM_INTERRUPT: usize = 32;
pub const DO_NUM_INTERRUPT: usize = 32;
pub static mut MAX_INPUT_SIZE: usize = 32;
/// Read ELF program headers to resolve physical load addresses.
fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr {
let ret;
for i in &tab.goblin().program_headers {
if i.vm_range().contains(&vaddr.try_into().unwrap()) {
ret = vaddr - TryInto::<GuestPhysAddr>::try_into(i.p_vaddr).unwrap()
+ TryInto::<GuestPhysAddr>::try_into(i.p_paddr).unwrap();
return ret - (ret % 2);
}
}
return vaddr;
}
extern "C" {
static mut libafl_interrupt_offsets : [u32; 32];
static mut libafl_num_interrupts : usize;
}
pub fn fuzz() {
unsafe {FUZZ_START_TIMESTAMP = SystemTime::now();}
let mut starttime = std::time::Instant::now();
if let Ok(s) = env::var("FUZZ_SIZE") {
str::parse::<usize>(&s).expect("FUZZ_SIZE was not a number");
};
// 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(
env::var("KERNEL").expect("KERNEL env not set"),
&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 = elf
.resolve_symbol(
&env::var("FUZZ_INPUT").unwrap_or_else(|_| "FUZZ_INPUT".to_owned()),
0,
)
.expect("Symbol or env FUZZ_INPUT not found") as GuestPhysAddr;
let input_addr = virt2phys(input_addr,&elf) as GuestPhysAddr;
println!("FUZZ_INPUT @ {:#x}", input_addr);
let test_length_ptr = elf
.resolve_symbol("FUZZ_LENGTH", 0).map(|x| x as GuestPhysAddr);
let test_length_ptr = Option::map_or(test_length_ptr, None, |x| Some(virt2phys(x,&elf)));
let input_counter_ptr = elf
.resolve_symbol(&env::var("FUZZ_POINTER").unwrap_or_else(|_| "FUZZ_POINTER".to_owned()), 0)
.map(|x| x as GuestPhysAddr);
let input_counter_ptr = Option::map_or(input_counter_ptr, None, |x| Some(virt2phys(x,&elf)));
#[cfg(feature = "systemstate")]
let curr_tcb_pointer = elf // loads to the address specified in elf, without respecting program headers
.resolve_symbol("pxCurrentTCB", 0)
.expect("Symbol pxCurrentTCBC not found");
// let curr_tcb_pointer = virt2phys(curr_tcb_pointer,&elf);
#[cfg(feature = "systemstate")]
println!("TCB pointer at {:#x}", curr_tcb_pointer);
#[cfg(feature = "systemstate")]
let task_queue_addr = elf
.resolve_symbol("pxReadyTasksLists", 0)
.expect("Symbol pxReadyTasksLists not found");
// let task_queue_addr = virt2phys(task_queue_addr,&elf.goblin());
#[cfg(feature = "systemstate")]
println!("Task Queue at {:#x}", task_queue_addr);
#[cfg(feature = "systemstate")]
let svh = elf
.resolve_symbol("xPortPendSVHandler", 0)
.expect("Symbol xPortPendSVHandler not found");
// let svh=virt2phys(svh, &elf);
// let svh = elf
// .resolve_symbol("vPortEnterCritical", 0)
// .expect("Symbol vPortEnterCritical not found");
#[cfg(feature = "systemstate")]
let app_start = elf
.resolve_symbol("__APP_CODE_START__", 0)
.expect("Symbol __APP_CODE_START__ not found");
#[cfg(feature = "systemstate")]
let app_end = elf
.resolve_symbol("__APP_CODE_END__", 0)
.expect("Symbol __APP_CODE_END__ not found");
#[cfg(feature = "systemstate")]
let app_range = app_start..app_end;
#[cfg(feature = "systemstate")]
dbg!(app_range.clone());
let breakpoint = elf
.resolve_symbol(
&env::var("BREAKPOINT").unwrap_or_else(|_| "BREAKPOINT".to_owned()),
0,
)
.expect("Symbol or env BREAKPOINT not found");
println!("Breakpoint address = {:#x}", breakpoint);
unsafe {
libafl_num_interrupts = 0;
}
if let Ok(input_len) = env::var("FUZZ_INPUT_LEN") {
unsafe {MAX_INPUT_SIZE = str::parse::<usize>(&input_len).expect("FUZZ_INPUT_LEN was not a number");}
}
unsafe {dbg!(MAX_INPUT_SIZE);}
if let Ok(seed) = env::var("SEED_RANDOM") {
unsafe {RNG_SEED = str::parse::<u64>(&seed).expect("SEED_RANDOM must be an integer.");}
}
let mut run_client = |state: Option<_>, mut mgr, _core_id| {
// Initialize QEMU
let args: Vec<String> = env::args().collect();
let env: Vec<(String, String)> = env::vars().collect();
let emu = Emulator::new(&args, &env);
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 mut start_tick : u32 = 0;
for i in 0..DO_NUM_INTERRUPT {
let mut t : [u8; 4] = [0,0,0,0];
if len > (i+1)*4 {
for j in 0 as usize..4 as usize {
t[j]=buf[i*4+j];
}
if i == 0 || true {
unsafe {start_tick = u32::from_le_bytes(t) % LIMIT;}
} else {
start_tick = u32::saturating_add(start_tick,max(MINIMUM_INTER_ARRIVAL_TIME,u32::from_le_bytes(t)));
}
libafl_interrupt_offsets[i] = start_tick;
libafl_num_interrupts = i+1;
}
}
if buf.len() > libafl_num_interrupts*4 {
buf = &buf[libafl_num_interrupts*4..];
len = buf.len();
}
// 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;
}
emu.write_phys_mem(input_addr, buf);
if let Some(s) = test_length_ptr {
emu.write_phys_mem(s as u64, &len.to_le_bytes())
}
emu.run();
// If the execution stops at any point other then the designated breakpoint (e.g. a breakpoint on a panic method) we consider it a crash
let mut pcs = (0..emu.num_cpus())
.map(|i| emu.cpu_from_index(i))
.map(|cpu| -> Result<u32, String> { cpu.read_reg(Regs::Pc) });
match pcs
.find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0)))
{
Some(_) => ExitKind::Ok,
None => ExitKind::Crash,
}
}
};
// Create an observation channel using the coverage map
let edges = unsafe { &mut edges::EDGES_MAP };
let edges_counter = unsafe { &mut edges::MAX_EDGES_NUM };
let edges_observer = VariableMapObserver::new("edges", edges, edges_counter);
#[cfg(feature = "observer_hitcounts")]
let edges_observer = HitcountsMapObserver::new(edges_observer);
// Create an observation channel to keep track of the execution time
let clock_time_observer = QemuClockObserver::new("clocktime");
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::new_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 = "systemstate",not(any(feature = "feed_systemgraph",feature = "feed_systemtrace"))))]
let mut feedback = feedback_or!(
feedback,
DumpSystraceFeedback::with_dump(env::var("TRACE_DUMP").ok().map(PathBuf::from))
);
#[cfg(feature = "feed_systemtrace")]
let mut feedback = feedback_or!(
feedback,
// AlwaysTrueFeedback::new(),
NovelSystemStateFeedback::default()
);
#[cfg(feature = "feed_systemgraph")]
let mut feedback = feedback_or!(
feedback,
SysMapFeedback::default()
);
// A feedback to choose if an input is a solution or not
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
// 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 = "feed_afl",feature = "feed_systemgraph",feature = "feed_systemtrace", feature = "feed_genetic")))]
let scheduler = QueueScheduler::new();
#[cfg(all(feature = "feed_afl",not(any(feature = "feed_systemgraph",feature = "feed_systemtrace"))))]
let scheduler = TimeMaximizerCorpusScheduler::new(QueueScheduler::new());
#[cfg(feature = "feed_systemtrace")]
let scheduler = LongestTraceScheduler::new(TimeStateMaximizerCorpusScheduler::new(QueueScheduler::new()));
#[cfg(feature = "feed_systemgraph")]
let scheduler = GraphMaximizerCorpusScheduler::new(QueueScheduler::new());
#[cfg(feature = "feed_genetic")]
let scheduler = GenerationScheduler::new();
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
#[cfg(not(feature = "systemstate"))]
let qhelpers = tuple_list!(
QemuEdgeCoverageHelper::default(),
QemuStateRestoreHelper::new()
);
#[cfg(feature = "systemstate")]
let qhelpers = tuple_list!(
QemuEdgeCoverageHelper::default(),
QemuStateRestoreHelper::new(),
QemuSystemStateHelper::new(svh,curr_tcb_pointer,task_queue_addr,input_counter_ptr,app_range.clone())
);
let mut hooks = QemuHooks::new(&emu,qhelpers);
#[cfg(not(feature = "systemstate"))]
let observer_list = tuple_list!(edges_observer, clock_time_observer);
#[cfg(feature = "systemstate")]
let observer_list = tuple_list!(edges_observer, clock_time_observer, systemstate_observer);
// 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);
// #[cfg(not(all(feature = "feed_systemtrace", feature = "fuzz_int")))]
// let mut stages = tuple_list!(StdMutationalStage::new(mutator));
// #[cfg(all(feature = "feed_systemtrace", feature = "fuzz_int"))]
#[cfg(feature = "fuzz_int")]
let mut stages = tuple_list!(StdMutationalStage::new(mutator),MyStateStage::new());
#[cfg(not(feature = "fuzz_int"))]
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
if env::var("DO_SHOWMAP").is_ok() {
let s = &env::var("DO_SHOWMAP").unwrap();
let show_input = if s=="-" {
let mut buf = Vec::<u8>::new();
std::io::stdin().read_to_end(&mut buf).expect("Could not read Stdin");
buf
} else if s=="$" {
env::var("SHOWMAP_TEXTINPUT").expect("SHOWMAP_TEXTINPUT not set").as_bytes().to_owned()
} else {
fs::read(s).expect("Input file for DO_SHOWMAP can not be read")
};
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, BytesInput::new(show_input))
.unwrap();
if let Ok(td) = env::var("TIME_DUMP") {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.append(true)
.open(td).expect("Could not open timedump");
if let Some(ichist) = state.metadata_mut().get_mut::<IcHist>() {
for i in ichist.0.drain(..) {
writeln!(file, "{},{}", i.0, i.1).expect("Write to dump failed");
}
}
}
} else {
if let Ok(_) = env::var("SEED_RANDOM") {
unsafe {
let mut rng = StdRng::seed_from_u64(RNG_SEED);
for i in 0..100 {
let inp = BytesInput::new(vec![rng.gen::<u8>(); MAX_INPUT_SIZE]);
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap();
}
}
}
else if let Ok(sf) = env::var("SEED_DIR") {
state
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[PathBuf::from(&sf)])
.unwrap_or_else(|_| {
println!("Failed to load initial corpus at {:?}", &corpus_dirs);
process::exit(0);
});
println!("We imported {} inputs from seedfile.", state.corpus().count());
} else if state.corpus().count() < 1 {
state
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs)
.unwrap_or_else(|_| {
println!("Failed to load initial corpus at {:?}", &corpus_dirs);
process::exit(0);
});
println!("We imported {} inputs from disk.", state.corpus().count());
}
match env::var("FUZZ_ITERS") {
Err(_) => {
fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.unwrap();
},
Ok(t) => {
println!("Iterations {}",t);
let num = str::parse::<u64>(&t).expect("FUZZ_ITERS was not a number");
if let Ok(s) = env::var("FUZZ_RANDOM") { unsafe {
if s.contains("watersv2_int") {
println!("V2");
LIMIT=7000000;
} else {
println!("V1");
LIMIT=5000000;
}
println!("Random Fuzzing, ignore corpus");
// let mut generator = RandBytesGenerator::new(MAX_INPUT_SIZE);
let target_duration = Duration::from_secs(num);
let start_time = std::time::Instant::now();
let mut rng = StdRng::seed_from_u64(RNG_SEED);
while start_time.elapsed() < target_duration {
// let inp = generator.generate(&mut state).unwrap();
// libafl's generator is too slow
let inp = BytesInput::new(vec![rng.gen::<u8>(); MAX_INPUT_SIZE]);
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap();
}
}} else {
// fuzzer
// .fuzz_loop_for_duration(&mut stages, &mut executor, &mut state, &mut mgr, Duration::from_secs(num))
// .unwrap();
fuzzer
.fuzz_loop_until(&mut stages, &mut executor, &mut state, &mut mgr, starttime.checked_add(Duration::from_secs(num)).unwrap())
.unwrap();
#[cfg(feature = "run_until_saturation")]
{
{
let mut dumper = |marker : String| {
if let Ok(td) = env::var("TIME_DUMP") {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.append(true)
.open(td).expect("Could not open timedump");
if let Some(ichist) = state.metadata_mut().get_mut::<IcHist>() {
for i in ichist.0.drain(..) {
writeln!(file, "{},{}", i.0, i.1).expect("Write to dump failed");
}
}
}
if let Ok(td) = env::var("CASE_DUMP") {
println!("Dumping worst case to {:?}", td);
let corpus = state.corpus();
let mut worst = Duration::new(0,0);
let mut worst_input = None;
for i in 0..corpus.count() {
let tc = corpus.get(i).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");
}
}
match worst_input {
Some(wi) => {
// let cd = format!("{}.case",&td);
let mut cd = td.clone();
cd.push_str(&marker);
fs::write(&cd,wi).expect("Failed to write worst corpus element");
},
None => (),
}
#[cfg(feature = "feed_systemgraph")]
{
let mut gd = String::from(&td);
gd.push_str(&format!(".graph{}", marker));
if let Some(md) = state.named_metadata_mut().get_mut::<SysGraphFeedbackState>("SysMap") {
fs::write(&gd,ron::to_string(&md).expect("Failed to serialize graph")).expect("Failed to write graph");
}
}
{
let mut gd = String::from(&td);
if let Some(md) = state.metadata_mut().get_mut::<TopRatedsMetadata>() {
let mut uniq: Vec<usize> = md.map.values().map(|x| x.clone()).collect();
uniq.sort();
uniq.dedup();
gd.push_str(&format!(".{}.toprated{}", uniq.len(), marker));
fs::write(&gd,ron::to_string(&md.map).expect("Failed to serialize metadata")).expect("Failed to write graph");
}
}
}
};
dumper(format!(".iter_{}",t));
}
println!("Start running until saturation");
let mut last = state.metadata().get::<IcHist>().unwrap().1;
while SystemTime::now().duration_since(unsafe {FUZZ_START_TIMESTAMP}).unwrap().as_millis() < last.1 + Duration::from_secs(10800).as_millis() {
starttime=starttime.checked_add(Duration::from_secs(30)).unwrap();
fuzzer
.fuzz_loop_until(&mut stages, &mut executor, &mut state, &mut mgr, starttime)
.unwrap();
let after = state.metadata().get::<IcHist>().unwrap().1;
if after.0 > last.0 {
last=after;
}
if let Ok(td) = env::var("CASE_DUMP") {
println!("Dumping worst case to {:?}", td);
let corpus = state.corpus();
let mut worst = Duration::new(0,0);
let mut worst_input = None;
for i in 0..corpus.count() {
let tc = corpus.get(i).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");
}
}
match worst_input {
Some(wi) => {
// let cd = format!("{}.case",&td);
let cd = td.clone();
fs::write(&cd,wi).expect("Failed to write worst corpus element");
},
None => (),
}
#[cfg(feature = "feed_systemgraph")]
{
let mut gd = String::from(&td);
gd.push_str(".graph" );
if let Some(md) = state.named_metadata_mut().get_mut::<SysGraphFeedbackState>("SysMap") {
fs::write(&gd,ron::to_string(&md).expect("Failed to serialize graph")).expect("Failed to write graph");
}
}
{
let mut gd = String::from(&td);
if let Some(md) = state.metadata_mut().get_mut::<TopRatedsMetadata>() {
let mut uniq: Vec<usize> = md.map.values().map(|x| x.clone()).collect();
uniq.sort();
uniq.dedup();
gd.push_str(&format!(".{}.toprated", uniq.len()));
fs::write(&gd,ron::to_string(&md.map).expect("Failed to serialize metadata")).expect("Failed to write graph");
}
}
}
}
}
}
if let Ok(td) = env::var("TIME_DUMP") {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.append(true)
.open(td).expect("Could not open timedump");
if let Some(ichist) = state.metadata_mut().get_mut::<IcHist>() {
for i in ichist.0.drain(..) {
writeln!(file, "{},{}", i.0, i.1).expect("Write to dump failed");
}
}
}
if let Ok(td) = env::var("CASE_DUMP") {
println!("Dumping worst case to {:?}", td);
let corpus = state.corpus();
let mut worst = Duration::new(0,0);
let mut worst_input = None;
for i in 0..corpus.count() {
let tc = corpus.get(i).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");
}
}
match worst_input {
Some(wi) => {
// let cd = format!("{}.case",&td);
let cd = td.clone();
fs::write(&cd,wi).expect("Failed to write worst corpus element");
},
None => (),
}
#[cfg(feature = "feed_systemgraph")]
{
let mut gd = String::from(&td);
gd.push_str(".graph");
if let Some(md) = state.named_metadata_mut().get_mut::<SysGraphFeedbackState>("SysMap") {
fs::write(&gd,ron::to_string(&md).expect("Failed to serialize graph")).expect("Failed to write graph");
}
}
{
let mut gd = String::from(&td);
if let Some(md) = state.metadata_mut().get_mut::<TopRatedsMetadata>() {
let mut uniq: Vec<usize> = md.map.values().map(|x| x.clone()).collect();
uniq.sort();
uniq.dedup();
gd.push_str(&format!(".{}.toprated", uniq.len()));
fs::write(&gd,ron::to_string(&md.map).expect("Failed to serialize metadata")).expect("Failed to write graph");
}
}
}
},
}
}
#[cfg(not(feature = "singlecore"))]
return Ok(());
};
// Special case where no fuzzing happens, but standard input is dumped
if let Ok(input_dump) = env::var("DUMP_SEED") {
// Initialize QEMU
let args: Vec<String> = env::args().collect();
let env: Vec<(String, String)> = env::vars().collect();
let emu = Emulator::new(&args, &env);
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_phys_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),
}
}
}

View File

@ -1,13 +0,0 @@
#![feature(is_sorted)]
#[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;

View File

@ -1,24 +0,0 @@
#![feature(is_sorted)]
//! 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!");
}

View File

@ -1,240 +0,0 @@
//| 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 libafl::{
bolts::rands::Rand,
corpus::{Corpus, self},
fuzzer::Evaluator,
mark_feature_time,
stages::{Stage},
start_timer,
state::{HasClientPerfMonitor, HasCorpus, HasRand, UsesState, HasMetadata},
Error, prelude::{HasBytesVec, UsesInput, new_hash_feedback, StdRand, RandomSeed, MutationResult, Mutator},
};
use crate::{systemstate::{FreeRTOSSystemStateMetadata, RefinedFreeRTOSSystemState}, fuzzer::DO_NUM_INTERRUPT, clock::IcHist};
pub const MINIMUM_INTER_ARRIVAL_TIME : u32 = 700 * 1000 * (1 << 4);
//======================= Custom mutator
/// The default mutational stage
#[derive(Clone, Debug, Default)]
pub struct MyStateStage<E, EM, Z> {
#[allow(clippy::type_complexity)]
phantom: PhantomData<(E, EM, Z)>,
}
impl<E, EM, Z> MyStateStage<E, EM, Z>
where
E: UsesState<State = Z::State>,
EM: UsesState<State = Z::State>,
Z: Evaluator<E, EM>,
Z::State: HasClientPerfMonitor + HasCorpus + HasRand,
{
pub fn new() -> Self {
Self { phantom: PhantomData }
}
}
impl<E, EM, Z> Stage<E, EM, Z> for MyStateStage<E, EM, Z>
where
E: UsesState<State = Z::State>,
EM: UsesState<State = Z::State>,
Z: Evaluator<E, EM>,
Z::State: HasClientPerfMonitor + HasCorpus + HasRand + HasMetadata,
<Z::State as UsesInput>::Input: HasBytesVec
{
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut Self::State,
manager: &mut EM,
corpus_idx: usize,
) -> 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 tmpinput = _input.input_mut().as_mut().unwrap().clone();
let mut do_rerun = false;
{
// need our own random generator, because borrowing rules
let mut myrand = StdRand::new();
let mut target_bytes : Vec<u8> = vec![];
{
let input = _input.input_mut().as_ref().unwrap();
let tmp = &mut state.rand_mut();
myrand.set_seed(tmp.next());
target_bytes = input.bytes().to_vec();
}
// produce a slice of absolute interrupt times
let mut interrupt_offsets : [u32; 32] = [0u32; 32];
let mut num_interrupts : usize = 0;
{
let mut start_tick : u32 = 0;
for i in 0..DO_NUM_INTERRUPT {
let mut t : [u8; 4] = [0,0,0,0];
if target_bytes.len() > (i+1)*4 {
for j in 0 as usize..4 as usize {
t[j]=target_bytes[i*4+j];
}
if i == 0 || true {
start_tick = u32::from_le_bytes(t);
} else {
start_tick = u32::saturating_add(start_tick,max(MINIMUM_INTER_ARRIVAL_TIME,u32::from_le_bytes(t)));
}
interrupt_offsets[i] = start_tick;
num_interrupts = i+1;
}
}
}
interrupt_offsets.sort();
// println!("Vor Mutator: {:?}", interrupt_offsets[0..num_interrupts].to_vec());
// let num_i = min(target_bytes.len() / 4, DO_NUM_INTERRUPT);
let mut suffix = target_bytes.split_off(4 * num_interrupts);
let mut prefix : Vec<[u8; 4]> = vec![];
// let mut suffix : Vec<u8> = vec![];
#[cfg(feature = "feed_systemtrace")]
{
let tmp = _input.metadata().get::<FreeRTOSSystemStateMetadata>();
if tmp.is_some() {
let trace = tmp.expect("FreeRTOSSystemStateMetadata not found");
// calculate hits and identify snippets
let mut last_m = false;
let mut marks : Vec<(&RefinedFreeRTOSSystemState, usize, usize)>= vec![]; // 1: got interrupted, 2: interrupt handler
for i in 0..trace.inner.len() {
let curr = &trace.inner[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.task_name);
} else if last_m {
marks.push((curr, i, 2));
// println!("2: {}",curr.current_task.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 = 0;
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],MINIMUM_INTER_ARRIVAL_TIME);
}
if i < num_interrupts-1 {
ub = u32::saturating_sub(interrupt_offsets[i+1],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 < (ub as u64) && (ub as u64) < x.0.end_tick )
).collect();
// in cases there are no alternatives
if alternatives.len() == 0 {
if old_hit.is_none() {
// choose something random
let untouched : Vec<_> = marks.iter().filter(
|x| x.2 == 0
).collect();
if untouched.len() > 0 {
let tmp = interrupt_offsets[i];
let choice = myrand.choose(untouched);
interrupt_offsets[i] = myrand.between(choice.0.start_tick, choice.0.end_tick)
.try_into().expect("tick > u32");
do_rerun = true;
}
// println!("no alternatives, choose random i: {} {} -> {}",i,tmp,interrupt_offsets[i]);
continue;
} else {
// do nothing
// println!("no alternatives, do nothing i: {} {}",i,interrupt_offsets[i]);
continue;
}
}
let replacement = myrand.choose(alternatives);
if (old_hit.map_or(false, |x| x == replacement)) {
// use the old value
// println!("chose old value, do nothing i: {} {}",i,interrupt_offsets[i]);
continue;
} else {
let extra = if (old_hit.map_or(false, |x| x.1 < replacement.1)) {
// move futher back, respect old_handler
old_handler.map_or(0, |x| x.0.end_tick - x.0.start_tick)
} else { 0 };
let tmp = interrupt_offsets[i];
interrupt_offsets[i] = (myrand.between(replacement.0.start_tick,
replacement.0.end_tick) + extra).try_into().expect("ticks > u32");
// println!("chose new alternative, i: {} {} -> {}",i,tmp, interrupt_offsets[i]);
do_rerun = true;
}
}
let mut numbers : Vec<u32> = interrupt_offsets[0..num_interrupts].to_vec();
numbers.sort();
// println!("Mutator: {:?}", numbers);
let mut start : u32 = 0;
// for i in 0..numbers.len() {
// let tmp = numbers[i];
// numbers[i] = numbers[i]-start;
// start = tmp;
// }
for i in 0..numbers.len() {
prefix.push(u32::to_le_bytes(numbers[i]));
}
}
}
#[cfg(not(feature = "feed_systemtrace"))]
{
let metadata = state.metadata();
let hist = metadata.get::<IcHist>().unwrap();
let maxtick : u64 = hist.1.0;
// let maxtick : u64 = (_input.exec_time().expect("No duration found").as_nanos() >> 4).try_into().unwrap();
let mut numbers : Vec<u32> = vec![];
for i in 0..num_interrupts {
prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64)).try_into().expect("ticks > u32")));
}
}
let mut n : Vec<u8> = vec![];
n = [prefix.concat(), suffix].concat();
newinput.bytes_mut().clear();
newinput.bytes_mut().append(&mut n);
}
// InterruptShifterMutator::mutate(&mut mymut, state, &mut input, 0)?;
if do_rerun {
let (_, corpus_idx) = fuzzer.evaluate_input(state, executor, manager, newinput)?;
}
Ok(())
}
}
impl<E, EM, Z> UsesState for MyStateStage<E, EM, Z>
where
E: UsesState<State = Z::State>,
EM: UsesState<State = Z::State>,
Z: Evaluator<E, EM>,
Z::State: HasClientPerfMonitor + HasCorpus + HasRand,
{
type State = Z::State;
}

View File

@ -1,96 +0,0 @@
use libafl::prelude::UsesInput;
use libafl_qemu::CPUArchState;
use libafl_qemu::Emulator;
use libafl_qemu::FastSnapshot;
use libafl_qemu::QemuExecutor;
use libafl_qemu::QemuHelper;
use libafl_qemu::QemuHelperTuple;
use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata};
use libafl_qemu::QemuHooks;
use libafl_qemu::{
emu,
};
// TODO be thread-safe maybe with https://amanieu.github.io/thread_local-rs/thread_local/index.html
#[derive(Debug)]
pub struct QemuStateRestoreHelper {
has_snapshot: bool,
use_snapshot: bool,
saved_cpu_states: Vec<CPUArchState>,
fastsnap: Option<FastSnapshot>
}
impl QemuStateRestoreHelper {
#[must_use]
pub fn new() -> Self {
Self {
has_snapshot: false,
use_snapshot: true,
saved_cpu_states: vec![],
fastsnap: None
}
}
}
impl Default for QemuStateRestoreHelper {
fn default() -> Self {
Self::new()
}
}
impl<S> QemuHelper<S> for QemuStateRestoreHelper
where
S: UsesInput,
{
const HOOKS_DO_SIDE_EFFECTS: bool = true;
fn init_hooks<QT>(&self, _hooks: &QemuHooks<'_, QT, S>)
where
QT: QemuHelperTuple<S>,
{
}
fn first_exec<QT>(&self, _hooks: &QemuHooks<'_, QT, S>)
where
QT: QemuHelperTuple<S>,
{
}
fn post_exec(&mut self, emulator: &Emulator, _input: &S::Input) {
// 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()) };
}
}

View File

@ -1,604 +0,0 @@
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 core::marker::PhantomData;
use libafl::state::HasCorpus;
use libafl::state::HasSolutions;
use libafl::state::HasRand;
use crate::worst::MaxExecsLenFavFactor;
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::HasClientPerfMonitor;
use libafl::feedbacks::Feedback;
use libafl::bolts::tuples::Named;
use libafl::Error;
use hashbrown::HashMap;
use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata};
use serde::{Deserialize, Serialize};
use super::RefinedFreeRTOSSystemState;
use super::FreeRTOSSystemStateMetadata;
use super::observers::QemuSystemStateObserver;
use petgraph::prelude::DiGraph;
use petgraph::graph::NodeIndex;
use petgraph::Direction;
use std::cmp::Ordering;
use libafl::bolts::rands::Rand;
//============================= Data Structures
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
pub struct VariantTuple
{
pub start_tick: u64,
pub end_tick: u64,
input_counter: u32,
pub input: Vec<u8>, // in the end any kind of input are bytes, regardless of type and lifetime
}
impl VariantTuple {
fn from(other: &RefinedFreeRTOSSystemState,input: Vec<u8>) -> Self {
VariantTuple{
start_tick: other.start_tick,
end_tick: other.end_tick,
input_counter: other.input_counter,
input: input,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct SysGraphNode
{
base: RefinedFreeRTOSSystemState,
pub variants: Vec<VariantTuple>,
}
impl SysGraphNode {
fn from(base: RefinedFreeRTOSSystemState, input: Vec<u8>) -> Self {
SysGraphNode{variants: vec![VariantTuple::from(&base, input)], base:base }
}
/// unites the variants of this value with another, draining the other if the bases are equal
fn unite(&mut self, other: &mut SysGraphNode) -> bool {
if self!=other {return false;}
self.variants.append(&mut other.variants);
self.variants.dedup();
return true;
}
/// add a Varint from a [`RefinedFreeRTOSSystemState`]
fn unite_raw(&mut self, other: &RefinedFreeRTOSSystemState, input: &Vec<u8>) -> bool {
if &self.base!=other {return false;}
self.variants.push(VariantTuple::from(other, input.clone()));
self.variants.dedup();
return true;
}
/// add a Varint from a [`RefinedFreeRTOSSystemState`], if it's interesting
fn unite_interesting(&mut self, other: &RefinedFreeRTOSSystemState, input: &Vec<u8>) -> bool {
if &self.base!=other {return false;}
let interesting =
self.variants.iter().all(|x| x.end_tick-x.start_tick<other.end_tick-other.start_tick) || // longest variant
self.variants.iter().all(|x| x.end_tick-x.start_tick>other.end_tick-other.start_tick) || // shortest variant
self.variants.iter().all(|x| x.input_counter>other.input_counter) || // longest input
self.variants.iter().all(|x| x.input_counter<other.input_counter); // shortest input
if interesting {
let var = VariantTuple::from(other, input.clone());
self.variants.push(var);
}
return interesting;
}
pub fn get_taskname(&self) -> &str {
&self.base.current_task.task_name
}
pub fn get_input_counts(&self) -> Vec<u32> {
self.variants.iter().map(|x| x.input_counter).collect()
}
}
impl PartialEq for SysGraphNode {
fn eq(&self, other: &SysGraphNode) -> bool {
self.base==other.base
}
}
// Wrapper around Vec<RefinedFreeRTOSSystemState> to attach as Metadata
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct SysGraphMetadata {
pub inner: Vec<NodeIndex>,
indices: Vec<usize>,
tcref: isize,
}
impl SysGraphMetadata {
pub fn new(inner: Vec<NodeIndex>) -> Self{
Self {indices: inner.iter().map(|x| x.index()).collect(), inner: inner, tcref: 0}
}
}
impl AsSlice for SysGraphMetadata {
/// Convert the slice of system-states to a slice of hashes over enumerated states
fn as_slice(&self) -> &[usize] {
self.indices.as_slice()
}
type Entry = usize;
}
impl HasRefCnt for SysGraphMetadata {
fn refcnt(&self) -> isize {
self.tcref
}
fn refcnt_mut(&mut self) -> &mut isize {
&mut self.tcref
}
}
libafl::impl_serdeany!(SysGraphMetadata);
pub type GraphMaximizerCorpusScheduler<CS> =
MinimizerScheduler<CS, MaxExecsLenFavFactor<<CS as UsesState>::State>,SysGraphMetadata>;
//============================= Graph Feedback
/// Improved System State Graph
#[derive(Serialize, Deserialize, Clone, Debug, Default, SerdeAny)]
pub struct SysGraphFeedbackState
{
pub graph: DiGraph<SysGraphNode, ()>,
entrypoint: NodeIndex,
exit: NodeIndex,
name: String,
}
impl SysGraphFeedbackState
{
pub fn new() -> Self {
let mut graph = DiGraph::<SysGraphNode, ()>::new();
let mut entry = SysGraphNode::default();
entry.base.current_task.task_name="Start".to_string();
let mut exit = SysGraphNode::default();
exit.base.current_task.task_name="End".to_string();
let entry = graph.add_node(entry);
let exit = graph.add_node(exit);
Self {graph: graph, entrypoint: entry, exit: exit, name: String::from("SysMap")}
}
fn insert(&mut self, list: Vec<RefinedFreeRTOSSystemState>, input: &Vec<u8>) {
let mut current_index = self.entrypoint;
for n in list {
let mut done = false;
for i in self.graph.neighbors_directed(current_index, Direction::Outgoing) {
if n == self.graph[i].base {
done = true;
current_index = i;
break;
}
}
if !done {
let j = self.graph.add_node(SysGraphNode::from(n,input.clone()));
self.graph.add_edge(current_index, j, ());
current_index = j;
}
}
}
/// Try adding a system state path from a [Vec<RefinedFreeRTOSSystemState>], return true if the path was interesting
fn update(&mut self, list: &Vec<RefinedFreeRTOSSystemState>, input: &Vec<u8>) -> (bool, Vec<NodeIndex>) {
let mut current_index = self.entrypoint;
let mut novel = false;
let mut trace : Vec<NodeIndex> = vec![current_index];
for n in list {
let mut matching : Option<NodeIndex> = None;
for i in self.graph.neighbors_directed(current_index, Direction::Outgoing) {
let tmp = &self.graph[i];
if n == &tmp.base {
matching = Some(i);
current_index = i;
break;
}
}
match matching {
None => {
novel = true;
let j = self.graph.add_node(SysGraphNode::from(n.clone(),input.clone()));
self.graph.add_edge(current_index, j, ());
current_index = j;
},
Some(i) => {
novel |= self.graph[i].unite_interesting(&n, input);
}
}
trace.push(current_index);
}
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 SysGraphFeedbackState
{
fn reset(&mut self) -> Result<(), Error> {
self.graph.clear();
let mut entry = SysGraphNode::default();
entry.base.current_task.task_name="Start".to_string();
let mut exit = SysGraphNode::default();
exit.base.current_task.task_name="End".to_string();
self.entrypoint = self.graph.add_node(entry);
self.exit = self.graph.add_node(exit);
Ok(())
}
}
/// A Feedback reporting novel System-State Transitions. Depends on [`QemuSystemStateObserver`]
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct SysMapFeedback
{
name: String,
last_trace: Option<Vec<NodeIndex>>,
}
impl SysMapFeedback {
pub fn new() -> Self {
Self {name: String::from("SysMapFeedback"), last_trace: None }
}
}
impl<S> Feedback<S> for SysMapFeedback
where
S: UsesInput + HasClientPerfMonitor + HasNamedMetadata,
S::Input: HasTargetBytes,
{
#[allow(clippy::wrong_self_convention)]
fn is_interesting<EM, OT>(
&mut self,
state: &mut S,
_manager: &mut EM,
_input: &S::Input,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>,
{
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
.expect("QemuSystemStateObserver not found");
let feedbackstate = match state
.named_metadata_mut()
.get_mut::<SysGraphFeedbackState>("SysMap") {
Some(s) => s,
None => {
let n=SysGraphFeedbackState::default();
state.named_metadata_mut().insert(n, "SysMap");
state.named_metadata_mut().get_mut::<SysGraphFeedbackState>("SysMap").unwrap()
}
};
let ret = feedbackstate.update(&observer.last_run, &observer.last_input);
self.last_trace = Some(ret.1);
Ok(ret.0)
}
/// Append to the testcase the generated metadata in case of a new corpus item
#[inline]
fn append_metadata(&mut self, _state: &mut S, testcase: &mut Testcase<S::Input>) -> Result<(), Error> {
let a = self.last_trace.take();
match a {
Some(s) => testcase.metadata_mut().insert(SysGraphMetadata::new(s)),
None => (),
}
Ok(())
}
/// Discard the stored metadata in case that the testcase is not added to the corpus
#[inline]
fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
self.last_trace = None;
Ok(())
}
}
impl Named for SysMapFeedback
{
#[inline]
fn name(&self) -> &str {
&self.name
}
}
//============================= Mutators
//=============================== Snippets
// pub struct RandGraphSnippetMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// phantom: PhantomData<(I, S)>,
// }
// impl<I, S> RandGraphSnippetMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// pub fn new() -> Self {
// RandGraphSnippetMutator{phantom: PhantomData}
// }
// }
// impl<I, S> Mutator<I, S> for RandGraphSnippetMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// fn mutate(
// &mut self,
// state: &mut S,
// input: &mut I,
// _stage_idx: i32
// ) -> Result<MutationResult, Error>
// {
// // need our own random generator, because borrowing rules
// let mut myrand = StdRand::new();
// let tmp = &mut state.rand_mut();
// myrand.set_seed(tmp.next());
// drop(tmp);
// let feedbackstate = state
// .feedback_states()
// .match_name::<SysGraphFeedbackState>("SysMap")
// .unwrap();
// let g = &feedbackstate.graph;
// let tmp = state.metadata().get::<SysGraphMetadata>();
// if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
// return Ok(MutationResult::Skipped);
// }
// let trace =tmp.expect("SysGraphMetadata not found");
// // follow the path, extract snippets from last reads, find common snippets.
// // those are likley keys parts. choose random parts from other sibling traces
// let sibling_inputs : Vec<&Vec<u8>>= g[*trace.inner.last().unwrap()].variants.iter().map(|x| &x.input).collect();
// let mut snippet_collector = vec![];
// let mut per_input_counters = HashMap::<&Vec<u8>,usize>::new(); // ugly workaround to track multiple inputs
// for t in &trace.inner {
// let node = &g[*t];
// let mut per_node_snippets = HashMap::<&Vec<u8>,&[u8]>::new();
// for v in &node.variants {
// match per_input_counters.get_mut(&v.input) {
// None => {
// if sibling_inputs.iter().any(|x| *x==&v.input) { // only collect info about siblin inputs from target
// per_input_counters.insert(&v.input, v.input_counter.try_into().unwrap());
// }
// },
// Some(x) => {
// let x_u = *x;
// if x_u<v.input_counter as usize {
// *x=v.input_counter as usize;
// per_node_snippets.insert(&v.input,&v.input[x_u..v.input_counter as usize]);
// }
// }
// }
// }
// snippet_collector.push(per_node_snippets);
// }
// let mut new_input : Vec<u8> = vec![];
// for c in snippet_collector {
// new_input.extend_from_slice(myrand.choose(c).1);
// }
// for i in new_input.iter().enumerate() {
// input.bytes_mut()[i.0]=*i.1;
// }
// Ok(MutationResult::Mutated)
// }
// fn post_exec(
// &mut self,
// _state: &mut S,
// _stage_idx: i32,
// _corpus_idx: Option<usize>
// ) -> Result<(), Error> {
// Ok(())
// }
// }
// impl<I, S> Named for RandGraphSnippetMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// fn name(&self) -> &str {
// "RandGraphSnippetMutator"
// }
// }
// //=============================== Snippets
// pub struct RandInputSnippetMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// phantom: PhantomData<(I, S)>,
// }
// impl<I, S> RandInputSnippetMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// pub fn new() -> Self {
// RandInputSnippetMutator{phantom: PhantomData}
// }
// }
// impl<I, S> Mutator<I, S> for RandInputSnippetMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// fn mutate(
// &mut self,
// state: &mut S,
// input: &mut I,
// _stage_idx: i32
// ) -> Result<MutationResult, Error>
// {
// // need our own random generator, because borrowing rules
// let mut myrand = StdRand::new();
// let tmp = &mut state.rand_mut();
// myrand.set_seed(tmp.next());
// drop(tmp);
// let feedbackstate = state
// .feedback_states()
// .match_name::<SysGraphFeedbackState>("SysMap")
// .unwrap();
// let g = &feedbackstate.graph;
// let tmp = state.metadata().get::<SysGraphMetadata>();
// if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
// return Ok(MutationResult::Skipped);
// }
// let trace = tmp.expect("SysGraphMetadata not found");
// let mut collection : Vec<Vec<u8>> = Vec::new();
// let mut current_pointer : usize = 0;
// for t in &trace.inner {
// let node = &g[*t];
// for v in &node.variants {
// if v.input == input.bytes() {
// if v.input_counter > current_pointer.try_into().unwrap() {
// collection.push(v.input[current_pointer..v.input_counter as usize].to_owned());
// current_pointer = v.input_counter as usize;
// }
// break;
// }
// }
// }
// let index_to_mutate = myrand.below(collection.len() as u64) as usize;
// for i in 0..collection[index_to_mutate].len() {
// collection[index_to_mutate][i] = myrand.below(0xFF) as u8;
// }
// for i in collection.concat().iter().enumerate() {
// input.bytes_mut()[i.0]=*i.1;
// }
// Ok(MutationResult::Mutated)
// }
// fn post_exec(
// &mut self,
// _state: &mut S,
// _stage_idx: i32,
// _corpus_idx: Option<usize>
// ) -> Result<(), Error> {
// Ok(())
// }
// }
// impl<I, S> Named for RandInputSnippetMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// fn name(&self) -> &str {
// "RandInputSnippetMutator"
// }
// }
// //=============================== Suffix
// pub struct RandGraphSuffixMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// phantom: PhantomData<(I, S)>,
// }
// impl<I, S> RandGraphSuffixMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// pub fn new() -> Self {
// RandGraphSuffixMutator{phantom: PhantomData}
// }
// }
// impl<I, S> Mutator<I, S> for RandGraphSuffixMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// fn mutate(
// &mut self,
// state: &mut S,
// input: &mut I,
// _stage_idx: i32
// ) -> Result<MutationResult, Error>
// {
// // need our own random generator, because borrowing rules
// let mut myrand = StdRand::new();
// let tmp = &mut state.rand_mut();
// myrand.set_seed(tmp.next());
// drop(tmp);
// let feedbackstate = state
// .feedback_states()
// .match_name::<SysGraphFeedbackState>("SysMap")
// .unwrap();
// let g = &feedbackstate.graph;
// let tmp = state.metadata().get::<SysGraphMetadata>();
// if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
// return Ok(MutationResult::Skipped);
// }
// let trace =tmp.expect("SysGraphMetadata not found");
// // follow the path, extract snippets from last reads, find common snippets.
// // those are likley keys parts. choose random parts from other sibling traces
// let inp_c_end = g[*trace.inner.last().unwrap()].base.input_counter;
// let mut num_to_reverse = myrand.below(trace.inner.len().try_into().unwrap());
// for t in trace.inner.iter().rev() {
// let int_c_prefix = g[*t].base.input_counter;
// if int_c_prefix < inp_c_end {
// num_to_reverse-=1;
// if num_to_reverse<=0 {
// let mut new_input=input.bytes()[..(int_c_prefix as usize)].to_vec();
// let mut ext : Vec<u8> = (int_c_prefix..inp_c_end).map(|_| myrand.next().to_le_bytes()).flatten().collect();
// new_input.append(&mut ext);
// for i in new_input.iter().enumerate() {
// if input.bytes_mut().len()>i.0 {
// input.bytes_mut()[i.0]=*i.1;
// }
// else { break };
// }
// break;
// }
// }
// }
// Ok(MutationResult::Mutated)
// }
// fn post_exec(
// &mut self,
// _state: &mut S,
// _stage_idx: i32,
// _corpus_idx: Option<usize>
// ) -> Result<(), Error> {
// Ok(())
// }
// }
// impl<I, S> Named for RandGraphSuffixMutator<I, S>
// where
// I: Input + HasBytesVec,
// S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
// {
// fn name(&self) -> &str {
// "RandGraphSuffixMutator"
// }
// }

View File

@ -1,209 +0,0 @@
use std::cell::UnsafeCell;
use std::io::Write;
use std::ops::Range;
use libafl::prelude::UsesInput;
use libafl_qemu::Emulator;
use libafl_qemu::GuestAddr;
use libafl_qemu::QemuHooks;
use libafl_qemu::edges::QemuEdgesMapMetadata;
use libafl_qemu::emu;
use libafl_qemu::hooks;
use crate::systemstate::RawFreeRTOSSystemState;
use crate::systemstate::CURRENT_SYSTEMSTATE_VEC;
use crate::systemstate::NUM_PRIOS;
use super::freertos::TCB_t;
use super::freertos::rtos_struct::List_Item_struct;
use super::freertos::rtos_struct::*;
use super::freertos;
use libafl_qemu::{
helper::{QemuHelper, QemuHelperTuple},
// edges::SAVED_JUMP,
};
//============================= Struct definitions
pub static mut INTR_OFFSET : Option<u64> = None;
pub static mut INTR_DONE : bool = true;
// only used when inputs are injected
pub static mut NEXT_INPUT : Vec<u8> = Vec::new();
//============================= Qemu Helper
/// A Qemu Helper with reads FreeRTOS specific structs from Qemu whenever certain syscalls occur, also inject inputs
#[derive(Debug)]
pub struct QemuSystemStateHelper {
kerneladdr: u32,
tcb_addr: u32,
ready_queues: u32,
input_counter: Option<u64>,
app_range: Range<u32>,
}
impl QemuSystemStateHelper {
#[must_use]
pub fn new(
kerneladdr: u32,
tcb_addr: u32,
ready_queues: u32,
input_counter: Option<u64>,
app_range: Range<u32>,
) -> Self {
QemuSystemStateHelper {
kerneladdr,
tcb_addr: tcb_addr,
ready_queues: ready_queues,
input_counter: input_counter,
app_range,
}
}
}
impl<S> QemuHelper<S> for QemuSystemStateHelper
where
S: UsesInput,
{
fn first_exec<QT>(&self, _hooks: &QemuHooks<'_, QT, S>)
where
QT: QemuHelperTuple<S>,
{
_hooks.instruction(self.kerneladdr, exec_syscall_hook::<QT, S>, false);
#[cfg(feature = "trace_abbs")]
_hooks.jmps(Some(gen_jmp_is_syscall::<QT, S>), Some(trace_api_call::<QT, S>));
}
// TODO: refactor duplicate code
fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {
unsafe {
CURRENT_SYSTEMSTATE_VEC.clear();
let p = LAST_API_CALL.with(|x| x.get());
*p = None;
}
}
fn post_exec(&mut self, emulator: &Emulator, _input: &S::Input) {
trigger_collection(emulator, self)
}
}
#[inline]
fn trigger_collection(emulator: &Emulator, h: &QemuSystemStateHelper) {
let listbytes : u32 = u32::try_from(std::mem::size_of::<freertos::List_t>()).unwrap();
let mut systemstate = RawFreeRTOSSystemState::default();
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()).can_do_io;
(*c.raw_ptr()).can_do_io = 1;
systemstate.qemu_tick = emu::icount_get_raw();
(*c.raw_ptr()).can_do_io = can_do_io;
}
let mut buf : [u8; 4] = [0,0,0,0];
match h.input_counter {
Some(s) => unsafe { emulator.read_phys_mem(s, &mut buf); },
None => (),
};
systemstate.input_counter = u32::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;
};
systemstate.current_tcb = freertos::emu_lookup::lookup(emulator,curr_tcb_addr);
unsafe {
LAST_API_CALL.with(|x|
match *x.get() {
Some(s) => {
systemstate.last_pc = Some(s.0 as u64);
},
None => (),
}
);
}
// println!("{:?}",std::str::from_utf8(&current_tcb.pcTaskName));
for i in 0..NUM_PRIOS {
let target : u32 = listbytes*u32::try_from(i).unwrap()+h.ready_queues;
systemstate.prio_ready_lists[i] = freertos::emu_lookup::lookup(emulator, target);
// println!("List at {}: {:?}",target, systemstate.prio_ready_lists[i]);
let mut next_index = systemstate.prio_ready_lists[i].pxIndex;
for _j in 0..systemstate.prio_ready_lists[i].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);
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));
}
}
unsafe { CURRENT_SYSTEMSTATE_VEC.push(systemstate); }
}
pub fn exec_syscall_hook<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>,
_pc: u32,
)
where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
let emulator = hooks.emulator();
let h = hooks.helpers().match_first_type::<QemuSystemStateHelper>().expect("QemuSystemHelper not found in helper tupel");
trigger_collection(emulator, h);
}
thread_local!(static LAST_API_CALL : UnsafeCell<Option<(GuestAddr,GuestAddr)>> = UnsafeCell::new(None));
pub fn gen_jmp_is_syscall<QT, S>(
hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>,
src: GuestAddr,
dest: GuestAddr,
) -> Option<u64>
where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
if let Some(h) = hooks.helpers().match_first_type::<QemuSystemStateHelper>() {
if h.app_range.contains(&src) && !h.app_range.contains(&dest) {
// println!("New jmp {:x} {:x}", src, dest);
return Some(1);
}
}
return None;
}
pub fn trace_api_call<QT, S>(
_hooks: &mut QemuHooks<'_, QT, S>,
_state: Option<&mut S>,
src: GuestAddr, dest: GuestAddr, id: u64
)
where
S: UsesInput,
QT: QemuHelperTuple<S>,
{
unsafe {
let p = LAST_API_CALL.with(|x| x.get());
*p = Some((src,dest));
// print!("*");
}
}

View File

@ -1,267 +0,0 @@
//! 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},
corpus::{Corpus, Testcase},
inputs::UsesInput,
schedulers::{Scheduler, TestcaseScore, minimizer::DEFAULT_SKIP_NON_FAVORED_PROB },
state::{HasCorpus, HasMetadata, HasRand, UsesState, State},
Error, SerdeAny, prelude::HasLen,
};
use crate::worst::MaxTimeFavFactor;
use super::FreeRTOSSystemStateMetadata;
/// A state metadata holding a map of favoreds testcases for each map entry
#[derive(Debug, Serialize, Deserialize, SerdeAny, Default)]
pub struct LongestTracesMetadata {
/// map index -> corpus index
pub max_trace_length: usize,
}
impl LongestTracesMetadata {
fn new(l : usize) -> Self {
Self {max_trace_length: l}
}
}
/// The [`MinimizerScheduler`] employs a genetic algorithm to compute a subset of the
/// corpus that exercise all the requested features (e.g. all the coverage seen so far)
/// prioritizing [`Testcase`]`s` using [`TestcaseScore`]
#[derive(Debug, Clone)]
pub struct LongestTraceScheduler<CS> {
base: CS,
skip_non_favored_prob: u64,
}
impl<CS> UsesState for LongestTraceScheduler<CS>
where
CS: UsesState,
{
type State = CS::State;
}
impl<CS> Scheduler for LongestTraceScheduler<CS>
where
CS: Scheduler,
CS::State: HasCorpus + HasMetadata + HasRand,
{
/// Add an entry to the corpus and return its index
fn on_add(&self, state: &mut CS::State, idx: usize) -> Result<(), Error> {
let l = state.corpus()
.get(idx)?
.borrow()
.metadata()
.get::<FreeRTOSSystemStateMetadata>().map_or(0, |x| x.trace_length);
self.get_update_trace_length(state,l);
self.base.on_add(state, idx)
}
/// Replaces the testcase at the given idx
fn on_replace(
&self,
state: &mut CS::State,
idx: usize,
testcase: &Testcase<<CS::State as UsesInput>::Input>,
) -> Result<(), Error> {
let l = state.corpus()
.get(idx)?
.borrow()
.metadata()
.get::<FreeRTOSSystemStateMetadata>().map_or(0, |x| x.trace_length);
self.get_update_trace_length(state, l);
self.base.on_replace(state, idx, testcase)
}
/// Removes an entry from the corpus, returning M if M was present.
fn on_remove(
&self,
state: &mut CS::State,
idx: usize,
testcase: &Option<Testcase<<CS::State as UsesInput>::Input>>,
) -> Result<(), Error> {
self.base.on_remove(state, idx, testcase)?;
Ok(())
}
/// Gets the next entry
fn next(&self, state: &mut CS::State) -> Result<usize, Error> {
let mut idx = self.base.next(state)?;
while {
let l = state.corpus()
.get(idx)?
.borrow()
.metadata()
.get::<FreeRTOSSystemStateMetadata>().map_or(0, |x| x.trace_length);
let m = self.get_update_trace_length(state,l);
state.rand_mut().below(m) > l as u64
} && state.rand_mut().below(100) < self.skip_non_favored_prob
{
idx = self.base.next(state)?;
}
Ok(idx)
}
}
impl<CS> LongestTraceScheduler<CS>
where
CS: Scheduler,
CS::State: HasCorpus + HasMetadata + HasRand,
{
pub fn get_update_trace_length(&self, state: &mut CS::State, par: usize) -> u64 {
// Create a new top rated meta if not existing
if let Some(td) = state.metadata_mut().get_mut::<LongestTracesMetadata>() {
let m = max(td.max_trace_length, par);
td.max_trace_length = m;
m as u64
} else {
state.add_metadata(LongestTracesMetadata::new(par));
par as u64
}
}
pub fn new(base: CS) -> Self {
Self {
base,
skip_non_favored_prob: DEFAULT_SKIP_NON_FAVORED_PROB,
}
}
}
//==========================================================================================
/// A state metadata holding a map of favoreds testcases for each map entry
#[derive(Debug, Serialize, Deserialize, SerdeAny, Default)]
pub struct GeneticMetadata {
pub current_gen: Vec<(usize, f64)>,
pub current_cursor: usize,
pub next_gen: Vec<(usize, f64)>,
pub gen: usize
}
impl GeneticMetadata {
fn new(current_gen: Vec<(usize, f64)>, next_gen: Vec<(usize, f64)>) -> Self {
Self {current_gen, current_cursor: 0, next_gen, gen: 0}
}
}
#[derive(Debug, Clone)]
pub struct GenerationScheduler<S> {
phantom: PhantomData<S>,
gen_size: usize,
}
impl<S> UsesState for GenerationScheduler<S>
where
S: UsesInput,
{
type State = S;
}
impl<S> Scheduler for GenerationScheduler<S>
where
S: 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(&self, state: &mut Self::State) -> Result<usize, Error> {
let mut to_remove : Vec<(usize, f64)> = vec![];
let mut to_return : usize = 0;
let c = state.corpus().count();
let gm = state.metadata_mut().get_mut::<GeneticMetadata>().expect("Corpus Scheduler empty");
// println!("index: {} curr: {:?} next: {:?} gen: {} corp: {}", gm.current_cursor, gm.current_gen.len(), gm.next_gen.len(), gm.gen,
// c);
match gm.current_gen.get(gm.current_cursor) {
Some(c) => {
gm.current_cursor+=1;
// println!("normal next: {}", (*c).0);
return Ok((*c).0)
},
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;
gm.current_cursor=1;
gm.gen+=1;
}
};
// 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();
for i in to_remove {
state.corpus_mut().remove(i.0).unwrap();
}
// println!("switch next: {to_return}");
return Ok(to_return);
}
/// Add the new input to the next generation
fn on_add(
&self,
state: &mut Self::State,
idx: usize
) -> Result<(), Error> {
// println!("On Add {idx}");
let mut tc = state.corpus_mut().get(idx).unwrap().borrow_mut().clone();
let ff = MaxTimeFavFactor::compute(&mut tc, state).unwrap();
if let Some(gm) = state.metadata_mut().get_mut::<GeneticMetadata>() {
gm.next_gen.push((idx,ff));
} else {
state.add_metadata(GeneticMetadata::new(vec![], vec![(idx,ff)]));
}
Ok(())
}
fn on_replace(
&self,
_state: &mut Self::State,
_idx: usize,
_prev: &Testcase<<Self::State as UsesInput>::Input>
) -> Result<(), Error> {
// println!("On Replace {_idx}");
Ok(())
}
fn on_remove(
&self,
state: &mut Self::State,
idx: usize,
_testcase: &Option<Testcase<<Self::State as UsesInput>::Input>>
) -> Result<(), Error> {
// println!("On Remove {idx}");
if let Some(gm) = state.metadata_mut().get_mut::<GeneticMetadata>() {
gm.next_gen = gm.next_gen.drain(..).into_iter().filter(|x| (*x).0 != idx).collect::<Vec<(usize, f64)>>();
gm.current_gen = gm.current_gen.drain(..).into_iter().filter(|x| (*x).0 != idx).collect::<Vec<(usize, f64)>>();
} else {
state.add_metadata(GeneticMetadata::new(vec![], vec![]));
}
Ok(())
}
}
impl<S> GenerationScheduler<S>
{
pub fn new() -> Self {
Self {
phantom: PhantomData,
gen_size: 100,
}
}
}

View File

@ -1,381 +0,0 @@
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, AsSlice};
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::{HasClientPerfMonitor, HasCorpus, UsesState};
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::{
tuples::Named,
HasLen,
},
observers::Observer,
Error,
};
use crate::clock::QemuClockObserver;
use crate::systemstate::FreeRTOSSystemStateMetadata;
//=========================== Scheduler
pub type TimeMaximizerCorpusScheduler<CS> =
MinimizerScheduler<CS, MaxTimeFavFactor<<CS as UsesState>::State>, MapIndexesMetadata>;
/// Multiply the testcase size with the execution time.
/// This favors small and quick testcases.
#[derive(Debug, Clone)]
pub struct MaxTimeFavFactor<S>
where
S: HasCorpus + HasMetadata,
S::Input: HasLen,
{
phantom: PhantomData<S>,
}
impl<S> TestcaseScore<S> for MaxTimeFavFactor<S>
where
S: HasCorpus + HasMetadata,
S::Input: HasLen,
{
fn compute(entry: &mut Testcase<<S as UsesInput>::Input>, state: &S) -> Result<f64, Error> {
// TODO maybe enforce entry.exec_time().is_some()
let et = entry.exec_time().expect("testcase.exec_time is needed for scheduler");
let tns : i64 = et.as_nanos().try_into().expect("failed to convert time");
Ok(-tns as f64)
}
}
pub type LenTimeMaximizerCorpusScheduler<CS> =
MinimizerScheduler<CS, MaxExecsLenFavFactor<<CS as UsesState>::State>, MapIndexesMetadata>;
pub type TimeStateMaximizerCorpusScheduler<CS> =
MinimizerScheduler<CS, MaxTimeFavFactor<<CS as UsesState>::State>, FreeRTOSSystemStateMetadata>;
/// Multiply the testcase size with the execution time.
/// This favors small and quick testcases.
#[derive(Debug, Clone)]
pub struct MaxExecsLenFavFactor<S>
where
S: HasCorpus + HasMetadata,
S::Input: HasLen,
{
phantom: PhantomData<S>,
}
impl<S> TestcaseScore<S> for MaxExecsLenFavFactor<S>
where
S: HasCorpus + HasMetadata,
S::Input: HasLen,
{
fn compute(entry: &mut Testcase<S::Input>, state: &S) -> Result<f64, Error> {
let execs_per_hour = (3600.0/entry.exec_time().expect("testcase.exec_time is needed for scheduler").as_secs_f64());
let execs_times_length_per_hour = execs_per_hour*entry.cached_len()? as f64;
Ok(execs_times_length_per_hour)
}
}
//===================================================================
/// A Feedback reporting if the Input consists of strictly decreasing bytes.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SortedFeedback {
}
impl<S> Feedback<S> for SortedFeedback
where
S: UsesInput + HasClientPerfMonitor,
S::Input: HasTargetBytes,
{
#[allow(clippy::wrong_self_convention)]
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &S::Input,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>,
{
let t = _input.target_bytes();
let tmp = t.as_slice();
if tmp.len()<32 {return Ok(false);}
let tmp = Vec::<u8>::from(&tmp[0..32]);
// tmp.reverse();
if tmp.is_sorted_by(|a,b| match a.partial_cmp(b).unwrap_or(Less) {
Less => Some(Greater),
Equal => Some(Greater),
Greater => Some(Less),
}) {return Ok(true)};
return Ok(false);
}
}
impl Named for SortedFeedback {
#[inline]
fn name(&self) -> &str {
"Sorted"
}
}
impl SortedFeedback {
/// Creates a new [`HitFeedback`]
#[must_use]
pub fn new() -> Self {
Self {}
}
}
impl Default for SortedFeedback {
fn default() -> Self {
Self::new()
}
}
//===================================================================
/// A Feedback which expects a certain minimum execution time
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ExecTimeReachedFeedback
{
target_time: u64,
}
impl<S> Feedback<S> for ExecTimeReachedFeedback
where
S: UsesInput + HasClientPerfMonitor,
{
#[allow(clippy::wrong_self_convention)]
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &S::Input,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>,
{
let observer = observers.match_name::<QemuClockObserver>("clock")
.expect("QemuClockObserver not found");
Ok(observer.last_runtime() >= self.target_time)
}
}
impl Named for ExecTimeReachedFeedback
{
#[inline]
fn name(&self) -> &str {
"ExecTimeReachedFeedback"
}
}
impl ExecTimeReachedFeedback
where
{
/// Creates a new [`ExecTimeReachedFeedback`]
#[must_use]
pub fn new(target_time : u64) -> Self {
Self {target_time: target_time}
}
}
pub static mut EXEC_TIME_COLLECTION : Vec<u32> = Vec::new();
/// A Noop Feedback which records a list of all execution times
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ExecTimeCollectorFeedback
{
}
impl<S> Feedback<S> for ExecTimeCollectorFeedback
where
S: UsesInput + HasClientPerfMonitor,
{
#[allow(clippy::wrong_self_convention)]
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &S::Input,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>,
{
let observer = observers.match_name::<QemuClockObserver>("clock")
.expect("QemuClockObserver not found");
unsafe { EXEC_TIME_COLLECTION.push(observer.last_runtime().try_into().unwrap()); }
Ok(false)
}
}
impl Named for ExecTimeCollectorFeedback
{
#[inline]
fn name(&self) -> &str {
"ExecTimeCollectorFeedback"
}
}
impl ExecTimeCollectorFeedback
where
{
/// Creates a new [`ExecTimeCollectorFeedback`]
#[must_use]
pub fn new() -> Self {
Self {}
}
}
/// Shared Metadata for a SysStateFeedback
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct ExecTimeCollectorFeedbackState
{
collection: Vec<u32>,
}
impl Named for ExecTimeCollectorFeedbackState
{
#[inline]
fn name(&self) -> &str {
"ExecTimeCollectorFeedbackState"
}
}
//===================================================================
/// A Feedback which expects a certain minimum execution time
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ExecTimeIncFeedback
{
longest_time: u64,
last_is_longest: bool
}
impl<S> Feedback<S> for ExecTimeIncFeedback
where
S: UsesInput + HasClientPerfMonitor,
{
#[allow(clippy::wrong_self_convention)]
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &S::Input,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>,
{
let observer = observers.match_name::<QemuClockObserver>("clocktime")
.expect("QemuClockObserver not found");
if observer.last_runtime() > self.longest_time {
self.longest_time = observer.last_runtime();
self.last_is_longest = true;
Ok(true)
} else {
self.last_is_longest = false;
Ok(false)
}
}
fn append_metadata(
&mut self,
_state: &mut S,
testcase: &mut Testcase<<S as UsesInput>::Input>,
) -> Result<(), Error> {
#[cfg(feature = "feed_afl")]
if self.last_is_longest {
let mim : Option<&mut MapIndexesMetadata>= testcase.metadata_mut().get_mut();
// pretend that the longest input alone excercises some non-existing edge, to keep it relevant
mim.unwrap().list.push(usize::MAX);
};
Ok(())
}
}
impl Named for ExecTimeIncFeedback
{
#[inline]
fn name(&self) -> &str {
"ExecTimeReachedFeedback"
}
}
impl ExecTimeIncFeedback
where
{
/// Creates a new [`ExecTimeReachedFeedback`]
#[must_use]
pub fn new() -> Self {
Self {longest_time: 0, last_is_longest: false}
}
}
/// A Noop Feedback which records a list of all execution times
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AlwaysTrueFeedback
{
}
impl<S> Feedback<S> for AlwaysTrueFeedback
where
S: UsesInput + HasClientPerfMonitor,
{
#[allow(clippy::wrong_self_convention)]
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &S::Input,
_observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>,
{
Ok(true)
}
}
impl Named for AlwaysTrueFeedback
{
#[inline]
fn name(&self) -> &str {
"AlwaysTrueFeedback"
}
}
impl AlwaysTrueFeedback
where
{
/// Creates a new [`ExecTimeCollectorFeedback`]
#[must_use]
pub fn new() -> Self {
Self {
}
}
}

View File

@ -82,7 +82,7 @@ windows_alias = "unsupported"
[tasks.run_unix]
script_runner = "@shell"
script='''
./${FUZZER_NAME} --cores 0 --input ./corpus
./${FUZZER_NAME} --cores 0 --input ./corpus &
'''
dependencies = [ "fuzzer" ]

View File

@ -82,7 +82,7 @@ windows_alias = "unsupported"
[tasks.run_unix]
script_runner = "@shell"
script='''
./${FUZZER_NAME} --cores 0 --input ./corpus
./${FUZZER_NAME} --cores 0 --input ./corpus &
'''
dependencies = [ "fuzzer" ]

View File

@ -82,7 +82,7 @@ windows_alias = "unsupported"
[tasks.run_unix]
script_runner = "@shell"
script='''
./${FUZZER_NAME} --cores 0 --input ./corpus
./${FUZZER_NAME} --cores 0 --input ./corpus &
'''
dependencies = [ "fuzzer" ]

View File

@ -9,10 +9,9 @@ default = ["std"]
std = []
[profile.release]
incremental = true
debug = true
lto = "fat"
lto = true
codegen-units = 1
debug = true
[dependencies]
libafl = { path = "../../libafl/" }

View File

@ -28,6 +28,7 @@ use libafl::{
stages::StdMutationalStage,
state::{HasCorpus, StdState},
Error,
//prelude::{SimpleMonitor, SimpleEventManager},
};
use libafl_qemu::{
edges, edges::QemuEdgeCoverageHelper, elf::EasyElf, emu::Emulator, GuestPhysAddr, QemuExecutor,
@ -87,15 +88,20 @@ pub fn fuzz() {
}
emu.remove_breakpoint(main_addr);
emu.set_breakpoint(breakpoint); // BREAKPOINT
// let saved_cpu_states: Vec<_> = (0..emu.num_cpus())
// .map(|i| emu.cpu_from_index(i).save_state())
// .collect();
// emu.save_snapshot("start", true);
let snap = emu.create_fast_snapshot(true);
emu.set_breakpoint(breakpoint); // BREAKPOINT
//use libafl_qemu::IntoEnumIterator;
// Save the GPRs
//let mut saved_regs: Vec<u64> = vec![];
//for r in Regs::iter() {
// saved_regs.push(emu.cpu_from_index(0).read_reg(r).unwrap());
//}
let mut saved_cpu_states: Vec<_> = (0..emu.num_cpus())
.map(|i| emu.cpu_from_index(i).save_state())
.collect();
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |input: &BytesInput| {
@ -108,6 +114,10 @@ pub fn fuzz() {
// len = MAX_INPUT_SIZE;
}
//for (r, v) in saved_regs.iter().enumerate() {
// emu.cpu_from_index(0).write_reg(r as i32, *v).unwrap();
//}
emu.write_phys_mem(input_addr, buf);
emu.run();
@ -116,24 +126,19 @@ pub fn fuzz() {
let mut pcs = (0..emu.num_cpus())
.map(|i| emu.cpu_from_index(i))
.map(|cpu| -> Result<u32, String> { cpu.read_reg(Regs::Pc) });
let _ret = match pcs
let ret = match pcs
.find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0)))
{
Some(_) => ExitKind::Ok,
None => ExitKind::Crash,
};
// OPTION 1: restore only the CPU state (registers et. al)
// for (i, s) in saved_cpu_states.iter().enumerate() {
// emu.cpu_from_index(i).restore_state(s);
// }
for (i, s) in saved_cpu_states.iter().enumerate() {
emu.cpu_from_index(i).restore_state(s);
}
// OPTION 2: restore a slow vanilla QEMU snapshot
// emu.load_snapshot("start", true);
// OPTION 3: restore a fast devices+mem snapshot
emu.restore_fast_snapshot(snap);
ret
}
};
@ -156,7 +161,7 @@ pub fn fuzz() {
);
// A feedback to choose if an input is a solution or not
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
let mut objective = CrashFeedback::new(); //feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());
// If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| {

View File

@ -1,13 +0,0 @@
[package]
name = "tinyinst_simple"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libafl = { version = "0.8.1", path = "../../libafl", features = ["tui"] }
libafl_tinyinst = { version = "0.8.1", path = "../../libafl_tinyinst" }
[profile.release]
codegen-units = 1
opt-level = 3

View File

@ -1,8 +0,0 @@
[tasks.build_test]
command = "cl"
args = ["./test/test.c", "-o", "./test/test.exe"]
[tasks.run]
dependencies = ["build_test"]
command = "cargo"
args = ["run", "--release"]

View File

@ -1,3 +0,0 @@
# Run Instruction
1. Open up developer powershell so that you have access to cl (Windows Default Compiler)
2. Run `cargo make run` to run the fuzzer

View File

@ -1,62 +0,0 @@
use std::path::PathBuf;
use libafl::{
bolts::{
rands::{RandomSeed, StdRand},
tuples::tuple_list,
},
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus, Testcase},
events::SimpleEventManager,
feedbacks::{CrashFeedback, ListFeedback},
inputs::BytesInput,
monitors::SimpleMonitor,
mutators::{havoc_mutations, StdScheduledMutator},
observers::ListObserver,
schedulers::RandScheduler,
stages::StdMutationalStage,
state::StdState,
Fuzzer, StdFuzzer,
};
use libafl_tinyinst::executor::TinyInstExecutor;
static mut COVERAGE: Vec<u64> = vec![];
fn main() {
// Tinyinst things
let tinyinst_args = vec!["-instrument_module".to_string(), "test.exe".to_string()];
let args = vec![".\\test\\test.exe".to_string(), "@@".to_string()];
let observer = ListObserver::new("cov", unsafe { &mut COVERAGE });
let mut feedback = ListFeedback::new_with_observer(&observer);
let input = BytesInput::new(b"bad".to_vec());
let rand = StdRand::new();
let mut corpus = CachedOnDiskCorpus::new(PathBuf::from("./corpus_discovered"), 64).unwrap();
corpus
.add(Testcase::new(input))
.expect("error in adding corpus");
let solutions = OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap();
let mut objective = CrashFeedback::new();
let mut state = StdState::new(rand, corpus, solutions, &mut feedback, &mut objective).unwrap();
let scheduler = RandScheduler::new();
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
let monitor = SimpleMonitor::new(|x| println!("{}", x));
let mut mgr = SimpleEventManager::new(monitor);
let mut executor = unsafe {
TinyInstExecutor::new(
&mut COVERAGE,
tinyinst_args,
args,
5000,
tuple_list!(observer),
)
};
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("error in fuzzing loop");
}

View File

@ -1 +0,0 @@
bad12

View File

@ -1 +0,0 @@
ok

View File

@ -1,36 +0,0 @@
#include <stdio.h>
void pass1(char *buf, int buf_size) {
char target[0x10];
if (buf[0] == 'b') {
if (buf[1] == 'a') {
if (buf[2] == 'd') {
if (buf[3] == '1') {
if (buf[4] == '2') {
printf("You got me\n");
memcpy(target, buf, 0x1000000);
printf("GG\n");
}
}
}
}
}
}
int main(int argc, char *argv[]) {
FILE *fp;
char buf[0x1000];
if (argc == 2) {
fp = fopen(argv[1], "r");
if (fp == NULL) {
printf("File not found\n");
printf("Received filename %s\n", argv[1]);
return 1;
}
fscanf(fp, "%s", buf);
pass1(buf, sizeof(buf));
} else {
printf("there is nothing\n");
}
}

View File

@ -1,4 +1,6 @@
*.qcow2
corpus
*.axf
*.qcow2
demo
*.ron
*.bsp
tmp*

View File

@ -0,0 +1,56 @@
[package]
name = "wcet_qemu_sys"
version = "0.1.2"
authors = [ "Alwin Berger <alwin.berger@tu-dortmund.de>" ]
edition = "2021"
[features]
default = ["std", "obj_collect"]
std = []
multicore = []
fuzz_interrupt = [] # use the first bytes of the input for a timed interrupts
fuzz_random = [] # just process random inputs
# select which feedbacks to use. enable at least one.
feed_known_edges = []
feed_afl = []
feed_clock = []
feed_state = []
feed_graph = []
# choose exactly one scheduler
sched_queue = []
sched_mapmax = []
sched_state = []
sched_graph = []
# objective selection, additive
obj_collect = []
obj_trace = []
obj_edges = []
obj_ticks = []
muta_input = [ "sched_graph" ]
muta_snip = [ "sched_graph" ]
muta_suffix = [ "sched_graph" ]
muta_interrupt = []
benchmark = [] # don't save corpus to disk, easy parallelizable
dump_infos = [] # dump select corpus items for analysis
[profile.release]
debug = true
[dependencies]
libafl = { path = "../../libafl/" }
libafl_qemu = { path = "../../libafl_qemu/", features = ["systemmode", "arm", "jmp_as_edge"] }
clap = { version = "3.0.0-beta.2", features = ["default"] }
serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib
ron = "0.7" # write serialized data - including hashmaps
hashbrown = { version = "0.11", features = ["serde", "ahash-compile-time-rng"], default-features=false } # A faster hashmap, nostd compatible
nix = "0.23.0"
goblin = "0.4.2"
either = "1.6.1"
num-traits = "0.2"
petgraph = { version="0.6.0", features = ["serde-1"] }

View File

@ -0,0 +1,113 @@
BENCHDIR = target/bench
TNAME = tmrint
TARGET = $(TNAME).axf
TARGET_TRACE = $(BENCHDIR)/traces/$(TNAME)_worst.ron
TARGET_EDGES = $(BENCHDIR)/edges/$(TNAME)_worst.ron
RUNTIME = 3600
INT_FLAG = ,fuzz_interrupt
COMMON_FLAGS = benchmark,dump_infos#,$(INT_FLAG)
NUM_JOB = 3
NUM_ITERATIONS = 10
LOCKFILE = /tmp/bench_sem
LOCK = ./bash_sem.sh $(LOCKFILE) lock
RELEASE = ./bash_sem.sh $(LOCKFILE) release
$(BENCHDIR)/bin:
mkdir -p $@
$(BENCHDIR)/target_random:
cargo build --target-dir $@ --features $(COMMON_FLAGS),sched_queue,fuzz_random
$(BENCHDIR)/target_known_edges:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_known_edges,sched_queue
$(BENCHDIR)/target_afl_queue:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_afl,sched_queue
$(BENCHDIR)/target_afl_mapmax:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_afl,sched_mapmax
$(BENCHDIR)/target_state:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_state,sched_state
$(BENCHDIR)/target_state_afl:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_state,feed_afl,sched_state
$(BENCHDIR)/target_state_afl_int:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_state,sched_state,muta_interrupt
$(BENCHDIR)/target_graph:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_graph,sched_graph
$(BENCHDIR)/target_graph_muta:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_graph,sched_graph,muta_snip,muta_input,muta_suffix
$(BENCHDIR)/target_graph_afl:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_graph,sched_graph,feed_afl
$(BENCHDIR)/target_graph_muta_afl:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_graph,sched_graph,feed_afl,muta_snip,muta_input,muta_suffix
$(BENCHDIR)/target_graph_muta_afl_int:
cargo build --bin fuzzer --target-dir $@ --features $(COMMON_FLAGS),feed_graph,sched_graph,feed_afl,muta_snip,muta_input,muta_suffix,muta_interrupt
binaries: $(BENCHDIR)/target_random $(BENCHDIR)/target_known_edges $(BENCHDIR)/target_afl_queue $(BENCHDIR)/target_afl_mapmax $(BENCHDIR)/target_state $(BENCHDIR)/target_state_afl $(BENCHDIR)/target_state_afl_int \
$(BENCHDIR)/target_graph $(BENCHDIR)/target_graph_muta $(BENCHDIR)/target_graph_afl $(BENCHDIR)/target_graph_muta_afl $(BENCHDIR)/target_graph_muta_afl_int
# variants: known_edges, afl_queue, afl_mapmax, state, state_afl, graph, graph_muta, graph_afl, graph_all
$(BENCHDIR)/bench_%.log: $(BENCHDIR)/target_% $(TARGET_TRACE)
mkdir -p $(BENCHDIR)/execs
for i in {1..$(NUM_ITERATIONS)}; do \
CASE=$$(basename -s.log $@ | cut -d'_' -f 2- ); \
[ -f $(BENCHDIR)/execs/$$CASE\_$$i.exec -a -f $@_$$i ] && continue; \
$(LOCK); \
echo $$CASE iteration $$i; \
mkdir -p $(BENCHDIR)/infos/$$CASE ; \
./bench_fuzzer.sh $</debug/fuzzer $(TARGET) --libafl-traces $(TARGET_TRACE) --libafl-fuzztime $(RUNTIME) \
--libafl-dump $(BENCHDIR)/infos/$$CASE/$$i \
--libafl-edges $(TARGET_EDGES) --libafl-exectimes $(BENCHDIR)/execs/$$CASE\_$$i.exec > $@_$$i && \
sed -i "1 i\\$$CASE " $(BENCHDIR)/execs/$$CASE\_$$i.exec && \
$(RELEASE) & \
done
wait
for i in $@_*; do grep Stats $$i | tail -n 1 >> $@; done
benchmarks_noint: target/bench/bench_known_edges.log target/bench/bench_afl_queue.log target/bench/bench_afl_mapmax.log target/bench/bench_state.log target/bench/bench_state_afl.log \
target/bench/bench_graph.log target/bench/bench_graph_muta.log target/bench/bench_graph_afl.log target/bench/bench_graph_muta_afl.log
# target/bench/bench_graph_all.log target/bench/bench_state_afl_int.log
benchmarks_int: target/bench/bench_graph_muta_afl.log target/bench/bench_graph_muta_afl_int.log target/bench/bench_state_afl.log target/bench/bench_state_afl_int.log target/bench/bench_afl_mapmax.log target/bench/bench_afl_queue.log
benchmark_random: target/bench/bench_random.log
all: binaries benchmarks_noint
clean_bench:
rm -rf $(BENCHDIR)/bench_*
clean_aggregate:
rm -rf $(BENCHDIR)/bench_*.log
clean:
rm -rf target/bench
echo $(NUM_JOB) > $(LOCKFILE)
rm -rf $(LOCKFILE)_lockdir
reset_sem:
echo $(NUM_JOB) > $(LOCKFILE)
rm -rf $(LOCKFILE)_lockdir
%.case: %_inputs $(BENCHDIR)/target_random
mkdir -p $(BENCHDIR)/traces $(BENCHDIR)/edges
for i in $</*.case; do \
CASE=$$(basename -s.case $$i); echo $$CASE; \
PROG=$$(basename -s.case $@); \
LD_LIBRARY_PATH=$(BENCHDIR)/target_random/debug $(BENCHDIR)/target_random/debug/showmap --libafl-snapshot tmp/dummy.qcow2 \
--libafl-out tmp/test_out --libafl-in tmp/test_in --libafl-kernel $(TARGET) \
--libafl-single $$i \
--libafl-edges $(BENCHDIR)/edges/$$PROG\_$$CASE.ron \
--libafl-traces $(BENCHDIR)/traces/$$PROG\_$$CASE.ron | \
grep "Qemu Ticks:"; \
done
$(TARGET_TRACE): $(TNAME).case

View File

@ -0,0 +1,10 @@
# Systemmode Fuzzing
This folder contains an example fuzzer tailored for system-images.
For the moment it is mostly hardcoded.
## Build
To build this example, run `cargo build --release` or `cargo build --release`.
Also build the FreeRTOS demo named CORTEX_M3_MPS2_QEMU_GCC.
Then run `./starter.sh RTOSDemo.axf` and observe.

View File

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# A generic counting semaphore in bash
# Parameter is the lockfile and operation
# Setup:
# rm /tmp/test
# echo $num > /tmp/test
set -e
if [[ ! -f $1 ]]; then echo "Parameter 1: File Does not exist"; exit; fi
if [[ $2 != "lock" ]] && [[ $2 != "release" ]]; then echo "Parameter 2: must be lock or release"; exit; fi
if [[ $2 = "lock" ]]; then
SEM=''
while [[ -z $SEM ]]; do
if (( $(cat $1 ) == 0 )); then sleep 1; wait; continue; fi
if mkdir $1_lockdir > /dev/null 2>&1 ; then
VAL=$(cat $1)
if (( $VAL > 0 ))
then
SEM=$(sed -i "s@$VAL@$(( $VAL - 1))@w /dev/stdout" $1)
echo "Take $VAL -> $SEM"
rmdir $1_lockdir
else
rmdir $1_lockdir
sleep 1; wait
fi
else
sleep 0.5;
fi
done
else
echo "Attempt unlock"
SEM=''
while [[ -z $SEM ]]; do
if mkdir $1_lockdir > /dev/null 2>&1 ; then
VAL=$(cat $1)
SEM=$(sed -i "s@$VAL@$(( $VAL + 1))@w /dev/stdout" $1)
echo "Give $VAL -> $(( $VAL + 1 ))"
else
sleep 0.1;
fi
rmdir $1_lockdir
done
fi
#SEM=''; while [[ -z SEM ]]; do VAL=$(cat /tmp/test); if (( $VAL > 0 )); then SEM=$(sed -i "s@$VAL@$(( $VAL - 1))@w /dev/stdout" /tmp/test); else sleep 1; wait; fi; done

View File

@ -0,0 +1,5 @@
mkdir -p tmp/test_in tmp/test_out
[ ! -f tmp/test_in/test ] && echo " !test" > tmp/test_in/test
[ ! -f tmp/dummy.qcow2 ] && qemu-img create -f qcow2 tmp/dummy.qcow2 32M
export LD_LIBRARY_PATH=target/debug
$1 --libafl-snapshot tmp/dummy.qcow2 --libafl-out tmp/test_out --libafl-in tmp/test_in --libafl-kernel ${@:2}

View File

@ -0,0 +1,4 @@
mkdir -p tmp/test_in tmp/test_out
[ ! -f tmp/test_in/test ] && echo " !test" > tmp/test_in/test
[ ! -f tmp/dummy.qcow2 ] && qemu-img create -f qcow2 tmp/dummy.qcow2 32M
LD_LIBRARY_PATH=target/debug target/debug/fuzzer --libafl-logfile tmp/libafl.log --libafl-snapshot tmp/dummy.qcow2 --libafl-out tmp/test_out --libafl-in tmp/test_in --libafl-kernel $@

View File

@ -0,0 +1,4 @@
mkdir -p tmp/test_in tmp/test_out
[ ! -f tmp/test_in/test ] && echo " !test" > tmp/test_in/test
[ ! -f tmp/dummy.qcow2 ] && qemu-img create -f qcow2 tmp/dummy.qcow2 32M
LD_LIBRARY_PATH=target/debug target/debug/showmap --libafl-snapshot tmp/dummy.qcow2 --libafl-out tmp/test_out --libafl-in tmp/test_in --libafl-kernel $@

View File

@ -0,0 +1,769 @@
//! A singlethreaded QEMU fuzzer that can auto-restart.
use libafl::Evaluator;
use libafl::generators::Generator;
use core::cmp::min;
use wcet_qemu_sys::sysstate::mutators::InterruptShifterMutator;
use wcet_qemu_sys::sysstate::IRQ_INPUT_OFFSET;
use std::str::FromStr;
use wcet_qemu_sys::worst::DumpEdgesMapMetadata;
use wcet_qemu_sys::worst::DumpMapFeedback;
use libafl::inputs::HasBytesVec;
use wcet_qemu_sys::sysstate::FreeRTOSSystemStateMetadata;
use wcet_qemu_sys::sysstate::feedbacks::DumpSystraceFeedback;
use libafl::state::HasSolutions;
use wcet_qemu_sys::worst::TimeMaximizerCorpusScheduler;
use libafl::corpus::InMemoryCorpus;
use wcet_qemu_sys::sysstate::graph::RandGraphSuffixMutator;
use wcet_qemu_sys::sysstate::graph::RandInputSnippetMutator;
use wcet_qemu_sys::worst::DummyFeedback;
use wcet_qemu_sys::worst::ExecTimeCollectorFeedback;
use wcet_qemu_sys::worst::EXEC_TIME_COLLECTION;
use wcet_qemu_sys::worst::ExecTimeReachedFeedback;
use libafl::inputs::Input;
use libafl::feedbacks::Feedback;
use libafl::HasFeedback;
use libafl::bolts::tuples::MatchName;
use libafl::state::HasFeedbackStates;
use wcet_qemu_sys::sysstate::graph::SysGraphMetadata;
use wcet_qemu_sys::sysstate::helpers::INTR_OFFSET;
use wcet_qemu_sys::sysstate::graph::RandGraphSnippetMutator;
use wcet_qemu_sys::sysstate::graph::GraphMaximizerCorpusScheduler;
use wcet_qemu_sys::sysstate::graph::SysMapFeedback;
use wcet_qemu_sys::sysstate::graph::SysGraphFeedbackState;
use libafl::stats::SimpleStats;
use wcet_qemu_sys::sysstate::feedbacks::HitSysStateFeedback;
use wcet_qemu_sys::sysstate::RefinedFreeRTOSSystemState;
use libafl::corpus::QueueCorpusScheduler;
use libafl_qemu::QemuInstrumentationFilter;
use wcet_qemu_sys::sysstate::helpers::QemuSystemStateHelper;
use wcet_qemu_sys::sysstate::observers::QemuSysStateObserver;
use wcet_qemu_sys::sysstate::feedbacks::SysStateFeedbackState;
use wcet_qemu_sys::sysstate::feedbacks::NovelSysStateFeedback;
use wcet_qemu_sys::sysstate::IRQ_INPUT_BYTES_NUMBER;
use wcet_qemu_sys::worst::QemuHashMapObserver;
use wcet_qemu_sys::minimizer::QemuCaseMinimizerStage;
use hashbrown::HashMap;
use clap::{App, Arg};
use core::{cell::RefCell, time::Duration};
use std::time::SystemTime;
#[cfg(unix)]
use nix::{self, unistd::dup};
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::{
env,
fs::{self, File, OpenOptions},
io::{self, Write},
path::PathBuf,
process,
};
use petgraph::prelude::DiGraph;
use petgraph::dot::{Dot, Config};
use libafl::{
bolts::{
current_nanos, current_time,
os::{dup2,Cores},
rands::StdRand,
tuples::{tuple_list, Merge},
shmem::{ShMemProvider,StdShMemProvider},
launcher::Launcher,
},
corpus::{
Corpus, OnDiskCorpus, PowerQueueCorpusScheduler,
},
executors::{ExitKind, ShadowExecutor, TimeoutExecutor},
feedback_or,
feedbacks::{MapFeedbackState, MaxMapFeedback},
fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes},
monitors::{MultiMonitor,SimpleMonitor},
mutators::{
scheduled::havoc_mutations, token_mutations::I2SRandReplace, tokens_mutations,
StdMOptMutator, StdScheduledMutator, Tokens,
},
observers::{VariableMapObserver},
stages::{
calibrate::CalibrationStage,
power::{PowerMutationalStage, PowerSchedule},
ShadowTracingStage, StdMutationalStage,
},
state::{HasCorpus, HasMetadata, StdState},
events::{SimpleEventManager,EventConfig},
generators::RandBytesGenerator,
Error,
};
use libafl_qemu::{
//asan::QemuAsanHelper,
cmplog,
cmplog::{CmpLogObserver, QemuCmpLogHelper},
edges,
edges::QemuEdgeCoverageHelper,
elf::EasyElf,
emu::Emulator, filter_qemu_args, libafl_int_offset, libafl_exec_block_hook,
snapshot_sys::QemuSysSnapshotHelper,
QemuExecutor,
clock,
QemuClockObserver,
clock::ClockFeedback,
clock::QemuClockIncreaseFeedback
};
use wcet_qemu_sys::worst::{SortedFeedback,HitFeedback,HitImprovingFeedback,TimeStateMaximizerCorpusScheduler,LenTimeMaximizerCorpusScheduler};
/// The fuzzer main
pub fn main() {
// Registry the metadata types used in this fuzzer
// Needed only on no_std
//RegistryBuilder::register::<Tokens>();
let res = match App::new("wcet_qemu_fuzzer")
.version("0.4.0")
.author("Alwin Berger")
.about("LibAFL-based fuzzer for WCET in System Kernels.")
.arg(
Arg::new("k")
.long("libafl-kernel")
.required(true)
.takes_value(true),
)
.arg(
Arg::new("out")
.help("The directory to place finds in ('corpus')")
.long("libafl-out")
.required(true)
.takes_value(true),
)
.arg(
Arg::new("in")
.help("The directory to read initial inputs from ('seeds')")
.long("libafl-in")
.required(true)
.takes_value(true),
)
.arg(
Arg::new("tokens")
.long("libafl-tokens")
.help("A file to read tokens from, to be used during fuzzing")
.takes_value(true),
)
.arg(
Arg::new("logfile")
.long("libafl-logfile")
.help("Duplicates all output to this file")
.default_value("libafl.log"),
)
.arg(
Arg::new("timeout")
.long("libafl-timeout")
.help("Timeout for each individual execution, in milliseconds")
.default_value("1000"),
)
.arg(
Arg::new("edges")
.long("libafl-edges")
.takes_value(true),
)
.arg(
Arg::new("traces")
.long("libafl-traces")
.takes_value(true),
)
.arg(
Arg::new("snapshot")
.help("The qcow2 file used for snapshots")
.long("libafl-snapshot")
.required(true)
.takes_value(true),
)
.arg(
Arg::new("exectimes")
.long("libafl-exectimes")
.takes_value(true),
)
.arg(
Arg::new("targettime")
.long("libafl-targettime")
.takes_value(true),
)
.arg(
Arg::new("dump")
.long("libafl-dump")
.takes_value(true),
)
.arg(
Arg::new("fuzztime")
.long("libafl-fuzztime")
.takes_value(true),
)
.arg(
Arg::new("graphdump")
.long("libafl-graphdump")
.takes_value(true),
)
.try_get_matches_from(filter_qemu_args())
{
Ok(res) => res,
Err(err) => {
println!(
"Syntax: {}, --libafl-in <input> --libafl-out <output>\n{:?}",
env::current_exe()
.unwrap_or_else(|_| "fuzzer".into())
.to_string_lossy(),
err.info,
);
return;
}
};
println!(
"Workdir: {:?}",
env::current_dir().unwrap().to_string_lossy().to_string()
);
// For fuzzbench, crashes and finds are inside the same `corpus` directory, in the "queue" and "crashes" subdir.
let mut out_dir = PathBuf::from(res.value_of("out").unwrap().to_string());
if fs::create_dir(&out_dir).is_err() {
println!("Out dir at {:?} already exists.", &out_dir);
if !out_dir.is_dir() {
println!("Out dir at {:?} is not a valid directory!", &out_dir);
return;
}
}
let mut crashes = out_dir.clone();
crashes.push("wcets");
out_dir.push("queue");
let in_dir = PathBuf::from(res.value_of("in").unwrap().to_string());
if !in_dir.is_dir() {
println!("In dir at {:?} is not a valid directory!", &in_dir);
return;
}
let tokens = res.value_of("tokens").map(PathBuf::from);
let logfile = PathBuf::from(res.value_of("logfile").unwrap().to_string());
let timeout = Duration::from_millis(
res.value_of("timeout")
.unwrap()
.to_string()
.parse()
.expect("Could not parse timeout in milliseconds"),
);
let kernel = PathBuf::from(res.value_of("k").unwrap().to_string());
let edges = match res.value_of("edges") {
Some(st) => Some(PathBuf::from(st.to_string())),
None => None
};
let traces = match res.value_of("traces") {
Some(st) => Some(PathBuf::from(st.to_string())),
None => None
};
let exectimes = match res.value_of("exectimes") {
Some(st) => Some(PathBuf::from(st.to_string())),
None => None
};
let targettime = match res.value_of("targettime") {
Some(st) => Some(st.to_string().parse::<u64>().expect("targettime not a number")),
None => None
};
let snapshot = PathBuf::from(res.value_of("snapshot").unwrap().to_string());
let dump_path = match res.value_of("dump") {
Some(st) => Some(PathBuf::from(st.to_string())),
None => None
};
let fuzztime = match res.value_of("fuzztime") {
Some(st) => Some(u64::from_str(st).expect("Failed parsing fuzztime")),
None => None
};
let graph_dump = match res.value_of("graphdump") {
Some(st) => Some(PathBuf::from(st.to_string())),
None => None
};
fuzz(out_dir, crashes, in_dir, tokens, logfile, timeout, kernel, edges, traces, exectimes, targettime, snapshot, dump_path, fuzztime, graph_dump)
.expect("An error occurred while fuzzing");
}
fn virt2phys(vaddr : u64, tab : &goblin::elf::Elf) -> u64 {
let ret;
for i in &tab.program_headers {
if i.vm_range().contains(&vaddr.try_into().expect("Can not cast u64 to usize")) {
ret = vaddr-i.p_vaddr+i.p_paddr;
return ret - (ret % 2);
}
}
ret = vaddr;
// unlike the arm-toolcahin goblin produces some off-by one errors when parsing arm
return ret - (ret % 2);
}
/// The actual fuzzer
fn fuzz(
corpus_dir: PathBuf,
objective_dir: PathBuf,
seed_dir: PathBuf,
tokenfile: Option<PathBuf>,
logfile: PathBuf,
_timeout: Duration,
kernel: PathBuf,
dump_edges: Option<PathBuf>,
dump_traces: Option<PathBuf>,
dump_exectimes: Option<PathBuf>,
targettime: Option<u64>,
snapshot: PathBuf,
dump_path: Option<PathBuf>,
fuzztime: Option<u64>,
graph_dump: Option<PathBuf>,
) -> Result<(), Error> {
env::remove_var("LD_LIBRARY_PATH");
//=========== Analyze the binary to find the target function address
let mut elf_buffer = Vec::new();
let elf = EasyElf::from_file(&kernel, &mut elf_buffer)?;
let input_addr = elf
.resolve_symbol("FUZZ_INPUT", 0)
.expect("Symbol FUZZ_INPUT not found");
let input_addr = virt2phys(input_addr,&elf.goblin());
println!("FUZZ_INPUT @ {:#x}", input_addr);
let test_length_ptr = elf
.resolve_symbol("FUZZ_LENGTH", 0)
.expect("Symbol FUZZ_LENGTH not found");
let test_length_ptr = virt2phys(test_length_ptr,&elf.goblin());
println!("FUZZ_LENGTH @ {:#x}", test_length_ptr);
let input_counter = elf
.resolve_symbol("FUZZ_POINTER", 0)
.expect("Symbol FUZZ_POINTER not found");
// let input_counter = virt2phys(input_counter,&elf.goblin());
println!("FUZZ_LENGTH @ {:#x}", test_length_ptr);
let check_breakpoint = elf
.resolve_symbol("trigger_Qemu_break", 0)
.expect("Symbol trigger_Qemu_break not found");
let check_breakpoint = virt2phys(check_breakpoint,&elf.goblin());
println!("Breakpoint at {:#x}", check_breakpoint);
let curr_tcb_pointer = elf // loads to the address specified in elf, without respecting program headers
.resolve_symbol("pxCurrentTCB", 0)
.expect("Symbol pxCurrentTCBC not found");
// let curr_tcb_pointer = virt2phys(curr_tcb_pointer,&elf.goblin());
println!("TCB pointer at {:#x}", curr_tcb_pointer);
let task_queue_addr = elf
.resolve_symbol("pxReadyTasksLists", 0)
.expect("Symbol pxReadyTasksLists not found");
// let task_queue_addr = virt2phys(task_queue_addr,&elf.goblin());
println!("Task Queue at {:#x}", task_queue_addr);
let svh = elf
.resolve_symbol("xPortPendSVHandler", 0)
.expect("Symbol xPortPendSVHandler not found");
let svh = virt2phys(svh,&elf.goblin());
//=========== Prepare Emulator Args
let args: Vec<String> = vec![
"qemu-system-arm",
"-machine","mps2-an385",
"-monitor", "null",
"-semihosting",
"--semihosting-config", "enable=on,target=native",
"-kernel", kernel.to_str().unwrap(),
"-serial", "stdio", "-nographic",
"-snapshot", "-drive", format!("if=none,format=qcow2,file={}",snapshot.to_string_lossy()).as_str(),
"-icount", "shift=3,align=off,sleep=off",
"-S"
].iter().map(|x| x.to_string()).collect();
let env: Vec<(String, String)> = env::vars().collect();
let log = RefCell::new(
OpenOptions::new()
.append(true)
.create(true)
.open(&logfile)?,
);
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
let monitor = MultiMonitor::new(|s| {
println!("{}",s);
writeln!(log.borrow_mut(), "{}",s).unwrap();
});
//====== Child Function
let mut run_client = |state: Option<StdState<_, _, _, _, _>>, mut mgr, _core_id| -> std::result::Result<(), libafl::Error> {
//====== Set up Emu and termination-point
let emu = Emulator::new(&args, &env);
emu.set_breakpoint(check_breakpoint); // trigger_Qemu_break
// Create an observation channel using the coverage map
let edges = unsafe { &mut edges::EDGES_MAP };
let edges_counter = unsafe { &mut edges::MAX_EDGES_NUM };
let edges_observer =
QemuHashMapObserver::new(VariableMapObserver::new("edges", edges, edges_counter));
// Create an observation channel to keep track of the execution time
// let time_observer = TimeObserver::new("time");
let clock_observer = QemuClockObserver::default();
// Create an observation channel using cmplog map
// let cmplog_observer = CmpLogObserver::new("cmplog", unsafe { &mut cmplog::CMPLOG_MAP }, true);
// The state of the edges feedback.
let feedback_state = MapFeedbackState::with_observer(&edges_observer);
let sysstate_observer = QemuSysStateObserver::new();
#[cfg(feature = "sched_state")]
let sysstate_feedback_state = SysStateFeedbackState::default();
#[cfg(not(feature = "sched_state"))]
let sysstate_feedback_state = SysGraphFeedbackState::new();
let target_map : HashMap<(u64,u64),u16> = match dump_edges {
None => HashMap::new(),
Some(ref s) => {
let raw = fs::read(s).expect("Can not read dumped edges");
let hmap : HashMap<(u64,u64),u16> = ron::from_str(&String::from_utf8_lossy(&raw)).expect("Can not parse HashMap");
hmap
},
};
let target_trace : Option<Vec<RefinedFreeRTOSSystemState>> = match dump_traces {
None => None,
Some(ref s) => {
let raw = fs::read(s).expect("Can not read dumped traces");
let trace : Vec<RefinedFreeRTOSSystemState> = ron::from_str(&String::from_utf8_lossy(&raw)).expect("Can not parse traces");
Some(trace)
},
};
// Feedback to rate the interestingness of an input
let feedback = ClockFeedback::new_with_observer(&clock_observer);
#[cfg(all(not(feature = "feed_state"), feature = "dump_infos"))] // for diagnostic purposes it's necessary to collect the state in any case
let feedback = feedback_or!(feedback, DumpSystraceFeedback::metadata_only());
#[cfg(all(feature = "obj_collect", feature = "fuzz_random"))] // random fuzzing does not trigger objective feedbacks
let feedback = feedback_or!(feedback, ExecTimeCollectorFeedback::new());
#[cfg(feature = "dump_infos")] // for diagnostic purposes it's necessary to collect the state in any case
let feedback = feedback_or!(feedback, DumpMapFeedback::metadata_only(&edges_observer));
#[cfg(feature = "feed_known_edges")]
let feedback = feedback_or!(feedback, HitImprovingFeedback::new(target_map.clone(), &edges_observer));
#[cfg(feature = "feed_afl")]
let feedback = feedback_or!(feedback, MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false));
#[cfg(feature = "feed_clock")]
let feedback = feedback_or!(feedback, QemuClockIncreaseFeedback::default());
#[cfg(feature = "feed_state")]
let feedback = feedback_or!(feedback, NovelSysStateFeedback::default());
#[cfg(feature = "feed_graph")]
let feedback = feedback_or!(feedback, SysMapFeedback::new());
// A feedback to choose if an input is a solution or not
let objective = DummyFeedback::new(false);
#[cfg(all(feature = "obj_collect",not(feature = "fuzz_random")))]
let objective = ExecTimeCollectorFeedback::new();
#[cfg(feature = "obj_trace")]
let objective = feedback_or!(HitSysStateFeedback::new(target_trace));
#[cfg(feature = "obj_edges")]
let objective = feedback_or!(HitFeedback::new(target_map,0.0,&edges_observer), objective);
#[cfg(feature = "obj_ticks")]
let objective = feedback_or!(ExecTimeReachedFeedback::new(targettime));
// create a State from scratch
#[cfg(feature = "benchmark")]
let mut state = state.unwrap_or_else(||{
StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved
InMemoryCorpus::new(),
// Corpus in which we store solutions (crashes in this example),
InMemoryCorpus::new(),
// States of the feedbacks.
// They are the data related to the feedbacks that you want to persist in the State.
tuple_list!(feedback_state,clock::MaxIcountMetadata::default(),sysstate_feedback_state),
)
});
#[cfg(not(feature = "benchmark"))]
let mut state = state.unwrap_or_else(||{
StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved
OnDiskCorpus::new(&corpus_dir).unwrap(),
// 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).unwrap(),
// States of the feedbacks.
// They are the data related to the feedbacks that you want to persist in the State.
tuple_list!(feedback_state,clock::MaxIcountMetadata::default(),sysstate_feedback_state),
)
});
// let calibration = CalibrationStage::new(&mut state, &edges_observer);
// Setup a randomic Input2State stage
// let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(I2SRandReplace::new())));
let mutator_list = havoc_mutations();
#[cfg(feature = "muta_input")]
let mutator_list = mutator_list.merge(tuple_list!(RandInputSnippetMutator::new()));
#[cfg(feature = "muta_suffix")]
let mutator_list = mutator_list.merge(tuple_list!(RandGraphSuffixMutator::new()));
#[cfg(feature = "muta_snip")]
let mutator_list = mutator_list.merge(tuple_list!(RandGraphSnippetMutator::new()));
#[cfg(all(feature = "muta_interrupt",feature = "fuzz_interrupt"))]
let mutator_list = mutator_list.merge(tuple_list!(InterruptShifterMutator::new()));
// Setup a MOPT mutator
let mutator = StdMOptMutator::new(&mut state, mutator_list,5)?;
// let power = PowerMutationalStage::new(mutator, PowerSchedule::FAST, &edges_observer);
let mutation = StdMutationalStage::new(mutator);
// A minimization+queue policy to get testcasess from the corpus
#[cfg(feature = "sched_queue")]
let scheduler = QueueCorpusScheduler::new();
#[cfg(feature = "sched_mapmax")]
let scheduler = TimeMaximizerCorpusScheduler::new(QueueCorpusScheduler::new());
#[cfg(feature = "sched_state")]
let scheduler = TimeStateMaximizerCorpusScheduler::new(QueueCorpusScheduler::new());
#[cfg(feature = "sched_graph")]
let scheduler = GraphMaximizerCorpusScheduler::new(QueueCorpusScheduler::new());
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// 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();
let mut int_tick : Option<u32> = None;
if len > IRQ_INPUT_BYTES_NUMBER as usize {
let mut t : [u8; 4] = [0,0,0,0]; // 4 extra bytes determine the tick to execute an interrupt
for i in 0..min(4,IRQ_INPUT_BYTES_NUMBER) {
t[i as usize]=buf[i as usize];
}
int_tick = Some(u32::from_le_bytes(t));
buf = &buf[IRQ_INPUT_BYTES_NUMBER as usize..];
len = buf.len();
}
if len >= 32 {
buf = &buf[0..32];
len = 32;
}
unsafe {
if IRQ_INPUT_BYTES_NUMBER!= 0 {
libafl_int_offset = IRQ_INPUT_OFFSET+int_tick.unwrap_or(0);
}
// INTR_OFFSET = int_tick;
emu.write_mem(test_length_ptr,&(len as u32).to_le_bytes());
emu.write_mem(input_addr,buf);
emu.run();
// since the breakpoint interrupted the last task the last state needs to be recorded
libafl_exec_block_hook(check_breakpoint);
}
ExitKind::Ok
};
let system_state_filter = QemuInstrumentationFilter::AllowList(vec![svh..svh+1,check_breakpoint..check_breakpoint+1]);
let mut executor = QemuExecutor::new(
&mut harness,
&emu,
tuple_list!(
QemuEdgeCoverageHelper::new(),
QemuCmpLogHelper::new(),
//QemuAsanHelper::new(),
QemuSysSnapshotHelper::new(),
QemuSystemStateHelper::with_instrumentation_filter(
system_state_filter,curr_tcb_pointer.try_into().unwrap(),
task_queue_addr.try_into().unwrap(),
input_counter.try_into().unwrap()
)
),
tuple_list!(edges_observer, clock_observer,sysstate_observer),
&mut fuzzer,
&mut state,
&mut mgr,
)?;
// Create the executor for an in-process function with one observer for edge coverage and one for the execution time
// let executor = TimeoutExecutor::new(executor, timeout);
// Show the cmplog observer
// let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer));
// Read tokens
if let Some(tokenfile) = &tokenfile {
if state.metadata().get::<Tokens>().is_none() {
state.add_metadata(Tokens::from_tokens_file(&tokenfile)?);
}
}
#[cfg(not(feature = "fuzz_random"))]
if state.corpus().count() < 1 {
if _core_id == 0 && state.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[seed_dir.clone()]).is_ok() {
// println!("We imported {} inputs from disk.", state.corpus().count());
} else {
let mut generator = RandBytesGenerator::new(32);
state
.generate_initial_inputs(
&mut fuzzer,
&mut executor,
&mut generator,
&mut mgr,
8,
)
.expect("Failed to generate the initial corpus");
}
}
// let tracing = ShadowTracingStage::new(&mut executor);
// The order of the stages matter!
let mut stages = tuple_list!(mutation,QemuCaseMinimizerStage::new(16));
// Remove target ouput (logs still survive)
#[cfg(unix)]
let file_null = File::open("/dev/null")?;
#[cfg(unix)]
#[cfg(feature = "multicore")]
{
let null_fd = file_null.as_raw_fd();
dup2(null_fd, io::stdout().as_raw_fd())?;
dup2(null_fd, io::stderr().as_raw_fd())?;
}
#[cfg(not(feature = "fuzz_random"))]
{
match fuzztime {
Some(t) => fuzzer
.fuzz_for_solution_or_n(&mut stages, &mut executor, &mut state, &mut mgr, t)
.expect("Error in the fuzzing loop"),
None => fuzzer
.fuzz_for_solution(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("Error in the fuzzing loop"),
};
}
#[cfg(feature = "fuzz_random")]
{
let mut generator = RandBytesGenerator::new(32);
let target_duration = Duration::from_secs(fuzztime.expect("Random fuzzing needs target time"));
let start_time = SystemTime::now();
while start_time.elapsed().unwrap() < target_duration {
let inp = generator.generate(&mut state).unwrap();
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp);
}
match fuzzer.fuzz_for_solution_or_n(&mut stages, &mut executor, &mut state, &mut mgr, 0) {
_ => (),
} // Just here for the typecheker
}
#[cfg(not(feature = "benchmark"))]
#[cfg(feature = "feed_graph")]
{
let feedbackstate = state
.feedback_states()
.match_name::<SysGraphFeedbackState>("SysMap")
.unwrap();
let newgraph = feedbackstate.graph.map(
|_, n| n.get_taskname(),
// |_, n| format!("{} {:?}",n.get_taskname(),n.get_input_counts().iter().min().unwrap_or(&0)),
|_, e| e,
);
let tempg = format!("{:?}",Dot::with_config(&newgraph, &[Config::EdgeNoLabel]));
match graph_dump {
Some(gd) => fs::write(gd,tempg).expect("Graph can not be written"),
None => (),
}
}
// Wite out the collected exec times
#[cfg(feature = "obj_collect")]
match dump_exectimes {
Some(et) => unsafe {
let mut stringforms : Vec<String> = Vec::new();
for i in EXEC_TIME_COLLECTION.drain(..) {
stringforms.push(format!("{}\n",i));
}
fs::write(et,stringforms.concat()).expect("Exec times can not be written");
}
None => (),
}
#[cfg(feature = "dump_infos")]
{
let c = if state.solutions().count()>0 { state.solutions() } else { state.corpus() };
let mut worst = Duration::new(0,0);
let mut worst_input : Option<Vec<u8>> = Some(vec![]);
let mut worst_trace : Option<FreeRTOSSystemStateMetadata> = None;
let mut worst_map : Option<DumpEdgesMapMetadata> = None;
for i in 0..c.count() {
let tc = c.get(i).expect("Could not get element from corpus").borrow();
if worst < tc.exec_time().expect("Testcase missing duration") {
worst = tc.exec_time().expect("Testcase missing duration");
let metadata = tc.metadata();
worst_trace = metadata.get::<FreeRTOSSystemStateMetadata>().cloned();
worst_map = metadata.get::<DumpEdgesMapMetadata>().cloned();
worst_input = Some(tc.input().as_ref().unwrap().bytes().to_owned());
}
}
let mut dump_path = dump_path;
match &mut dump_path {
Some(dp) => {
dp.set_extension("trace");
println!("Path found: {:?}",dp);
fs::write(&dp,ron::to_string(&worst_trace).expect("Error serializing Trace")).expect("Exec times can not be written");
dp.set_extension("map");
println!("Path found: {:?}",dp);
fs::write(&dp,ron::to_string(&worst_map).expect("Error serializing hashmap")).expect("Exec times can not be written");
dp.set_extension("input");
println!("Path found: {:?}",dp);
fs::write(&dp,worst_input.unwrap());
dp.set_extension("time");
println!("Path found: {:?}",dp);
fs::write(&dp,format!("{}",worst.as_nanos()>>3));
},
None => (),
}
}
// Never reached
Ok(())
};
// Multicore Variant
#[cfg(feature = "multicore")]
{
match Launcher::builder()
.shmem_provider(shmem_provider)
.configuration(EventConfig::AlwaysUnique)
.monitor(monitor)
.run_client(&mut run_client)
.cores(&Cores::from_cmdline("all").unwrap())
// .broker_port(1337)
// .remote_broker_addr(remote_broker_addr)
//.stdout_file(Some("/dev/null"))
.build()
.launch()
{
Ok(_) | Err(Error::ShuttingDown) => (),
Err(e) => panic!("{:?}", e),
};
Ok(())
}
// Simple Variant
#[cfg(not(feature = "multicore"))]
{
let stats = SimpleStats::new(|s| println!("{}", s));
let mgr = SimpleEventManager::new(stats);
run_client(None, mgr, 0)
}
}

View File

@ -0,0 +1,432 @@
//! A singlethreaded QEMU fuzzer that can auto-restart.
use core::cmp::min;
use wcet_qemu_sys::sysstate::IRQ_INPUT_OFFSET;
use wcet_qemu_sys::sysstate::IRQ_INPUT_BYTES_NUMBER;
use wcet_qemu_sys::sysstate::helpers::INTR_OFFSET;
use std::io::Read;
use wcet_qemu_sys::sysstate::observers::QemuSysStateObserver;
use wcet_qemu_sys::sysstate::feedbacks::DumpSystraceFeedback;
use wcet_qemu_sys::worst::QemuHashMapObserver;
use wcet_qemu_sys::{
worst::{DumpMapFeedback,DummyFeedback},
sysstate::helpers::QemuSystemStateHelper,
};
use clap::{App, Arg};
use std::{
env,
fs::{self},
path::PathBuf,
};
use libafl::{
bolts::{
current_nanos,
rands::StdRand,
tuples::{tuple_list},
},
corpus::{Corpus,InMemoryCorpus,QueueCorpusScheduler},
executors::{ExitKind},
fuzzer::{StdFuzzer},
inputs::{BytesInput, HasTargetBytes},
observers::{VariableMapObserver},
state::{HasCorpus,StdState},
Error,
Evaluator,
stats::SimpleStats,
events::SimpleEventManager,
stages::StdMutationalStage,
mutators::BitFlipMutator,
Fuzzer,
feedback_or,
};
use libafl_qemu::{
edges,
edges::QemuEdgeCoverageHelper,
emu::Emulator, filter_qemu_args, libafl_int_offset, libafl_exec_block_hook,
elf::EasyElf,
snapshot_sys::QemuSysSnapshotHelper,
clock::{QemuClockObserver},
QemuInstrumentationFilter,
QemuExecutor,
};
use either::{Either,Left,Right};
/// The fuzzer main
pub fn main() {
// Registry the metadata types used in this fuzzer
// Needed only on no_std
//RegistryBuilder::register::<Tokens>();
let res = match App::new("wcet_qemu_fuzzer")
.version("0.4.0")
.author("Alwin Berger")
.about("LibAFL-based fuzzer for WCET in System Kernels.")
.arg(
Arg::new("k")
.long("libafl-kernel")
.required(true)
.takes_value(true),
)
.arg(
Arg::new("out")
.long("libafl-out")
.required(false)
.takes_value(true),
)
.arg(
Arg::new("in")
.long("libafl-in")
.required(true)
.takes_value(true),
)
.arg(
Arg::new("tokens")
.long("libafl-tokens")
.takes_value(true),
)
.arg(
Arg::new("logfile")
.long("libafl-logfile")
.default_value("libafl.log"),
)
.arg(
Arg::new("timeout")
.long("libafl-timeout")
.default_value("1000"),
)
.arg(
Arg::new("edges")
.long("libafl-edges")
.takes_value(true),
)
.arg(
Arg::new("traces")
.long("libafl-traces")
.takes_value(true),
)
.arg(
Arg::new("snapshot")
.help("The qcow2 file used for snapshots")
.long("libafl-snapshot")
.required(true)
.takes_value(true),
)
.arg(
Arg::new("single")
.long("libafl-single")
.takes_value(true)
)
.try_get_matches_from(filter_qemu_args())
{
Ok(res) => res,
Err(err) => {
println!(
"Syntax: {}, --libafl-in <input>\n{:?}",
env::current_exe()
.unwrap_or_else(|_| "fuzzer".into())
.to_string_lossy(),
err.info,
);
return;
}
};
println!(
"Workdir: {:?}",
env::current_dir().unwrap().to_string_lossy().to_string()
);
// For fuzzbench, crashes and finds are inside the same `corpus` directory, in the "queue" and "crashes" subdir.
let mut out_dir = PathBuf::from(res.value_of("out").unwrap().to_string());
if fs::create_dir(&out_dir).is_err() {
println!("Out dir at {:?} already exists.", &out_dir);
if !out_dir.is_dir() {
println!("Out dir at {:?} is not a valid directory!", &out_dir);
return;
}
}
let mut worstcases = out_dir.clone();
worstcases.push("worstcase");
out_dir.push("queue");
let seed = match res.value_of("single") {
Some(s) => if s=="-" {
let mut buf = Vec::<u8>::new();
std::io::stdin().read_to_end(&mut buf).expect("Could not read Stdin");
Left(buf)
} else {
Left(fs::read(s).expect("Input file for --libafl-single can not be read"))
},
None => {
let in_dir = PathBuf::from(res.value_of("in").unwrap().to_string());
if !in_dir.is_dir() {
println!("In dir at {:?} is not a valid directory!", &in_dir);
return;
}
Right(in_dir)
},
};
println!("{:?}",seed);
let kernel = PathBuf::from(res.value_of("k").unwrap().to_string());
let edges = match res.value_of("edges") {
Some(st) => Some(PathBuf::from(st.to_string())),
None => None
};
let traces = match res.value_of("traces") {
Some(st) => Some(PathBuf::from(st.to_string())),
None => None
};
let snapshot = PathBuf::from(res.value_of("snapshot").unwrap().to_string());
fuzz(seed, kernel, edges, traces, snapshot)
.expect("An error occurred while fuzzing");
}
fn virt2phys(vaddr : u64, tab : &goblin::elf::Elf) -> u64 {
let ret;
for i in &tab.program_headers {
if i.vm_range().contains(&vaddr.try_into().expect("Can not cast u64 to usize")) {
ret = vaddr-i.p_vaddr+i.p_paddr;
return ret - (ret % 2);
}
}
ret = vaddr;
// unlike the arm-toolcahin goblin produces some off-by one errors when parsing arm
return ret - (ret % 2);
}
/// The actual fuzzer
fn fuzz(
seed: Either<Vec<u8>,PathBuf>,
kernel: PathBuf,
dump_edges: Option<PathBuf>,
dump_traces: Option<PathBuf>,
snapshot: PathBuf,
) -> Result<(), Error> {
//=========== Setup emulator
let mut env: Vec<(String, String)> = env::vars().collect();
let mut args: Vec<String> = vec![
"qemu-system-arm",
"-machine","mps2-an385",
"-monitor", "null",
"-semihosting",
"--semihosting-config", "enable=on,target=native",
"-kernel", kernel.to_str().unwrap(),
"-serial", "stdio", "-nographic",
"-snapshot", "-drive", format!("if=none,format=qcow2,file={}",snapshot.to_string_lossy()).as_str(),
"-icount", "shift=3,align=off,sleep=off",
"-S"
].iter().map(|x| x.to_string()).collect();
let emu = Emulator::new(&mut args, &mut env);
//=========== Analyze the binary to find the target function address
let mut elf_buffer = Vec::new();
let bin_path=kernel;
let elf = EasyElf::from_file(bin_path, &mut elf_buffer)?;
let test_one_input_ptr = elf
.resolve_symbol("FUZZ_INPUT", 0)
.expect("Symbol FUZZ_INPUT not found");
let test_one_input_ptr = virt2phys(test_one_input_ptr,&elf.goblin());
println!("FUZZ_INPUT @ {:#x}", test_one_input_ptr);
let test_length_ptr = elf
.resolve_symbol("FUZZ_LENGTH", 0)
.expect("Symbol FUZZ_LENGTH not found");
let test_length_ptr = virt2phys(test_length_ptr,&elf.goblin());
println!("FUZZ_LENGTH @ {:#x}", test_length_ptr);
let input_counter = elf
.resolve_symbol("FUZZ_POINTER", 0)
.expect("Symbol FUZZ_POINTER not found");
// let input_counter = virt2phys(input_counter,&elf.goblin());
println!("FUZZ_LENGTH @ {:#x}", test_length_ptr);
let check_breakpoint = elf
.resolve_symbol("trigger_Qemu_break", 0)
.expect("Symbol trigger_Qemu_break not found");
let check_breakpoint = virt2phys(check_breakpoint,&elf.goblin());
println!("Breakpoint at {:#x}", check_breakpoint);
let curr_tcb_pointer = elf // loads to the address specified in elf, without respecting program headers
.resolve_symbol("pxCurrentTCB", 0)
.expect("Symbol pxCurrentTCBC not found");
// let curr_tcb_pointer = virt2phys(curr_tcb_pointer,&elf.goblin());
println!("TCB pointer at {:#x}", curr_tcb_pointer);
let task_queue_addr = elf
.resolve_symbol("pxReadyTasksLists", 0)
.expect("Symbol pxReadyTasksLists not found");
// let task_queue_addr = virt2phys(task_queue_addr,&elf.goblin());
println!("Task Queue at {:#x}", task_queue_addr);
let systick_handler = elf
.resolve_symbol("xPortSysTickHandler", 0)
.expect("Symbol xPortSysTickHandler not found");
let systick_handler = virt2phys(systick_handler,&elf.goblin());
println!("SysTick at {:#x}", systick_handler);
let svc_handle = elf
.resolve_symbol("vPortSVCHandler", 0)
.expect("Symbol vPortSVCHandler not found");
let svc_handle = virt2phys(svc_handle,&elf.goblin());
println!("SVChandle at {:#x}", svc_handle);
let svh = elf
.resolve_symbol("xPortPendSVHandler", 0)
.expect("Symbol xPortPendSVHandler not found");
let svh = virt2phys(svh,&elf.goblin());
println!("PendHandle at {:#x}", svh);
let app_code_start = elf
.resolve_symbol("__APP_CODE_START__", 0)
.expect("Symbol __APP_CODE_START__ not found");
let app_code_start = virt2phys(app_code_start,&elf.goblin());
let app_code_end = elf
.resolve_symbol("__APP_CODE_END__", 0)
.expect("Symbol __APP_CODE_END__ not found");
let app_code_end = virt2phys(app_code_end,&elf.goblin());
let app_range = app_code_start..app_code_end;
println!("App Code {:x}-{:x}",app_code_start,app_code_end);
//====== Create the input field
let input_addr = test_one_input_ptr;
println!("Placing input at {:#x}", input_addr);
emu.set_breakpoint(check_breakpoint);
//====== Create the most simple status display and managers.
let stats = SimpleStats::new(|s| println!("{}", s));
let mut mgr = SimpleEventManager::new(stats);
//========== EDGES_MAP is static field which somehow(?) gets handed over to the Qemu instrumentation
//========== Otherwise setup generic observers
// Create an observation channel using the coverage map
let edges = unsafe { &mut edges::EDGES_MAP };
let edges_counter = unsafe { &mut edges::MAX_EDGES_NUM };
let edges_observer =
QemuHashMapObserver::new(VariableMapObserver::new("edges", edges, edges_counter));
//========== Observe Execution Cycles
let clock_observer = QemuClockObserver::default();
//========= Feedback-Function evaluate the Maps. Need to dump it for debugging and check if it reaches targets.
let feedback = feedback_or!(DumpMapFeedback::with_dump(dump_edges, &edges_observer),DumpSystraceFeedback::with_dump(dump_traces));
// A feedback to choose if an input is a solution or not
let objective = DummyFeedback::new(false);
// create a State from scratch
let mut state = StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// 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
InMemoryCorpus::new(),
// States of the feedbacks.
// They are the data related to the feedbacks that you want to persist in the State.
(),
);
// A minimization+queue policy to get testcasess from the corpus
let scheduler = QueueCorpusScheduler::new();
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
//======== The harness has to start the execution of the emulator
// 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();
let mut int_tick : Option<u32> = None;
if len > IRQ_INPUT_BYTES_NUMBER as usize && IRQ_INPUT_BYTES_NUMBER!=0{
let mut t : [u8; 4] = [0,0,0,0]; // 4 extra bytes determine the tick to execute an interrupt
for i in 0..min(4,IRQ_INPUT_BYTES_NUMBER) {
t[i as usize]=buf[i as usize];
}
int_tick = Some(u32::from_le_bytes(t));
buf = &buf[IRQ_INPUT_BYTES_NUMBER as usize..];
len = buf.len();
}
if len >= 32 {
buf = &buf[0..32];
len = 32;
}
unsafe {
if IRQ_INPUT_BYTES_NUMBER!= 0 {
libafl_int_offset = IRQ_INPUT_OFFSET+int_tick.unwrap_or(0);
}
// INTR_OFFSET = int_tick;
emu.write_mem(test_length_ptr,&(len as u32).to_le_bytes());
emu.write_mem(input_addr,buf);
emu.run();
// since the breakpoint interrupted the last task the last state needs to be recorded
libafl_exec_block_hook(check_breakpoint);
println!("Qemu Ticks: {}",emu.get_ticks());
}
ExitKind::Ok
};
//======= Set System-State watchpoints
let system_state_filter = QemuInstrumentationFilter::AllowList(vec![svh..svh+1,check_breakpoint..check_breakpoint+1]);
//======= Construct the executor, including the Helpers. The edges_observer still contains the ref to EDGES_MAP
let mut executor = QemuExecutor::new(
&mut harness,
&emu,
tuple_list!(
QemuEdgeCoverageHelper::with_app_range(app_range),
// QemuCmpLogHelper::new(),
// QemuAsanHelper::new(),
QemuSysSnapshotHelper::new(),
QemuSystemStateHelper::with_instrumentation_filter(
system_state_filter,curr_tcb_pointer.try_into().unwrap(),
task_queue_addr.try_into().unwrap(),
input_counter.try_into().unwrap()
)
),
tuple_list!(edges_observer,clock_observer,QemuSysStateObserver::new()),
&mut fuzzer,
&mut state,
&mut mgr,
)?;
match seed {
Right(pb) => {
if state.corpus().count() < 1 {
state
.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[pb.clone()])
.unwrap_or_else(|_| {
println!("Failed to load initial corpus at {:?}", &pb);
return;
});
println!("We imported {} inputs from disk.", state.corpus().count());
}
// fuzzer
// .fuzz_one(&mut tuple_list!(StdMutationalStage::new(BitFlipMutator::new())), &mut executor, &mut state, &mut mgr)
// .expect("Error in the fuzzing loop");
},
Left(s) => {
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, BytesInput::new(s)).expect("Evaluation failed");
}
}
// let firstinput = match seed.clone().is_dir() {
// true => seed.clone().read_dir().expect("Directory not a directory?").next().expect("Directory empty?").expect("File not in directory?").path(),
// false => seed.clone()
// };
// let secondinput = match seed_dir.clone().is_dir() {
// true => {
// let mut a = seed_dir.clone().read_dir().expect("Directory not a directory?");
// a.advance_by(1);
// a.next().unwrap().expect("File not in directory?").path()
// },
// false => seed_dir.clone()
// };
// fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, Input::from_file(&secondinput).expect("Could not load file")).expect("Evaluation failed");
// println!("Nach Eval");
return Ok(());
}

View File

@ -0,0 +1,7 @@
#![feature(iter_advance_by)]
#![feature(is_sorted)]
#[cfg(target_os = "linux")]
pub mod sysstate;
pub mod worst;
pub mod minimizer;

View File

@ -0,0 +1,70 @@
use libafl::inputs::HasBytesVec;
use libafl::corpus::Corpus;
use libafl::bolts::rands::Rand;
use libafl::inputs::BytesInput;
use libafl::stages::Stage;
use libafl::stages::MutationalStage;
use libafl::stages::mutational::DEFAULT_MUTATIONAL_MAX_ITERATIONS;
use core::marker::PhantomData;
use libafl::Evaluator;
use libafl::state::HasRand;
use libafl::state::HasCorpus;
use libafl::Error;
use libafl::state::HasClientPerfMonitor;
use libafl::mutators::Mutator;
use libafl::inputs::Input;
#[derive(Clone, Debug)]
pub struct QemuCaseMinimizerStage<E, EM, S, Z>
where
S: HasClientPerfMonitor + HasCorpus<BytesInput> + HasRand,
Z: Evaluator<E, EM, BytesInput, S>,
{
max_input_length: usize,
#[allow(clippy::type_complexity)]
phantom: PhantomData<(E, EM, S, Z)>,
}
impl<E, EM, S, Z> Stage<E, EM, S, Z> for QemuCaseMinimizerStage<E, EM, S, Z>
where
S: HasClientPerfMonitor + HasCorpus<BytesInput> + HasRand,
Z: Evaluator<E, EM, BytesInput, S>,
{
#[inline]
#[allow(clippy::let_and_return)]
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut S,
manager: &mut EM,
corpus_idx: usize,
) -> Result<(), Error> {
let mut corpus = state.corpus_mut();
let mut case = corpus.get(corpus_idx).unwrap().borrow_mut();
let mut input = case.input_mut().as_mut().unwrap();
let mut bytes = input.bytes_mut();
if bytes.len() > self.max_input_length {
bytes.drain(self.max_input_length..);
}
#[cfg(feature = "introspection")]
state.introspection_monitor_mut().finish_stage();
Ok(())
}
}
impl<E, EM, S, Z> QemuCaseMinimizerStage<E, EM, S, Z>
where
S: HasClientPerfMonitor + HasCorpus<BytesInput> + HasRand,
Z: Evaluator<E, EM, BytesInput, S>,
{
/// Creates a new default mutational stage
pub fn new(max: usize) -> Self {
Self {
max_input_length: max,
phantom: PhantomData,
}
}
}

View File

@ -1,11 +1,11 @@
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_qemu::clock::QemuClockObserver;
// use libafl::feedbacks::FeedbackState;
use libafl::corpus::Testcase;
// use libafl::state::HasFeedbackStates;
use libafl::bolts::tuples::MatchName;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize};
use super::RefinedFreeRTOSSystemState;
use super::FreeRTOSSystemStateMetadata;
use super::observers::QemuSystemStateObserver;
use super::observers::QemuSysStateObserver;
use petgraph::prelude::DiGraph;
use petgraph::graph::NodeIndex;
use petgraph::Direction;
@ -29,21 +29,21 @@ use std::cmp::Ordering;
//============================= Feedback
/// Shared Metadata for a systemstateFeedback
#[derive(Debug, Serialize, Deserialize, SerdeAny, Clone, Default)]
pub struct SystemStateFeedbackState
/// Shared Metadata for a SysStateFeedback
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct SysStateFeedbackState
{
known_traces: HashMap<u64,(u64,u64,usize)>, // encounters,ticks,length
longest: Vec<RefinedFreeRTOSSystemState>,
}
impl Named for SystemStateFeedbackState
impl Named for SysStateFeedbackState
{
#[inline]
fn name(&self) -> &str {
"systemstate"
"sysstate"
}
}
// impl FeedbackState for systemstateFeedbackState
// impl FeedbackState for SysStateFeedbackState
// {
// fn reset(&mut self) -> Result<(), Error> {
// self.longest.clear();
@ -52,48 +52,38 @@ impl Named for SystemStateFeedbackState
// }
// }
/// A Feedback reporting novel System-State Transitions. Depends on [`QemuSystemStateObserver`]
/// A Feedback reporting novel System-State Transitions. Depends on [`QemuSysStateObserver`]
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct NovelSystemStateFeedback
pub struct NovelSysStateFeedback
{
last_trace: Option<Vec<RefinedFreeRTOSSystemState>>,
// known_traces: HashMap<u64,(u64,usize)>,
}
impl<S> Feedback<S> for NovelSystemStateFeedback
impl<S> Feedback<S> for NovelSysStateFeedback
where
S: UsesInput + HasClientPerfMonitor + HasNamedMetadata,
S: UsesInput + HasClientPerfMonitor,
{
fn is_interesting<EM, OT>(
&mut self,
state: &mut S,
manager: &mut EM,
input: &S::Input,
_manager: &mut EM,
_input: &S::Input,
observers: &OT,
exit_kind: &ExitKind,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>
OT: ObserversTuple<S>,
{
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
.expect("QemuSystemStateObserver not found");
let clock_observer = observers.match_name::<QemuClockObserver>("clocktime") //TODO not fixed
.expect("QemuClockObserver not found");
let feedbackstate = match state
.named_metadata_mut()
.get_mut::<SystemStateFeedbackState>("systemstate") {
Some(s) => s,
None => {
let n=SystemStateFeedbackState::default();
state.named_metadata_mut().insert(n, "systemstate");
state.named_metadata_mut().get_mut::<SystemStateFeedbackState>("systemstate").unwrap()
}
};
// let feedbackstate = state
// .feedback_states_mut()
// .match_name_mut::<systemstateFeedbackState>("systemstate")
// .unwrap();
let observer = observers.match_name::<QemuSysStateObserver>("sysstate")
.expect("QemuSysStateObserver not found");
let clock_observer = observers.match_name::<QemuClockObserver>("clock") //TODO not fixed
.expect("QemuSysStateObserver not found");
let feedbackstate = state
.feedback_states_mut()
.match_name_mut::<SysStateFeedbackState>("sysstate")
.unwrap();
// Do Stuff
let mut hasher = DefaultHasher::new();
observer.last_run.hash(&mut hasher);
@ -140,11 +130,11 @@ where
}
}
impl Named for NovelSystemStateFeedback
impl Named for NovelSysStateFeedback
{
#[inline]
fn name(&self) -> &str {
"systemstate"
"sysstate"
}
}
@ -167,35 +157,35 @@ pub fn match_traces_name(target: &Vec<String>, last: &Vec<RefinedFreeRTOSSystemS
ret
}
/// A Feedback reporting novel System-State Transitions. Depends on [`QemuSystemStateObserver`]
/// A Feedback reporting novel System-State Transitions. Depends on [`QemuSysStateObserver`]
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct HitSystemStateFeedback
pub struct HitSysStateFeedback
{
target: Option<Vec<String>>,
}
impl<S> Feedback<S> for HitSystemStateFeedback
impl<S> Feedback<S> for HitSysStateFeedback
where
S: UsesInput + HasClientPerfMonitor,
{
fn is_interesting<EM, OT>(
&mut self,
state: &mut S,
manager: &mut EM,
input: &S::Input,
_state: &mut S,
_manager: &mut EM,
_input: &S::Input,
observers: &OT,
exit_kind: &ExitKind,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>
OT: ObserversTuple<S>,
{
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
.expect("QemuSystemStateObserver not found");
let observer = observers.match_name::<QemuSysStateObserver>("sysstate")
.expect("QemuSysStateObserver not found");
// Do Stuff
match &self.target {
Some(s) => {
// #[cfg(debug_assertions)] eprintln!("Hit systemstate Feedback trigger");
// #[cfg(debug_assertions)] eprintln!("Hit SysState Feedback trigger");
Ok(match_traces_name(s, &observer.last_run))
},
None => Ok(false),
@ -203,21 +193,21 @@ where
}
}
impl Named for HitSystemStateFeedback
impl Named for HitSysStateFeedback
{
#[inline]
fn name(&self) -> &str {
"hit_systemstate"
"hit_sysstate"
}
}
impl HitSystemStateFeedback {
impl HitSysStateFeedback {
pub fn new(target: Option<Vec<RefinedFreeRTOSSystemState>>) -> Self {
Self {target: target.map(|x| x.into_iter().map(|y| y.current_task.task_name).collect())}
}
}
//=========================== Debugging Feedback
/// A [`Feedback`] meant to dump the system-traces for debugging. Depends on [`QemuSystemStateObserver`]
/// A [`Feedback`] meant to dump the system-traces for debugging. Depends on [`QemuSysStateObserver`]
#[derive(Debug)]
pub struct DumpSystraceFeedback
{
@ -232,25 +222,24 @@ where
{
fn is_interesting<EM, OT>(
&mut self,
state: &mut S,
manager: &mut EM,
input: &S::Input,
_state: &mut S,
_manager: &mut EM,
_input: &S::Input,
observers: &OT,
exit_kind: &ExitKind,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>
OT: ObserversTuple<S>,
{
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
.expect("QemuSystemStateObserver not found");
let names : Vec<String> = observer.last_run.iter().map(|x| x.current_task.task_name.clone()).collect();
let observer = observers.match_name::<QemuSysStateObserver>("sysstate")
.expect("QemuSysStateObserver not found");
match &self.dumpfile {
Some(s) => {
std::fs::write(s,ron::to_string(&observer.last_run).expect("Error serializing hashmap")).expect("Can not dump to file");
self.dumpfile = None
},
None => if !self.dump_metadata {println!("{:?}\n{:?}",observer.last_run,names);}
None => if !self.dump_metadata {println!("{:?}",observer.last_run);}
};
if self.dump_metadata {self.last_trace=Some(observer.last_run.clone());}
Ok(!self.dump_metadata)
@ -279,7 +268,7 @@ impl Named for DumpSystraceFeedback
{
#[inline]
fn name(&self) -> &str {
"Dumpsystemstate"
"DumpSysState"
}
}

View File

@ -103,7 +103,7 @@ pub enum rtos_struct {
#[macro_export]
macro_rules! impl_emu_lookup {
($struct_name:ident) => {
impl $crate::systemstate::freertos::emu_lookup for $struct_name {
impl $crate::sysstate::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 {

View File

@ -0,0 +1,592 @@
/// 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 core::marker::PhantomData;
use libafl::state::HasCorpus;
use libafl::state::HasSolutions;
use libafl::state::HasRand;
use crate::worst::MaxExecsLenFavFactor;
// use libafl::corpus::MinimizerCorpusScheduler;
use libafl::bolts::HasRefCnt;
use libafl::bolts::AsSlice;
use libafl::bolts::ownedref::OwnedSlice;
use libafl::inputs::BytesInput;
use std::path::PathBuf;
use libafl_qemu::clock::QemuClockObserver;
// use libafl::feedbacks::FeedbackState;
use libafl::corpus::Testcase;
// use libafl::state::HasFeedbackStates;
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::HasClientPerfMonitor;
use libafl::feedbacks::Feedback;
use libafl::bolts::tuples::Named;
use libafl::Error;
use hashbrown::HashMap;
use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata};
use serde::{Deserialize, Serialize};
use super::RefinedFreeRTOSSystemState;
use super::FreeRTOSSystemStateMetadata;
use super::observers::QemuSysStateObserver;
use petgraph::prelude::DiGraph;
use petgraph::graph::NodeIndex;
use petgraph::Direction;
use std::cmp::Ordering;
use libafl::bolts::rands::Rand;
//============================= Data Structures
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
pub struct VariantTuple
{
pub start_tick: u64,
pub end_tick: u64,
input_counter: u32,
pub input: Vec<u8>, // in the end any kind of input are bytes, regardless of type and lifetime
}
impl VariantTuple {
fn from(other: &RefinedFreeRTOSSystemState,input: Vec<u8>) -> Self {
VariantTuple{
start_tick: other.start_tick,
end_tick: other.end_tick,
input_counter: other.input_counter,
input: input,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct SysGraphNode
{
base: RefinedFreeRTOSSystemState,
pub variants: Vec<VariantTuple>,
}
impl SysGraphNode {
fn from(base: RefinedFreeRTOSSystemState, input: Vec<u8>) -> Self {
SysGraphNode{variants: vec![VariantTuple::from(&base, input)], base:base }
}
/// unites the variants of this value with another, draining the other if the bases are equal
fn unite(&mut self, other: &mut SysGraphNode) -> bool {
if self!=other {return false;}
self.variants.append(&mut other.variants);
self.variants.dedup();
return true;
}
/// add a Varint from a [`RefinedFreeRTOSSystemState`]
fn unite_raw(&mut self, other: &RefinedFreeRTOSSystemState, input: &Vec<u8>) -> bool {
if &self.base!=other {return false;}
self.variants.push(VariantTuple::from(other, input.clone()));
self.variants.dedup();
return true;
}
/// add a Varint from a [`RefinedFreeRTOSSystemState`], if it's interesting
fn unite_interesting(&mut self, other: &RefinedFreeRTOSSystemState, input: &Vec<u8>) -> bool {
if &self.base!=other {return false;}
let interesting =
self.variants.iter().all(|x| x.end_tick-x.start_tick<other.end_tick-other.start_tick) || // longest variant
self.variants.iter().all(|x| x.end_tick-x.start_tick>other.end_tick-other.start_tick) || // shortest variant
self.variants.iter().all(|x| x.input_counter>other.input_counter) || // longest input
self.variants.iter().all(|x| x.input_counter<other.input_counter); // shortest input
if interesting {
let var = VariantTuple::from(other, input.clone());
self.variants.push(var);
}
return interesting;
}
pub fn get_taskname(&self) -> &str {
&self.base.current_task.task_name
}
pub fn get_input_counts(&self) -> Vec<u32> {
self.variants.iter().map(|x| x.input_counter).collect()
}
}
impl PartialEq for SysGraphNode {
fn eq(&self, other: &SysGraphNode) -> bool {
self.base==other.base
}
}
// Wrapper around Vec<RefinedFreeRTOSSystemState> to attach as Metadata
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct SysGraphMetadata {
pub inner: Vec<NodeIndex>,
indices: Vec<usize>,
tcref: isize,
}
impl SysGraphMetadata {
pub fn new(inner: Vec<NodeIndex>) -> Self{
Self {indices: inner.iter().map(|x| x.index()).collect(), inner: inner, tcref: 0}
}
}
impl AsSlice<usize> 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()
}
}
impl HasRefCnt for SysGraphMetadata {
fn refcnt(&self) -> isize {
self.tcref
}
fn refcnt_mut(&mut self) -> &mut isize {
&mut self.tcref
}
}
libafl::impl_serdeany!(SysGraphMetadata);
pub type GraphMaximizerCorpusScheduler<CS, I, S> =
MinimizerCorpusScheduler<CS, MaxExecsLenFavFactor<I>, I, SysGraphMetadata, S>;
//============================= Graph Feedback
/// Improved System State Graph
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct SysGraphFeedbackState
{
pub graph: DiGraph<SysGraphNode, ()>,
entrypoint: NodeIndex,
exit: NodeIndex,
name: String,
}
impl SysGraphFeedbackState
{
pub fn new() -> Self {
let mut graph = DiGraph::<SysGraphNode, ()>::new();
let mut entry = SysGraphNode::default();
entry.base.current_task.task_name="Start".to_string();
let mut exit = SysGraphNode::default();
exit.base.current_task.task_name="End".to_string();
let entry = graph.add_node(entry);
let exit = graph.add_node(exit);
Self {graph: graph, entrypoint: entry, exit: exit, name: String::from("SysMap")}
}
fn insert(&mut self, list: Vec<RefinedFreeRTOSSystemState>, input: &Vec<u8>) {
let mut current_index = self.entrypoint;
for n in list {
let mut done = false;
for i in self.graph.neighbors_directed(current_index, Direction::Outgoing) {
if n == self.graph[i].base {
done = true;
current_index = i;
break;
}
}
if !done {
let j = self.graph.add_node(SysGraphNode::from(n,input.clone()));
self.graph.add_edge(current_index, j, ());
current_index = j;
}
}
}
/// Try adding a system state path from a [Vec<RefinedFreeRTOSSystemState>], return true if the path was interesting
fn update(&mut self, list: &Vec<RefinedFreeRTOSSystemState>, input: &Vec<u8>) -> (bool, Vec<NodeIndex>) {
let mut current_index = self.entrypoint;
let mut novel = false;
let mut trace : Vec<NodeIndex> = vec![current_index];
for n in list {
let mut matching : Option<NodeIndex> = None;
for i in self.graph.neighbors_directed(current_index, Direction::Outgoing) {
let tmp = &self.graph[i];
if n == &tmp.base {
matching = Some(i);
current_index = i;
break;
}
}
match matching {
None => {
novel = true;
let j = self.graph.add_node(SysGraphNode::from(n.clone(),input.clone()));
self.graph.add_edge(current_index, j, ());
current_index = j;
},
Some(i) => {
novel |= self.graph[i].unite_interesting(&n, input);
}
}
trace.push(current_index);
}
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 FeedbackState for 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 [`QemuSysStateObserver`]
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct SysMapFeedback
{
name: String,
last_trace: Option<Vec<NodeIndex>>,
}
impl SysMapFeedback {
pub fn new() -> Self {
Self {name: String::from("SysMapFeedback"), last_trace: None }
}
}
impl<I, S> Feedback<I, S> for SysMapFeedback
where
I: Input,
S: HasClientPerfMonitor + HasMetadata,
{
fn is_interesting<EM, OT>(
&mut self,
state: &mut S,
_manager: &mut EM,
_input: &I,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S>,
{
let observer = observers.match_name::<QemuSysStateObserver>("sysstate")
.expect("QemuSysStateObserver not found");
let feedbackstate = state
.feedback_states_mut()
.match_name_mut::<SysGraphFeedbackState>("SysMap")
.unwrap();
let ret = feedbackstate.update(&observer.last_run, &observer.last_input);
self.last_trace = Some(ret.1);
Ok(ret.0)
}
/// Append to the testcase the generated metadata in case of a new corpus item
#[inline]
fn append_metadata(&mut self, _state: &mut S, testcase: &mut Testcase<I>) -> Result<(), Error> {
let a = self.last_trace.take();
match a {
Some(s) => testcase.metadata_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: &I) -> Result<(), Error> {
self.last_trace = None;
Ok(())
}
}
impl Named for SysMapFeedback
{
#[inline]
fn name(&self) -> &str {
&self.name
}
}
//============================= Mutators
//=============================== Snippets
pub struct RandGraphSnippetMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
{
phantom: PhantomData<(I, S)>,
}
impl<I, S> RandGraphSnippetMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
{
pub fn new() -> Self {
RandGraphSnippetMutator{phantom: PhantomData}
}
}
impl<I, S> Mutator<I, S> for RandGraphSnippetMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I> + HasFeedbackStates,
{
fn mutate(
&mut self,
state: &mut S,
input: &mut I,
_stage_idx: i32
) -> Result<MutationResult, Error>
{
// need our own random generator, because borrowing rules
let mut myrand = StdRand::new();
let tmp = &mut state.rand_mut();
myrand.set_seed(tmp.next());
drop(tmp);
let feedbackstate = state
.feedback_states()
.match_name::<SysGraphFeedbackState>("SysMap")
.unwrap();
let g = &feedbackstate.graph;
let tmp = state.metadata().get::<SysGraphMetadata>();
if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
return Ok(MutationResult::Skipped);
}
let trace =tmp.expect("SysGraphMetadata not found");
// follow the path, extract snippets from last reads, find common snippets.
// those are likley keys parts. choose random parts from other sibling traces
let sibling_inputs : Vec<&Vec<u8>>= g[*trace.inner.last().unwrap()].variants.iter().map(|x| &x.input).collect();
let mut snippet_collector = vec![];
let mut per_input_counters = HashMap::<&Vec<u8>,usize>::new(); // ugly workaround to track multiple inputs
for t in &trace.inner {
let node = &g[*t];
let mut per_node_snippets = HashMap::<&Vec<u8>,&[u8]>::new();
for v in &node.variants {
match per_input_counters.get_mut(&v.input) {
None => {
if sibling_inputs.iter().any(|x| *x==&v.input) { // only collect info about siblin inputs from target
per_input_counters.insert(&v.input, v.input_counter.try_into().unwrap());
}
},
Some(x) => {
let x_u = *x;
if x_u<v.input_counter as usize {
*x=v.input_counter as usize;
per_node_snippets.insert(&v.input,&v.input[x_u..v.input_counter as usize]);
}
}
}
}
snippet_collector.push(per_node_snippets);
}
let mut new_input : Vec<u8> = vec![];
for c in snippet_collector {
new_input.extend_from_slice(myrand.choose(c).1);
}
for i in new_input.iter().enumerate() {
input.bytes_mut()[i.0]=*i.1;
}
Ok(MutationResult::Mutated)
}
fn post_exec(
&mut self,
_state: &mut S,
_stage_idx: i32,
_corpus_idx: Option<usize>
) -> Result<(), Error> {
Ok(())
}
}
impl<I, S> Named for RandGraphSnippetMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I> + HasFeedbackStates,
{
fn name(&self) -> &str {
"RandGraphSnippetMutator"
}
}
//=============================== Snippets
pub struct RandInputSnippetMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
{
phantom: PhantomData<(I, S)>,
}
impl<I, S> RandInputSnippetMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
{
pub fn new() -> Self {
RandInputSnippetMutator{phantom: PhantomData}
}
}
impl<I, S> Mutator<I, S> for RandInputSnippetMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I> + HasFeedbackStates,
{
fn mutate(
&mut self,
state: &mut S,
input: &mut I,
_stage_idx: i32
) -> Result<MutationResult, Error>
{
// need our own random generator, because borrowing rules
let mut myrand = StdRand::new();
let tmp = &mut state.rand_mut();
myrand.set_seed(tmp.next());
drop(tmp);
let feedbackstate = state
.feedback_states()
.match_name::<SysGraphFeedbackState>("SysMap")
.unwrap();
let g = &feedbackstate.graph;
let tmp = state.metadata().get::<SysGraphMetadata>();
if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
return Ok(MutationResult::Skipped);
}
let trace = tmp.expect("SysGraphMetadata not found");
let mut collection : Vec<Vec<u8>> = Vec::new();
let mut current_pointer : usize = 0;
for t in &trace.inner {
let node = &g[*t];
for v in &node.variants {
if v.input == input.bytes() {
if v.input_counter > current_pointer.try_into().unwrap() {
collection.push(v.input[current_pointer..v.input_counter as usize].to_owned());
current_pointer = v.input_counter as usize;
}
break;
}
}
}
let index_to_mutate = myrand.below(collection.len() as u64) as usize;
for i in 0..collection[index_to_mutate].len() {
collection[index_to_mutate][i] = myrand.below(0xFF) as u8;
}
for i in collection.concat().iter().enumerate() {
input.bytes_mut()[i.0]=*i.1;
}
Ok(MutationResult::Mutated)
}
fn post_exec(
&mut self,
_state: &mut S,
_stage_idx: i32,
_corpus_idx: Option<usize>
) -> Result<(), Error> {
Ok(())
}
}
impl<I, S> Named for RandInputSnippetMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I> + HasFeedbackStates,
{
fn name(&self) -> &str {
"RandInputSnippetMutator"
}
}
//=============================== Suffix
pub struct RandGraphSuffixMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
{
phantom: PhantomData<(I, S)>,
}
impl<I, S> RandGraphSuffixMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
{
pub fn new() -> Self {
RandGraphSuffixMutator{phantom: PhantomData}
}
}
impl<I, S> Mutator<I, S> for RandGraphSuffixMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I> + HasFeedbackStates,
{
fn mutate(
&mut self,
state: &mut S,
input: &mut I,
_stage_idx: i32
) -> Result<MutationResult, Error>
{
// need our own random generator, because borrowing rules
let mut myrand = StdRand::new();
let tmp = &mut state.rand_mut();
myrand.set_seed(tmp.next());
drop(tmp);
let feedbackstate = state
.feedback_states()
.match_name::<SysGraphFeedbackState>("SysMap")
.unwrap();
let g = &feedbackstate.graph;
let tmp = state.metadata().get::<SysGraphMetadata>();
if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
return Ok(MutationResult::Skipped);
}
let trace =tmp.expect("SysGraphMetadata not found");
// follow the path, extract snippets from last reads, find common snippets.
// those are likley keys parts. choose random parts from other sibling traces
let inp_c_end = g[*trace.inner.last().unwrap()].base.input_counter;
let mut num_to_reverse = myrand.below(trace.inner.len().try_into().unwrap());
for t in trace.inner.iter().rev() {
let int_c_prefix = g[*t].base.input_counter;
if int_c_prefix < inp_c_end {
num_to_reverse-=1;
if num_to_reverse<=0 {
let mut new_input=input.bytes()[..(int_c_prefix as usize)].to_vec();
let mut ext : Vec<u8> = (int_c_prefix..inp_c_end).map(|_| myrand.next().to_le_bytes()).flatten().collect();
new_input.append(&mut ext);
for i in new_input.iter().enumerate() {
if input.bytes_mut().len()>i.0 {
input.bytes_mut()[i.0]=*i.1;
}
else { break };
}
break;
}
}
}
Ok(MutationResult::Mutated)
}
fn post_exec(
&mut self,
_state: &mut S,
_stage_idx: i32,
_corpus_idx: Option<usize>
) -> Result<(), Error> {
Ok(())
}
}
impl<I, S> Named for RandGraphSuffixMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I> + HasFeedbackStates,
{
fn name(&self) -> &str {
"RandGraphSuffixMutator"
}
}

View File

@ -0,0 +1,163 @@
use libafl::prelude::UsesInput;
use libafl_qemu::edges::QemuEdgesMapMetadata;
use crate::sysstate::RawFreeRTOSSystemState;
use crate::sysstate::CURRENT_SYSSTATE_VEC;
use crate::sysstate::NUM_PRIOS;
use super::freertos::TCB_t;
use super::freertos::rtos_struct::List_Item_struct;
use super::freertos::rtos_struct::*;
use super::freertos;
use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata};
use libafl_qemu::{
emu::Emulator,
executor::QemuExecutor,
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
edges::SAVED_JUMP,
};
//============================= Struct definitions
pub static mut INTR_OFFSET : Option<u64> = None;
pub static mut INTR_DONE : bool = true;
//============================= Qemu Helper
/// A Qemu Helper with reads FreeRTOS specific structs from Qemu whenever certain syscalls occur
#[derive(Debug)]
pub struct QemuSystemStateHelper {
filter: QemuInstrumentationFilter,
tcb_addr: u32,
ready_queues: u32,
input_counter: u32,
}
impl QemuSystemStateHelper {
#[must_use]
pub fn new(
tcb_addr: u32,
ready_queues: u32,
input_counter: u32
) -> Self {
Self {
filter: QemuInstrumentationFilter::None,
tcb_addr: tcb_addr,
ready_queues: ready_queues,
input_counter: input_counter,
}
}
#[must_use]
pub fn with_instrumentation_filter(
filter: QemuInstrumentationFilter,
tcb_addr: u32,
ready_queues: u32,
input_counter: u32
) -> Self {
Self { filter, tcb_addr, ready_queues, input_counter}
}
#[must_use]
pub fn must_instrument(&self, addr: u64) -> bool {
self.filter.allowed(addr)
}
}
impl<S> QemuHelper<S> for QemuSystemStateHelper
where
S: UsesInput,
{
fn init_hooks<QT>(&self, executor: &QemuExecutor<QT>)
where
QT: QemuHelperTuple<S>,
{
// emu::Emulator{_private: ()}.set_gen_block_hook(test_gen_hook);
executor.hook_block_generation(gen_not_exec_block_hook::<I, QT, S>);
executor.hook_block_execution(exec_syscall_hook::<I, QT, S>);
}
}
pub fn exec_syscall_hook<I, QT, S>(
emulator: &Emulator,
helpers: &mut QT,
state: &mut S,
pc: u64,
)
where
S: HasMetadata,
I: Input,
QT: QemuHelperTuple<I, S>,
{
let h = helpers.match_first_type::<QemuSystemStateHelper>().expect("QemuSystemHelper not found in helper tupel");
if !h.must_instrument(pc) {
return;
}
let listbytes : u32 = u32::try_from(std::mem::size_of::<freertos::List_t>()).unwrap();
let mut sysstate = RawFreeRTOSSystemState::default();
sysstate.qemu_tick = emulator.get_ticks();
let mut buf : [u8; 4] = [0,0,0,0];
unsafe { emulator.read_mem(h.input_counter.into(), &mut buf) };
sysstate.input_counter = u32::from_le_bytes(buf);
let curr_tcb_addr : freertos::void_ptr = freertos::emu_lookup::lookup(emulator, h.tcb_addr);
sysstate.current_tcb = freertos::emu_lookup::lookup(emulator,curr_tcb_addr);
unsafe {
match SAVED_JUMP.take() {
Some(s) => {
sysstate.last_pc = Some(s.0);
},
None => (),
}
}
// println!("{:?}",std::str::from_utf8(&current_tcb.pcTaskName));
for i in 0..NUM_PRIOS {
let target : u32 = listbytes*u32::try_from(i).unwrap()+h.ready_queues;
sysstate.prio_ready_lists[i] = freertos::emu_lookup::lookup(emulator, target);
// println!("List at {}: {:?}",target, sysstate.prio_ready_lists[i]);
let mut next_index = sysstate.prio_ready_lists[i].pxIndex;
for _j in 0..sysstate.prio_ready_lists[i].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;
sysstate.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);
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);
sysstate.dumping_ground.insert(next_item.pvOwner,TCB_struct(next_tcb.clone()));
sysstate.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);
sysstate.dumping_ground.insert(next_index,List_MiniItem_struct(next_item));
}
}
unsafe { CURRENT_SYSSTATE_VEC.push(sysstate); }
}
pub fn gen_not_exec_block_hook<I, QT, S>(
_emulator: &Emulator,
helpers: &mut QT,
_state: &mut S,
pc: u64,
)
-> Option<u64> where
S: HasMetadata,
I: Input,
QT: QemuHelperTuple<I, S>,
{
let h = helpers.match_first_type::<QemuSystemStateHelper>().expect("QemuSystemHelper not found in helper tupel");
if !h.must_instrument(pc) {
None
} else {Some(1)}
}

View File

@ -1,4 +1,4 @@
//! systemstate referes to the State of a FreeRTOS fuzzing target
//! Sysstate referes to the State of a FreeRTOS fuzzing target
use std::collections::hash_map::DefaultHasher;
use libafl::bolts::HasRefCnt;
use libafl::bolts::AsSlice;
@ -14,13 +14,13 @@ pub mod helpers;
pub mod observers;
pub mod feedbacks;
pub mod graph;
pub mod schedulers;
pub mod mutators;
// #[cfg(feature = "fuzz_interrupt")]
// pub const IRQ_INPUT_BYTES_NUMBER : u32 = 2; // Offset for interrupt bytes
// #[cfg(not(feature = "fuzz_interrupt"))]
// pub const IRQ_INPUT_BYTES_NUMBER : u32 = 0; // Offset for interrupt bytes
// pub const IRQ_INPUT_OFFSET : u32 = 347780; // Tick offset for app code start
#[cfg(feature = "fuzz_interrupt")]
pub const IRQ_INPUT_BYTES_NUMBER : u32 = 2; // Offset for interrupt bytes
#[cfg(not(feature = "fuzz_interrupt"))]
pub const IRQ_INPUT_BYTES_NUMBER : u32 = 0; // Offset for interrupt bytes
pub const IRQ_INPUT_OFFSET : u32 = 347780; // Tick offset for app code start
// Constants
const NUM_PRIOS: usize = 5;
@ -37,14 +37,14 @@ pub struct RawFreeRTOSSystemState {
last_pc: Option<u64>,
}
/// List of system state dumps from QemuHelpers
static mut CURRENT_SYSTEMSTATE_VEC: Vec<RawFreeRTOSSystemState> = vec![];
static mut CURRENT_SYSSTATE_VEC: Vec<RawFreeRTOSSystemState> = vec![];
/// A reduced version of freertos::TCB_t
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
pub struct RefinedTCB {
pub task_name: String,
pub priority: u32,
pub base_priority: u32,
task_name: String,
priority: u32,
base_priority: u32,
mutexes_held: u32,
notify_value: u32,
notify_state: u8,
@ -53,10 +53,9 @@ pub struct RefinedTCB {
impl Hash for RefinedTCB {
fn hash<H: Hasher>(&self, state: &mut H) {
self.task_name.hash(state);
self.priority.hash(state);
self.mutexes_held.hash(state);
#[cfg(not(feature = "no_hash_state"))]
self.notify_state.hash(state);
// self.priority.hash(state);
// self.mutexes_held.hash(state);
// self.notify_state.hash(state);
// self.notify_value.hash(state);
}
}
@ -95,11 +94,11 @@ impl RefinedTCB {
/// Refined information about the states an execution transitioned between
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct RefinedFreeRTOSSystemState {
pub start_tick: u64,
pub end_tick: u64,
start_tick: u64,
end_tick: u64,
last_pc: Option<u64>,
input_counter: u32,
pub current_task: RefinedTCB,
current_task: RefinedTCB,
ready_list_after: Vec<RefinedTCB>,
}
impl PartialEq for RefinedFreeRTOSSystemState {
@ -113,7 +112,7 @@ impl Hash for RefinedFreeRTOSSystemState {
fn hash<H: Hasher>(&self, state: &mut H) {
self.current_task.hash(state);
self.ready_list_after.hash(state);
// self.last_pc.hash(state);
self.last_pc.hash(state);
}
}
impl RefinedFreeRTOSSystemState {
@ -125,15 +124,14 @@ impl RefinedFreeRTOSSystemState {
// Wrapper around Vec<RefinedFreeRTOSSystemState> to attach as Metadata
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct FreeRTOSSystemStateMetadata {
pub inner: Vec<RefinedFreeRTOSSystemState>,
trace_length: usize,
inner: Vec<RefinedFreeRTOSSystemState>,
indices: Vec<usize>, // Hashed enumeration of States
tcref: isize,
}
impl FreeRTOSSystemStateMetadata {
pub fn new(inner: Vec<RefinedFreeRTOSSystemState>) -> 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}
Self {inner: inner, indices: tmp, tcref: 0}
}
}
pub fn compute_hash<T>(obj: T) -> u64
@ -145,13 +143,11 @@ where
s.finish()
}
impl AsSlice for FreeRTOSSystemStateMetadata {
impl AsSlice<usize> 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 {

View File

@ -0,0 +1,123 @@
use crate::sysstate::graph::SysGraphMetadata;
use crate::sysstate::graph::SysGraphNode;
use libafl::state::HasFeedbackStates;
use crate::sysstate::IRQ_INPUT_OFFSET;
use crate::sysstate::IRQ_INPUT_BYTES_NUMBER;
use crate::sysstate::graph::SysGraphFeedbackState;
use libafl::inputs::HasBytesVec;
use libafl::bolts::rands::RandomSeed;
use libafl::bolts::rands::StdRand;
use libafl::mutators::Mutator;
use libafl::mutators::MutationResult;
use core::marker::PhantomData;
use libafl::state::HasCorpus;
use libafl::state::HasSolutions;
use libafl::state::HasRand;
use libafl::bolts::tuples::MatchName;
use libafl::bolts::tuples::Named;
use libafl::Error;
use libafl::{inputs::Input, state::HasMetadata};
use super::FreeRTOSSystemStateMetadata;
use libafl::bolts::rands::Rand;
//=============================== Interrupt
/// Sets up the interrupt to a random block in the trace. Works for both state and graph metadata
pub struct InterruptShifterMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
{
phantom: PhantomData<(I, S)>,
}
impl<I, S> InterruptShifterMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I>,
{
pub fn new() -> Self {
InterruptShifterMutator{phantom: PhantomData}
}
}
impl<I, S> Mutator<I, S> for InterruptShifterMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I> + HasFeedbackStates,
{
fn mutate(
&mut self,
state: &mut S,
input: &mut I,
_stage_idx: i32
) -> Result<MutationResult, Error>
{
// need our own random generator, because borrowing rules
let mut myrand = StdRand::new();
let tmp = &mut state.rand_mut();
myrand.set_seed(tmp.next());
drop(tmp);
let target_bytes = input.bytes_mut();
let mut target_tick = 0;
#[cfg(feature = "sched_state")]
{
let tmp = state.metadata().get::<FreeRTOSSystemStateMetadata>();
if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
return Ok(MutationResult::Skipped);
}
let trace =tmp.expect("FreeRTOSSystemStateMetadata not found");
let target_block = myrand.choose(trace.inner.iter());
target_tick = myrand.between(target_block.start_tick,target_block.end_tick)-IRQ_INPUT_OFFSET as u64;
}
#[cfg(feature = "sched_state")]
{
let feedbackstate = state
.feedback_states()
.match_name::<SysGraphFeedbackState>("SysMap")
.unwrap();
let g = &feedbackstate.graph;
let tmp = state.metadata().get::<SysGraphMetadata>();
if tmp.is_none() { // if there are no metadata it was probably not interesting anyways
return Ok(MutationResult::Skipped);
}
let trace = tmp.expect("SysGraphMetadata not found");
let target_block : &SysGraphNode = &g[*myrand.choose(trace.inner.iter())];
target_tick = match target_block.variants.iter().find(|x| &x.input == target_bytes) {
Some(s) => myrand.between(s.start_tick,s.end_tick)-IRQ_INPUT_OFFSET as u64,
None => myrand.between(target_block.variants[0].start_tick,target_block.variants[0].end_tick)-IRQ_INPUT_OFFSET as u64,
};
}
if target_bytes.len() > IRQ_INPUT_BYTES_NUMBER as usize && IRQ_INPUT_BYTES_NUMBER > 0 {
for i in 0..IRQ_INPUT_BYTES_NUMBER as usize {
target_bytes[i] = u64::to_le_bytes(target_tick)[i];
}
return Ok(MutationResult::Mutated);
} else {
return Ok(MutationResult::Skipped);
}
}
fn post_exec(
&mut self,
_state: &mut S,
_stage_idx: i32,
_corpus_idx: Option<usize>
) -> Result<(), Error> {
Ok(())
}
}
impl<I, S> Named for InterruptShifterMutator<I, S>
where
I: Input + HasBytesVec,
S: HasRand + HasMetadata + HasCorpus<I> + HasSolutions<I> + HasFeedbackStates,
{
fn name(&self) -> &str {
"InterruptShifterMutator"
}
}

View File

@ -1,6 +1,5 @@
// use crate::systemstate::IRQ_INPUT_BYTES_NUMBER;
use libafl::prelude::{ExitKind, AsSlice};
use libafl::{inputs::HasTargetBytes, prelude::UsesInput};
use crate::sysstate::IRQ_INPUT_BYTES_NUMBER;
use libafl::inputs::HasTargetBytes;
use libafl::bolts::HasLen;
use libafl::bolts::tuples::Named;
use libafl::Error;
@ -9,7 +8,7 @@ use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use super::{
CURRENT_SYSTEMSTATE_VEC,
CURRENT_SYSSTATE_VEC,
RawFreeRTOSSystemState,
RefinedTCB,
RefinedFreeRTOSSystemState,
@ -18,37 +17,44 @@ use super::{
//============================= Observer
/// The Qemusystemstate Observer retrieves the systemstate
/// The QemuSysState Observer retrieves the SysState
/// that will get updated by the target.
#[derive(Serialize, Deserialize, Debug, Default)]
#[allow(clippy::unsafe_derive_deserialize)]
pub struct QemuSystemStateObserver
pub struct QemuSysStateObserver
{
pub last_run: Vec<RefinedFreeRTOSSystemState>,
pub last_input: Vec<u8>,
name: String,
}
impl<S> Observer<S> for QemuSystemStateObserver
impl<I, S> Observer<I, S> for QemuSysStateObserver
where
S: UsesInput,
S::Input : HasTargetBytes,
I: HasTargetBytes
{
#[inline]
fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
unsafe {CURRENT_SYSTEMSTATE_VEC.clear(); }
fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
unsafe {CURRENT_SYSSTATE_VEC.clear(); }
Ok(())
}
#[inline]
fn post_exec(&mut self, _state: &mut S, _input: &S::Input, _exit_kind: &ExitKind) -> Result<(), Error> {
unsafe {self.last_run = refine_system_states(&mut CURRENT_SYSTEMSTATE_VEC);}
fn post_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
unsafe {self.last_run = refine_system_states(&mut CURRENT_SYSSTATE_VEC);}
self.last_input=_input.target_bytes().as_slice().to_owned();
// let mut hasher = DefaultHasher::new();
// let mut a = self.parse_last();
// a[0].start_tick=21355;
// a[0].end_tick=2131;
// a.hash(&mut hasher);
// let somehash = hasher.finish();
// println!("HashValue: {}",somehash);
// println!("{:#?}",self.parse_last());
Ok(())
}
}
impl Named for QemuSystemStateObserver
impl Named for QemuSysStateObserver
{
#[inline]
fn name(&self) -> &str {
@ -56,7 +62,7 @@ impl Named for QemuSystemStateObserver
}
}
impl HasLen for QemuSystemStateObserver
impl HasLen for QemuSysStateObserver
{
#[inline]
fn len(&self) -> usize {
@ -64,9 +70,9 @@ impl HasLen for QemuSystemStateObserver
}
}
impl QemuSystemStateObserver {
impl QemuSysStateObserver {
pub fn new() -> Self {
Self{last_run: vec![], last_input: vec![], name: "systemstate".to_string()}
Self{last_run: vec![], last_input: vec![], name: "sysstate".to_string()}
}
}
@ -124,7 +130,7 @@ for mut i in input.drain(..) {
start_tick: start_tick,
end_tick: i.qemu_tick,
ready_list_after: collector,
input_counter: i.input_counter,//+IRQ_INPUT_BYTES_NUMBER,
input_counter: i.input_counter+IRQ_INPUT_BYTES_NUMBER,
last_pc: i.last_pc,
});
start_tick=i.qemu_tick;

View File

@ -0,0 +1,763 @@
use libafl::feedbacks::FeedbackState;
use libafl_qemu::QemuClockObserver;
use crate::sysstate::FreeRTOSSystemStateMetadata;
use num_traits::PrimInt;
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::corpus::FavFactor;
use core::marker::PhantomData;
use libafl::corpus::MinimizerCorpusScheduler;
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::HasClientPerfMonitor;
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::{
tuples::Named,
HasLen,
},
observers::Observer,
Error,
};
//===================================================================
/// A wrapper around some other [`MapObserver`], using [`QemuEdgesMapMetadata`] to offer a convinient Hashmap.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "M: serde::de::DeserializeOwned")]
pub struct QemuHashMapObserver<M,T>
where
M: serde::Serialize + serde::de::DeserializeOwned,
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
{
base: M,
pub edgemap: HashMap<(u64,u64),T>,
}
impl<I, S, M, T> Observer<I, S> for QemuHashMapObserver<M, T>
where
M: MapObserver<T> + Observer<I, S>,
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
S: HasMetadata, // Need to grab the HashMap from a Helper
{
#[inline]
fn pre_exec(&mut self, state: &mut S, input: &I) -> Result<(), Error> {
self.edgemap = HashMap::new();
self.base.pre_exec(state, input)
}
#[inline]
fn post_exec(&mut self, state: &mut S, input: &I) -> Result<(), Error> {
let original_hashmap=&state.metadata().get::<QemuEdgesMapMetadata>().expect("QemuEdgesMapMetadata not found").map;
for (key, val) in original_hashmap.iter() {
self.edgemap.insert(*key,*self.base.get(*val as usize));
}
self.base.post_exec(state, input)
}
}
impl<M, T> Named for QemuHashMapObserver<M, T>
where
M: Named + serde::Serialize + serde::de::DeserializeOwned,
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
{
#[inline]
fn name(&self) -> &str {
self.base.name()
}
}
impl<M, T> HasLen for QemuHashMapObserver<M, T>
where
M: MapObserver<T>,
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
{
#[inline]
fn len(&self) -> usize {
self.base.len()
}
}
impl<M, T> MapObserver<T> for QemuHashMapObserver<M, T>
where
M: MapObserver<T>,
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
{
#[inline]
fn map(&self) -> Option<&[T]> {
self.base.map()
}
#[inline]
fn map_mut(&mut self) -> Option<&mut [T]> {
self.base.map_mut()
}
#[inline]
fn usable_count(&self) -> usize {
self.base.usable_count()
}
#[inline]
fn initial(&self) -> T {
self.base.initial()
}
#[inline]
fn initial_mut(&mut self) -> &mut T {
self.base.initial_mut()
}
#[inline]
fn set_initial(&mut self, initial: T) {
self.base.set_initial(initial);
}
}
impl<M, T> QemuHashMapObserver<M, T>
where
M: serde::Serialize + serde::de::DeserializeOwned,
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
{
/// Creates a new [`MapObserver`]
pub fn new(base: M) -> Self {
Self { edgemap:HashMap::new(), base }
}
}
//===================================================================
/// A [`HitFeedback`] reports as interesting when all predicted worst case edges have been matched.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "T: serde::de::DeserializeOwned")]
pub struct HitFeedback<O, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
target_map: HashMap<(u64,u64),T>,
target_msd: f64,
phantom: PhantomData<(O, T)>,
}
impl<I, S, T, O> Feedback<I, S> for HitFeedback<O, T>
where
I: Input,
S: HasClientPerfMonitor,
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &I,
_observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S>,
{
let observer = _observers.match_name::<QemuHashMapObserver<O, T>>("edges")
.expect("QemuHashMapObserver not found");
if self.target_map.len() == 0 { return Ok(false) };
let mut sum_of_square_difference : u64 = 0; // does not include found edges not in target
for (edg, val) in &self.target_map {
match observer.edgemap.get(&edg) {
Some(x) => {
sum_of_square_difference+=((cmp::max(*x,*val)-cmp::min(*x,*val)).to_u64().unwrap()).pow(2);
},
None => sum_of_square_difference+=((*val).to_u64().unwrap()).pow(2),
}
}
let mean_sum_of_squares = (sum_of_square_difference as f64) / (self.target_map.len() as f64);
let hit_target = mean_sum_of_squares <= self.target_msd;
if hit_target {
// #[cfg(debug_assertions)] eprintln!("Hit Feedback trigger");
Ok(true)
} else {
Ok(false)
}
}
}
impl<O, T> Named for HitFeedback<O, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
#[inline]
fn name(&self) -> &str {
"HitFeedback"
}
}
impl<O, T> HitFeedback<O, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
/// Creates a new [`HitFeedback`]
#[must_use]
pub fn new(target_map: HashMap<(u64,u64),T>, target_msd: f64, _map_observer: &QemuHashMapObserver<O, T>) -> Self {
Self {target_map: target_map, target_msd: target_msd, phantom: PhantomData}
}
}
//===================================================================
// /// A [`MapHitIncreaseFeedback`] reports as interesting when the total number of used edges increases.
// #[derive(Serialize, Deserialize, Clone, Debug)]
// pub struct MapHitIncreaseFeedback {
// record_high : u64,
// }
// impl<I, S, O, T> Feedback<I, S> for MapHitIncreaseFeedback
// where
// I: Input,
// S: HasClientPerfMonitor,
// T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
// O: MapObserver<T>,
// {
// fn is_interesting<EM, OT>(
// &mut self,
// _state: &mut S,
// _manager: &mut EM,
// _input: &I,
// _observers: &OT,
// _exit_kind: &ExitKind,
// ) -> Result<bool, Error>
// where
// EM: EventFirer<I>,
// OT: ObserversTuple<I, S>,
// {
// let observer = _observers.match_name::<QemuHashMapObserver<O, T>>("edges")
// .expect("QemuHashMapObserver not found");
// let cur = observer.edgemap.values().fold(0,|a,b| a+(*b as u64));
// if cur > self.record_high {
// self.record_high = cur;
// return Ok(true);
// }
// return Ok(false);
// }
// }
// impl Named for MapHitIncreaseFeedback {
// #[inline]
// fn name(&self) -> &str {
// "HitFeedback"
// }
// }
// impl MapHitIncreaseFeedback {
// /// Creates a new [`HitFeedback`]
// #[must_use]
// pub fn new() -> Self {
// Self {record_high: 0}
// }
// }
// impl Default for MapHitIncreaseFeedback {
// fn default() -> Self {
// Self::new()
// }
// }
//===================================================================
/// A [`HitFeedback`] reports as interesting when all predicted worst case edges have been matched.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "T: serde::de::DeserializeOwned")]
pub struct HitImprovingFeedback<O, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
target_map: HashMap<(u64,u64),T>,
best_msd: f64,
phantom: PhantomData<(O, T)>,
}
impl<O, T, I, S> Feedback<I, S> for HitImprovingFeedback<O, T>
where
I: Input,
S: HasClientPerfMonitor,
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &I,
_observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S>,
{
let observer = _observers.match_name::<QemuHashMapObserver<O, T>>("edges")
.expect("QemuHashMapObserver not found");
if self.target_map.len() == 0 { return Ok(false) };
let mut sum_of_square_difference : u64 = 0; // does not include found edges not in target
for (edg, val) in &self.target_map {
match observer.edgemap.get(&edg) {
Some(x) => {
sum_of_square_difference+=((cmp::max(*x,*val)-cmp::min(*x,*val)).to_u64().unwrap()).pow(2);
},
None => sum_of_square_difference+=((*val).to_u64().unwrap()).pow(2),
}
}
let mean_sum_of_squares = (sum_of_square_difference as f64) / (self.target_map.len() as f64);
let hit_target = mean_sum_of_squares < self.best_msd;
if hit_target {
// #[cfg(debug_assertions)] eprintln!("Hit Improving: {}",mean_sum_of_squares);
self.best_msd = mean_sum_of_squares;
Ok(true)
} else {
Ok(false)
}
}
}
impl<O, T> Named for HitImprovingFeedback<O, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
#[inline]
fn name(&self) -> &str {
"HitImprovingFeedback"
}
}
impl<O, T> HitImprovingFeedback<O, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
/// Creates a new [`HitImprovingFeedback`]
#[must_use]
pub fn new(target_map: HashMap<(u64,u64),T>, _map_observer: &QemuHashMapObserver<O, T>) -> Self {
Self {target_map: target_map, best_msd: f64::MAX, phantom: PhantomData}
}
}
//=========================== Debugging Feedback
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct DumpEdgesMapMetadata
{
pub map: HashMap<(u64, u64), usize>,
}
impl DumpEdgesMapMetadata
{
#[must_use]
pub fn new(input: HashMap<(u64,u64),usize>) -> Self {
Self {
map: input,
}
}
}
libafl::impl_serdeany!(DumpEdgesMapMetadata);
/// A [`Feedback`] meant to dump the edgemap for debugging.
#[derive(Debug)]
pub struct DumpMapFeedback<O, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
dumpfile: Option<PathBuf>,
phantom: PhantomData<(O, T)>,
dump_metadata: bool,
last_map: Option<HashMap<(u64,u64),T>>,
}
impl<I, S, O, T> Feedback<I, S> for DumpMapFeedback<O, T>
where
I: Input,
S: HasClientPerfMonitor,
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &I,
_observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S>,
{
let observer = _observers.match_name::<QemuHashMapObserver<O, T>>("edges")
.expect("QemuHashMapObserver not found");
match &self.dumpfile {
Some(s) => {
fs::write(s,ron::to_string(&observer.edgemap).expect("Error serializing hashmap")).expect("Can not dump to file");
self.dumpfile = None
},
None => if !self.dump_metadata {println!("{:?}",observer.edgemap);}
};
if self.dump_metadata {self.last_map=Some(observer.edgemap.clone());}
Ok(!self.dump_metadata)
}
/// Append to the testcase the generated metadata in case of a new corpus item
#[inline]
fn append_metadata(&mut self, _state: &mut S, testcase: &mut Testcase<I>) -> Result<(), Error> {
if !self.dump_metadata {return Ok(());}
let a = self.last_map.take();
match a {
Some(s) => {
let mut tmp : HashMap<(u64,u64),usize> = HashMap::new();
for (k, v) in s.iter() {
tmp.insert(*k, T::to_usize(v).unwrap());
}
testcase.metadata_mut().insert(DumpEdgesMapMetadata::new(tmp));
}
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: &I) -> Result<(), Error> {
self.last_map = None;
Ok(())
}
}
impl<O, T> Named for DumpMapFeedback<O, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
#[inline]
fn name(&self) -> &str {
"HitFeedback"
}
}
impl<O, T> DumpMapFeedback<O, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
O: MapObserver<T>,
{
/// Creates a new [`HitFeedback`]
#[must_use]
pub fn new(_map_observer: &QemuHashMapObserver<O, T>) -> Self {
Self {dumpfile: None, phantom: PhantomData, dump_metadata: false, last_map: None}
}
pub fn with_dump(dumpfile: Option<PathBuf>,_map_observer: &QemuHashMapObserver<O, T>) -> Self {
Self {dumpfile: dumpfile, phantom: PhantomData, dump_metadata: false, last_map: None}
}
pub fn metadata_only(_map_observer: &QemuHashMapObserver<O, T>) -> Self {
Self {dumpfile: None, phantom: PhantomData, dump_metadata: true, last_map: None}
}
}
//=========================== Debugging Feedback
/// A NoOp [`Feedback`] with fixed response.
#[derive(Debug)]
pub struct DummyFeedback {
response: bool
}
impl<I, S> Feedback<I, S> for DummyFeedback
where
I: Input,
S: HasClientPerfMonitor,
{
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &I,
_observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S>,
{
// eprintln!("Input was: {:?}",_input);
Ok(self.response)
}
}
impl Named for DummyFeedback {
#[inline]
fn name(&self) -> &str {
"DummyFeedback"
}
}
impl DummyFeedback {
/// Creates a new [`HitFeedback`]
#[must_use]
pub fn new(response: bool) -> Self {
Self {response: response}
}
}
impl Default for DummyFeedback {
fn default() -> Self {
Self::new(true)
}
}
//=========================== Scheduler
pub type TimeMaximizerCorpusScheduler<CS, I, S> =
MinimizerCorpusScheduler<CS, MaxTimeFavFactor<I>, I, MapIndexesMetadata, S>;
/// Multiply the testcase size with the execution time.
/// This favors small and quick testcases.
#[derive(Debug, Clone)]
pub struct MaxTimeFavFactor<I>
where
I: Input + HasLen,
{
phantom: PhantomData<I>,
}
impl<I> FavFactor<I> for MaxTimeFavFactor<I>
where
I: Input + HasLen,
{
fn compute(entry: &mut Testcase<I>) -> Result<u64, Error> {
// TODO maybe enforce entry.exec_time().is_some()
let execs_per_hour = (3600.0/entry.exec_time().expect("testcase.exec_time is needed for scheduler").as_secs_f64()) as u64;
Ok(execs_per_hour)
}
}
pub type LenTimeMaximizerCorpusScheduler<CS, I, S> =
MinimizerCorpusScheduler<CS, MaxExecsLenFavFactor<I>, I, MapIndexesMetadata, S>;
pub type TimeStateMaximizerCorpusScheduler<CS, I, S> =
MinimizerCorpusScheduler<CS, MaxExecsLenFavFactor<I>, I, FreeRTOSSystemStateMetadata, S>;
/// Multiply the testcase size with the execution time.
/// This favors small and quick testcases.
#[derive(Debug, Clone)]
pub struct MaxExecsLenFavFactor<I>
where
I: Input + HasLen,
{
phantom: PhantomData<I>,
}
impl<I> FavFactor<I> for MaxExecsLenFavFactor<I>
where
I: Input + HasLen,
{
fn compute(entry: &mut Testcase<I>) -> Result<u64, Error> {
let execs_per_hour = (3600.0/entry.exec_time().expect("testcase.exec_time is needed for scheduler").as_secs_f64()) as u64;
let execs_times_length_per_hour = execs_per_hour*entry.cached_len()? as u64;
Ok(execs_times_length_per_hour)
}
}
//===================================================================
/// A Feedback reporting if the Input consists of strictly decreasing bytes.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SortedFeedback {
}
impl<S> Feedback<BytesInput, S> for SortedFeedback
where
S: HasClientPerfMonitor,
{
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &BytesInput,
_observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<BytesInput>,
OT: ObserversTuple<BytesInput, S>,
{
let t = _input.target_bytes();
let tmp = t.as_slice();
if tmp.len()<32 {return Ok(false);}
let tmp = Vec::<u8>::from(&tmp[0..32]);
// tmp.reverse();
if tmp.is_sorted_by(|a,b| match a.partial_cmp(b).unwrap_or(Less) {
Less => Some(Greater),
Equal => Some(Greater),
Greater => Some(Less),
}) {return Ok(true)};
return Ok(false);
}
}
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<I, S> Feedback<I, S> for ExecTimeReachedFeedback
where
I: Input,
S: HasClientPerfMonitor,
{
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &I,
_observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S>,
{
let observer = _observers.match_name::<QemuClockObserver>("clock")
.expect("QemuClockObserver not found");
Ok(observer.last_runtime() >= self.target_time)
}
}
impl Named for ExecTimeReachedFeedback
{
#[inline]
fn name(&self) -> &str {
"ExecTimeReachedFeedback"
}
}
impl ExecTimeReachedFeedback
where
{
/// Creates a new [`ExecTimeReachedFeedback`]
#[must_use]
pub fn new(target_time : u64) -> Self {
Self {target_time: target_time}
}
}
pub static mut EXEC_TIME_COLLECTION : Vec<u32> = Vec::new();
/// A Noop Feedback which records a list of all execution times
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ExecTimeCollectorFeedback
{
}
impl<I, S> Feedback<I, S> for ExecTimeCollectorFeedback
where
I: Input,
S: HasClientPerfMonitor,
{
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &I,
_observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S>,
{
let observer = _observers.match_name::<QemuClockObserver>("clock")
.expect("QemuClockObserver not found");
unsafe { EXEC_TIME_COLLECTION.push(observer.last_runtime().try_into().unwrap()); }
Ok(false)
}
}
impl Named for ExecTimeCollectorFeedback
{
#[inline]
fn name(&self) -> &str {
"ExecTimeCollectorFeedback"
}
}
impl ExecTimeCollectorFeedback
where
{
/// Creates a new [`ExecTimeCollectorFeedback`]
#[must_use]
pub fn new() -> Self {
Self {}
}
}
/// Shared Metadata for a SysStateFeedback
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct ExecTimeCollectorFeedbackState
{
collection: Vec<u32>,
}
impl Named for ExecTimeCollectorFeedbackState
{
#[inline]
fn name(&self) -> &str {
"ExecTimeCollectorFeedbackState"
}
}
impl FeedbackState for ExecTimeCollectorFeedbackState
{
fn reset(&mut self) -> Result<(), Error> {
self.collection.clear();
Ok(())
}
}

View File

@ -0,0 +1 @@
*.case

View File

@ -0,0 +1,4 @@
echo "" > best.case
echo "\x00\x00" > best_success.case
echo "\x05\x29\x07\x1f\x0b\x17\x00\x17" > worst_trace.case
echo "\xFF\xF6\xFC\xF8\xFD\xFD\xFF\xFD" > worst.case

View File

@ -0,0 +1 @@
*.case

View File

@ -0,0 +1,6 @@
echo "\xff\xff" > best.case
echo "\xff\xff\x00\x00" > best_success.case
echo "\xff\x05\x05\x29\x07\x1f\x0b\x17\x00\x17" > worst_trace.case
echo "\xff\x05\xFF\xF6\xFC\xF8\xFD\xFD\xFF\xFD" > worst.case
echo "\x3F\x05\xFF\xF6\xFC\xF8\xFD\xFD\xFF\xFD" > worst_preempt.case
echo "\x93\x1F\xFF\xF6\xFC\xF8\xFD\xFD\xFF\xFD" > worst_preempt_adv.case

View File

@ -48,7 +48,10 @@ fn adder_loop(port: u16) -> ! {
}
if current_result != last_result {
println!("Adder handled {msg_counter} messages, reporting {current_result} to broker");
println!(
"Adder handled {} messages, reporting {} to broker",
msg_counter, current_result
);
client
.send_buf(_TAG_MATH_RESULT_V1, &current_result.to_le_bytes())

View File

@ -132,7 +132,8 @@ impl Cores {
if cores.is_empty() {
return Err(Error::illegal_argument(format!(
"No cores specified! parsed: {args}"
"No cores specified! parsed: {}",
args
)));
}
@ -590,7 +591,8 @@ mod apple {
Ok(())
} else {
Err(Error::unknown(format!(
"Failed to set_for_current {result:?}"
"Failed to set_for_current {:?}",
result
)))
}
}
@ -613,7 +615,8 @@ mod apple {
Ok(())
} else {
Err(Error::unknown(format!(
"Failed to set_for_current {result:?}"
"Failed to set_for_current {:?}",
result
)))
}
}

View File

@ -165,7 +165,7 @@ const LLMP_PAGE_HEADER_LEN: usize = size_of::<LlmpPage>();
/// The llmp broker registers a signal handler for cleanups on `SIGINT`.
#[cfg(unix)]
static mut LLMP_SIGHANDLER_STATE: LlmpShutdownSignalHandler = LlmpShutdownSignalHandler {
static mut GLOBAL_SIGHANDLER_STATE: LlmpBrokerSignalHandler = LlmpBrokerSignalHandler {
shutting_down: false,
};
@ -466,7 +466,9 @@ unsafe fn _llmp_page_init<SHM: ShMem>(shmem: &mut SHM, sender_id: ClientId, allo
if !allow_reinit {
assert!(
(*page).magic != PAGE_INITIALIZED_MAGIC,
"Tried to initialize page {page:?} twice (for shmem {shmem:?})"
"Tried to initialize page {:?} twice (for shmem {:?})",
page,
shmem
);
}
@ -639,7 +641,10 @@ where
}
Err(Error::File(e, _)) if e.kind() == ErrorKind::AddrInUse => {
// We are the client :)
println!("We're the client (internal port already bound by broker, {e:#?})");
println!(
"We're the client (internal port already bound by broker, {:#?})",
e
);
Ok(LlmpConnection::IsClient {
client: LlmpClient::create_attach_to_tcp(shmem_provider, port)?,
})
@ -1077,7 +1082,8 @@ where
let page = self.out_shmems.last_mut().unwrap().page_mut();
if msg.is_null() || !llmp_msg_in_page(page, msg) {
return Err(Error::unknown(format!(
"Llmp Message {msg:?} is null or not in current page"
"Llmp Message {:?} is null or not in current page",
msg
)));
}
@ -1183,7 +1189,8 @@ where
match unsafe { self.alloc_next_if_space(buf_len) } {
Some(msg) => Ok(msg),
None => Err(Error::unknown(format!(
"Error allocating {buf_len} bytes in shmap"
"Error allocating {} bytes in shmap",
buf_len
))),
}
}
@ -1251,7 +1258,8 @@ where
|| tag == LLMP_TAG_UNSET
{
return Err(Error::unknown(format!(
"Reserved tag supplied to send_buf ({tag:#X})"
"Reserved tag supplied to send_buf ({:#X})",
tag
)));
}
@ -1274,7 +1282,8 @@ where
|| tag == LLMP_TAG_UNSET
{
return Err(Error::unknown(format!(
"Reserved tag supplied to send_buf ({tag:#X})"
"Reserved tag supplied to send_buf ({:#X})",
tag
)));
}
@ -1733,7 +1742,8 @@ where
let page_size = self.shmem.as_slice().len() - size_of::<LlmpPage>();
if offset > page_size {
Err(Error::illegal_argument(format!(
"Msg offset out of bounds (size: {page_size}, requested offset: {offset})"
"Msg offset out of bounds (size: {}, requested offset: {})",
page_size, offset
)))
} else {
unsafe { Ok(((*page).messages.as_mut_ptr() as *mut u8).add(offset) as *mut LlmpMsg) }
@ -1760,12 +1770,12 @@ where
/// A signal handler for the [`LlmpBroker`].
#[cfg(unix)]
#[derive(Debug, Clone)]
pub struct LlmpShutdownSignalHandler {
pub struct LlmpBrokerSignalHandler {
shutting_down: bool,
}
#[cfg(unix)]
impl Handler for LlmpShutdownSignalHandler {
impl Handler for LlmpBrokerSignalHandler {
fn handle(&mut self, _signal: Signal, _info: siginfo_t, _context: &mut ucontext_t) {
unsafe {
ptr::write_volatile(&mut self.shutting_down, true);
@ -1947,7 +1957,7 @@ where
#[cfg(unix)]
#[allow(clippy::unused_self)]
fn is_shutting_down(&self) -> bool {
unsafe { ptr::read_volatile(&LLMP_SIGHANDLER_STATE.shutting_down) }
unsafe { ptr::read_volatile(&GLOBAL_SIGHANDLER_STATE.shutting_down) }
}
/// Always returns true on platforms, where no shutdown signal handlers are supported
@ -1966,7 +1976,7 @@ where
F: FnMut(ClientId, Tag, Flags, &[u8]) -> Result<LlmpMsgHookResult, Error>,
{
#[cfg(unix)]
if let Err(_e) = unsafe { setup_signal_handler(&mut LLMP_SIGHANDLER_STATE) } {
if let Err(_e) = unsafe { setup_signal_handler(&mut GLOBAL_SIGHANDLER_STATE) } {
// We can live without a proper ctrl+c signal handler. Print and ignore.
#[cfg(feature = "std")]
println!("Failed to setup signal handlers: {_e}");
@ -2038,7 +2048,7 @@ where
/// This function returns the [`ShMemDescription`] the client uses to place incoming messages.
/// The thread exits, when the remote broker disconnects.
#[cfg(feature = "std")]
#[allow(clippy::let_and_return, clippy::too_many_lines)]
#[allow(clippy::let_and_return)]
fn b2b_thread_on(
mut stream: TcpStream,
b2b_client_id: ClientId,
@ -2086,17 +2096,16 @@ where
#[cfg(all(feature = "llmp_debug", feature = "std"))]
println!("B2B: Starting proxy loop :)");
let peer_address = stream.peer_addr().unwrap();
loop {
// first, forward all data we have.
loop {
match local_receiver.recv_buf_with_flags() {
Ok(None) => break, // no more data to forward
Ok(Some((client_id, tag, flags, payload))) => {
while let Some((client_id, tag, flags, payload)) = local_receiver
.recv_buf_with_flags()
.expect("Error reading from local page!")
{
if client_id == b2b_client_id {
println!(
"Ignored message we probably sent earlier (same id), TAG: {tag:x}"
"Ignored message we probably sent earlier (same id), TAG: {:x}",
tag
);
continue;
}
@ -2107,7 +2116,7 @@ where
payload.len()
);
// We got a new message! Forward...
if let Err(e) = send_tcp_msg(
send_tcp_msg(
&mut stream,
&TcpRemoteNewMessage {
client_id,
@ -2115,17 +2124,8 @@ where
flags,
payload: payload.to_vec(),
},
) {
println!("Got error {e} while trying to forward a message to broker {peer_address}, exiting thread");
return;
}
}
Err(Error::ShuttingDown) => {
println!("Local broker is shutting down, exiting thread");
return;
}
Err(e) => panic!("Error reading from local page! {e}"),
}
)
.expect("Error sending message via broker 2 broker");
}
// Then, see if we can receive something.
@ -2134,8 +2134,7 @@ where
// Forwarding happens between each recv, too, as simplification.
// We ignore errors completely as they may be timeout, or stream closings.
// Instead, we catch stream close when/if we next try to send.
match recv_tcp_msg(&mut stream) {
Ok(val) => {
if let Ok(val) = recv_tcp_msg(&mut stream) {
let msg: TcpRemoteNewMessage = val.try_into().expect(
"Illegal message received from broker 2 broker connection - shutting down.",
);
@ -2149,28 +2148,13 @@ where
// TODO: Could probably optimize this somehow to forward all queued messages between locks... oh well.
// Todo: somehow mangle in the other broker id? ClientId?
new_sender
.send_buf_with_flags(
msg.tag,
msg.flags | LLMP_FLAG_FROM_B2B,
&msg.payload,
)
.send_buf_with_flags(msg.tag, msg.flags | LLMP_FLAG_FROM_B2B, &msg.payload)
.expect("B2B: Error forwarding message. Exiting.");
}
Err(e) => {
if let Error::File(e, _) = e {
if e.kind() == ErrorKind::UnexpectedEof {
println!(
"Broker {peer_address} seems to have disconnected, exiting"
);
return;
}
}
} else {
#[cfg(all(feature = "llmp_debug", feature = "std"))]
println!("Received no input, timeout or closed. Looping back up :)");
}
}
}
});
let ret = recv.recv().map_err(|_| {
@ -2289,11 +2273,12 @@ where
loop {
match listener.accept() {
ListenerStream::Tcp(mut stream, addr) => {
eprintln!(
"New connection: {:?}/{:?}",
addr,
stream.peer_addr().unwrap()
);
// For some reason stderr is not avalible in child
// eprintln!(
// "New connection: {:?}/{:?}",
// addr,
// stream.peer_addr().unwrap()
// );
// Send initial information, without anyone asking.
// This makes it a tiny bit easier to map the broker map for new Clients.
@ -2405,7 +2390,8 @@ where
println!("Error adding client! Ignoring: {e:?}");
#[cfg(not(feature = "std"))]
return Err(Error::unknown(format!(
"Error adding client! PANIC! {e:?}"
"Error adding client! PANIC! {:?}",
e
)));
}
};

View File

@ -391,7 +391,8 @@ pub unsafe fn setup_signal_handler<T: 'static + Handler>(handler: &mut T) -> Res
// Rust always panics on OOM, so we will, too.
assert!(
!SIGNAL_STACK_PTR.is_null(),
"Failed to allocate signal stack with {SIGNAL_STACK_SIZE} bytes!"
"Failed to allocate signal stack with {} bytes!",
SIGNAL_STACK_SIZE
);
}
let mut ss: stack_t = mem::zeroed();

View File

@ -23,14 +23,9 @@ pub use windows::Win32::{
use crate::Error;
// For VEH
const EXCEPTION_CONTINUE_EXECUTION: c_long = -1;
// For VEH
//const EXCEPTION_CONTINUE_EXECUTION: c_long = -1;
//const EXCEPTION_CONTINUE_SEARCH: c_long = 0;
// For SEH
//const EXCEPTION_EXECUTE_HANDLER: c_long = 1;
const EXCEPTION_EXECUTE_HANDLER: c_long = 1;
// From https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-headers/crt/signal.h
pub const SIGINT: i32 = 2;
@ -319,9 +314,9 @@ unsafe fn internal_handle_exception(
Some(handler_holder) => {
let handler = &mut **handler_holder.handler.get();
handler.handle(exception_code, exception_pointers);
EXCEPTION_CONTINUE_EXECUTION
EXCEPTION_EXECUTE_HANDLER
}
None => EXCEPTION_CONTINUE_EXECUTION,
None => EXCEPTION_EXECUTE_HANDLER,
}
}
@ -381,7 +376,7 @@ pub unsafe fn setup_exception_handler<T: 'static + Handler>(handler: &mut T) ->
// SetUnhandledFilter does not work with frida since the stack is changed and exception handler is lost with Stalker enabled.
// See https://github.com/AFLplusplus/LibAFL/pull/403
AddVectoredExceptionHandler(
0,
1,
Some(core::mem::transmute(handle_exception as *const c_void)),
);
Ok(())

View File

@ -468,7 +468,8 @@ where
Ok(())
} else {
Err(Error::unknown(format!(
"Wrong result read from pipe! Expected 0, got {ret:?}"
"Wrong result read from pipe! Expected 0, got {:?}",
ret
)))
}
}
@ -629,7 +630,8 @@ pub mod unix_shmem {
if shm_fd == -1 {
perror(b"shm_open\0".as_ptr() as *const _);
return Err(Error::unknown(format!(
"Failed to shm_open map with id {shmem_ctr:?}"
"Failed to shm_open map with id {:?}",
shmem_ctr
)));
}
@ -638,7 +640,8 @@ pub mod unix_shmem {
perror(b"ftruncate\0".as_ptr() as *const _);
shm_unlink(filename_path.as_ptr() as *const _);
return Err(Error::unknown(format!(
"setup_shm(): ftruncate() failed for map with id {shmem_ctr:?}"
"setup_shm(): ftruncate() failed for map with id {:?}",
shmem_ctr
)));
}
@ -656,7 +659,8 @@ pub mod unix_shmem {
close(shm_fd);
shm_unlink(filename_path.as_ptr() as *const _);
return Err(Error::unknown(format!(
"mmap() failed for map with id {shmem_ctr:?}"
"mmap() failed for map with id {:?}",
shmem_ctr
)));
}
@ -687,7 +691,8 @@ pub mod unix_shmem {
perror(b"mmap\0".as_ptr() as *const _);
close(shm_fd);
return Err(Error::unknown(format!(
"mmap() failed for map with fd {shm_fd:?}"
"mmap() failed for map with fd {:?}",
shm_fd
)));
}
@ -992,7 +997,8 @@ pub mod unix_shmem {
let fd = open(device_path.as_ptr(), O_RDWR);
if fd == -1 {
return Err(Error::unknown(format!(
"Failed to open the ashmem device at {device_path:?}"
"Failed to open the ashmem device at {:?}",
device_path
)));
}

View File

@ -31,11 +31,6 @@ use crate::bolts::{
};
#[cfg(feature = "std")]
use crate::bolts::{llmp::LlmpConnection, shmem::StdShMemProvider, staterestore::StateRestorer};
#[cfg(all(unix, feature = "std"))]
use crate::{
bolts::os::unix_signals::setup_signal_handler,
events::{shutdown_handler, SHUTDOWN_SIGHANDLER_DATA},
};
use crate::{
bolts::{
llmp::{self, Flags, LlmpClient, LlmpClientDescription, Tag},
@ -398,7 +393,10 @@ where
executions: _,
} => {
#[cfg(feature = "std")]
println!("Received new Testcase from {_client_id} ({client_config:?})");
println!(
"Received new Testcase from {} ({:?})",
_client_id, client_config
);
let _res = if client_config.match_with(&self.configuration)
&& observers_buf.is_some()
@ -887,42 +885,17 @@ where
mgr.to_env(_ENV_FUZZER_BROKER_CLIENT_INITIAL);
// First, create a channel from the current fuzzer to the next to store state between restarts.
#[cfg(unix)]
let mut staterestorer: StateRestorer<SP> =
StateRestorer::new(self.shmem_provider.new_shmem(256 * 1024 * 1024)?);
#[cfg(not(unix))]
let staterestorer: StateRestorer<SP> =
StateRestorer::new(self.shmem_provider.new_shmem(256 * 1024 * 1024)?);
// Store the information to a map.
staterestorer.write_to_env(_ENV_FUZZER_SENDER)?;
#[cfg(unix)]
unsafe {
let data = &mut SHUTDOWN_SIGHANDLER_DATA;
// Write the pointer to staterestorer so we can release its shmem later
core::ptr::write_volatile(
&mut data.staterestorer_ptr,
&mut staterestorer as *mut _ as *mut std::ffi::c_void,
);
data.allocator_pid = std::process::id() as usize;
data.shutdown_handler = shutdown_handler::<SP> as *const std::ffi::c_void;
}
// We setup signal handlers to clean up shmem segments used by state restorer
#[cfg(unix)]
if let Err(_e) = unsafe { setup_signal_handler(&mut SHUTDOWN_SIGHANDLER_DATA) } {
// We can live without a proper ctrl+c signal handler. Print and ignore.
#[cfg(feature = "std")]
println!("Failed to setup signal handlers: {_e}");
}
let mut ctr: u64 = 0;
// Client->parent loop
loop {
println!("Spawning next client (id {ctr})");
// On Unix, we fork (when fork feature is enabled)
// On Unix, we fork
#[cfg(all(unix, feature = "fork"))]
let child_status = {
self.shmem_provider.pre_fork()?;

View File

@ -8,8 +8,6 @@ use alloc::{
string::{String, ToString},
vec::Vec,
};
#[cfg(all(unix, feature = "std"))]
use core::ffi::c_void;
use core::{fmt, hash::Hasher, marker::PhantomData, time::Duration};
use ahash::AHasher;
@ -18,10 +16,6 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use uuid::Uuid;
#[cfg(all(unix, feature = "std"))]
use crate::bolts::os::unix_signals::{siginfo_t, ucontext_t, Handler, Signal};
#[cfg(all(unix, feature = "std"))]
use crate::bolts::{shmem::ShMemProvider, staterestore::StateRestorer};
use crate::{
bolts::current_time,
executors::ExitKind,
@ -33,80 +27,6 @@ use crate::{
Error,
};
/// Check if ctrl-c is sent with this struct
#[cfg(all(unix, feature = "std"))]
pub static mut SHUTDOWN_SIGHANDLER_DATA: ShutdownSignalData = ShutdownSignalData {
allocator_pid: 0,
staterestorer_ptr: core::ptr::null_mut(),
shutdown_handler: core::ptr::null(),
};
/// A signal handler for releasing `StateRestore` `ShMem`
/// This struct holds a pointer to `StateRestore` and clean up the `ShMem` segment used by it.
#[cfg(all(unix, feature = "std"))]
#[derive(Debug, Clone)]
pub struct ShutdownSignalData {
allocator_pid: usize,
staterestorer_ptr: *mut c_void,
shutdown_handler: *const c_void,
}
/// Type for shutdown handler
#[cfg(all(unix, feature = "std"))]
pub type ShutdownFuncPtr =
unsafe fn(Signal, siginfo_t, &mut ucontext_t, data: &mut ShutdownSignalData);
/// Shutdown handler. `SigTerm`, `SigInterrupt`, `SigQuit` call this
/// We can't handle SIGKILL in the signal handler, this means that you shouldn't kill your fuzzer with `kill -9` because then the shmem segments are never freed
///
/// # Safety
///
/// This will acceess `data` and write to the global `data.staterestorer_ptr` if it's not null.
#[cfg(all(unix, feature = "std"))]
pub unsafe fn shutdown_handler<SP>(
signal: Signal,
_info: siginfo_t,
_context: &mut ucontext_t,
data: &mut ShutdownSignalData,
) where
SP: ShMemProvider,
{
println!(
"Fuzzer shutdown by Signal: {} Pid: {}",
signal,
std::process::id()
);
let ptr = data.staterestorer_ptr;
if ptr.is_null() || data.allocator_pid != std::process::id() as usize {
// Do nothing
} else {
// The process allocated the staterestorer map must take care of it
let sr = (ptr as *mut StateRestorer<SP>).as_mut().unwrap();
// println!("{:#?}", sr);
std::ptr::drop_in_place(sr);
}
println!("Bye!");
std::process::exit(0);
}
#[cfg(all(unix, feature = "std"))]
impl Handler for ShutdownSignalData {
fn handle(&mut self, signal: Signal, info: siginfo_t, context: &mut ucontext_t) {
unsafe {
let data = &mut SHUTDOWN_SIGHANDLER_DATA;
if !data.shutdown_handler.is_null() {
let func: ShutdownFuncPtr = std::mem::transmute(data.shutdown_handler);
(func)(signal, info, context, data);
}
}
}
fn signals(&self) -> Vec<Signal> {
vec![Signal::SigTerm, Signal::SigInterrupt, Signal::SigQuit]
}
}
/// A per-fuzzer unique `ID`, usually starting with `0` and increasing
/// by `1` in multiprocessed `EventManager`s, such as [`self::llmp::LlmpEventManager`].
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]

View File

@ -5,10 +5,6 @@ use alloc::{
string::{String, ToString},
vec::Vec,
};
#[cfg(all(unix, feature = "std"))]
use core::ffi::c_void;
#[cfg(all(unix, feature = "std"))]
use core::ptr::write_volatile;
#[cfg(feature = "std")]
use core::sync::atomic::{compiler_fence, Ordering};
use core::{fmt::Debug, marker::PhantomData};
@ -21,11 +17,6 @@ use super::{CustomBufEventResult, CustomBufHandlerFn, HasCustomBufHandlers, Prog
use crate::bolts::os::startable_self;
#[cfg(all(feature = "std", feature = "fork", unix))]
use crate::bolts::os::{fork, ForkResult};
#[cfg(all(unix, feature = "std"))]
use crate::{
bolts::os::unix_signals::setup_signal_handler,
events::{shutdown_handler, SHUTDOWN_SIGHANDLER_DATA},
};
#[cfg(feature = "std")]
use crate::{
bolts::{shmem::ShMemProvider, staterestore::StateRestorer},
@ -292,7 +283,8 @@ where
Ok(())
} else {
Err(Error::unknown(format!(
"Received illegal message that message should not have arrived: {event:?}."
"Received illegal message that message should not have arrived: {:?}.",
event
)))
}
}
@ -444,36 +436,11 @@ where
// We start ourself as child process to actually fuzz
let mut staterestorer = if std::env::var(_ENV_FUZZER_SENDER).is_err() {
// First, create a place to store state in, for restarts.
#[cfg(unix)]
let mut staterestorer: StateRestorer<SP> =
StateRestorer::new(shmem_provider.new_shmem(256 * 1024 * 1024)?);
#[cfg(not(unix))]
let staterestorer: StateRestorer<SP> =
StateRestorer::new(shmem_provider.new_shmem(256 * 1024 * 1024)?);
//let staterestorer = { LlmpSender::new(shmem_provider.clone(), 0, false)? };
staterestorer.write_to_env(_ENV_FUZZER_SENDER)?;
#[cfg(unix)]
unsafe {
let data = &mut SHUTDOWN_SIGHANDLER_DATA;
// Write the pointer to staterestorer so we can release its shmem later
write_volatile(
&mut data.staterestorer_ptr,
&mut staterestorer as *mut _ as *mut c_void,
);
data.allocator_pid = std::process::id() as usize;
data.shutdown_handler = shutdown_handler::<SP> as *const c_void;
}
// We setup signal handlers to clean up shmem segments used by state restorer
#[cfg(unix)]
if let Err(_e) = unsafe { setup_signal_handler(&mut SHUTDOWN_SIGHANDLER_DATA) } {
// We can live without a proper ctrl+c signal handler. Print and ignore.
#[cfg(feature = "std")]
println!("Failed to setup signal handlers: {_e}");
}
let mut ctr: u64 = 0;
// Client->parent loop
loop {

View File

@ -152,7 +152,7 @@ impl CommandConfigurator for StdCommandConfigurator {
/// Construct a `CommandExecutor` by implementing [`CommandConfigurator`] for a type of your choice and calling [`CommandConfigurator::into_executor`] on it.
/// Instead, you can use [`CommandExecutor::builder()`] to construct a [`CommandExecutor`] backed by a [`StdCommandConfigurator`].
pub struct CommandExecutor<EM, OT, S, T, Z> {
/// The wrapped command configurer
/// The wrapped comand configurer
configurer: T,
/// The observers used by this executor
observers: OT,
@ -526,7 +526,7 @@ impl CommandExecutorBuilder {
self
}
/// Builds the `CommandExecutor`
/// Builds the `ComandExecutor`
pub fn build<EM, OT, S, Z>(
&self,
observers: OT,
@ -537,7 +537,7 @@ impl CommandExecutorBuilder {
{
let Some(program) = &self.program else {
return Err(Error::illegal_argument(
"CommandExecutor::builder: no program set!",
"ComandExecutor::builder: no program set!",
));
};

View File

@ -249,7 +249,8 @@ impl Forkserver {
Ok(_) => (),
Err(err) => {
return Err(Error::illegal_state(format!(
"Could not spawn the forkserver: {err:#?}"
"Could not spawn the forkserver: {:#?}",
err
)))
}
};

View File

@ -188,7 +188,6 @@ where
{
let handlers = InProcessHandlers::new::<Self, EM, OF, Z, H>()?;
#[cfg(windows)]
// Some initialization necessary for windows.
unsafe {
/*
See https://github.com/AFLplusplus/LibAFL/pull/403
@ -845,7 +844,8 @@ mod unix_signal_handler {
let si_addr = { _info.si_addr() as usize };
eprintln!(
"We crashed at addr 0x{si_addr:x}, but are not in the target... Bug in the fuzzer? Exiting."
"We crashed at addr 0x{:x}, but are not in the target... Bug in the fuzzer? Exiting.",
si_addr
);
#[cfg(all(feature = "std", unix))]
@ -873,149 +873,6 @@ mod unix_signal_handler {
}
}
/// Same as `inproc_crash_handler`, but this is called when address sanitizer exits, not from the exception handler
#[cfg(all(windows, feature = "std"))]
pub mod windows_asan_handler {
use alloc::string::String;
use core::{
ptr,
sync::atomic::{compiler_fence, Ordering},
};
#[cfg(feature = "std")]
use std::io::{stdout, Write};
use windows::Win32::System::Threading::{
EnterCriticalSection, LeaveCriticalSection, RTL_CRITICAL_SECTION,
};
use crate::{
corpus::{Corpus, Testcase},
events::{Event, EventFirer, EventRestarter},
executors::{inprocess::GLOBAL_STATE, Executor, ExitKind, HasObservers},
feedbacks::Feedback,
fuzzer::HasObjective,
inputs::UsesInput,
observers::ObserversTuple,
state::{HasClientPerfMonitor, HasMetadata, HasSolutions},
};
/// # Safety
/// ASAN deatch handler
pub unsafe extern "C" fn asan_death_handler<E, EM, OF, Z>()
where
E: Executor<EM, Z> + HasObservers,
EM: EventFirer<State = E::State> + EventRestarter<State = E::State>,
OF: Feedback<E::State>,
E::State: HasSolutions + HasClientPerfMonitor,
Z: HasObjective<Objective = OF, State = E::State>,
{
let mut data = &mut GLOBAL_STATE;
// Have we set a timer_before?
if !(data.tp_timer as *mut windows::Win32::System::Threading::TP_TIMER).is_null() {
/*
We want to prevent the timeout handler being run while the main thread is executing the crash handler
Timeout handler runs if it has access to the critical section or data.in_target == 0
Writing 0 to the data.in_target makes the timeout handler makes the timeout handler invalid.
*/
compiler_fence(Ordering::SeqCst);
EnterCriticalSection(data.critical as *mut RTL_CRITICAL_SECTION);
compiler_fence(Ordering::SeqCst);
data.in_target = 0;
compiler_fence(Ordering::SeqCst);
LeaveCriticalSection(data.critical as *mut RTL_CRITICAL_SECTION);
compiler_fence(Ordering::SeqCst);
}
#[cfg(feature = "std")]
eprintln!("ASAN detected crash!");
if data.current_input_ptr.is_null() {
#[cfg(feature = "std")]
{
eprintln!("Double crash\n");
eprintln!(
"ASAN detected crash but we're not in the target... Bug in the fuzzer? Exiting.",
);
}
#[cfg(feature = "std")]
{
eprintln!("Type QUIT to restart the child");
let mut line = String::new();
while line.trim() != "QUIT" {
std::io::stdin().read_line(&mut line).unwrap();
}
}
// TODO tell the parent to not restart
} else {
let executor = data.executor_mut::<E>();
// reset timer
if !data.tp_timer.is_null() {
executor.post_run_reset();
data.tp_timer = ptr::null_mut();
}
let state = data.state_mut::<E::State>();
let fuzzer = data.fuzzer_mut::<Z>();
let event_mgr = data.event_mgr_mut::<EM>();
let observers = executor.observers_mut();
#[cfg(feature = "std")]
eprintln!("Child crashed!");
#[cfg(feature = "std")]
drop(stdout().flush());
// Make sure we don't crash in the crash handler forever.
let input = data.take_current_input::<<E::State as UsesInput>::Input>();
#[cfg(feature = "std")]
eprintln!("Child crashed!");
#[cfg(feature = "std")]
drop(stdout().flush());
observers
.post_exec_all(state, input, &ExitKind::Crash)
.expect("Observers post_exec_all failed");
let interesting = fuzzer
.objective_mut()
.is_interesting(state, event_mgr, input, observers, &ExitKind::Crash)
.expect("In crash handler objective failure.");
if interesting {
let new_input = input.clone();
let mut new_testcase = Testcase::new(new_input);
new_testcase.add_metadata(ExitKind::Crash);
fuzzer
.objective_mut()
.append_metadata(state, &mut new_testcase)
.expect("Failed adding metadata");
state
.solutions_mut()
.add(new_testcase)
.expect("In crash handler solutions failure.");
event_mgr
.fire(
state,
Event::Objective {
objective_size: state.solutions().count(),
},
)
.expect("Could not send crashing input");
}
event_mgr.on_restart(state).unwrap();
#[cfg(feature = "std")]
eprintln!("Waiting for broker...");
event_mgr.await_restart_safe();
#[cfg(feature = "std")]
eprintln!("Bye!");
}
// Don't need to exit, Asan will exit for us
// ExitProcess(1);
}
}
#[cfg(all(windows, feature = "std"))]
mod windows_exception_handler {
#[cfg(feature = "std")]
@ -1033,9 +890,7 @@ mod windows_exception_handler {
panic,
};
use windows::Win32::System::Threading::{
EnterCriticalSection, ExitProcess, LeaveCriticalSection, RTL_CRITICAL_SECTION,
};
use windows::Win32::System::Threading::ExitProcess;
use crate::{
bolts::os::windows_exceptions::{
@ -1049,7 +904,6 @@ mod windows_exception_handler {
},
feedbacks::Feedback,
fuzzer::HasObjective,
inputs::UsesInput,
observers::ObserversTuple,
state::{HasClientPerfMonitor, HasMetadata, HasSolutions},
};
@ -1081,6 +935,12 @@ mod windows_exception_handler {
}
}
use windows::Win32::System::Threading::{
EnterCriticalSection, LeaveCriticalSection, RTL_CRITICAL_SECTION,
};
use crate::inputs::UsesInput;
/// invokes the `post_exec` hook on all observer in case of panic
#[cfg(feature = "std")]
pub fn setup_panic_hook<E, EM, OF, Z>()
@ -1170,7 +1030,6 @@ mod windows_exception_handler {
}));
}
/// Timeout handler for windows
pub unsafe extern "system" fn inproc_timeout_handler<E, EM, OF, Z>(
_p0: *mut u8,
global_state: *mut c_void,
@ -1294,10 +1153,10 @@ mod windows_exception_handler {
compiler_fence(Ordering::SeqCst);
}
#[cfg(feature = "std")]
if let Some(exception_pointers) = exception_pointers.as_mut() {
let code = ExceptionCode::try_from(
exception_pointers
.as_mut()
.unwrap()
.ExceptionRecord
.as_mut()
.unwrap()
@ -1305,11 +1164,9 @@ mod windows_exception_handler {
.0,
)
.unwrap();
eprintln!("Crashed with {}", code);
} else {
eprintln!("Crashed without exception (probably due to SIGABRT)");
};
#[cfg(feature = "std")]
eprintln!("Crashed with {}", code);
if data.current_input_ptr.is_null() {
#[cfg(feature = "std")]
{

View File

@ -75,7 +75,8 @@ where
let o2_name = o2.name().to_string();
if o1_name == o2_name {
Err(Error::illegal_argument(format!(
"DiffFeedback: observer names must be different (both were {o1_name})"
"DiffFeedback: observer names must be different (both were {})",
o1_name
)))
} else {
Ok(Self {

View File

@ -963,7 +963,6 @@ where
T: Debug + Serialize + serde::de::DeserializeOwned,
{
name: String,
last_addr: usize,
phantom: PhantomData<T>,
}
@ -1013,7 +1012,6 @@ where
pub fn new(name: &'static str) -> Self {
Self {
name: name.to_string(),
last_addr: 0,
phantom: PhantomData,
}
}
@ -1023,7 +1021,6 @@ where
pub fn new_with_observer(observer: &ListObserver<T>) -> Self {
Self {
name: observer.name().to_string(),
last_addr: 0,
phantom: PhantomData,
}
}

View File

@ -190,6 +190,44 @@ where
}
}
// /// Fuzz until the first solution.
// fn fuzz_for_solution(
// &mut self,
// stages: &mut ST,
// executor: &mut E,
// state: &mut EM::State,
// manager: &mut EM,
// ) -> Result<usize, Error> {
// let mut last = current_time();
// let monitor_timeout = STATS_TIMEOUT_DEFAULT;
// let mut done = false;
// while !done {
// self.fuzz_one(stages, executor, state, manager)?;
// last = manager.maybe_report_progress(state, last, monitor_timeout)?;
// done = state.solutions().count() > 0;
// }
// return Ok(0);
// }
// /// Fuzz until solution with limit
// fn fuzz_for_solution_or_n(
// &mut self,
// stages: &mut ST,
// executor: &mut E,
// state: &mut EM::State,
// manager: &mut EM,
// iters: u64,
// ) -> Result<usize, Error> {
// let mut last = current_time();
// let monitor_timeout = STATS_TIMEOUT_DEFAULT;
// for _ in 0..iters {
// self.fuzz_one(stages, executor, state, manager)?;
// last = manager.maybe_report_progress(state, last, monitor_timeout)?;
// if state.solutions().count() > 0 {break;}
// }
// return Ok(0);
// }
/// Fuzz for n iterations.
/// Returns the index of the last fuzzed corpus item.
/// (Note: An iteration represents a complete run of every stage.
@ -229,82 +267,6 @@ where
Ok(ret)
}
/// 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<usize, Error> {
if time==Duration::ZERO {
return Err(Error::illegal_argument(
"Cannot fuzz for 0 duration!".to_string(),
));
}
let mut ret = 0;
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 = self.fuzz_one(stages, executor, state, manager)?;
last = manager.maybe_report_progress(state, last, 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)
}
/// 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<usize, Error> {
let mut ret = 0;
let mut last = current_time();
let monitor_timeout = STATS_TIMEOUT_DEFAULT;
while std::time::Instant::now() < time {
ret = self.fuzz_one(stages, executor, state, manager)?;
last = manager.maybe_report_progress(state, last, 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)
}
}
/// The corpus this input should be added to

View File

@ -832,7 +832,8 @@ impl core::fmt::Display for ClientPerfMonitor {
// Create the formatted string
writeln!(
f,
" {scheduler_percent:6.4}: Scheduler\n {manager_percent:6.4}: Manager"
" {:6.4}: Scheduler\n {:6.4}: Manager",
scheduler_percent, manager_percent
)?;
// Calculate each stage

View File

@ -113,7 +113,8 @@ impl Tokens {
}
if token_stop < token_start {
return Err(Error::illegal_argument(format!(
"Tried to create tokens from illegal section: stop < start ({token_stop:?} < {token_start:?})"
"Tried to create tokens from illegal section: stop < start ({:?} < {:?})",
token_stop, token_start
)));
}
let section_size: usize = token_stop.offset_from(token_start).try_into().unwrap();
@ -184,7 +185,8 @@ impl Tokens {
Ok(val) => val,
Err(_) => {
return Err(Error::illegal_argument(format!(
"Illegal line (hex decoding): {line}"
"Illegal line (hex decoding): {}",
line
)))
}
};

View File

@ -108,6 +108,14 @@ pub trait MapObserver: HasLen + Named + Serialize + serde::de::DeserializeOwned
/// Get the initial value for reset()
fn initial(&self) -> Self::Entry;
/// Get the initial value for reset() (mutable)
fn initial_mut(&mut self) -> &mut Self::Entry;
/// Set the initial value for reset()
fn set_initial(&mut self, initial: Self::Entry) {
*self.initial_mut() = initial;
}
/// Reset the map
fn reset_map(&mut self) -> Result<(), Error>;
@ -381,6 +389,11 @@ where
self.initial
}
#[inline]
fn initial_mut(&mut self) -> &mut T {
&mut self.initial
}
fn to_vec(&self) -> Vec<T> {
self.as_slice().to_vec()
}
@ -763,6 +776,11 @@ where
self.initial
}
#[inline]
fn initial_mut(&mut self) -> &mut T {
&mut self.initial
}
#[inline]
fn get(&self, idx: usize) -> &T {
&self.as_slice()[idx]
@ -1032,6 +1050,11 @@ where
self.initial
}
#[inline]
fn initial_mut(&mut self) -> &mut T {
&mut self.initial
}
#[inline]
fn usable_count(&self) -> usize {
*self.size.as_ref()
@ -1238,6 +1261,11 @@ where
self.base.initial()
}
#[inline]
fn initial_mut(&mut self) -> &mut u8 {
self.base.initial_mut()
}
#[inline]
fn usable_count(&self) -> usize {
self.base.usable_count()
@ -1461,6 +1489,11 @@ where
self.base.initial()
}
#[inline]
fn initial_mut(&mut self) -> &mut u8 {
self.base.initial_mut()
}
#[inline]
fn usable_count(&self) -> usize {
self.base.usable_count()
@ -1701,6 +1734,11 @@ where
self.initial
}
#[inline]
fn initial_mut(&mut self) -> &mut T {
&mut self.initial
}
fn count_bytes(&self) -> u64 {
let initial = self.initial();
let mut res = 0;
@ -2052,6 +2090,16 @@ where
self.initial
}
#[inline]
fn initial_mut(&mut self) -> &mut T {
&mut self.initial
}
#[inline]
fn set_initial(&mut self, initial: T) {
self.initial = initial;
}
/// Reset the map
#[inline]
fn reset_map(&mut self) -> Result<(), Error> {
@ -2430,6 +2478,17 @@ pub mod pybind {
mapob_unwrap_me!($wrapper_name, self.wrapper, m, { m.initial() })
}
#[inline]
fn initial_mut(&mut self) -> &mut $datatype {
let ptr = mapob_unwrap_me_mut!($wrapper_name, self.wrapper, m, { m.initial_mut() as *mut $datatype });
unsafe { ptr.as_mut().unwrap() }
}
#[inline]
fn set_initial(&mut self, initial: $datatype) {
mapob_unwrap_me_mut!($wrapper_name, self.wrapper, m, { m.set_initial(initial) });
}
#[inline]
fn reset_map(&mut self) -> Result<(), Error> {
mapob_unwrap_me_mut!($wrapper_name, self.wrapper, m, { m.reset_map() })

View File

@ -18,12 +18,12 @@ pub use stacktrace::*;
pub mod concolic;
pub mod value;
// Rust is breaking this with 'error: intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `type_id`' and so we disable this component for the moment
//#[cfg(unstable_feature)]
//pub mod owned;
//#[cfg(unstable_feature)]
//pub use owned::*;
use alloc::{
string::{String, ToString},
vec::Vec,
@ -31,7 +31,6 @@ use alloc::{
use core::{fmt::Debug, time::Duration};
use serde::{Deserialize, Serialize};
pub use value::*;
use crate::{
bolts::{

View File

@ -1,90 +0,0 @@
//! A simple observer with a single value.
use alloc::{
boxed::Box,
string::{String, ToString},
};
use core::fmt::Debug;
use serde::{Deserialize, Serialize};
use super::Observer;
use crate::{
bolts::{ownedref::OwnedRef, tuples::Named},
inputs::UsesInput,
Error,
};
/// A simple observer with a single value.
///
/// The intent is that the value is something with interior mutability (e.g., a
/// `RefCell`), which the target could write to even though this observer has a
/// reference to it.
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "T: serde::de::DeserializeOwned")]
pub struct ValueObserver<'a, T>
where
T: Debug + Serialize,
{
/// The name of this observer.
name: String,
/// The value.
pub value: OwnedRef<'a, T>,
}
impl<'a, T> ValueObserver<'a, T>
where
T: Debug + Serialize + serde::de::DeserializeOwned,
{
/// Creates a new [`ValueObserver`] with the given name.
#[must_use]
pub fn new(name: &'static str, value: &'a T) -> Self {
Self {
name: name.to_string(),
value: OwnedRef::Ref(value),
}
}
/// Get a reference to the underlying value.
#[must_use]
pub fn get_ref(&self) -> &T {
self.value.as_ref()
}
/// Set the value.
pub fn set(&mut self, new_value: T) {
self.value = OwnedRef::Owned(Box::new(new_value));
}
/// Clone or move the current value out of this object.
#[must_use]
pub fn take(self) -> T
where
T: Clone,
{
match self.value {
OwnedRef::Ref(r) => r.clone(),
OwnedRef::Owned(v) => *v,
}
}
}
/// This *does not* reset the value inside the observer.
impl<'a, S, T> Observer<S> for ValueObserver<'a, T>
where
S: UsesInput,
T: Debug + Serialize + serde::de::DeserializeOwned,
{
fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
Ok(())
}
}
impl<'a, T> Named for ValueObserver<'a, T>
where
T: Debug + Serialize + serde::de::DeserializeOwned,
{
fn name(&self) -> &str {
&self.name
}
}

View File

@ -210,7 +210,8 @@ where
let must_remove = {
let old_meta = old.metadata_mut().get_mut::<AccountingIndexesMetadata>().ok_or_else(|| {
Error::key_not_found(format!(
"AccountingIndexesMetadata, needed by CoverageAccountingScheduler, not found in testcase #{old_idx}"
"AccountingIndexesMetadata, needed by CoverageAccountingScheduler, not found in testcase #{}",
old_idx
))
})?;
*old_meta.refcnt_mut() -= 1;

View File

@ -209,7 +209,8 @@ where
let factor = F::compute(&mut *entry, state)?;
let meta = entry.metadata_mut().get_mut::<M>().ok_or_else(|| {
Error::key_not_found(format!(
"Metadata needed for MinimizerScheduler not found in testcase #{idx}"
"Metadata needed for MinimizerScheduler not found in testcase #{}",
idx
))
})?;
for elem in meta.as_slice() {
@ -228,7 +229,8 @@ where
let must_remove = {
let old_meta = old.metadata_mut().get_mut::<M>().ok_or_else(|| {
Error::key_not_found(format!(
"Metadata needed for MinimizerScheduler not found in testcase #{old_idx}"
"Metadata needed for MinimizerScheduler not found in testcase #{}",
old_idx
))
})?;
*old_meta.refcnt_mut() -= 1;
@ -281,7 +283,8 @@ where
let mut entry = state.corpus().get(*idx)?.borrow_mut();
let meta = entry.metadata().get::<M>().ok_or_else(|| {
Error::key_not_found(format!(
"Metadata needed for MinimizerScheduler not found in testcase #{idx}"
"Metadata needed for MinimizerScheduler not found in testcase #{}",
idx
))
})?;
for elem in meta.as_slice() {

View File

@ -133,7 +133,7 @@ impl SchedulerMetadata {
/// The power schedule to use
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum PowerSchedule {
/// The `explore` power schedule
/// The `explore" power schedule
EXPLORE,
/// The `exploit` power schedule
EXPLOIT,

View File

@ -127,7 +127,8 @@ where
let original = input.clone();
let meta = entry.metadata().get::<MapNoveltiesMetadata>().ok_or_else(|| {
Error::key_not_found(format!(
"MapNoveltiesMetadata needed for GeneralizationStage not found in testcase #{corpus_idx} (check the arguments of MapFeedback::new(...))"
"MapNoveltiesMetadata needed for GeneralizationStage not found in testcase #{} (check the arguments of MapFeedback::new(...))",
corpus_idx
))
})?;
(payload, original, meta.as_slice().to_vec())

View File

@ -1,7 +1,7 @@
//! While normal stages call the executor over and over again, push stages turn this concept upside down:
//! A push stage instead returns an iterator that generates a new result for each time it gets called.
//! With the new testcase, you will have to take care about testcase execution, manually.
//! The push stage relies on internal mutability of the supplied `Observers`.
//! The push stage relies on internal muttability of the supplied `Observers`.
//!
/// Mutational stage is the normal fuzzing stage.

View File

@ -46,7 +46,8 @@ fn find_llvm_config_brew() -> Result<PathBuf, String> {
match glob_results.last() {
Some(path) => Ok(path.unwrap()),
None => Err(format!(
"No llvm-config found in brew cellar with pattern {cellar_glob}"
"No llvm-config found in brew cellar with pattern {}",
cellar_glob
)),
}
}

View File

@ -182,7 +182,8 @@ fn write_symcc_runtime_bindings_file(out_path: &Path, cpp_bindings: &bindgen::Bi
let function_name = &captures[1];
writeln!(
bindings_file,
"#[link_name=\"{SYMCC_RUNTIME_FUNCTION_NAME_PREFIX}{function_name}\"]"
"#[link_name=\"{}{}\"]",
SYMCC_RUNTIME_FUNCTION_NAME_PREFIX, function_name
)
.unwrap();
}

View File

@ -440,7 +440,8 @@ impl Allocator {
Some(metadata) => metadata.size,
None => {
panic!(
"Attempted to get_usable_size on a pointer ({ptr:?}) which was not allocated!"
"Attempted to get_usable_size on a pointer ({:?}) which was not allocated!",
ptr
);
}
}

View File

@ -103,13 +103,6 @@ pub const ASAN_SAVE_REGISTER_NAMES: [&str; ASAN_SAVE_REGISTER_COUNT] = [
#[cfg(target_arch = "aarch64")]
pub const ASAN_SAVE_REGISTER_COUNT: usize = 32;
#[cfg(target_arch = "aarch64")]
const ASAN_EH_FRAME_DWORD_COUNT: usize = 14;
#[cfg(target_arch = "aarch64")]
const ASAN_EH_FRAME_FDE_OFFSET: u32 = 20;
#[cfg(target_arch = "aarch64")]
const ASAN_EH_FRAME_FDE_ADDRESS_OFFSET: u32 = 28;
/// The frida address sanitizer runtime, providing address sanitization.
/// When executing in `ASAN`, each memory access will get checked, using frida stalker under the hood.
/// The runtime can report memory errors that occurred during execution,
@ -138,9 +131,6 @@ pub struct AsanRuntime {
module_map: Option<ModuleMap>,
suppressed_addresses: Vec<usize>,
shadow_check_func: Option<extern "C" fn(*const c_void, usize) -> bool>,
#[cfg(target_arch = "aarch64")]
eh_frame: [u32; ASAN_EH_FRAME_DWORD_COUNT],
}
impl Debug for AsanRuntime {
@ -314,9 +304,6 @@ impl AsanRuntime {
module_map: None,
suppressed_addresses: Vec::new(),
shadow_check_func: None,
#[cfg(target_arch = "aarch64")]
eh_frame: [0; ASAN_EH_FRAME_DWORD_COUNT],
}
}
@ -399,7 +386,8 @@ impl AsanRuntime {
self.allocator
.map_shadow_for_region(tls_start, tls_end, true);
println!(
"registering thread with stack {stack_start:x}:{stack_end:x} and tls {tls_start:x}:{tls_end:x}"
"registering thread with stack {:x}:{:x} and tls {:x}:{:x}",
stack_start, stack_end, tls_start, tls_end
);
}
@ -1546,8 +1534,7 @@ impl AsanRuntime {
}
#[cfg(target_arch = "aarch64")]
// identity_op appears to be a false positive in ubfx
#[allow(clippy::unused_self, clippy::identity_op, clippy::too_many_lines)]
#[allow(clippy::unused_self, clippy::identity_op)] // identity_op appears to be a false positive in ubfx
fn generate_shadow_check_function(&mut self) {
let shadow_bit = self.allocator.shadow_bit();
let mut ops = dynasmrt::VecAssembler::<dynasmrt::aarch64::Aarch64Relocation>::new(0);
@ -1654,34 +1641,18 @@ impl AsanRuntime {
);
let blob = ops.finalize().unwrap();
let mut map_flags = MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE;
// apple aarch64 requires MAP_JIT to allocates WX pages
if cfg!(all(target_vendor = "apple", target_arch = "aarch64")) {
map_flags |= MapFlags::MAP_JIT;
}
unsafe {
let mapping = mmap(
std::ptr::null_mut(),
0x1000,
ProtFlags::all(),
map_flags,
MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE,
-1,
0,
)
.unwrap();
// on apple aarch64, WX pages can't be both writable and executable at the same time.
// pthread_jit_write_protect_np flips them from executable (1) to writable (0)
#[cfg(all(target_vendor = "apple", target_arch = "aarch64"))]
libc::pthread_jit_write_protect_np(0);
blob.as_ptr()
.copy_to_nonoverlapping(mapping as *mut u8, blob.len());
#[cfg(all(target_vendor = "apple", target_arch = "aarch64"))]
libc::pthread_jit_write_protect_np(1);
self.shadow_check_func = Some(std::mem::transmute(mapping as *mut u8));
}
}
@ -1996,10 +1967,8 @@ impl AsanRuntime {
; stp x0, x1, [x28]
; adr x25, <report
; adr x15, >eh_frame_cie_addr
; ldr x15, [x15]
; add x0, x15, ASAN_EH_FRAME_FDE_OFFSET // eh_frame_fde
; add x27, x15, ASAN_EH_FRAME_FDE_ADDRESS_OFFSET // fde_address
; adr x0, >eh_frame_fde
; adr x27, >fde_address
; ldr w26, [x27]
; cmp w26, #0x0
; b.ne >skip_register
@ -2044,25 +2013,29 @@ impl AsanRuntime {
; .qword AsanRuntime::handle_trap as *mut c_void as i64
; register_frame_func:
; .qword __register_frame as *mut c_void as i64
; eh_frame_cie_addr:
; .qword addr_of_mut!(self.eh_frame) as i64
);
self.eh_frame = [
0x14, 0, 0x00527a01, 0x011e7c01, 0x001f0c1b, //
// eh_frame_fde
0x14, 0x18, //
// fde_address
0, // <-- address offset goes here
0x104,
; eh_frame_cie:
; .dword 0x14
; .dword 0x00
; .dword 0x00527a01
; .dword 0x011e7c01
; .dword 0x001f0c1b
; eh_frame_fde:
; .dword 0x14
; .dword 0x18
; fde_address:
; .dword 0x0 // <-- address offset goes here
; .dword 0x104
//advance_loc 12
//def_cfa r29 (x29) at offset 16
//offset r30 (x30) at cfa-8
//offset r29 (x29) at cfa-16
0x1d0c4c00, 0x9d029e10, 0x4, //
; .dword 0x1d0c4c00
; .dword 0x9d029e10u32 as i32
; .dword 0x04
// empty next FDE:
0, 0,
];
; .dword 0x0
; .dword 0x0
);
self.blob_report = Some(ops_report.finalize().unwrap().into_boxed_slice());
self.blob_check_mem_byte = Some(self.generate_shadow_check_blob(0));

View File

@ -312,7 +312,8 @@ impl AsanErrors {
| AsanError::BadFuncArgWrite((name, _pc, address, size, backtrace)) => {
writeln!(
output,
" in call to {name}, argument {address:#016x}, size: {size:#x}"
" in call to {}, argument {:#016x}, size: {:#x}",
name, address, size
)
.unwrap();
output.reset().unwrap();
@ -431,7 +432,12 @@ impl AsanErrors {
)
.unwrap();
} else {
writeln!(output, " at 0x{pc:x}, faulting address 0x{fault_address:x}").unwrap();
writeln!(
output,
" at 0x{:x}, faulting address 0x{:x}",
pc, fault_address
)
.unwrap();
}
output.reset().unwrap();

View File

@ -1,12 +1,5 @@
//! Functionality regarding binary-only coverage collection.
use core::ptr::addr_of_mut;
use std::{
cell::{Ref, RefCell},
marker::PhantomPinned,
ops::Deref,
pin::Pin,
rc::Rc,
};
use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi};
#[cfg(target_arch = "x86_64")]
@ -22,19 +15,15 @@ use crate::helper::FridaRuntime;
/// (Default) map size for frida coverage reporting
pub const MAP_SIZE: usize = 64 * 1024;
/// Frida binary-only coverage
#[derive(Debug)]
struct CoverageRuntimeInner {
pub struct CoverageRuntime {
map: [u8; MAP_SIZE],
previous_pc: u64,
current_log_impl: u64,
blob_maybe_log: Option<Box<[u8]>>,
_pinned: PhantomPinned,
}
/// Frida binary-only coverage
#[derive(Debug)]
pub struct CoverageRuntime(Pin<Rc<RefCell<CoverageRuntimeInner>>>);
impl Default for CoverageRuntime {
fn default() -> Self {
Self::new()
@ -43,7 +32,6 @@ impl Default for CoverageRuntime {
impl FridaRuntime for CoverageRuntime {
/// Initialize the coverage runtime
/// The struct MUST NOT be moved after this function is called, as the generated assembly references it
fn init(
&mut self,
_gum: &frida_gum::Gum,
@ -72,24 +60,23 @@ impl CoverageRuntime {
/// Create a new coverage runtime
#[must_use]
pub fn new() -> Self {
Self(Rc::pin(RefCell::new(CoverageRuntimeInner {
Self {
map: [0_u8; MAP_SIZE],
previous_pc: 0,
current_log_impl: 0,
blob_maybe_log: None,
_pinned: PhantomPinned,
})))
}
}
/// Retrieve the coverage map pointer
pub fn map_ptr_mut(&mut self) -> *mut u8 {
self.0.borrow_mut().map.as_mut_ptr()
self.map.as_mut_ptr()
}
/// Retrieve the `maybe_log` code blob, that will write coverage into the map
#[must_use]
pub fn blob_maybe_log(&self) -> impl Deref<Target = Box<[u8]>> + '_ {
Ref::map(self.0.borrow(), |s| s.blob_maybe_log.as_ref().unwrap())
pub fn blob_maybe_log(&self) -> &[u8] {
self.blob_maybe_log.as_ref().unwrap()
}
/// A minimal `maybe_log` implementation. We insert this into the transformed instruction stream
@ -117,13 +104,12 @@ impl CoverageRuntime {
; ldp x1, x2, [sp], #0x10
; ret
;map_addr:
;.qword addr_of_mut!(self.0.borrow_mut().map) as i64
;.qword addr_of_mut!(self.map) as i64
;previous_loc:
;.qword 0
);
let ops_vec = ops.finalize().unwrap();
self.0.borrow_mut().blob_maybe_log =
Some(ops_vec[..ops_vec.len() - 8].to_vec().into_boxed_slice());
self.blob_maybe_log = Some(ops_vec[..ops_vec.len() - 8].to_vec().into_boxed_slice());
}
/// A minimal `maybe_log` implementation. We insert this into the transformed instruction stream
@ -154,13 +140,12 @@ impl CoverageRuntime {
; popfq
; ret
;map_addr:
;.qword addr_of_mut!(self.0.borrow_mut().map) as i64
;.qword addr_of_mut!(self.map) as i64
;previous_loc:
;.qword 0
);
let ops_vec = ops.finalize().unwrap();
self.0.borrow_mut().blob_maybe_log =
Some(ops_vec[..ops_vec.len() - 8].to_vec().into_boxed_slice());
self.blob_maybe_log = Some(ops_vec[..ops_vec.len() - 8].to_vec().into_boxed_slice());
}
/// Emits coverage mapping into the current basic block.
@ -171,10 +156,9 @@ impl CoverageRuntime {
let writer = output.writer();
#[allow(clippy::cast_possible_wrap)] // gum redzone size is u32, we need an offset as i32.
let redzone_size = i64::from(frida_gum_sys::GUM_RED_ZONE_SIZE);
if self.0.borrow().current_log_impl == 0
|| !writer.can_branch_directly_to(self.0.borrow().current_log_impl)
|| !writer
.can_branch_directly_between(writer.pc() + 128, self.0.borrow().current_log_impl)
if self.current_log_impl == 0
|| !writer.can_branch_directly_to(self.current_log_impl)
|| !writer.can_branch_directly_between(writer.pc() + 128, self.current_log_impl)
{
let after_log_impl = writer.code_offset() + 1;
@ -183,9 +167,9 @@ impl CoverageRuntime {
#[cfg(target_arch = "aarch64")]
writer.put_b_label(after_log_impl);
self.0.borrow_mut().current_log_impl = writer.pc();
writer.put_bytes(&self.blob_maybe_log());
let prev_loc_pointer = addr_of_mut!(self.0.borrow_mut().previous_pc) as u64; // Get the pointer to self.previous_pc
self.current_log_impl = writer.pc();
writer.put_bytes(self.blob_maybe_log());
let prev_loc_pointer = addr_of_mut!(self.previous_pc) as u64; // Get the pointer to self.previous_pc
writer.put_bytes(&prev_loc_pointer.to_ne_bytes());
@ -196,7 +180,7 @@ impl CoverageRuntime {
writer.put_lea_reg_reg_offset(X86Register::Rsp, X86Register::Rsp, -(redzone_size));
writer.put_push_reg(X86Register::Rdi);
writer.put_mov_reg_address(X86Register::Rdi, h64 & (MAP_SIZE as u64 - 1));
writer.put_call_address(self.0.borrow().current_log_impl);
writer.put_call_address(self.current_log_impl);
writer.put_pop_reg(X86Register::Rdi);
writer.put_lea_reg_reg_offset(X86Register::Rsp, X86Register::Rsp, redzone_size);
}
@ -211,7 +195,7 @@ impl CoverageRuntime {
);
writer.put_ldr_reg_u64(Aarch64Register::X0, h64 & (MAP_SIZE as u64 - 1));
writer.put_bl_imm(self.0.borrow().current_log_impl);
writer.put_bl_imm(self.current_log_impl);
writer.put_ldp_reg_reg_reg_offset(
Aarch64Register::Lr,
Aarch64Register::X0,

View File

@ -31,7 +31,6 @@ where
S::Input: HasTargetBytes,
S: UsesInput,
OT: ObserversTuple<S>,
'a: 'b,
{
base: InProcessExecutor<'a, H, OT, S>,
/// Frida's dynamic rewriting engine
@ -39,7 +38,6 @@ where
/// User provided callback for instrumentation
helper: &'c mut FridaInstrumentationHelper<'b, RT>,
followed: bool,
gum: &'b Gum,
_phantom: PhantomData<&'b u8>,
}
@ -85,8 +83,8 @@ where
self.stalker.activate(NativePointer(core::ptr::null_mut()));
} else {
self.followed = true;
let transformer = self.helper.transformer(self.gum);
self.stalker.follow_me::<NoneEventSink>(&transformer, None);
self.stalker
.follow_me::<NoneEventSink>(self.helper.transformer(), None);
}
}
let res = self.base.run_target(fuzzer, state, mgr, input);
@ -187,7 +185,6 @@ where
base,
stalker,
helper,
gum,
followed: false,
_phantom: PhantomData,
}

View File

@ -115,6 +115,8 @@ where
/// An helper that feeds `FridaInProcessExecutor` with edge-coverage instrumentation
pub struct FridaInstrumentationHelper<'a, RT> {
/// Transformer that has to be passed to FridaInProcessExecutor
transformer: Option<Transformer<'a>>,
#[cfg(unix)]
capstone: Capstone,
ranges: RangeMap<usize, (u16, String)>,
@ -202,6 +204,7 @@ where
modules_to_instrument.iter().map(AsRef::as_ref).collect();
let mut helper = Self {
transformer: None,
#[cfg(target_arch = "aarch64")]
capstone: Capstone::new()
.arm64()
@ -242,12 +245,122 @@ where
}
}
// make sure we aren't in the instrumented list, as it would cause recursions
assert!(
!helper.ranges.contains_key(&(Self::new as usize)),
"instrumented libraries must not include the fuzzer"
);
let transformer = Transformer::from_callback(gum, |basic_block, output| {
let mut first = true;
for instruction in basic_block {
let instr = instruction.instr();
#[cfg(unix)]
let instr_size = instr.bytes().len();
let address = instr.address();
//println!("block @ {:x} transformed to {:x}", address, output.writer().pc());
//println!(
//"address: {:x} contains: {:?}",
//address,
//helper.ranges.contains_key(&(address as usize))
//);
// println!("Ranges: {:#?}", helper.ranges);
if helper.ranges.contains_key(&(address as usize)) {
if first {
first = false;
//println!("block @ {:x} transformed to {:x}", address, output.writer().pc());
if let Some(rt) = helper.runtime_mut::<CoverageRuntime>() {
rt.emit_coverage_mapping(address, &output);
}
#[cfg(unix)]
if let Some(rt) = helper.runtime_mut::<DrCovRuntime>() {
instruction.put_callout(|context| {
let real_address = rt.real_address_for_stalked(pc(&context));
//let (range, (id, name)) = helper.ranges.get_key_value(&real_address).unwrap();
//println!("{}:0x{:016x}", name, real_address - range.start);
rt.drcov_basic_blocks.push(DrCovBasicBlock::new(
real_address,
real_address + instr_size,
));
});
}
}
#[cfg(unix)]
let res = if let Some(_rt) = helper.runtime::<AsanRuntime>() {
AsanRuntime::asan_is_interesting_instruction(
&helper.capstone,
address,
instr,
)
} else {
None
};
#[cfg(all(target_arch = "x86_64", unix))]
if let Some((segment, width, basereg, indexreg, scale, disp)) = res {
if let Some(rt) = helper.runtime_mut::<AsanRuntime>() {
rt.emit_shadow_check(
address, &output, segment, width, basereg, indexreg, scale,
disp,
);
}
}
#[cfg(target_arch = "aarch64")]
if let Some((basereg, indexreg, displacement, width, shift, extender)) = res
{
if let Some(rt) = helper.runtime_mut::<AsanRuntime>() {
rt.emit_shadow_check(
address,
&output,
basereg,
indexreg,
displacement,
width,
shift,
extender,
);
}
}
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
if let Some(rt) = helper.runtime::<CmpLogRuntime>() {
if let Some((op1, op2, special_case)) =
CmpLogRuntime::cmplog_is_interesting_instruction(
&helper.capstone,
address,
instr,
)
{
//emit code that saves the relevant data in runtime(passes it to x0, x1)
rt.emit_comparison_handling(
address,
&output,
&op1,
&op2,
special_case,
);
}
}
#[cfg(unix)]
if let Some(rt) = helper.runtime_mut::<AsanRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
}
#[cfg(unix)]
if let Some(rt) = helper.runtime_mut::<DrCovRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
}
}
instruction.keep();
}
});
helper.transformer = Some(transformer);
helper
.runtimes
.init_all(gum, &helper.ranges, &modules_to_instrument);
@ -272,110 +385,8 @@ where
}
/// Returns ref to the Transformer
pub fn transformer(&mut self, gum: &'a Gum) -> Transformer<'a> {
Transformer::from_callback(gum, |basic_block, output| {
let mut first = true;
for instruction in basic_block {
let instr = instruction.instr();
#[cfg(unix)]
let instr_size = instr.bytes().len();
let address = instr.address();
//println!("block @ {:x} transformed to {:x}", address, output.writer().pc());
//println!(
//"address: {:x} contains: {:?}",
//address,
//self.ranges().contains_key(&(address as usize))
//);
// println!("Ranges: {:#?}", self.ranges());
if self.ranges().contains_key(&(address as usize)) {
if first {
first = false;
//println!("block @ {:x} transformed to {:x}", address, output.writer().pc());
if let Some(rt) = self.runtime_mut::<CoverageRuntime>() {
rt.emit_coverage_mapping(address, &output);
}
#[cfg(unix)]
if let Some(rt) = self.runtime_mut::<DrCovRuntime>() {
instruction.put_callout(|context| {
let real_address = rt.real_address_for_stalked(pc(&context));
//let (range, (id, name)) = helper.ranges.get_key_value(&real_address).unwrap();
//println!("{}:0x{:016x}", name, real_address - range.start);
rt.drcov_basic_blocks.push(DrCovBasicBlock::new(
real_address,
real_address + instr_size,
));
});
}
}
#[cfg(unix)]
let res = if let Some(_rt) = self.runtime::<AsanRuntime>() {
AsanRuntime::asan_is_interesting_instruction(&self.capstone, address, instr)
} else {
None
};
#[cfg(all(target_arch = "x86_64", unix))]
if let Some((segment, width, basereg, indexreg, scale, disp)) = res {
if let Some(rt) = self.runtime_mut::<AsanRuntime>() {
rt.emit_shadow_check(
address, &output, segment, width, basereg, indexreg, scale, disp,
);
}
}
#[cfg(target_arch = "aarch64")]
if let Some((basereg, indexreg, displacement, width, shift, extender)) = res {
if let Some(rt) = self.runtime_mut::<AsanRuntime>() {
rt.emit_shadow_check(
address,
&output,
basereg,
indexreg,
displacement,
width,
shift,
extender,
);
}
}
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
if let Some(rt) = self.runtime::<CmpLogRuntime>() {
if let Some((op1, op2, special_case)) =
CmpLogRuntime::cmplog_is_interesting_instruction(
&self.capstone,
address,
instr,
)
{
//emit code that saves the relevant data in runtime(passes it to x0, x1)
rt.emit_comparison_handling(address, &output, &op1, &op2, special_case);
}
}
#[cfg(unix)]
if let Some(rt) = self.runtime_mut::<AsanRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
}
#[cfg(unix)]
if let Some(rt) = self.runtime_mut::<DrCovRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
}
}
instruction.keep();
}
})
pub fn transformer(&self) -> &Transformer<'a> {
self.transformer.as_ref().unwrap()
}
/// Initializa all

View File

@ -9,16 +9,14 @@ echo "[*] Making sure all Nyx is checked out"
git status 1>/dev/null 2>/dev/null
if [ ! -e ./QEMU-Nyx/.git ]; then
rm -rf ./QEMU-Nyx
if [ ! -d ./QEMU-Nyx ]; then
git clone https://github.com/nyx-fuzz/QEMU-Nyx.git || exit 1
pushd QEMU-Nyx
git reset --hard 80f22f77d6aab14e62bf11c80db4e210bbca5fb5
popd
fi
if [ ! -e ./packer/.git ]; then
rm -rf ./packer
if [ ! -d ./packer ]; then
git clone https://github.com/syheliel/packer.git || exit 1
pushd QEMU-Nyx
git reset --hard 86b159bafc0b2ba8feeaa8761a45b6201d34084f

View File

@ -21,8 +21,6 @@ x86_64 = ["libafl_qemu_sys/x86_64"] # build qemu for x86_64 (default)
i386 = ["libafl_qemu_sys/i386"] # build qemu for i386
arm = ["libafl_qemu_sys/arm"] # build qemu for arm
aarch64 = ["libafl_qemu_sys/aarch64"] # build qemu for aarch64
mips = ["libafl_qemu_sys/mips"] # build qemu for mips (el, use with the 'be' feature of mips be)
be = ["libafl_qemu_sys/be"]
usermode = ["libafl_qemu_sys/usermode"]
@ -32,6 +30,8 @@ slirp = [ "systemmode", "libafl_qemu_sys/slirp" ] # build qemu with host libslir
clippy = [] # special feature for clippy, don't use in normal projects§
jmp_as_edge = [] # Add all jumps in app code to edges, circumvents bugs in the original instrumentation
[dependencies]
libafl = { path = "../libafl", version = "0.8.2", default-features = false, features = ["std", "derive", "llmp_compression"] }
libafl_targets = { path = "../libafl_targets", version = "0.8.2" }

View File

@ -26,8 +26,6 @@ pub fn build() {
"aarch64".to_string()
} else if cfg!(feature = "i386") {
"i386".to_string()
} else if cfg!(feature = "mips") {
"mips".to_string()
} else {
env::var("CPU_TARGET").unwrap_or_else(|_| {
"x86_64".to_string()

View File

@ -45,8 +45,6 @@ const WRAPPER_HEADER: &str = r#"
#include "sysemu/tcg.h"
#include "sysemu/replay.h"
#include "libafl_extras/syx-snapshot/syx-snapshot.h"
#endif
#include "exec/cpu-common.h"
@ -107,10 +105,6 @@ pub fn generate(
.allowlist_function("tlb_plugin_lookup")
.allowlist_function("qemu_plugin_hwaddr_phys_addr")
.allowlist_function("qemu_plugin_get_hwaddr")
.allowlist_function("syx_snapshot_init")
.allowlist_function("syx_snapshot_create")
.allowlist_function("syx_snapshot_root_restore")
.allowlist_function("syx_snapshot_dirty_list_add")
.blocklist_function("main_loop_wait") // bindgen issue #1313
.parse_callbacks(Box::new(bindgen::CargoCallbacks));

View File

@ -8,7 +8,7 @@ use which::which;
const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
const QEMU_REVISION: &str = "e5424c34d223c2b638af6e4c9eef039db8b69dd4";
const QEMU_REVISION: &str = "9707dd2d211221367915d5da21fe8693d6842eaf";
fn build_dep_check(tools: &[&str]) {
for tool in tools {
@ -39,10 +39,6 @@ pub fn build(
cpu_target += "eb";
}
if !is_big_endian && cpu_target == "mips" && !cfg!(feature = "clippy") {
cpu_target += "el";
}
let custum_qemu_dir = env::var_os("CUSTOM_QEMU_DIR").map(|x| x.to_string_lossy().to_string());
let custum_qemu_no_build = env::var("CUSTOM_QEMU_NO_BUILD").is_ok();
let custum_qemu_no_configure = env::var("CUSTOM_QEMU_NO_CONFIGURE").is_ok();
@ -73,7 +69,10 @@ pub fn build(
}
if !qemu_path.is_dir() {
println!("cargo:warning=Qemu not found, cloning with git ({QEMU_REVISION})...");
println!(
"cargo:warning=Qemu not found, cloning with git ({})...",
QEMU_REVISION
);
fs::create_dir_all(&qemu_path).unwrap();
Command::new("git")
.current_dir(&qemu_path)

Some files were not shown because too many files have changed in this diff Show More