diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..5bd1f81ef5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +## Pull Request Checklist + +Please make sure you've completed the following steps before submitting: + +- [ ] I have run `./script/fmt_all.sh` to format the code +- [ ] I have run `./script/clippy.sh` and fixed all errors/warnings \ No newline at end of file diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 1b79bb67f9..2b95630abd 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -237,7 +237,6 @@ jobs: needs: - fuzzers-preflight strategy: - fail-fast: false matrix: os: [ ubuntu-24.04 ] fuzzer: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8cc60e368..a7477a9636 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ For bugs, feel free to open issues or contact us directly. Thank you for your support. <3 -## Pull Request guideline +## Pull Request Guideline Even though we will gladly assist you in finishing up your PR, try to: @@ -14,7 +14,7 @@ Even though we will gladly assist you in finishing up your PR, try to: Some of the parts in this list may be hard, don't be afraid to open a PR if you cannot fix them by yourself, so we can help. -### Pre-commit hooks +### Pre-commit Hooks Some of these checks can be performed automatically during commit using [pre-commit](https://pre-commit.com/). Once the package is installed, simply run `pre-commit install` to enable the hooks, the checks will run automatically before the commit becomes effective. @@ -27,7 +27,10 @@ Before making your pull requests, try to see if your code follows these rules. - `PhantomData` should have the smallest set of types needed. Try not adding `PhantomData` to your struct unless it is really necessary. Also even when you really need `PhantomData`, try to keep the types `T` used in `PhantomData` as smallest as possible - Wherever possible, trait implementations with lifetime specifiers should use '_ lifetime elision. - Complex constructors should be replaced with `typed_builder`, or write code in the builder pattern for yourself. -- Remove generic restrictions at the definitions (e.g., we do not need to specify that types impl `Serialize`, `Deserialize`, or `Debug` anymore at the struct definitions). Therefore, try avoiding code like this unless the constraint is really necessary. + + +## Rules for Generics and Associated Types +1. Remove generic restrictions at the definitions (e.g., we do not need to specify that types impl `Serialize`, `Deserialize`, or `Debug` anymore at the struct definitions). Therefore, try avoiding code like this unless the constraint is really necessary. ```rust pub struct X where @@ -36,7 +39,7 @@ pub struct X fn ... } ``` -- Reduce generics to the least restrictive necessary. __Never overspecify the constraints__. There's no automated tool to check the useless constraints, so you have to verify this manually. +2. Reduce generics to the least restrictive necessary. __Never overspecify the constraints__. There's no automated tool to check the useless constraints, so you have to verify this manually. ```rust pub struct X where @@ -46,8 +49,7 @@ pub struct X } ``` -- Prefer generic to associated types in traits definition as much as possible. They are much easier to use around, and avoid tricky caveats / type repetition in the code. It is also much easier to have unconstrained struct definitions. - +3. Prefer generic to associated types in traits definition as much as possible. They are much easier to use around, and avoid tricky caveats / type repetition in the code. It is also much easier to have unconstrained struct definitions. Try not to write this: ```rust pub trait X @@ -65,7 +67,7 @@ pub trait X } ``` -- Traits which have an associated type (if you have made sure you cannot use a generic instead) should refer to the associated type, not the concrete/generic. In other words, you should only have the associated type when you can define a getter to it. For example, in the following code, you can define a associate type. +4. Traits which have an associated type (if you have made sure you cannot use a generic instead) should refer to the associated type, not the concrete/generic. In other words, you should only have the associated type when you can define a getter to it. For example, in the following code, you can define a associate type. ```rust pub trait X { @@ -74,17 +76,7 @@ pub trait X fn a(&self) -> Self::A; } ``` - -- __Ideally__ the types used in the arguments of methods in traits should have the same as the types defined on the traits. -```rust -pub trait X // <- this trait have 3 generics, A, B, and C -{ - fn do_stuff(&self, a: A, b: B, c: C); // <- this is good because it uses all A, B, and C. - - fn do_other_stuff(&self, a: A, b: B); // <- this is not ideal because it does not have C. -} -``` -- Generic naming should be consistent. Do NOT use multiple name for the same generic, it just makes things more confusing. Do: +5. Generic naming should be consistent. Do NOT use multiple name for the same generic, it just makes things more confusing. Do: ```rust pub struct X { phantom: PhanomData, @@ -100,7 +92,38 @@ pub struct X { impl X {} // <- Do NOT do that, use A instead of B ``` -- Always alphabetically order the type generics. Therefore, +6. __Ideally__ the types used in the arguments of methods in traits should have the same as the types defined on the traits. +```rust +pub trait X // <- this trait have 3 generics, A, B, and C +{ + fn do_stuff(&self, a: A, b: B, c: C); // <- this is good because it uses all A, B, and C. + + fn do_other_stuff(&self, a: A, b: B); // <- this is not ideal because it does not have C. +} +``` +7. Try to avoid cyclical dependency if possible. Sometimes it is necessary but try to avoid it. For example, The following code is a bad example. +```rust +pub struct X {} +pub struct Y {} + +pub trait Fuzzer: Sized { + fn fuzz(&self, em: &EM) + where + EM: EventManager + { + em.do_stuff(self); + } +} + +pub trait EventManager: Sized { + fn do_stuff(&self, fuzzer: &Z); // <- This function signature should not take fuzzer +} +``` +trait `EventManager` should not implement any method that takes fuzzer, any object that could implement `Fuzzer` trait. + + +## Formatting +1. Always alphabetically order the type generics. Therefore, ```rust pub struct X {}; // <- Generics are alphabetically ordered ``` @@ -108,7 +131,8 @@ But not, ```rust pub struct X {}; // <- Generics are not ordered ``` -- Similarly, generic bounds in `where` clauses should be alphabetically sorted. Prefer: +2. Similarly, generic bounds in `where` clauses should be alphabetically sorted. +Prefer: ```rust pub trait FooA {} pub trait FooB {} diff --git a/fuzzers/binary_only/qemu_launcher/Justfile b/fuzzers/binary_only/qemu_launcher/Justfile index 36288989d0..149d4690a2 100644 --- a/fuzzers/binary_only/qemu_launcher/Justfile +++ b/fuzzers/binary_only/qemu_launcher/Justfile @@ -55,7 +55,7 @@ test_inner: harness build ./tests/injection/test.sh || exit 1 # complie again with simple mgr - cargo build --profile={{PROFILE}} --features="simplemgr,{{ARCH}}" --target-dir={{ TARGET_DIR }} + cargo build --profile={{PROFILE}} --features="simplemgr,{{ARCH}}" --target-dir={{ TARGET_DIR }} || exit 1 ./tests/qasan/test.sh || exit 1 [unix] diff --git a/fuzzers/forkserver/libafl-fuzz/Justfile b/fuzzers/forkserver/libafl-fuzz/Justfile index d8cce7b03f..f4bcd11e2d 100644 --- a/fuzzers/forkserver/libafl-fuzz/Justfile +++ b/fuzzers/forkserver/libafl-fuzz/Justfile @@ -53,7 +53,7 @@ build_libafl_fuzz_fuzzbench: test_instr: build_afl build_libafl_fuzz #!/bin/bash - AFL_PATH={{AFL_DIR}} {{AFL_CC_PATH}} ./test/test-instr.c -o ./test/out-instr + AFL_PATH={{AFL_DIR}} {{AFL_CC_PATH}} -O0 ./test/test-instr.c -o ./test/out-instr export LIBAFL_DEBUG_OUTPUT=1 export AFL_CORES=0 diff --git a/fuzzers/forkserver/libafl-fuzz/src/main.rs b/fuzzers/forkserver/libafl-fuzz/src/main.rs index 9e6aa8a002..715a93fb37 100644 --- a/fuzzers/forkserver/libafl-fuzz/src/main.rs +++ b/fuzzers/forkserver/libafl-fuzz/src/main.rs @@ -256,14 +256,15 @@ struct Opt { #[arg(short = 't', default_value_t = 1000)] hang_timeout: u64, + #[arg(short = 'd')] + debug_child: bool, + // Environment Variables #[clap(skip)] bench_just_one: bool, #[clap(skip)] bench_until_crash: bool, - #[clap(skip)] - debug_child: bool, #[clap(skip)] is_persistent: bool, #[clap(skip)] diff --git a/fuzzers/forkserver/libafl-fuzz/src/stages/mutational_stage.rs b/fuzzers/forkserver/libafl-fuzz/src/stages/mutational_stage.rs index 7e91d65027..009dbe6a47 100644 --- a/fuzzers/forkserver/libafl-fuzz/src/stages/mutational_stage.rs +++ b/fuzzers/forkserver/libafl-fuzz/src/stages/mutational_stage.rs @@ -1,21 +1,21 @@ use std::{borrow::Cow, marker::PhantomData}; use libafl::{ - stages::{MutationalStage, Stage}, + stages::{MutationalStage, Restartable, Stage}, Error, }; use libafl_bolts::Named; #[derive(Debug)] -pub enum SupportedMutationalStages { +pub enum SupportedMutationalStages { StdMutational(SM, PhantomData

), PowerMutational(P, PhantomData), } -impl MutationalStage for SupportedMutationalStages +impl MutationalStage for SupportedMutationalStages where - SM: MutationalStage, P: MutationalStage, + SM: MutationalStage, { type Mutator = SM::Mutator; /// The mutator, added to this stage @@ -44,10 +44,10 @@ where } } -impl Named for SupportedMutationalStages +impl Named for SupportedMutationalStages where - SM: Named, P: Named, + SM: Named, { fn name(&self) -> &Cow<'static, str> { match self { @@ -57,10 +57,10 @@ where } } -impl Stage for SupportedMutationalStages +impl Stage for SupportedMutationalStages where - SM: Stage, P: Stage, + SM: Stage, { #[inline] fn perform( @@ -75,7 +75,13 @@ where Self::PowerMutational(p, _) => p.perform(fuzzer, executor, state, manager), } } +} +impl Restartable for SupportedMutationalStages +where + P: Restartable, + SM: Restartable, +{ fn should_restart(&mut self, state: &mut S) -> Result { match self { Self::StdMutational(m, _) => m.should_restart(state), diff --git a/fuzzers/inprocess/fuzzbench/Justfile b/fuzzers/inprocess/fuzzbench/Justfile index 4892a9d85f..6316f40605 100644 --- a/fuzzers/inprocess/fuzzbench/Justfile +++ b/fuzzers/inprocess/fuzzbench/Justfile @@ -40,12 +40,13 @@ fuzzer: [linux] [macos] -run: cxx fuzz_o +run: cxx fuzz_o fuzzer #!/bin/bash rm -rf libafl_unix_shmem_server || true mkdir in || true echo a > in/a ./{{FUZZER_NAME}} -o out -i in + RUST_LOG=info ./{{FUZZER_NAME}} -o out -i seed [windows] run: diff --git a/fuzzers/inprocess/fuzzbench_ctx/Justfile b/fuzzers/inprocess/fuzzbench_ctx/Justfile index aad1ca4816..a0bf7510da 100644 --- a/fuzzers/inprocess/fuzzbench_ctx/Justfile +++ b/fuzzers/inprocess/fuzzbench_ctx/Justfile @@ -40,7 +40,7 @@ fuzzer: [linux] [macos] -run: cxx fuzz_o +run: cxx fuzz_o fuzzer #!/bin/bash rm -rf libafl_unix_shmem_server || true mkdir in || true diff --git a/fuzzers/inprocess/libfuzzer_libpng/Justfile b/fuzzers/inprocess/libfuzzer_libpng/Justfile index 300ebd626a..10c6446ed0 100644 --- a/fuzzers/inprocess/libfuzzer_libpng/Justfile +++ b/fuzzers/inprocess/libfuzzer_libpng/Justfile @@ -1,117 +1,40 @@ -FUZZER_NAME := 'fuzzer_libpng' -PROJECT_DIR := absolute_path(".") -PROFILE := env("PROFILE", "release") -PROFILE_DIR := if PROFILE == "release" { "release" } else if PROFILE == "dev" { "debug" } else { "debug" } -CARGO_TARGET_DIR := env("CARGO_TARGET_DIR", "target") -FUZZER := PROJECT_DIR / CARGO_TARGET_DIR / PROFILE_DIR / FUZZER_NAME -LIBAFL_CC := PROJECT_DIR / CARGO_TARGET_DIR / PROFILE_DIR / "libafl_cc" -LIBAFL_CXX := PROJECT_DIR / CARGO_TARGET_DIR / PROFILE_DIR / "libafl_cxx" - - -alias cc := cxx - -[linux] -[macos] -libpng: - #!/bin/bash - if [ ! -f v1.6.37.tar.gz ]; then - wget https://github.com/glennrp/libpng/archive/refs/tags/v1.6.37.tar.gz - fi - tar -xvf v1.6.37.tar.gz - -[windows] -libpng: - echo "Unsupported on this platform" - -[linux] -[macos] -cxx: - cargo build --profile {{PROFILE}} - -[windows] -cxx: - echo "Unsupported on this platform" - -[linux] -[macos] -crash_cxx: - cargo build --profile {{PROFILE}} --features=crash - -[windows] -crash_cxx: - echo "Unsupported on this platform" - -[linux] -[macos] -lib: libpng cxx - #!/bin/bash - cd libpng-1.6.37 && ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes - cd {{PROJECT_DIR}} - make -C libpng-1.6.37 CC="{{LIBAFL_CC}}" CXX="{{LIBAFL_CXX}}" - -[windows] -lib: - echo "Unsupported on this platform" - -[linux] -[macos] -crash_lib: libpng crash_cxx - #!/bin/bash - cd libpng-1.6.37 && ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes - cd {{PROJECT_DIR}} - make -C libpng-1.6.37 CC="{{LIBAFL_CC}}" CXX="{{LIBAFL_CXX}}" - -[windows] -crash_lib: - echo "Unsupported on this platform" +import "../../../just/libafl-cc-libpng.just" -[linux] -[macos] -fuzzer: lib cxx - pwd - {{LIBAFL_CXX}} {{PROJECT_DIR}}/harness.cc {{PROJECT_DIR}}/libpng-1.6.37/.libs/libpng16.a -I {{PROJECT_DIR}}/libpng-1.6.37/ -o {{FUZZER_NAME}} -lm -lz - -[windows] -fuzzer: - echo "Unsupported on this platform" - -[linux] -[macos] -crash_fuzzer: crash_lib crash_cxx - {{LIBAFL_CXX}} {{PROJECT_DIR}}/harness.cc {{PROJECT_DIR}}/libpng-1.6.37/.libs/libpng16.a -I {{PROJECT_DIR}}/libpng-1.6.37/ -o {{FUZZER_NAME}} -lm -lz - -[windows] -crash_fuzzer: - echo "Unsupported on this platform" - - -[linux] -[macos] -run: fuzzer - #!/bin/bash - ./{{FUZZER_NAME}} & - sleep 0.2 - ./{{FUZZER_NAME}} 2>/dev/null - -[windows] -run: fuzzer - echo "Unsupported on this platform" +FUZZER_NAME := 'fuzzer_libpng' -[linux] -[macos] -crash: crash_fuzzer +[unix] +cc feat: + cargo build --profile {{PROFILE}} --features="{{feat}}" + +[unix] +cxx feat: + cargo build --profile {{PROFILE}} --features="{{feat}}" + +[unix] +lib feat: (libpng feat) (cxx feat) + +# Feat is either nothing or "crash" +[unix] +fuzzer feat="": (lib feat) (cxx feat) + {{LIBAFL_CXX}} {{PROJECT_DIR}}/harness.cc \ + "{{LIBPNG_BUILD}}/.libs/libpng16.a" \ + "{{ZLIB_BUILD}}/libz.a" \ + -I"{{LIBPNG_INCLUDE}}" \ + -I"{{LIBPNG_BUILD}}" \ + -I"{{ZLIB_INCLUDE}}" \ + -L"{{ZLIB_LIB}}" \ + -o {{FUZZER_NAME}} \ + -lm -lz + +# Feat is either nothing or "crash" +[unix] +run feat="": (fuzzer feat) #!/bin/bash ./{{FUZZER_NAME}} & sleep 0.2 ./{{FUZZER_NAME}} 2>/dev/null -[windows] -crash: fuzzer - echo "Unsupported on this platform" - - -[linux] -[macos] +[unix] test: fuzzer #!/bin/bash rm -rf libafl_unix_shmem_server || true @@ -125,12 +48,7 @@ test: fuzzer exit 1 fi -[windows] -test: fuzzer - echo "Unsupported on this platform" - clean: rm -rf {{FUZZER_NAME}} make -C libpng-1.6.37 clean || true - cargo clean - + cargo clean \ No newline at end of file diff --git a/just/libafl-cc-libpng.just b/just/libafl-cc-libpng.just new file mode 100644 index 0000000000..da253ef241 --- /dev/null +++ b/just/libafl-cc-libpng.just @@ -0,0 +1,60 @@ +import "libafl-cc.just" + +OPTIMIZATIONS := env("OPTIMIZATIONS", "yes") + +LIBPNG_ROOT := DEPS_DIR / "libpng-1.6.37" +LIBPNG_BUILD := TARGET_DIR / "build-png" +LIBPNG_INCLUDE := LIBPNG_ROOT + +ZLIB_ROOT := DEPS_DIR / "zlib-1.2.13" +ZLIB_BUILD := TARGET_DIR / "build-zlib" +ZLIB_INCLUDE := ZLIB_BUILD / "zlib" / "include" +ZLIB_LIB := ZLIB_BUILD / "zlib" / "lib" + +DEPS_DIR := TARGET_DIR / "deps" + +[unix] +target_dir: + mkdir -p {{ TARGET_DIR }} + +[unix] +deps_dir: + mkdir -p {{ DEPS_DIR }} + +[unix] +zlib_wget: deps_dir + wget -O "{{ DEPS_DIR }}/zlib-1.2.13.tar.gz" https://zlib.net/fossils/zlib-1.2.13.tar.gz + + tar zxvf {{ DEPS_DIR }}/zlib-1.2.13.tar.gz -C {{ DEPS_DIR }} + +[unix] +zlib feat: zlib_wget (cc feat) + rm -rf {{ ZLIB_BUILD }} + mkdir {{ ZLIB_BUILD }} + + cd {{ ZLIB_BUILD }} && CC={{ LIBAFL_CC }} {{ ZLIB_ROOT }}/configure --prefix=./zlib + + make -j -C {{ ZLIB_BUILD }} install + +[unix] +libpng_wget: deps_dir + wget -O "{{ DEPS_DIR }}/v1.6.37.tar.gz" https://github.com/glennrp/libpng/archive/refs/tags/v1.6.37.tar.gz + + tar -xvf "{{ DEPS_DIR }}/v1.6.37.tar.gz" -C {{ DEPS_DIR }} + + rm -rf {{ LIBPNG_BUILD }} + mkdir {{ LIBPNG_BUILD }} + +[unix] +libpng feat: (zlib feat) libpng_wget (cc feat) + cd {{ LIBPNG_BUILD }}/ && \ + CC="{{LIBAFL_CC}}" \ + CFLAGS="-I{{ ZLIB_INCLUDE }}" \ + CPPFLAGS="-I{{ ZLIB_INCLUDE }}" \ + LDFLAGS="-L{{ ZLIB_LIB }}" \ + {{ DEPS_DIR }}/libpng-1.6.37/configure \ + --enable-shared=no \ + --with-pic=yes \ + --enable-hardware-optimizations={{ OPTIMIZATIONS }} + + make -j -C {{ LIBPNG_BUILD }} diff --git a/just/libafl-cc.just b/just/libafl-cc.just new file mode 100644 index 0000000000..9058c2aa5e --- /dev/null +++ b/just/libafl-cc.just @@ -0,0 +1,4 @@ +import "libafl.just" + +LIBAFL_CC := BUILD_DIR / "libafl_cc" +LIBAFL_CXX := BUILD_DIR / "libafl_cxx" diff --git a/just/libafl-qemu-libpng.just b/just/libafl-qemu-libpng.just index 6a23a5d645..e68a2cca23 100644 --- a/just/libafl-qemu-libpng.just +++ b/just/libafl-qemu-libpng.just @@ -79,6 +79,6 @@ libpng: arch_dir zlib libpng_wget --enable-shared=no \ --with-pic=yes \ --enable-hardware-optimizations={{ OPTIMIZATIONS }} \ - --host={{ ARCH }} \ + --host={{ ARCH }} make -j -C {{ TARGET_DIR }}/build-png/ \ No newline at end of file diff --git a/just/libafl.just b/just/libafl.just index 2bf9258c56..f11e5d1534 100644 --- a/just/libafl.just +++ b/just/libafl.just @@ -12,11 +12,13 @@ # - `FUZZER`: Executable path. PROFILE := env("PROFILE", "release") +FUZZER_EXTENSION := if os_family() == "windows" { ".exe" } else { "" } +FUZZER := BUILD_DIR / FUZZER_NAME + FUZZER_EXTENSION + +PROJECT_DIR := absolute_path(".") PROFILE_DIR := if PROFILE == "dev" { "debug" } else { "release" } TARGET_DIR := absolute_path(env("TARGET_DIR", "target")) BUILD_DIR := TARGET_DIR / PROFILE_DIR -FUZZER_EXTENSION := if os_family() == "windows" { ".exe" } else { "" } -FUZZER := BUILD_DIR / FUZZER_NAME + FUZZER_EXTENSION JUSTHASHES := ".justhashes" diff --git a/libafl/src/events/llmp/restarting.rs b/libafl/src/events/llmp/restarting.rs index 0a05f9bbe3..31722536cd 100644 --- a/libafl/src/events/llmp/restarting.rs +++ b/libafl/src/events/llmp/restarting.rs @@ -63,10 +63,9 @@ use crate::{ inputs::Input, monitors::Monitor, observers::TimeObserver, - stages::HasCurrentStageId, state::{ - HasCurrentTestcase, HasExecutions, HasImported, HasLastReportTime, HasSolutions, - MaybeHasClientPerfMonitor, Stoppable, + HasCurrentStageId, HasCurrentTestcase, HasExecutions, HasImported, HasLastReportTime, + HasSolutions, MaybeHasClientPerfMonitor, Stoppable, }, Error, }; diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index 02d2622199..154889e2ec 100644 --- a/libafl/src/events/mod.rs +++ b/libafl/src/events/mod.rs @@ -108,7 +108,7 @@ pub struct EventManagerId( use crate::events::multi_machine::NodeId; #[cfg(feature = "introspection")] use crate::monitors::stats::ClientPerfStats; -use crate::{observers::TimeObserver, stages::HasCurrentStageId}; +use crate::{observers::TimeObserver, state::HasCurrentStageId}; /// The log event severity #[derive(Serialize, Deserialize, Debug, Clone, Copy)] diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index 77acfbb02b..a31499b7f7 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -32,8 +32,9 @@ use crate::{ SendExiting, }, monitors::{stats::ClientStatsManager, Monitor}, - stages::HasCurrentStageId, - state::{HasExecutions, HasLastReportTime, MaybeHasClientPerfMonitor, Stoppable}, + state::{ + HasCurrentStageId, HasExecutions, HasLastReportTime, MaybeHasClientPerfMonitor, Stoppable, + }, Error, HasMetadata, }; #[cfg(feature = "std")] diff --git a/libafl/src/events/tcp.rs b/libafl/src/events/tcp.rs index 246f1a67dc..48c8fb5811 100644 --- a/libafl/src/events/tcp.rs +++ b/libafl/src/events/tcp.rs @@ -48,10 +48,9 @@ use crate::{ }, inputs::Input, monitors::{stats::ClientStatsManager, Monitor}, - stages::HasCurrentStageId, state::{ - HasCurrentTestcase, HasExecutions, HasImported, HasLastReportTime, HasSolutions, - MaybeHasClientPerfMonitor, Stoppable, + HasCurrentStageId, HasCurrentTestcase, HasExecutions, HasImported, HasLastReportTime, + HasSolutions, MaybeHasClientPerfMonitor, Stoppable, }, Error, HasMetadata, }; diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index 993857df9e..169de9ff3f 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -157,7 +157,8 @@ pub fn common_signals() -> Vec { } #[cfg(test)] -mod test { +/// Tester for executor +pub mod test { use core::marker::PhantomData; use libafl_bolts::{AsSlice, Error}; diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index e07a520ee7..a3d4790d23 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -24,11 +24,11 @@ use crate::{ mark_feature_time, observers::ObserversTuple, schedulers::Scheduler, - stages::{HasCurrentStageId, StagesTuple}, + stages::StagesTuple, start_timer, state::{ - HasCorpus, HasCurrentTestcase, HasExecutions, HasImported, HasLastFoundTime, - HasLastReportTime, HasSolutions, MaybeHasClientPerfMonitor, Stoppable, + HasCorpus, HasCurrentStageId, HasCurrentTestcase, HasExecutions, HasImported, + HasLastFoundTime, HasLastReportTime, HasSolutions, MaybeHasClientPerfMonitor, Stoppable, }, Error, HasMetadata, }; diff --git a/libafl/src/inputs/value.rs b/libafl/src/inputs/value.rs index 4948112431..68ce3645fb 100644 --- a/libafl/src/inputs/value.rs +++ b/libafl/src/inputs/value.rs @@ -1,4 +1,5 @@ -//! Newtype pattern style wrapper for [`super::Input`]s +//! Newtype pattern style wrapper for [`Input`]s +//! This allows us to wrap common types as [`Input`], such as [`alloc::vec::Vec`] as [`crate::inputs::BytesInput`] and use those for mutations. use alloc::vec::Vec; use core::{fmt::Debug, hash::Hash}; @@ -11,46 +12,45 @@ use { std::{fs::File, io::Read, path::Path}, }; -use super::Input; -use crate::mutators::numeric::Numeric; +use crate::{inputs::Input, mutators::numeric::Numeric}; /// Newtype pattern wrapper around an underlying structure to implement inputs /// /// This does not blanket implement [`super::Input`], because for certain inputs, writing them to disk does not make sense, because they don't own their data (like [`super::MutVecInput`]) #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Default)] -pub struct ValueInput(I); +pub struct ValueInput(T); -impl From for ValueInput { - fn from(value: I) -> Self { +impl From for ValueInput { + fn from(value: T) -> Self { Self(value) } } -impl ValueInput { +impl ValueInput { /// Create a new [`ValueInput`] - pub const fn new(value: I) -> Self { + pub const fn new(value: T) -> Self { Self(value) } /// Extract the inner value - pub fn into_inner(self) -> I { + pub fn into_inner(self) -> T { self.0 } } -impl AsRef for ValueInput { - fn as_ref(&self) -> &I { +impl AsRef for ValueInput { + fn as_ref(&self) -> &T { &self.0 } } -impl AsMut for ValueInput { - fn as_mut(&mut self) -> &mut I { +impl AsMut for ValueInput { + fn as_mut(&mut self) -> &mut T { &mut self.0 } } -impl Copy for ValueInput {} +impl Copy for ValueInput {} // Macro to implement the `Input` trait and create type aliases for `WrappingInput` macro_rules! impl_input_for_value_input { @@ -105,9 +105,9 @@ impl Input for ValueInput> { } } -impl Numeric for ValueInput +impl Numeric for ValueInput where - I: Numeric, + T: Numeric, { fn flip_all_bits(&mut self) { self.as_mut().flip_all_bits(); @@ -179,26 +179,26 @@ mod tests { $prep let mut j = $value; - j.flip_bit_at(size_of::() * 8 - 1); + j.flip_bit_at(size_of::() * 8 - 1); $prep - assert_ne!(j, $value, "{:?}.flip_bit_at({}) for {}", j, size_of::() * 8 - 1, type_name::<$type>()); + assert_ne!(j, $value, "{:?}.flip_bit_at({}) for {}", j, size_of::() * 8 - 1, type_name::<$type>()); }}; } #[cfg(feature = "std")] #[expect(unused_mut)] - fn take_numeric(i: &I, check_twos_complement: bool) { - apply_all_ops!({}, i.clone(), I, check_twos_complement); + fn take_numeric(val: &T, check_twos_complement: bool) { + apply_all_ops!({}, val.clone(), T, check_twos_complement); apply_all_ops!( {}, - ValueInput::from(i.clone()), - ValueInput, + ValueInput::from(val.clone()), + ValueInput, check_twos_complement ); apply_all_ops!( - let mut i_clone = i.clone(), - &mut i_clone, - &mut I, + let mut val_clone = val.clone(), + &mut val_clone, + &mut T, check_twos_complement ); } diff --git a/libafl/src/stages/afl_stats.rs b/libafl/src/stages/afl_stats.rs index acec81e61d..2efe98e67b 100644 --- a/libafl/src/stages/afl_stats.rs +++ b/libafl/src/stages/afl_stats.rs @@ -30,7 +30,7 @@ use crate::{ mutators::Tokens, observers::MapObserver, schedulers::{minimizer::IsFavoredMetadata, HasQueueCycles}, - stages::{calibrate::UnstableEntriesMetadata, Stage}, + stages::{calibrate::UnstableEntriesMetadata, Restartable, Stage}, state::{HasCorpus, HasExecutions, HasImported, HasStartTime, Stoppable}, std::string::ToString, Error, HasMetadata, HasNamedMetadata, HasScheduler, @@ -432,7 +432,9 @@ where Ok(()) } +} +impl Restartable for AflStatsStage { fn should_restart(&mut self, _state: &mut S) -> Result { Ok(true) } diff --git a/libafl/src/stages/calibrate.rs b/libafl/src/stages/calibrate.rs index cfd163fb4e..2203d8b556 100644 --- a/libafl/src/stages/calibrate.rs +++ b/libafl/src/stages/calibrate.rs @@ -22,7 +22,7 @@ use crate::{ monitors::stats::{AggregatorOps, UserStats, UserStatsValue}, observers::{MapObserver, ObserversTuple}, schedulers::powersched::SchedulerMetadata, - stages::{RetryCountRestartHelper, Stage}, + stages::{Restartable, RetryCountRestartHelper, Stage}, state::{HasCorpus, HasCurrentTestcase, HasExecutions}, Error, HasMetadata, HasNamedMetadata, }; @@ -364,7 +364,12 @@ where Ok(()) } +} +impl Restartable for CalibrationStage +where + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, +{ fn should_restart(&mut self, state: &mut S) -> Result { // Calibration stage disallow restarts // If a testcase that causes crash/timeout in the queue, we need to remove it from the queue immediately. diff --git a/libafl/src/stages/colorization.rs b/libafl/src/stages/colorization.rs index 8f3f57072c..4af91aaf5e 100644 --- a/libafl/src/stages/colorization.rs +++ b/libafl/src/stages/colorization.rs @@ -22,7 +22,7 @@ use crate::{ mutators::mutations::buffer_copy, nonzero, observers::ObserversTuple, - stages::{RetryCountRestartHelper, Stage}, + stages::{Restartable, RetryCountRestartHelper, Stage}, state::{HasCorpus, HasCurrentTestcase, HasRand}, Error, HasMetadata, HasNamedMetadata, }; @@ -98,7 +98,12 @@ where Ok(()) } +} +impl Restartable for ColorizationStage +where + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, +{ fn should_restart(&mut self, state: &mut S) -> Result { // This is a deterministic stage // Once it failed, then don't retry, diff --git a/libafl/src/stages/concolic.rs b/libafl/src/stages/concolic.rs index dd8659f46f..a1175c7173 100644 --- a/libafl/src/stages/concolic.rs +++ b/libafl/src/stages/concolic.rs @@ -18,7 +18,7 @@ use crate::{ corpus::HasCurrentCorpusId, executors::{Executor, HasObservers}, observers::{concolic::ConcolicObserver, ObserversTuple}, - stages::{RetryCountRestartHelper, Stage, TracingStage}, + stages::{Restartable, RetryCountRestartHelper, Stage, TracingStage}, state::{HasCorpus, HasCurrentTestcase, HasExecutions, MaybeHasClientPerfMonitor}, Error, HasMetadata, HasNamedMetadata, }; @@ -76,7 +76,12 @@ where } Ok(()) } +} +impl Restartable for ConcolicTracingStage<'_, EM, I, TE, S, Z> +where + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, +{ fn should_restart(&mut self, state: &mut S) -> Result { // This is a deterministic stage // Once it failed, then don't retry, @@ -418,7 +423,13 @@ where } Ok(()) } +} +#[cfg(feature = "concolic_mutation")] +impl Restartable for SimpleConcolicMutationalStage +where + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, +{ #[inline] fn should_restart(&mut self, state: &mut S) -> Result { // This is a deterministic stage diff --git a/libafl/src/stages/dump.rs b/libafl/src/stages/dump.rs index 97e043c89b..62ff6163be 100644 --- a/libafl/src/stages/dump.rs +++ b/libafl/src/stages/dump.rs @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; use crate::{ corpus::{Corpus, CorpusId, Testcase}, inputs::Input, - stages::Stage, + stages::{Restartable, Stage}, state::{HasCorpus, HasRand, HasSolutions}, Error, HasMetadata, }; @@ -60,7 +60,11 @@ where ) -> Result<(), Error> { self.dump_state_to_disk(state) } +} +impl Restartable + for DumpToDiskStage, &CorpusId) -> String, EM, I, S, Z> +{ #[inline] fn should_restart(&mut self, _state: &mut S) -> Result { // Not executing the target, so restart safety is not needed diff --git a/libafl/src/stages/generalization.rs b/libafl/src/stages/generalization.rs index 6dd1b11ccd..d1d6a289fe 100644 --- a/libafl/src/stages/generalization.rs +++ b/libafl/src/stages/generalization.rs @@ -23,7 +23,7 @@ use crate::{ mark_feature_time, observers::{CanTrack, MapObserver, ObserversTuple}, require_novelties_tracking, - stages::{RetryCountRestartHelper, Stage}, + stages::{Restartable, RetryCountRestartHelper, Stage}, start_timer, state::{HasCorpus, HasExecutions, MaybeHasClientPerfMonitor}, Error, HasMetadata, HasNamedMetadata, @@ -62,19 +62,9 @@ impl Named for GeneralizationStage } } -impl Stage - for GeneralizationStage +impl Restartable for GeneralizationStage where - C: CanTrack + AsRef + Named, - E: Executor + HasObservers, - E::Observers: ObserversTuple, - O: MapObserver, - S: HasExecutions - + HasMetadata - + HasCorpus - + HasNamedMetadata - + HasCurrentCorpusId - + MaybeHasClientPerfMonitor, + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, { #[inline] fn should_restart(&mut self, state: &mut S) -> Result { @@ -87,7 +77,22 @@ where // TODO: We need to be able to resume better if something crashes or times out RetryCountRestartHelper::clear_progress::(state, &self.name) } +} +impl Stage + for GeneralizationStage +where + C: CanTrack + AsRef + Named, + E: Executor + HasObservers, + E::Observers: ObserversTuple, + O: MapObserver, + S: HasExecutions + + HasMetadata + + HasCorpus + + HasNamedMetadata + + HasCurrentCorpusId + + MaybeHasClientPerfMonitor, +{ #[inline] #[expect(clippy::too_many_lines)] fn perform( diff --git a/libafl/src/stages/generation.rs b/libafl/src/stages/generation.rs index faf56656f4..ac312f48f1 100644 --- a/libafl/src/stages/generation.rs +++ b/libafl/src/stages/generation.rs @@ -6,7 +6,12 @@ use core::marker::PhantomData; -use crate::{generators::Generator, stages::Stage, state::HasRand, Error, Evaluator}; +use crate::{ + generators::Generator, + stages::{Restartable, Stage}, + state::HasRand, + Error, Evaluator, +}; /// A [`Stage`] that generates a single input via a [`Generator`] and evaluates /// it using the fuzzer, possibly adding it to the corpus. @@ -40,7 +45,9 @@ where fuzzer.evaluate_filtered(state, executor, manager, &input)?; Ok(()) } +} +impl Restartable for GenStage { fn should_restart(&mut self, _state: &mut S) -> Result { // It's a random generation stage // so you can restart for whatever times you want diff --git a/libafl/src/stages/logics.rs b/libafl/src/stages/logics.rs index 5a3497a14c..00adc546f1 100644 --- a/libafl/src/stages/logics.rs +++ b/libafl/src/stages/logics.rs @@ -3,7 +3,8 @@ use core::marker::PhantomData; use crate::{ - stages::{HasNestedStageStatus, Stage, StageId, StagesTuple}, + stages::{Restartable, Stage, StageId, StagesTuple}, + state::HasNestedStage, Error, }; @@ -14,7 +15,7 @@ pub struct NestedStageRetryCountRestartHelper; impl NestedStageRetryCountRestartHelper { fn should_restart(state: &mut S, _stage: &ST) -> Result where - S: HasNestedStageStatus, + S: HasNestedStage, { state.enter_inner_stage()?; Ok(true) @@ -22,7 +23,7 @@ impl NestedStageRetryCountRestartHelper { fn clear_progress(state: &mut S, _stage: &ST) -> Result<(), Error> where - S: HasNestedStageStatus, + S: HasNestedStage, { state.exit_inner_stage()?; Ok(()) @@ -41,7 +42,7 @@ impl Stage for WhileStage where CB: FnMut(&mut Z, &mut E, &mut S, &mut EM) -> Result, ST: StagesTuple, - S: HasNestedStageStatus, + S: HasNestedStage, { fn perform( &mut self, @@ -58,7 +59,12 @@ where Ok(()) } +} +impl Restartable for WhileStage +where + S: HasNestedStage, +{ fn should_restart(&mut self, state: &mut S) -> Result { NestedStageRetryCountRestartHelper::should_restart(state, self) } @@ -95,7 +101,7 @@ impl Stage for IfStage where CB: FnMut(&mut Z, &mut E, &mut S, &mut EM) -> Result, ST: StagesTuple, - S: HasNestedStageStatus, + S: HasNestedStage, { fn perform( &mut self, @@ -111,7 +117,12 @@ where } Ok(()) } +} +impl Restartable for IfStage +where + S: HasNestedStage, +{ fn should_restart(&mut self, state: &mut S) -> Result { NestedStageRetryCountRestartHelper::should_restart(state, self) } @@ -150,7 +161,7 @@ where CB: FnMut(&mut Z, &mut E, &mut S, &mut EM) -> Result, ST1: StagesTuple, ST2: StagesTuple, - S: HasNestedStageStatus, + S: HasNestedStage, { fn perform( &mut self, @@ -161,10 +172,12 @@ where ) -> Result<(), Error> { let current = state.current_stage_id()?; + // this is None if you didn't recover from restart + // because should_restart() which is called right before this will create a new stage stack let fresh = current.is_none(); - let closure_return = fresh && (self.closure)(fuzzer, executor, state, manager)?; + let closure_res = fresh && (self.closure)(fuzzer, executor, state, manager)?; - if current == Some(StageId(0)) || closure_return { + if current == Some(StageId(0)) || closure_res { if fresh { state.set_current_stage_id(StageId(0))?; } @@ -185,7 +198,12 @@ where Ok(()) } +} +impl Restartable for IfElseStage +where + S: HasNestedStage, +{ fn should_restart(&mut self, state: &mut S) -> Result { NestedStageRetryCountRestartHelper::should_restart(state, self) } @@ -220,7 +238,7 @@ pub struct OptionalStage { impl Stage for OptionalStage where ST: StagesTuple, - S: HasNestedStageStatus, + S: HasNestedStage, { fn perform( &mut self, @@ -235,7 +253,12 @@ where Ok(()) } } +} +impl Restartable for OptionalStage +where + S: HasNestedStage, +{ fn should_restart(&mut self, state: &mut S) -> Result { NestedStageRetryCountRestartHelper::should_restart(state, self) } @@ -273,3 +296,355 @@ impl OptionalStage { } } } + +#[cfg(test)] +mod test { + use alloc::rc::Rc; + use core::{cell::RefCell, marker::PhantomData}; + + use libafl_bolts::{ + impl_serdeany, + tuples::{tuple_list, tuple_list_type}, + Error, + }; + use serde::{Deserialize, Serialize}; + + #[cfg(any(not(feature = "serdeany_autoreg"), miri))] + use crate::stages::RetryCountRestartHelper; + use crate::{ + events::NopEventManager, + executors::test::NopExecutor, + stages::{ + ClosureStage, CorpusId, HasCurrentCorpusId, IfElseStage, IfStage, Restartable, Stage, + StagesTuple, WhileStage, + }, + state::{HasCurrentStageId, StdState}, + HasMetadata, NopFuzzer, + }; + + #[derive(Debug)] + pub struct ResumeSucceededStage { + phantom: PhantomData, + } + #[derive(Debug)] + pub struct ResumeFailedStage { + completed: Rc>, + phantom: PhantomData, + } + #[derive(Serialize, Deserialize, Debug)] + pub struct TestProgress { + count: usize, + } + + impl_serdeany!(TestProgress); + + impl TestProgress { + #[expect(clippy::unnecessary_wraps)] + fn should_restart(state: &mut S, _stage: &ST) -> Result + where + S: HasMetadata, + { + // check if we're resuming + let _metadata = state.metadata_or_insert_with(|| Self { count: 0 }); + Ok(true) + } + + fn clear_progress(state: &mut S, _stage: &ST) -> Result<(), Error> + where + S: HasMetadata, + { + if state.remove_metadata::().is_none() { + return Err(Error::illegal_state( + "attempted to clear status metadata when none was present", + )); + } + Ok(()) + } + } + + impl Stage for ResumeSucceededStage + where + S: HasMetadata, + { + fn perform( + &mut self, + _fuzzer: &mut Z, + _executor: &mut E, + state: &mut S, + _manager: &mut EM, + ) -> Result<(), Error> { + // metadata is attached by the status + let meta = state.metadata_mut::().unwrap(); + meta.count += 1; + assert!( + meta.count == 1, + "Test failed; we resumed a succeeded stage!" + ); + Ok(()) + } + } + + impl Restartable for ResumeSucceededStage + where + S: HasMetadata, + { + fn should_restart(&mut self, state: &mut S) -> Result { + TestProgress::should_restart(state, self) + } + + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + TestProgress::clear_progress(state, self) + } + } + + impl Stage for ResumeFailedStage + where + S: HasMetadata, + { + fn perform( + &mut self, + _fuzzer: &mut Z, + _executor: &mut E, + state: &mut S, + _manager: &mut EM, + ) -> Result<(), Error> { + // metadata is attached by the status + let meta = state.metadata_mut::().unwrap(); + meta.count += 1; + if meta.count == 1 { + return Err(Error::shutting_down()); + } else if meta.count > 2 { + panic!("Resume was somehow corrupted?") + } else { + self.completed.replace(true); + } + Ok(()) + } + } + + impl Restartable for ResumeFailedStage + where + S: HasMetadata, + { + fn should_restart(&mut self, state: &mut S) -> Result { + TestProgress::should_restart(state, self) + } + + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + TestProgress::clear_progress(state, self) + } + } + + #[must_use] + #[allow(clippy::type_complexity)] + pub fn test_resume_stages() -> ( + Rc>, + tuple_list_type!(ResumeSucceededStage, ResumeFailedStage), + ) { + let completed = Rc::new(RefCell::new(false)); + ( + completed.clone(), + tuple_list!( + ResumeSucceededStage { + phantom: PhantomData + }, + ResumeFailedStage { + completed, + phantom: PhantomData + }, + ), + ) + } + + pub fn test_resume(completed: &Rc>, state: &mut S, mut stages: ST) + where + ST: StagesTuple, NopEventManager, S, NopFuzzer>, + S: HasCurrentStageId + HasCurrentCorpusId, + { + #[cfg(any(not(feature = "serdeany_autoreg"), miri))] + unsafe { + TestProgress::register(); + RetryCountRestartHelper::register(); + } + + let mut fuzzer = NopFuzzer::new(); + let mut executor = NopExecutor::new(); + let mut manager = NopEventManager::new(); + for _ in 0..2 { + completed.replace(false); + // fake one, just any number so retryhelper won't fail. + // in reality you always have corpus id set by stdfuzzer + state.set_corpus_id(CorpusId::from(0_usize)).unwrap(); + let Err(e) = stages.perform_all(&mut fuzzer, &mut executor, state, &mut manager) else { + panic!("Test failed; stages should fail the first time.") + }; + assert!( + matches!(e, Error::ShuttingDown), + "Unexpected error encountered." + ); + assert!(!*completed.borrow(), "Unexpectedly complete?"); + state + .on_restart() + .expect("Couldn't notify state of restart."); + assert!( + stages + .perform_all(&mut fuzzer, &mut executor, state, &mut manager) + .is_ok(), + "Test failed; stages should pass the second time." + ); + assert!( + *completed.borrow(), + "Test failed; we did not set completed." + ); + } + } + + #[test] + fn check_resumability_while() { + let once = RefCell::new(true); + + let (completed, stages) = test_resume_stages(); + let whilestage = WhileStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(once.replace(false)), + stages, + ); + let resetstage = ClosureStage::new(|_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| { + once.replace(true); + Ok(()) + }); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(whilestage, resetstage)); + } + + #[test] + fn check_resumability_if() { + let once = RefCell::new(true); + let (completed, stages) = test_resume_stages(); + let ifstage = IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(once.replace(false)), + stages, + ); + let resetstage = ClosureStage::new(|_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| { + once.replace(true); + Ok(()) + }); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(ifstage, resetstage)); + } + + #[test] + fn check_resumability_if_deep() { + let (completed, stages) = test_resume_stages(); + let ifstage = IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(true), + tuple_list!(IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(true), + tuple_list!(IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(true), + tuple_list!(IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(true), + tuple_list!(IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(true), + stages + ),), + ),), + )) + )), + ); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(ifstage)); + } + + #[derive(Debug)] + pub struct PanicStage { + phantom: PhantomData, + } + impl PanicStage { + pub fn new() -> Self { + Self { + phantom: PhantomData, + } + } + } + impl Stage for PanicStage { + fn perform( + &mut self, + _fuzzer: &mut Z, + _executor: &mut E, + _state: &mut S, + _manager: &mut EM, + ) -> Result<(), Error> { + panic!("Test failed; panic stage should never be executed."); + } + } + + impl Restartable for PanicStage { + fn should_restart(&mut self, _state: &mut S) -> Result { + Ok(true) + } + + fn clear_progress(&mut self, _state: &mut S) -> Result<(), Error> { + Ok(()) + } + } + #[test] + fn check_resumability_if_else_if() { + let once = RefCell::new(true); + let (completed, stages) = test_resume_stages(); + let ifstage = IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(once.replace(false)), + stages, + tuple_list!(PanicStage::new()), + ); + let resetstage = ClosureStage::new(|_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| { + once.replace(true); + Ok(()) + }); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(ifstage, resetstage)); + } + + #[test] + fn check_resumability_if_else_else() { + let once = RefCell::new(false); + let (completed, stages) = test_resume_stages(); + let ifstage = IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(once.replace(true)), + tuple_list!(PanicStage::new()), + stages, + ); + let resetstage = ClosureStage::new(|_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| { + once.replace(false); + Ok(()) + }); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(ifstage, resetstage)); + } + #[test] + fn check_resumability_if_else_else_deep() { + let (completed, stages) = test_resume_stages(); + let ifstage = IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(false), + tuple_list!(PanicStage::new()), + tuple_list!(IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(false), + tuple_list!(PanicStage::new()), + tuple_list!(IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(false), + tuple_list!(PanicStage::new()), + tuple_list!(IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(false), + tuple_list!(PanicStage::new()), + tuple_list!(IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(false), + tuple_list!(PanicStage::new()), + stages, + )), + )), + )), + )), + ); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(ifstage)); + } +} diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index 9451a26d5f..16a3c578dc 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -49,7 +49,7 @@ pub use verify_timeouts::{TimeoutsToVerify, VerifyTimeoutsStage}; use crate::{ corpus::{CorpusId, HasCurrentCorpusId}, events::SendExiting, - state::{HasExecutions, Stoppable}, + state::{HasCurrentStageId, HasExecutions, Stoppable}, Error, HasNamedMetadata, }; @@ -58,6 +58,9 @@ pub mod mutational; pub mod push; pub mod tmin; +pub mod replay; +pub use replay::*; + #[cfg(feature = "std")] pub mod afl_stats; pub mod calibrate; @@ -84,22 +87,11 @@ pub mod verify_timeouts; /// A stage is one step in the fuzzing process. /// Multiple stages will be scheduled one by one for each input. pub trait Stage { - /// This method will be called before every call to [`Stage::perform`]. - /// Initialize the restart tracking for this stage, _if it is not yet initialized_. - /// On restart, this will be called again. - /// As long as [`Stage::clear_progress`], all subsequent calls happen on restart. - /// Returns `true`, if the stage's [`Stage::perform`] method should run, else `false`. - fn should_restart(&mut self, state: &mut S) -> Result; - - /// Clear the current status tracking of the associated stage - fn clear_progress(&mut self, state: &mut S) -> Result<(), Error>; - /// Run the stage. /// - /// Before a call to perform, [`Stage::should_restart`] will be (must be!) called. - /// After returning (so non-target crash or timeout in a restarting case), [`Stage::clear_progress`] gets called. - /// A call to [`Stage::perform_restartable`] will do these things implicitly. - /// DON'T call this function directly except from `preform_restartable` !! + /// If you want this stage to restart, then + /// Before a call to perform, [`Restartable::should_restart`] will be (must be!) called. + /// After returning (so non-target crash or timeout in a restarting case), [`Restartable::clear_progress`] gets called. fn perform( &mut self, fuzzer: &mut Z, @@ -107,20 +99,19 @@ pub trait Stage { state: &mut S, manager: &mut EM, ) -> Result<(), Error>; +} - /// Run the stage, calling [`Stage::should_restart`] and [`Stage::clear_progress`] appropriately - fn perform_restartable( - &mut self, - fuzzer: &mut Z, - executor: &mut E, - state: &mut S, - manager: &mut EM, - ) -> Result<(), Error> { - if self.should_restart(state)? { - self.perform(fuzzer, executor, state, manager)?; - } - self.clear_progress(state) - } +/// Restartable trait takes care of stage restart. +pub trait Restartable { + /// This method will be called before every call to [`Stage::perform`]. + /// Initialize the restart tracking for this stage, _if it is not yet initialized_. + /// On restart, this will be called again. + /// As long as [`Restartable::clear_progress`], all subsequent calls happen on restart. + /// Returns `true`, if the stage's [`Stage::perform`] method should run, else `false`. + fn should_restart(&mut self, state: &mut S) -> Result; + + /// Clear the current status tracking of the associated stage + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error>; } /// A tuple holding all `Stages` used for fuzzing. @@ -158,7 +149,7 @@ where impl StagesTuple for (Head, Tail) where - Head: Stage, + Head: Stage + Restartable, Tail: StagesTuple + HasConstLen, S: HasCurrentStageId + Stoppable, EM: SendExiting, @@ -194,6 +185,7 @@ where state.set_current_stage_id(StageId(Self::LEN))?; let stage = &mut self.0; + stage.perform_restartable(fuzzer, executor, state, manager)?; state.clear_stage_id()?; @@ -246,7 +238,36 @@ impl IntoVec>> for Vec StagesTuple for Vec>> +trait RestartableStage: Stage + Restartable { + fn perform_restartable( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + state: &mut S, + manager: &mut EM, + ) -> Result<(), Error>; +} + +impl RestartableStage for ST +where + ST: Stage + Restartable, +{ + /// Run the stage, calling [`Stage::should_restart`] and [`Stage::clear_progress`] appropriately + fn perform_restartable( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + state: &mut S, + manager: &mut EM, + ) -> Result<(), Error> { + if self.should_restart(state)? { + self.perform(fuzzer, executor, state, manager)?; + } + self.clear_progress(state) + } +} + +impl StagesTuple for Vec>> where EM: SendExiting, S: HasCurrentStageId + Stoppable, @@ -261,13 +282,13 @@ where state: &mut S, manager: &mut EM, ) -> Result<(), Error> { - self.iter_mut().try_for_each(|x| { + self.iter_mut().try_for_each(|stage| { if state.stop_requested() { state.discard_stop_request(); manager.on_shutdown()?; return Err(Error::shutting_down()); } - x.perform_restartable(fuzzer, executor, state, manager) + stage.perform_restartable(fuzzer, executor, state, manager) }) } } @@ -304,7 +325,12 @@ where ) -> Result<(), Error> { (self.closure)(fuzzer, executor, state, manager) } +} +impl Restartable for ClosureStage +where + S: HasNamedMetadata + HasCurrentCorpusId, +{ #[inline] fn should_restart(&mut self, state: &mut S) -> Result { // There's no restart safety in the content of the closure. @@ -418,34 +444,6 @@ impl fmt::Display for StageId { } } -/// Trait for types which track the current stage -pub trait HasCurrentStageId { - /// Set the current stage; we have started processing this stage - fn set_current_stage_id(&mut self, id: StageId) -> Result<(), Error>; - - /// Clear the current stage; we are done processing this stage - fn clear_stage_id(&mut self) -> Result<(), Error>; - - /// Fetch the current stage -- typically used after a state recovery or transfer - fn current_stage_id(&self) -> Result, Error>; - - /// Notify of a reset from which we may recover - fn on_restart(&mut self) -> Result<(), Error> { - Ok(()) - } -} - -/// Trait for types which track nested stages. Stages which themselves contain stage tuples should -/// ensure that they constrain the state with this trait accordingly. -pub trait HasNestedStageStatus: HasCurrentStageId { - /// Enter a stage scope, potentially resuming to an inner stage status. Returns Ok(true) if - /// resumed. - fn enter_inner_stage(&mut self) -> Result<(), Error>; - - /// Exit a stage scope - fn exit_inner_stage(&mut self) -> Result<(), Error>; -} - impl_serdeany!(ExecutionCountRestartHelperMetadata); /// `SerdeAny` metadata used to keep track of executions since start for a given stage. @@ -528,87 +526,16 @@ impl ExecutionCountRestartHelper { #[cfg(test)] mod test { use alloc::borrow::Cow; - use core::marker::PhantomData; - use libafl_bolts::{impl_serdeany, Error, Named}; - use serde::{Deserialize, Serialize}; + use libafl_bolts::{Error, Named}; use crate::{ corpus::{Corpus, HasCurrentCorpusId, Testcase}, inputs::NopInput, - stages::{RetryCountRestartHelper, Stage}, + stages::RetryCountRestartHelper, state::{HasCorpus, StdState}, - HasMetadata, }; - /// A stage that succeeds to resume - #[derive(Debug)] - pub struct ResumeSucceededStage { - phantom: PhantomData, - } - - /// A progress state for testing - #[derive(Serialize, Deserialize, Debug)] - pub struct TestProgress { - count: usize, - } - - impl_serdeany!(TestProgress); - - impl TestProgress { - #[expect(clippy::unnecessary_wraps)] - fn should_restart(state: &mut S, _stage: &ST) -> Result - where - S: HasMetadata, - { - // check if we're resuming - let metadata = state.metadata_or_insert_with(|| Self { count: 0 }); - - metadata.count += 1; - assert!( - metadata.count == 1, - "Test failed; we resumed a succeeded stage!" - ); - - Ok(true) - } - - fn clear_progress(state: &mut S, _stage: &ST) -> Result<(), Error> - where - S: HasMetadata, - { - if state.remove_metadata::().is_none() { - return Err(Error::illegal_state( - "attempted to clear status metadata when none was present", - )); - } - Ok(()) - } - } - - impl Stage for ResumeSucceededStage - where - S: HasMetadata, - { - fn perform( - &mut self, - _fuzzer: &mut Z, - _executor: &mut E, - _state: &mut S, - _manager: &mut EM, - ) -> Result<(), Error> { - Ok(()) - } - - fn should_restart(&mut self, state: &mut S) -> Result { - TestProgress::should_restart(state, self) - } - - fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { - TestProgress::clear_progress(state, self) - } - } - /// Test to test retries in stages #[test] fn test_tries_progress() -> Result<(), Error> { diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index 4e630ed7ca..5f44a916b4 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -18,7 +18,7 @@ use crate::{ mark_feature_time, mutators::{MultiMutator, MutationResult, Mutator}, nonzero, - stages::{RetryCountRestartHelper, Stage}, + stages::{Restartable, RetryCountRestartHelper, Stage}, start_timer, state::{HasCorpus, HasCurrentTestcase, HasExecutions, HasRand, MaybeHasClientPerfMonitor}, Error, HasMetadata, HasNamedMetadata, @@ -170,7 +170,12 @@ where ret } +} +impl Restartable for StdMutationalStage +where + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, +{ fn should_restart(&mut self, state: &mut S) -> Result { RetryCountRestartHelper::should_restart(state, &self.name, 3) } @@ -316,17 +321,6 @@ where S: HasRand + HasNamedMetadata + HasCurrentTestcase + HasCurrentCorpusId, Z: Evaluator, { - #[inline] - fn should_restart(&mut self, state: &mut S) -> Result { - // Make sure we don't get stuck crashing on a single testcase - RetryCountRestartHelper::should_restart(state, &self.name, 3) - } - - #[inline] - fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { - RetryCountRestartHelper::clear_progress(state, &self.name) - } - #[inline] fn perform( &mut self, @@ -354,6 +348,22 @@ where } } +impl Restartable for MultiMutationalStage +where + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, +{ + #[inline] + fn should_restart(&mut self, state: &mut S) -> Result { + // Make sure we don't get stuck crashing on a single testcase + RetryCountRestartHelper::should_restart(state, &self.name, 3) + } + + #[inline] + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + RetryCountRestartHelper::clear_progress(state, &self.name) + } +} + impl MultiMutationalStage { /// Creates a new [`MultiMutationalStage`] pub fn new(mutator: M) -> Self { diff --git a/libafl/src/stages/power.rs b/libafl/src/stages/power.rs index fb4ea30913..e19cfe0a9e 100644 --- a/libafl/src/stages/power.rs +++ b/libafl/src/stages/power.rs @@ -19,7 +19,7 @@ use crate::{ schedulers::{testcase_score::CorpusPowerTestcaseScore, TestcaseScore}, stages::{ mutational::{MutatedTransform, MutatedTransformPost}, - MutationalStage, RetryCountRestartHelper, Stage, + MutationalStage, Restartable, RetryCountRestartHelper, Stage, }, start_timer, state::{HasCurrentTestcase, HasExecutions, HasRand, MaybeHasClientPerfMonitor}, @@ -101,7 +101,12 @@ where let ret = self.perform_mutational(fuzzer, executor, state, manager); ret } +} +impl Restartable for PowerMutationalStage +where + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, +{ fn should_restart(&mut self, state: &mut S) -> Result { // Make sure we don't get stuck crashing on a single testcase RetryCountRestartHelper::should_restart(state, &self.name, 3) diff --git a/libafl/src/stages/push/mod.rs b/libafl/src/stages/push/mod.rs index 5ad8b6115b..92855ee6e7 100644 --- a/libafl/src/stages/push/mod.rs +++ b/libafl/src/stages/push/mod.rs @@ -28,7 +28,7 @@ use crate::{ executors::{Executor, ExitKind, HasObservers}, observers::ObserversTuple, schedulers::Scheduler, - stages::{RetryCountRestartHelper, Stage}, + stages::{Restartable, RetryCountRestartHelper, Stage}, state::{HasCorpus, HasExecutions, HasLastReportTime, HasRand}, Error, EvaluatorObservers, ExecutesInput, ExecutionProcessor, HasMetadata, HasScheduler, }; @@ -244,6 +244,22 @@ impl Named for PushStageAdapter { } } +impl Restartable for PushStageAdapter +where + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, +{ + #[inline] + fn should_restart(&mut self, state: &mut S) -> Result { + // TODO: Proper restart handling - call post_exec at the right time, etc... + RetryCountRestartHelper::no_retry(state, &self.name) + } + + #[inline] + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + RetryCountRestartHelper::clear_progress(state, &self.name) + } +} + impl Stage for PushStageAdapter where CS: Scheduler, @@ -263,17 +279,6 @@ where + EvaluatorObservers + HasScheduler, { - #[inline] - fn should_restart(&mut self, state: &mut S) -> Result { - // TODO: Proper restart handling - call post_exec at the right time, etc... - RetryCountRestartHelper::no_retry(state, &self.name) - } - - #[inline] - fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { - RetryCountRestartHelper::clear_progress(state, &self.name) - } - fn perform( &mut self, fuzzer: &mut Z, diff --git a/libafl/src/stages/replay.rs b/libafl/src/stages/replay.rs new file mode 100644 index 0000000000..868e829d45 --- /dev/null +++ b/libafl/src/stages/replay.rs @@ -0,0 +1,173 @@ +//! The replay stage can scan all inputs and executes them once per input + +use alloc::{ + borrow::{Cow, ToOwned}, + string::ToString, + vec::Vec, +}; +use core::marker::PhantomData; + +use hashbrown::HashSet; +use libafl_bolts::{impl_serdeany, Named}; +use serde::{Deserialize, Serialize}; + +use crate::{ + corpus::{Corpus, CorpusId}, + stages::{Restartable, Stage}, + state::{HasCorpus, HasSolutions}, + Error, Evaluator, +}; + +/// Replay all inputs +#[derive(Debug)] +pub struct ReplayStage { + name: Cow<'static, str>, + restart_helper: ReplayRestartingHelper, + phantom: PhantomData, +} + +impl Default for ReplayStage { + fn default() -> Self { + Self::new() + } +} + +impl Named for ReplayStage { + fn name(&self) -> &Cow<'static, str> { + &self.name + } +} + +/// Restart helper for replay stage +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct ReplayRestartingHelper { + done_corpus: HashSet, + done_solution: HashSet, +} + +impl ReplayRestartingHelper { + /// constructor + #[must_use] + pub fn new() -> Self { + Self { + done_corpus: HashSet::default(), + done_solution: HashSet::default(), + } + } + + /// clear history + pub fn clear(&mut self) { + self.done_corpus.clear(); + self.done_solution.clear(); + } + + /// check we've scaned this corpus entry + pub fn corpus_probe(&mut self, id: &CorpusId) -> bool { + self.done_corpus.contains(id) + } + + /// check we've scaned this solution entry + pub fn solution_probe(&mut self, id: &CorpusId) -> bool { + self.done_solution.contains(id) + } + + /// mark this corpus entry as finished + pub fn corpus_finish(&mut self, id: CorpusId) { + self.done_corpus.insert(id); + } + + /// mark this solution entry as finished + pub fn solution_finish(&mut self, id: CorpusId) { + self.done_solution.insert(id); + } +} + +impl_serdeany!(ReplayRestartingHelper); + +/// The counter for giving this stage unique id +static mut REPLAY_STAGE_ID: usize = 0; +/// The name for tracing stage +pub static REPLAY_STAGE_NAME: &str = "tracing"; + +impl ReplayStage { + #[must_use] + /// Create a new replay stage + pub fn new() -> Self { + // unsafe but impossible that you create two threads both instantiating this instance + let stage_id = unsafe { + let ret = REPLAY_STAGE_ID; + REPLAY_STAGE_ID += 1; + ret + }; + + Self { + name: Cow::Owned(REPLAY_STAGE_NAME.to_owned() + ":" + stage_id.to_string().as_ref()), + restart_helper: ReplayRestartingHelper::new(), + phantom: PhantomData, + } + } +} + +impl Stage for ReplayStage +where + S: HasCorpus + HasSolutions, + Z: Evaluator, + I: Clone, +{ + #[inline] + fn perform( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + state: &mut S, + manager: &mut EM, + ) -> Result<(), Error> { + let corpus_ids: Vec = state.corpus().ids().collect(); + + for id in corpus_ids { + if self.restart_helper.corpus_probe(&id) { + continue; + } + log::info!("Replaying corpus: {id}"); + let input = { + let mut tc = state.corpus().get(id)?.borrow_mut(); + let input = tc.load_input(state.corpus())?; + input.clone() + }; + + fuzzer.evaluate_input(state, executor, manager, &input)?; + + self.restart_helper.corpus_finish(id); + } + + let solution_ids: Vec = state.solutions().ids().collect(); + for id in solution_ids { + if self.restart_helper.solution_probe(&id) { + continue; + } + log::info!("Replaying solution: {id}"); + let input = { + let mut tc = state.corpus().get(id)?.borrow_mut(); + let input = tc.load_input(state.corpus())?; + input.clone() + }; + + fuzzer.evaluate_input(state, executor, manager, &input)?; + + self.restart_helper.solution_finish(id); + } + + Ok(()) + } +} + +impl Restartable for ReplayStage { + fn should_restart(&mut self, _state: &mut S) -> Result { + Ok(true) + } + + fn clear_progress(&mut self, _state: &mut S) -> Result<(), Error> { + self.restart_helper.clear(); + Ok(()) + } +} diff --git a/libafl/src/stages/sync.rs b/libafl/src/stages/sync.rs index 4c52c50c34..af6eaf1c50 100644 --- a/libafl/src/stages/sync.rs +++ b/libafl/src/stages/sync.rs @@ -21,7 +21,7 @@ use crate::{ executors::{Executor, ExitKind, HasObservers}, fuzzer::{Evaluator, EvaluatorObservers, ExecutionProcessor}, inputs::{Input, InputConverter}, - stages::{RetryCountRestartHelper, Stage}, + stages::{Restartable, RetryCountRestartHelper, Stage}, state::{ HasCorpus, HasCurrentTestcase, HasExecutions, HasRand, HasSolutions, MaybeHasClientPerfMonitor, Stoppable, @@ -85,18 +85,6 @@ where + HasCurrentCorpusId + MaybeHasClientPerfMonitor, { - #[inline] - fn should_restart(&mut self, state: &mut S) -> Result { - // TODO: Needs proper crash handling for when an imported testcase crashes - // For now, Make sure we don't get stuck crashing on this testcase - RetryCountRestartHelper::no_retry(state, &self.name) - } - - #[inline] - fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { - RetryCountRestartHelper::clear_progress(state, &self.name) - } - #[inline] fn perform( &mut self, @@ -158,6 +146,23 @@ where } } +impl Restartable for SyncFromDiskStage +where + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, +{ + #[inline] + fn should_restart(&mut self, state: &mut S) -> Result { + // TODO: Needs proper crash handling for when an imported testcase crashes + // For now, Make sure we don't get stuck crashing on this testcase + RetryCountRestartHelper::no_retry(state, &self.name) + } + + #[inline] + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + RetryCountRestartHelper::clear_progress(state, &self.name) + } +} + impl SyncFromDiskStage { /// Creates a new [`SyncFromDiskStage`] #[must_use] @@ -249,18 +254,6 @@ where SP: ShMemProvider, Z: EvaluatorObservers + ExecutionProcessor, { - #[inline] - fn should_restart(&mut self, _state: &mut S) -> Result { - // No restart handling needed - does not execute the target. - Ok(true) - } - - #[inline] - fn clear_progress(&mut self, _state: &mut S) -> Result<(), Error> { - // Not needed - does not execute the target. - Ok(()) - } - #[inline] fn perform( &mut self, @@ -320,6 +313,20 @@ where } } +impl Restartable for SyncFromBrokerStage { + #[inline] + fn should_restart(&mut self, _state: &mut S) -> Result { + // No restart handling needed - does not execute the target. + Ok(true) + } + + #[inline] + fn clear_progress(&mut self, _state: &mut S) -> Result<(), Error> { + // Not needed - does not execute the target. + Ok(()) + } +} + impl SyncFromBrokerStage { /// Creates a new [`SyncFromBrokerStage`] #[must_use] diff --git a/libafl/src/stages/time_tracker.rs b/libafl/src/stages/time_tracker.rs index b39ae2cd49..1e6d75df3f 100644 --- a/libafl/src/stages/time_tracker.rs +++ b/libafl/src/stages/time_tracker.rs @@ -3,7 +3,10 @@ use std::{marker::PhantomData, time::Duration}; use libafl_bolts::{current_time, Error}; -use crate::{stages::Stage, HasMetadata}; +use crate::{ + stages::{Restartable, Stage}, + HasMetadata, +}; /// Track an inner Stage's execution time #[derive(Debug)] pub struct TimeTrackingStageWrapper { @@ -40,10 +43,20 @@ where self.inner.perform(fuzzer, executor, state, manager)?; let after_run = current_time(); self.count += after_run - before_run; - *state.metadata_mut::()? = T::from(self.count); + + if let Ok(meta) = state.metadata_mut::() { + *meta = T::from(self.count); + } else { + state.add_metadata::(T::from(self.count)); + } Ok(()) } +} +impl Restartable for TimeTrackingStageWrapper +where + ST: Restartable, +{ fn should_restart(&mut self, state: &mut S) -> Result { self.inner.should_restart(state) } @@ -51,15 +64,4 @@ where fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { self.inner.clear_progress(state) } - - fn perform_restartable( - &mut self, - fuzzer: &mut Z, - executor: &mut E, - state: &mut S, - manager: &mut M, - ) -> Result<(), Error> { - self.inner - .perform_restartable(fuzzer, executor, state, manager) - } } diff --git a/libafl/src/stages/tmin.rs b/libafl/src/stages/tmin.rs index 26749c7840..20667f950a 100644 --- a/libafl/src/stages/tmin.rs +++ b/libafl/src/stages/tmin.rs @@ -30,7 +30,7 @@ use crate::{ schedulers::RemovableScheduler, stages::{ mutational::{MutatedTransform, MutatedTransformPost}, - ExecutionCountRestartHelper, Stage, + ExecutionCountRestartHelper, Restartable, Stage, }, start_timer, state::{ @@ -82,14 +82,6 @@ where M: Mutator, I: Input + Hash + HasLen, { - fn should_restart(&mut self, state: &mut S) -> Result { - self.restart_helper.should_restart(state, &self.name) - } - - fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { - self.restart_helper.clear_progress::(state, &self.name) - } - fn perform( &mut self, fuzzer: &mut Z, @@ -106,6 +98,19 @@ where } } +impl Restartable for StdTMinMutationalStage +where + S: HasNamedMetadata + HasExecutions, +{ + fn should_restart(&mut self, state: &mut S) -> Result { + self.restart_helper.should_restart(state, &self.name) + } + + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + self.restart_helper.clear_progress::(state, &self.name) + } +} + impl FeedbackFactory for StdTMinMutationalStage where diff --git a/libafl/src/stages/tracing.rs b/libafl/src/stages/tracing.rs index 1952a40179..7430358ba4 100644 --- a/libafl/src/stages/tracing.rs +++ b/libafl/src/stages/tracing.rs @@ -16,7 +16,7 @@ use crate::{ inputs::Input, mark_feature_time, observers::ObserversTuple, - stages::{RetryCountRestartHelper, Stage}, + stages::{Restartable, RetryCountRestartHelper, Stage}, start_timer, state::{HasCorpus, HasCurrentTestcase, HasExecutions, MaybeHasClientPerfMonitor}, Error, HasNamedMetadata, @@ -93,7 +93,12 @@ where ) -> Result<(), Error> { self.trace(fuzzer, state, manager) } +} +impl Restartable for TracingStage +where + S: HasNamedMetadata + HasCurrentCorpusId, +{ fn should_restart(&mut self, state: &mut S) -> Result { RetryCountRestartHelper::no_retry(state, &self.name) } @@ -174,14 +179,6 @@ where + HasCurrentCorpusId + MaybeHasClientPerfMonitor, { - fn should_restart(&mut self, state: &mut S) -> Result { - RetryCountRestartHelper::no_retry(state, &self.name) - } - - fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { - RetryCountRestartHelper::clear_progress(state, &self.name) - } - #[inline] fn perform( &mut self, @@ -219,6 +216,19 @@ where } } +impl Restartable for ShadowTracingStage +where + S: HasNamedMetadata + HasCurrentCorpusId, +{ + fn should_restart(&mut self, state: &mut S) -> Result { + RetryCountRestartHelper::no_retry(state, &self.name) + } + + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + RetryCountRestartHelper::clear_progress(state, &self.name) + } +} + impl ShadowTracingStage where E: Executor + HasObservers, diff --git a/libafl/src/stages/tuneable.rs b/libafl/src/stages/tuneable.rs index d2535e11dc..92764d8352 100644 --- a/libafl/src/stages/tuneable.rs +++ b/libafl/src/stages/tuneable.rs @@ -14,7 +14,7 @@ use crate::{ nonzero, stages::{ mutational::{MutatedTransform, MutatedTransformPost, DEFAULT_MUTATIONAL_MAX_ITERATIONS}, - ExecutionCountRestartHelper, MutationalStage, Stage, + ExecutionCountRestartHelper, MutationalStage, Restartable, Stage, }, start_timer, state::{HasCurrentTestcase, HasExecutions, HasRand, MaybeHasClientPerfMonitor}, @@ -217,7 +217,12 @@ where ret } +} +impl Restartable for TuneableMutationalStage +where + S: HasNamedMetadata + HasExecutions, +{ fn should_restart(&mut self, state: &mut S) -> Result { self.restart_helper.should_restart(state, &self.name) } diff --git a/libafl/src/stages/unicode.rs b/libafl/src/stages/unicode.rs index a75b30a36f..79ff778164 100644 --- a/libafl/src/stages/unicode.rs +++ b/libafl/src/stages/unicode.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::{ inputs::{BytesInput, HasTargetBytes}, - stages::Stage, + stages::{Restartable, Stage}, state::{HasCorpus, HasCurrentTestcase}, HasMetadata, }; @@ -112,6 +112,18 @@ impl Stage for UnicodeIdentificationStage + HasCurrentTestcase, { + fn perform( + &mut self, + _fuzzer: &mut Z, + _executor: &mut E, + state: &mut S, + _manager: &mut EM, + ) -> Result<(), Error> { + UnicodeIdentificationStage::identify_unicode_in_current_testcase(state) + } +} + +impl Restartable for UnicodeIdentificationStage { #[inline] fn should_restart(&mut self, _state: &mut S) -> Result { // Stage does not run the target. No reset helper needed. @@ -123,14 +135,4 @@ where // Stage does not run the target. No reset helper needed. Ok(()) } - - fn perform( - &mut self, - _fuzzer: &mut Z, - _executor: &mut E, - state: &mut S, - _manager: &mut EM, - ) -> Result<(), Error> { - UnicodeIdentificationStage::identify_unicode_in_current_testcase(state) - } } diff --git a/libafl/src/stages/verify_timeouts.rs b/libafl/src/stages/verify_timeouts.rs index 668df1f3f9..bad419487e 100644 --- a/libafl/src/stages/verify_timeouts.rs +++ b/libafl/src/stages/verify_timeouts.rs @@ -13,7 +13,7 @@ use crate::{ executors::{Executor, HasObservers, HasTimeout}, inputs::BytesInput, observers::ObserversTuple, - stages::Stage, + stages::{Restartable, Stage}, Evaluator, HasMetadata, }; @@ -111,6 +111,9 @@ where *res = TimeoutsToVerify::::new(); Ok(()) } +} + +impl Restartable for VerifyTimeoutsStage { fn should_restart(&mut self, _state: &mut S) -> Result { Ok(true) } diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index 3b3a4327a6..1ed86ebaef 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -35,7 +35,7 @@ use crate::{ fuzzer::{Evaluator, ExecuteInputResult}, generators::Generator, inputs::{Input, NopInput}, - stages::{HasCurrentStageId, HasNestedStageStatus, StageId}, + stages::StageId, Error, HasMetadata, HasNamedMetadata, }; @@ -547,7 +547,35 @@ impl HasCurrentStageId for StdState { } } -impl HasNestedStageStatus for StdState { +/// Trait for types which track the current stage +pub trait HasCurrentStageId { + /// Set the current stage; we have started processing this stage + fn set_current_stage_id(&mut self, id: StageId) -> Result<(), Error>; + + /// Clear the current stage; we are done processing this stage + fn clear_stage_id(&mut self) -> Result<(), Error>; + + /// Fetch the current stage -- typically used after a state recovery or transfer + fn current_stage_id(&self) -> Result, Error>; + + /// Notify of a reset from which we may recover + fn on_restart(&mut self) -> Result<(), Error> { + Ok(()) + } +} + +/// Trait for types which track nested stages. Stages which themselves contain stage tuples should +/// ensure that they constrain the state with this trait accordingly. +pub trait HasNestedStage: HasCurrentStageId { + /// Enter a stage scope, potentially resuming to an inner stage status. Returns Ok(true) if + /// resumed. + fn enter_inner_stage(&mut self) -> Result<(), Error>; + + /// Exit a stage scope + fn exit_inner_stage(&mut self) -> Result<(), Error>; +} + +impl HasNestedStage for StdState { fn enter_inner_stage(&mut self) -> Result<(), Error> { self.stage_stack.enter_inner_stage() } diff --git a/libafl/src/state/stack.rs b/libafl/src/state/stack.rs index c07278cfa8..392f2ba1d4 100644 --- a/libafl/src/state/stack.rs +++ b/libafl/src/state/stack.rs @@ -3,7 +3,10 @@ use alloc::vec::Vec; use libafl_bolts::Error; use serde::{Deserialize, Serialize}; -use crate::stages::{HasCurrentStageId, HasNestedStageStatus, StageId}; +use crate::{ + stages::StageId, + state::{HasCurrentStageId, HasNestedStage}, +}; /// A stack to keep track of which stage is executing #[derive(Serialize, Deserialize, Clone, Debug, Default)] @@ -41,7 +44,7 @@ impl HasCurrentStageId for StageStack { } } -impl HasNestedStageStatus for StageStack { +impl HasNestedStage for StageStack { fn enter_inner_stage(&mut self) -> Result<(), Error> { self.stage_depth += 1; Ok(()) diff --git a/libafl_libfuzzer/log b/libafl_libfuzzer/log new file mode 100644 index 0000000000..14f7c8c6dd --- /dev/null +++ b/libafl_libfuzzer/log @@ -0,0 +1,4 @@ +warning: profiles for the non root package will be ignored, specify profiles at the workspace root: +package: /home/toka/LibAFL/bindings/pylibafl/Cargo.toml +workspace: /home/toka/LibAFL/Cargo.toml + Compiling libafl_libfuzzer v0.15.0 (/home/toka/LibAFL/libafl_libfuzzer) diff --git a/libafl_libfuzzer/runtime/src/fuzz.rs b/libafl_libfuzzer/runtime/src/fuzz.rs index 2f6c9e2457..9a352c3e01 100644 --- a/libafl_libfuzzer/runtime/src/fuzz.rs +++ b/libafl_libfuzzer/runtime/src/fuzz.rs @@ -11,8 +11,8 @@ use libafl::{ }, executors::ExitKind, monitors::{tui::TuiMonitor, Monitor, MultiMonitor}, - stages::{HasCurrentStageId, StagesTuple}, - state::{HasExecutions, HasLastReportTime, HasSolutions, Stoppable}, + stages::StagesTuple, + state::{HasCurrentStageId, HasExecutions, HasLastReportTime, HasSolutions, Stoppable}, Error, Fuzzer, HasMetadata, }; use libafl_bolts::{ diff --git a/libafl_libfuzzer/runtime/src/report.rs b/libafl_libfuzzer/runtime/src/report.rs index 88068d6f33..aeee85062f 100644 --- a/libafl_libfuzzer/runtime/src/report.rs +++ b/libafl_libfuzzer/runtime/src/report.rs @@ -5,8 +5,8 @@ use libafl::{ executors::HasObservers, feedbacks::MapFeedbackMetadata, monitors::SimpleMonitor, - stages::{HasCurrentStageId, StagesTuple}, - state::{HasExecutions, HasLastReportTime, Stoppable}, + stages::StagesTuple, + state::{HasCurrentStageId, HasExecutions, HasLastReportTime, Stoppable}, Error, Fuzzer, HasMetadata, HasNamedMetadata, }; diff --git a/libafl_qemu/libqasan/libqasan.c b/libafl_qemu/libqasan/libqasan.c index b91dd0f212..6005765f86 100644 --- a/libafl_qemu/libqasan/libqasan.c +++ b/libafl_qemu/libqasan/libqasan.c @@ -84,6 +84,12 @@ static void __libqasan_map_shadow(void *addr, void *limit) { addr, limit + 1, errno); abort(); } + + if (madvise(addr, size, MADV_DONTDUMP) != 0) { + QASAN_LOG("Failed to madvise (MADV_DONTDUMP) shadow: %p-%p, errno: %d", + addr, limit + 1, errno); + abort(); + } } #endif diff --git a/libafl_qemu/src/modules/usermode/asan_guest.rs b/libafl_qemu/src/modules/usermode/asan_guest.rs index f1dfcfa5c5..0d3f0589eb 100644 --- a/libafl_qemu/src/modules/usermode/asan_guest.rs +++ b/libafl_qemu/src/modules/usermode/asan_guest.rs @@ -269,9 +269,21 @@ where self.asan_lib = Some(asan_lib); } - fn post_qemu_init(&mut self, qemu: Qemu, _emulator_modules: &mut EmulatorModules) + fn post_qemu_init(&mut self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules) where ET: EmulatorModuleTuple, + { + } + + fn first_exec( + &mut self, + qemu: Qemu, + emulator_modules: &mut EmulatorModules, + _state: &mut S, + ) where + ET: EmulatorModuleTuple, + I: Unpin, + S: Unpin, { for mapping in qemu.mappings() { println!("mapping: {mapping:#?}"); @@ -305,18 +317,7 @@ where for mapping in &mappings { println!("asan mapping: {mapping:#?}"); } - } - fn first_exec( - &mut self, - _qemu: Qemu, - emulator_modules: &mut EmulatorModules, - _state: &mut S, - ) where - ET: EmulatorModuleTuple, - I: Unpin, - S: Unpin, - { emulator_modules.reads( Hook::Function(gen_readwrite_guest_asan::), Hook::Function(guest_trace_error_asan::), diff --git a/libafl_targets/src/cmps/stages/aflpptracing.rs b/libafl_targets/src/cmps/stages/aflpptracing.rs index 54309d8a25..71782a46f1 100644 --- a/libafl_targets/src/cmps/stages/aflpptracing.rs +++ b/libafl_targets/src/cmps/stages/aflpptracing.rs @@ -6,7 +6,7 @@ use libafl::{ executors::{Executor, HasObservers}, inputs::BytesInput, observers::ObserversTuple, - stages::{colorization::TaintMetadata, RetryCountRestartHelper, Stage}, + stages::{colorization::TaintMetadata, Restartable, RetryCountRestartHelper, Stage}, state::{HasCorpus, HasCurrentTestcase}, Error, HasMetadata, HasNamedMetadata, }; @@ -111,7 +111,12 @@ where Ok(()) } +} +impl Restartable for AFLppCmplogTracingStage<'_, EM, TE, S, Z> +where + S: HasMetadata + HasNamedMetadata + HasCurrentCorpusId, +{ fn should_restart(&mut self, state: &mut S) -> Result { // Tracing stage is always deterministic // don't restart diff --git a/libafl_targets/src/forkserver.c b/libafl_targets/src/forkserver.c index e3f8f831b1..f5d16d4bc5 100644 --- a/libafl_targets/src/forkserver.c +++ b/libafl_targets/src/forkserver.c @@ -272,8 +272,7 @@ void __afl_start_forkserver(void) { uint32_t len = (__token_stop - __token_start), offset = 0; if (write(FORKSRV_FD + 1, &len, 4) != 4) { - write(2, "Error: could not send autotokens len\n", - strlen("Error: could not send autotokens len\n")); + fprintf(stderr, "Error: could not send autotokens len\n"); _exit(1); }